| #!/usr/bin/env python |
| # |
| # Copyright (C) 2017 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import logging |
| import os |
| import re |
| import shutil |
| import tempfile |
| |
| from vts.runners.host import asserts |
| from vts.runners.host import base_test |
| from vts.runners.host import test_runner |
| from vts.runners.host import utils |
| from vts.utils.python.controllers import android_device |
| from vts.utils.python.file import target_file_utils |
| from vts.utils.python.library import elf_parser |
| from vts.utils.python.os import path_utils |
| |
| |
| class VtsVndkDependencyTest(base_test.BaseTestClass): |
| """A test case to verify vendor library dependency. |
| |
| Attributes: |
| _dut: The AndroidDevice under test. |
| _shell: The ShellMirrorObject to execute commands |
| _temp_dir: The temporary directory to which the vendor partition is |
| copied. |
| _LOW_LEVEL_NDK: List of strings. The names of low-level NDK libraries in |
| /system/lib[64]. |
| _SAME_PROCESS_HAL: List of patterns. The names of same-process HAL |
| libraries expected to be in /vendor/lib[64]. |
| _SAME_PROCESS_NDK: List if strings. The names of same-process NDK |
| libraries in /system/lib[64]. |
| """ |
| _TARGET_VENDOR_DIR = "/vendor" |
| _TARGET_VNDK_SP_DIR_32 = "/system/lib/vndk-sp" |
| _TARGET_VNDK_SP_DIR_64 = "/system/lib64/vndk-sp" |
| |
| # copied from development/vndk/tools/definition-tool/vndk_definition_tool.py |
| _LOW_LEVEL_NDK = [ |
| "libandroid_net.so", "libc.so", "libdl.so", "liblog.so", "libm.so", |
| "libstdc++.so", "libvndksupport.so", "libz.so" |
| ] |
| _SAME_PROCESS_HAL = [ |
| re.compile(p) |
| for p in [ |
| "android\\.hardware\\.graphics\\.mapper@\\d+\\.\\d+-impl\\.so$", |
| "gralloc\\..*\\.so$", "libEGL_.*\\.so$", "libGLES_.*\\.so$", |
| "libGLESv1_CM_.*\\.so$", "libGLESv2_.*\\.so$", |
| "libGLESv3_.*\\.so$", "libPVRRS\\.so$", "libRSDriver.*\\.so$", |
| "vulkan.*\\.so$" |
| ] |
| ] |
| _SAME_PROCESS_NDK = [ |
| "libEGL.so", "libGLESv1_CM.so", "libGLESv2.so", "libGLESv3.so", |
| "libnativewindow.so", "libsync.so", "libvulkan.so" |
| ] |
| _SP_HAL_LINK_PATHS_32 = [ |
| "/vendor/lib/egl", "/vendor/lib/hw", "/vendor/lib" |
| ] |
| _SP_HAL_LINK_PATHS_64 = [ |
| "/vendor/lib64/egl", "/vendor/lib64/hw", "/vendor/lib64" |
| ] |
| |
| class ElfObject(object): |
| """Contains dependencies of an ELF file on target device. |
| |
| Attributes: |
| target_path: String. The path to the ELF file on target. |
| name: String. File name of the ELF. |
| target_dir: String. The directory containing the ELF file on target. |
| bitness: Integer. Bitness of the ELF. |
| deps: List of strings. The names of the depended libraries. |
| """ |
| |
| def __init__(self, target_path, bitness, deps): |
| self.target_path = target_path |
| self.name = path_utils.TargetBaseName(target_path) |
| self.target_dir = path_utils.TargetDirName(target_path) |
| self.bitness = bitness |
| self.deps = deps |
| |
| def setUpClass(self): |
| """Initializes device and temporary directory.""" |
| self._dut = self.registerController(android_device)[0] |
| self._dut.shell.InvokeTerminal("one") |
| self._shell = self._dut.shell.one |
| self._temp_dir = tempfile.mkdtemp() |
| logging.info("adb pull %s %s", self._TARGET_VENDOR_DIR, self._temp_dir) |
| pull_output = self._dut.adb.pull(self._TARGET_VENDOR_DIR, |
| self._temp_dir) |
| logging.debug(pull_output) |
| |
| def tearDownClass(self): |
| """Deletes the temporary directory.""" |
| logging.info("Delete %s", self._temp_dir) |
| shutil.rmtree(self._temp_dir) |
| |
| def _loadElfObjects(self, host_dir, target_dir, elf_error_handler): |
| """Scans a host directory recursively and loads all ELF files in it. |
| |
| Args: |
| host_dir: The host directory to scan. |
| target_dir: The path from which host_dir is copied. |
| elf_error_handler: A function that takes 2 arguments |
| (target_path, exception). It is called when |
| the parser fails to read an ELF file. |
| |
| Returns: |
| List of ElfObject. |
| """ |
| objs = [] |
| for root_dir, file_name in utils.iterate_files(host_dir): |
| full_path = os.path.join(root_dir, file_name) |
| rel_path = os.path.relpath(full_path, host_dir) |
| target_path = path_utils.JoinTargetPath( |
| target_dir, *rel_path.split(os.path.sep)) |
| try: |
| elf = elf_parser.ElfParser(full_path) |
| except elf_parser.ElfError: |
| logging.debug("%s is not an ELF file", target_path) |
| continue |
| try: |
| deps = elf.ListDependencies() |
| except elf_parser.ElfError as e: |
| elf_error_handler(target_path, e) |
| continue |
| finally: |
| elf.Close() |
| |
| logging.info("%s depends on: %s", target_path, ", ".join(deps)) |
| objs.append(self.ElfObject(target_path, elf.bitness, deps)) |
| return objs |
| |
| def _isAllowedSpHalDependency(self, lib_name, vndk_sp_names, |
| linkable_libs): |
| """Checks whether a same-process HAL library dependency is allowed. |
| |
| A same-process HAL library is allowed to depend on |
| - Low-level NDK |
| - Same-process NDK |
| - vndk-sp |
| - Other libraries in vendor/lib[64] |
| |
| Args: |
| lib_name: String. The name of the depended library. |
| vndk_sp_names: Set of strings. The names of the libraries in |
| vndk-sp directory. |
| linkable_libs: Dictionary. The keys are the names of the libraries |
| which can be linked to same-process HAL. |
| |
| Returns: |
| A boolean representing whether the dependency is allowed. |
| """ |
| if (lib_name in self._LOW_LEVEL_NDK or |
| lib_name in self._SAME_PROCESS_NDK or |
| lib_name in vndk_sp_names or lib_name in linkable_libs): |
| return True |
| return False |
| |
| def _getTargetVndkSpDir(self, bitness): |
| """Returns 32/64-bit vndk-sp directory path on target device.""" |
| return getattr(self, "_TARGET_VNDK_SP_DIR_" + str(bitness)) |
| |
| def _getSpHalLinkPaths(self, bitness): |
| """Returns 32/64-bit same-process HAL link paths""" |
| return getattr(self, "_SP_HAL_LINK_PATHS_" + str(bitness)) |
| |
| def _isInSpHalLinkPaths(self, lib): |
| """Checks whether a library can be linked to same-process HAL. |
| |
| Args: |
| lib: ElfObject. The library to check. |
| |
| Returns: |
| True if can be linked to same-process HAL; False otherwise. |
| """ |
| return lib.target_dir in self._getSpHalLinkPaths(lib.bitness) |
| |
| def _spHalLinkOrder(self, lib): |
| """Returns the key for sorting libraries in linker search order. |
| |
| Args: |
| lib: ElfObject. |
| |
| Returns: |
| An integer representing linker search order. |
| """ |
| link_paths = self._getSpHalLinkPaths(lib.bitness) |
| for order in range(len(link_paths)): |
| if lib.target_dir == link_paths[order]: |
| return order |
| order = len(link_paths) |
| if lib.name in self._LOW_LEVEL_NDK: |
| return order |
| order += 1 |
| if lib.name in self._SAME_PROCESS_NDK: |
| return order |
| order += 1 |
| return order |
| |
| def _dfsDependencies(self, lib, searched, searchable): |
| """Depth-first-search for library dependencies. |
| |
| Args: |
| lib: ElfObject. The library to search dependencies. |
| searched: The set of searched libraries. |
| searchable: The dictionary that maps file names to libraries. |
| """ |
| if lib in searched: |
| return |
| searched.add(lib) |
| for dep_name in lib.deps: |
| if dep_name in searchable: |
| self._dfsDependencies(searchable[dep_name], searched, |
| searchable) |
| |
| def _testSpHalDependency(self, bitness, objs): |
| """Scans same-process HAL dependency on vendor partition. |
| |
| Returns: |
| List of tuples (path, dependency_names). The library with |
| disallowed dependencies and list of the dependencies. |
| """ |
| vndk_sp_dir = self._getTargetVndkSpDir(bitness) |
| vndk_sp_paths = target_file_utils.FindFiles(self._shell, vndk_sp_dir, |
| "*.so") |
| vndk_sp_names = set( |
| path_utils.TargetBaseName(x) for x in vndk_sp_paths) |
| logging.info("%s libraries: %s" % (vndk_sp_dir, |
| ", ".join(vndk_sp_names))) |
| # map file names to libraries which can be linked to same-process HAL |
| linkable_libs = dict() |
| for obj in [ |
| x for x in objs |
| if x.bitness == bitness and self._isInSpHalLinkPaths(x) |
| ]: |
| if obj.name not in linkable_libs: |
| linkable_libs[obj.name] = obj |
| else: |
| linkable_libs[obj.name] = min( |
| linkable_libs[obj.name], obj, key=self._spHalLinkOrder) |
| # find same-process HAL and dependencies |
| sp_hal_libs = set() |
| for file_name, obj in linkable_libs.iteritems(): |
| if any([x.match(file_name) for x in self._SAME_PROCESS_HAL]): |
| self._dfsDependencies(obj, sp_hal_libs, linkable_libs) |
| logging.info("%d-bit SP HAL libraries: %s" % |
| (bitness, ", ".join([x.name for x in sp_hal_libs]))) |
| # check disallowed dependencies |
| dep_errors = [] |
| for obj in sp_hal_libs: |
| disallowed_libs = [ |
| x for x in obj.deps |
| if not self._isAllowedSpHalDependency(x, vndk_sp_names, |
| linkable_libs) |
| ] |
| if disallowed_libs: |
| dep_errors.append((obj.target_path, disallowed_libs)) |
| return dep_errors |
| |
| def testElfDependency(self): |
| """Scans library/executable dependency on vendor partition.""" |
| read_errors = [] |
| objs = self._loadElfObjects( |
| self._temp_dir, |
| path_utils.TargetDirName(self._TARGET_VENDOR_DIR), |
| lambda p, e: read_errors.append((p, str(e)))) |
| |
| dep_errors = self._testSpHalDependency(32, objs) |
| if self._dut.is64Bit: |
| dep_errors.extend(self._testSpHalDependency(64, objs)) |
| # TODO(hsinyichen): check other vendor libraries |
| |
| if read_errors: |
| logging.error("%d read errors:", len(read_errors)) |
| for x in read_errors: |
| logging.error("%s: %s", x[0], x[1]) |
| if dep_errors: |
| logging.error("%d disallowed dependencies:", len(dep_errors)) |
| for x in dep_errors: |
| logging.error("%s: %s", x[0], ", ".join(x[1])) |
| error_count = len(read_errors) + len(dep_errors) |
| asserts.assertEqual(error_count, 0, |
| "Total number of errors: " + str(error_count)) |
| |
| |
| if __name__ == "__main__": |
| test_runner.main() |