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