1#!/usr/bin/env python 2# 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18# TODO(b/147454897): Keep the test logic in sync with VtsVndkDependency.py 19# until it is removed. 20import collections 21import logging 22import os 23import posixpath as target_path_module 24import re 25import shutil 26import sys 27import tempfile 28import unittest 29 30from vts.testcases.vndk import utils 31from vts.testcases.vndk.golden import vndk_data 32from vts.utils.python.library import elf_parser 33from vts.utils.python.vndk import vndk_utils 34 35 36class VtsVndkDependencyTest(unittest.TestCase): 37 """A test case to verify vendor library dependency. 38 39 Attributes: 40 _dut: The AndroidDevice under test. 41 _temp_dir: The temporary directory to which the odm and vendor 42 partitions are copied. 43 _ll_ndk: Set of strings. The names of low-level NDK libraries in 44 /system/lib[64]. 45 _sp_hal: List of patterns. The names of the same-process HAL libraries 46 expected to be in /vendor/lib[64]. 47 _vndk: Set of strings. The names of VNDK-core libraries. 48 _vndk_sp: Set of strings. The names of VNDK-SP libraries. 49 _SP_HAL_LINK_PATHS: Format strings of same-process HAL's link paths. 50 _VENDOR_LINK_PATHS: Format strings of vendor processes' link paths. 51 """ 52 _TARGET_DIR_SEP = "/" 53 _TARGET_ROOT_DIR = "/" 54 _TARGET_ODM_DIR = "/odm" 55 _TARGET_VENDOR_DIR = "/vendor" 56 57 _SP_HAL_LINK_PATHS = [ 58 "/odm/{LIB}/egl", "/odm/{LIB}/hw", "/odm/{LIB}", 59 "/vendor/{LIB}/egl", "/vendor/{LIB}/hw", "/vendor/{LIB}" 60 ] 61 _VENDOR_LINK_PATHS = [ 62 "/odm/{LIB}/hw", "/odm/{LIB}/egl", "/odm/{LIB}", 63 "/vendor/{LIB}/hw", "/vendor/{LIB}/egl", "/vendor/{LIB}" 64 ] 65 _DEFAULT_PROGRAM_INTERPRETERS = [ 66 "/system/bin/linker", "/system/bin/linker64" 67 ] 68 69 class ElfObject(object): 70 """Contains dependencies of an ELF file on target device. 71 72 Attributes: 73 target_path: String. The path to the ELF file on target. 74 name: String. File name of the ELF. 75 target_dir: String. The directory containing the ELF file on 76 target. 77 bitness: Integer. Bitness of the ELF. 78 deps: List of strings. The names of the depended libraries. 79 runpaths: List of strings. The library search paths. 80 """ 81 82 def __init__(self, target_path, bitness, deps, runpaths): 83 self.target_path = target_path 84 self.name = target_path_module.basename(target_path) 85 self.target_dir = target_path_module.dirname(target_path) 86 self.bitness = bitness 87 self.deps = deps 88 # Format runpaths 89 self.runpaths = [] 90 lib_dir_name = "lib64" if bitness == 64 else "lib" 91 for runpath in runpaths: 92 path = runpath.replace("${LIB}", lib_dir_name) 93 path = path.replace("$LIB", lib_dir_name) 94 path = path.replace("${ORIGIN}", self.target_dir) 95 path = path.replace("$ORIGIN", self.target_dir) 96 self.runpaths.append(path) 97 98 def setUp(self): 99 """Initializes device, temporary directory, and VNDK lists.""" 100 serial_number = os.environ.get("ANDROID_SERIAL") 101 self.assertTrue(serial_number, "$ANDROID_SERIAL is empty.") 102 self._dut = utils.AndroidDevice(serial_number) 103 self.assertTrue(self._dut.IsRoot(), "This test requires adb root.") 104 105 self._temp_dir = tempfile.mkdtemp() 106 for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR): 107 if self._dut.IsDirectory(target_dir): 108 logging.info("adb pull %s %s", target_dir, self._temp_dir) 109 self._dut.AdbPull(target_dir, self._temp_dir) 110 else: 111 logging.info("Skip adb pull %s", target_dir) 112 113 vndk_lists = vndk_data.LoadVndkLibraryListsFromResources( 114 self._dut.GetVndkVersion(), 115 vndk_data.SP_HAL, 116 vndk_data.LL_NDK, 117 vndk_data.VNDK, 118 vndk_data.VNDK_SP) 119 self.assertTrue(vndk_lists, "Cannot load VNDK library lists.") 120 121 sp_hal_strings = vndk_lists[0] 122 self._sp_hal = [re.compile(x) for x in sp_hal_strings] 123 (self._ll_ndk, self._vndk, self._vndk_sp) = vndk_lists[1:] 124 125 logging.debug("LL_NDK: %s", self._ll_ndk) 126 logging.debug("SP_HAL: %s", sp_hal_strings) 127 logging.debug("VNDK: %s", self._vndk) 128 logging.debug("VNDK_SP: %s", self._vndk_sp) 129 130 def tearDown(self): 131 """Deletes the temporary directory.""" 132 logging.info("Delete %s", self._temp_dir) 133 shutil.rmtree(self._temp_dir, ignore_errors=True) 134 135 def _IsElfObjectForAp(self, elf, target_path, abi_list): 136 """Checks whether an ELF object is for application processor. 137 138 Args: 139 elf: The object of elf_parser.ElfParser. 140 target_path: The path to the ELF file on target. 141 abi_list: A list of strings, the ABIs of the application processor. 142 143 Returns: 144 A boolean, whether the ELF object is for application processor. 145 """ 146 if not any(elf.MatchCpuAbi(x) for x in abi_list): 147 logging.debug("%s does not match the ABI", target_path) 148 return False 149 150 # b/115567177 Skip an ELF file if it meets the following 3 conditions: 151 # The ELF type is executable. 152 if not elf.IsExecutable(): 153 return True 154 155 # It requires special program interpreter. 156 interp = elf.GetProgramInterpreter() 157 if not interp or interp in self._DEFAULT_PROGRAM_INTERPRETERS: 158 return True 159 160 # It does not have execute permission in the file system. 161 if self._dut.IsExecutable(target_path): 162 return True 163 164 return False 165 166 def _IsElfObjectBuiltForAndroid(self, elf, target_path): 167 """Checks whether an ELF object is built for Android. 168 169 Some ELF objects in vendor partition require special program 170 interpreters. Such executable files have .interp sections, but shared 171 libraries don't. As there is no reliable way to identify those 172 libraries. This method checks .note.android.ident section which is 173 created by Android build system. 174 175 Args: 176 elf: The object of elf_parser.ElfParser. 177 target_path: The path to the ELF file on target. 178 179 Returns: 180 A boolean, whether the ELF object is built for Android. 181 """ 182 # b/133399940 Skip an ELF file if it does not have .note.android.ident 183 # section and meets one of the following conditions: 184 if elf.HasAndroidIdent(): 185 return True 186 187 # It's in the specific directory and is a shared library. 188 if (target_path.startswith("/vendor/arib/lib/") and 189 ".so" in target_path and 190 elf.IsSharedObject()): 191 return False 192 193 # It's in the specific directory, requires special program interpreter, 194 # and is executable. 195 if target_path.startswith("/vendor/arib/bin/"): 196 interp = elf.GetProgramInterpreter() 197 if interp and interp not in self._DEFAULT_PROGRAM_INTERPRETERS: 198 if elf.IsExecutable() or self._dut.IsExecutable(target_path): 199 return False 200 201 return True 202 203 @staticmethod 204 def _IterateFiles(host_dir): 205 """Iterates files in a host directory. 206 207 Args: 208 host_dir: The host directory. 209 210 Yields: 211 The file paths under the directory. 212 """ 213 for root_dir, dir_names, file_names in os.walk(host_dir): 214 for file_name in file_names: 215 yield os.path.join(root_dir, file_name) 216 217 def _LoadElfObjects(self, host_dir, target_dir, abi_list, 218 elf_error_handler): 219 """Scans a host directory recursively and loads all ELF files in it. 220 221 Args: 222 host_dir: The host directory to scan. 223 target_dir: The path from which host_dir is copied. 224 abi_list: A list of strings, the ABIs of the ELF files to load. 225 elf_error_handler: A function that takes 2 arguments 226 (target_path, exception). It is called when 227 the parser fails to read an ELF file. 228 229 Returns: 230 List of ElfObject. 231 """ 232 objs = [] 233 for full_path in self._IterateFiles(host_dir): 234 rel_path = os.path.relpath(full_path, host_dir) 235 target_path = target_path_module.join( 236 target_dir, *rel_path.split(os.path.sep)) 237 try: 238 elf = elf_parser.ElfParser(full_path) 239 except elf_parser.ElfError: 240 logging.debug("%s is not an ELF file", target_path) 241 continue 242 try: 243 if not self._IsElfObjectForAp(elf, target_path, abi_list): 244 logging.info("%s is not for application processor", 245 target_path) 246 continue 247 if not self._IsElfObjectBuiltForAndroid(elf, target_path): 248 logging.warning("%s is not built for Android, which is no " 249 "longer exempted.", target_path) 250 251 deps, runpaths = elf.ListDependencies() 252 except elf_parser.ElfError as e: 253 elf_error_handler(target_path, e) 254 continue 255 finally: 256 elf.Close() 257 258 logging.info("%s depends on: %s", target_path, ", ".join(deps)) 259 if runpaths: 260 logging.info("%s has runpaths: %s", 261 target_path, ":".join(runpaths)) 262 objs.append(self.ElfObject(target_path, elf.bitness, deps, 263 runpaths)) 264 return objs 265 266 def _FindLibsInLinkPaths(self, bitness, link_paths, objs): 267 """Finds libraries in link paths. 268 269 Args: 270 bitness: 32 or 64, the bitness of the returned libraries. 271 link_paths: List of strings, the default link paths. 272 objs: List of ElfObject, the libraries/executables to be filtered 273 by bitness and path. 274 275 Returns: 276 A defaultdict, {dir: {name: obj}} where obj is an ElfObject, dir 277 is obj.target_dir, and name is obj.name. 278 """ 279 namespace = collections.defaultdict(dict) 280 for obj in objs: 281 if (obj.bitness == bitness and 282 any(obj.target_path.startswith(link_path + 283 self._TARGET_DIR_SEP) 284 for link_path in link_paths)): 285 namespace[obj.target_dir][obj.name] = obj 286 return namespace 287 288 def _DfsDependencies(self, lib, searched, namespace, link_paths): 289 """Depth-first-search for library dependencies. 290 291 Args: 292 lib: ElfObject, the library to search for dependencies. 293 searched: The set of searched libraries. 294 namespace: Defaultdict, {dir: {name: obj}} containing all 295 searchable libraries. 296 link_paths: List of strings, the default link paths. 297 """ 298 if lib in searched: 299 return 300 searched.add(lib) 301 for dep_name in lib.deps: 302 for link_path in lib.runpaths + link_paths: 303 if dep_name in namespace[link_path]: 304 self._DfsDependencies(namespace[link_path][dep_name], 305 searched, namespace, link_paths) 306 break 307 308 def _FindDisallowedDependencies(self, objs, namespace, link_paths, 309 *vndk_lists): 310 """Tests if libraries/executables have disallowed dependencies. 311 312 Args: 313 objs: Collection of ElfObject, the libraries/executables under 314 test. 315 namespace: Defaultdict, {dir: {name: obj}} containing all libraries 316 in the linker namespace. 317 link_paths: List of strings, the default link paths. 318 vndk_lists: Collections of library names in VNDK, VNDK-SP, etc. 319 320 Returns: 321 List of tuples (path, disallowed_dependencies). 322 """ 323 dep_errors = [] 324 for obj in objs: 325 disallowed_libs = [] 326 for dep_name in obj.deps: 327 if any((dep_name in vndk_list) for vndk_list in vndk_lists): 328 continue 329 if any((dep_name in namespace[link_path]) for link_path in 330 obj.runpaths + link_paths): 331 continue 332 disallowed_libs.append(dep_name) 333 334 if disallowed_libs: 335 dep_errors.append((obj.target_path, disallowed_libs)) 336 return dep_errors 337 338 def _TestElfDependency(self, bitness, objs): 339 """Tests vendor libraries/executables and SP-HAL dependencies. 340 341 Args: 342 bitness: 32 or 64, the bitness of the vendor libraries. 343 objs: List of ElfObject. The libraries/executables in odm and 344 vendor partitions. 345 346 Returns: 347 List of tuples (path, disallowed_dependencies). 348 """ 349 vndk_sp_ext_dirs = vndk_utils.GetVndkSpExtDirectories(bitness) 350 351 vendor_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for 352 x in self._VENDOR_LINK_PATHS] 353 vendor_namespace = self._FindLibsInLinkPaths(bitness, 354 vendor_link_paths, objs) 355 # Exclude VNDK and VNDK-SP extensions from vendor libraries. 356 for vndk_ext_dir in (vndk_utils.GetVndkExtDirectories(bitness) + 357 vndk_utils.GetVndkSpExtDirectories(bitness)): 358 vendor_namespace.pop(vndk_ext_dir, None) 359 logging.info("%d-bit odm, vendor, and SP-HAL libraries:", bitness) 360 for dir_path, libs in vendor_namespace.items(): 361 logging.info("%s: %s", dir_path, ",".join(libs.keys())) 362 363 sp_hal_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for 364 x in self._SP_HAL_LINK_PATHS] 365 sp_hal_namespace = self._FindLibsInLinkPaths(bitness, 366 sp_hal_link_paths, objs) 367 368 # Find same-process HAL and dependencies 369 sp_hal_libs = set() 370 for link_path in sp_hal_link_paths: 371 for obj in sp_hal_namespace[link_path].values(): 372 if any(x.match(obj.target_path) for x in self._sp_hal): 373 self._DfsDependencies(obj, sp_hal_libs, sp_hal_namespace, 374 sp_hal_link_paths) 375 logging.info("%d-bit SP-HAL libraries: %s", 376 bitness, ", ".join(x.name for x in sp_hal_libs)) 377 378 # Find VNDK-SP extension libraries and their dependencies. 379 vndk_sp_ext_libs = set(obj for obj in objs if 380 obj.bitness == bitness and 381 obj.target_dir in vndk_sp_ext_dirs) 382 vndk_sp_ext_deps = set() 383 for lib in vndk_sp_ext_libs: 384 self._DfsDependencies(lib, vndk_sp_ext_deps, sp_hal_namespace, 385 sp_hal_link_paths) 386 logging.info("%d-bit VNDK-SP extension libraries and dependencies: %s", 387 bitness, ", ".join(x.name for x in vndk_sp_ext_deps)) 388 389 # A vendor library/executable is allowed to depend on 390 # LL-NDK 391 # VNDK 392 # VNDK-SP 393 # Other libraries in vendor link paths 394 vendor_objs = {obj for obj in objs if 395 obj.bitness == bitness and 396 obj not in sp_hal_libs and 397 obj not in vndk_sp_ext_deps} 398 dep_errors = self._FindDisallowedDependencies( 399 vendor_objs, vendor_namespace, vendor_link_paths, 400 self._ll_ndk, self._vndk, self._vndk_sp) 401 402 # A VNDK-SP extension library/dependency is allowed to depend on 403 # LL-NDK 404 # VNDK-SP 405 # Libraries in vendor link paths 406 # Other VNDK-SP extension libraries, which is a subset of VNDK-SP 407 # 408 # However, it is not allowed to indirectly depend on VNDK. i.e., the 409 # depended vendor libraries must not depend on VNDK. 410 # 411 # vndk_sp_ext_deps and sp_hal_libs may overlap. Their dependency 412 # restrictions are the same. 413 dep_errors.extend(self._FindDisallowedDependencies( 414 vndk_sp_ext_deps - sp_hal_libs, vendor_namespace, 415 vendor_link_paths, self._ll_ndk, self._vndk_sp)) 416 417 if not vndk_utils.IsVndkRuntimeEnforced(self._dut): 418 logging.warning("Ignore dependency errors: %s", dep_errors) 419 dep_errors = [] 420 421 # A same-process HAL library is allowed to depend on 422 # LL-NDK 423 # VNDK-SP 424 # Other same-process HAL libraries and dependencies 425 dep_errors.extend(self._FindDisallowedDependencies( 426 sp_hal_libs, sp_hal_namespace, sp_hal_link_paths, 427 self._ll_ndk, self._vndk_sp)) 428 return dep_errors 429 430 def testElfDependency(self): 431 """Tests vendor libraries/executables and SP-HAL dependencies.""" 432 read_errors = [] 433 abi_list = self._dut.GetCpuAbiList() 434 objs = self._LoadElfObjects( 435 self._temp_dir, self._TARGET_ROOT_DIR, abi_list, 436 lambda p, e: read_errors.append((p, str(e)))) 437 438 dep_errors = self._TestElfDependency(32, objs) 439 if self._dut.GetCpuAbiList(64): 440 dep_errors.extend(self._TestElfDependency(64, objs)) 441 442 assert_lines = [] 443 if read_errors: 444 error_lines = ["%s: %s" % (x[0], x[1]) for x in read_errors] 445 logging.error("%d read errors:\n%s", 446 len(read_errors), "\n".join(error_lines)) 447 assert_lines.extend(error_lines[:20]) 448 449 if dep_errors: 450 error_lines = ["%s: %s" % (x[0], ", ".join(x[1])) 451 for x in dep_errors] 452 logging.error("%d disallowed dependencies:\n%s", 453 len(dep_errors), "\n".join(error_lines)) 454 assert_lines.extend(error_lines[:20]) 455 456 error_count = len(read_errors) + len(dep_errors) 457 if error_count: 458 if error_count > len(assert_lines): 459 assert_lines.append("...") 460 assert_lines.append("Total number of errors: " + str(error_count)) 461 self.fail("\n".join(assert_lines)) 462 463 464if __name__ == "__main__": 465 # The logs are written to stdout so that TradeFed test runner can parse the 466 # results from stderr. 467 logging.basicConfig(stream=sys.stdout) 468 # Setting verbosity is required to generate output that the TradeFed test 469 # runner can parse. 470 unittest.main(verbosity=3) 471