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