Home | History | Annotate | Download | only in dependency
      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 import logging
     19 import os
     20 import re
     21 import shutil
     22 import tempfile
     23 
     24 from vts.runners.host import asserts
     25 from vts.runners.host import base_test
     26 from vts.runners.host import test_runner
     27 from vts.runners.host import utils
     28 from vts.utils.python.controllers import android_device
     29 from vts.utils.python.file import file_utils
     30 from vts.utils.python.os import path_utils
     31 from vts.testcases.vndk.dependency import elf_parser
     32 
     33 
     34 class VtsVndkDependencyTest(base_test.BaseTestClass):
     35     """A test case to verify vendor library dependency.
     36 
     37     Attributes:
     38         _dut: The AndroidDevice under test.
     39         _shell: The ShellMirrorObject to execute commands
     40         _temp_dir: The temporary directory to which the vendor partition is
     41                    copied.
     42         _LOW_LEVEL_NDK: List of strings. The names of low-level NDK libraries in
     43                         /system/lib[64].
     44         _SAME_PROCESS_HAL: List of patterns. The names of same-process HAL
     45                            libraries expected to be in /vendor/lib[64].
     46         _SAME_PROCESS_NDK: List if strings. The names of same-process NDK
     47                            libraries in /system/lib[64].
     48     """
     49     _TARGET_VENDOR_DIR = "/vendor"
     50     _TARGET_VNDK_SP_DIR_32 = "/system/lib/vndk-sp"
     51     _TARGET_VNDK_SP_DIR_64 = "/system/lib64/vndk-sp"
     52 
     53     # copied from development/vndk/tools/definition-tool/vndk_definition_tool.py
     54     _LOW_LEVEL_NDK = [
     55         "libandroid_net.so",
     56         "libc.so",
     57         "libdl.so",
     58         "liblog.so",
     59         "libm.so",
     60         "libstdc++.so",
     61         "libvndksupport.so",
     62         "libz.so"
     63     ]
     64     _SAME_PROCESS_HAL = [re.compile(p) for p in [
     65         "android\\.hardware\\.graphics\\.mapper@\\d+\\.\\d+-impl\\.so$",
     66         "gralloc\\..*\\.so$",
     67         "libEGL_.*\\.so$",
     68         "libGLES_.*\\.so$",
     69         "libGLESv1_CM_.*\\.so$",
     70         "libGLESv2_.*\\.so$",
     71         "libGLESv3_.*\\.so$",
     72         "libPVRRS\\.so$",
     73         "libRSDriver.*\\.so$",
     74         "vulkan.*\\.so$"
     75     ]]
     76     _SAME_PROCESS_NDK = [
     77         "libEGL.so",
     78         "libGLESv1_CM.so",
     79         "libGLESv2.so",
     80         "libGLESv3.so",
     81         "libnativewindow.so",
     82         "libsync.so",
     83         "libvulkan.so"
     84     ]
     85     _SP_HAL_LINK_PATHS_32 = [
     86         "/vendor/lib/egl",
     87         "/vendor/lib/hw",
     88         "/vendor/lib"
     89     ]
     90     _SP_HAL_LINK_PATHS_64 = [
     91         "/vendor/lib64/egl",
     92         "/vendor/lib64/hw",
     93         "/vendor/lib64"
     94     ]
     95 
     96     class ElfObject(object):
     97         """Contains dependencies of an ELF file on target device.
     98 
     99         Attributes:
    100             target_path: String. The path to the ELF file on target.
    101             name: String. File name of the ELF.
    102             target_dir: String. The directory containing the ELF file on target.
    103             bitness: Integer. Bitness of the ELF.
    104             deps: List of strings. The names of the depended libraries.
    105         """
    106         def __init__(self, target_path, bitness, deps):
    107             self.target_path = target_path
    108             self.name = path_utils.TargetBaseName(target_path)
    109             self.target_dir = path_utils.TargetDirName(target_path)
    110             self.bitness = bitness
    111             self.deps = deps
    112 
    113     def setUpClass(self):
    114         """Initializes device and temporary directory."""
    115         self._dut = self.registerController(android_device)[0]
    116         self._dut.shell.InvokeTerminal("one")
    117         self._shell = self._dut.shell.one
    118         self._temp_dir = tempfile.mkdtemp()
    119         logging.info("adb pull %s %s", self._TARGET_VENDOR_DIR, self._temp_dir)
    120         pull_output = self._dut.adb.pull(
    121                 self._TARGET_VENDOR_DIR, self._temp_dir)
    122         logging.debug(pull_output)
    123 
    124     def tearDownClass(self):
    125         """Deletes the temporary directory."""
    126         logging.info("Delete %s", self._temp_dir)
    127         shutil.rmtree(self._temp_dir)
    128 
    129     def _loadElfObjects(self, host_dir, target_dir, elf_error_handler):
    130         """Scans a host directory recursively and loads all ELF files in it.
    131 
    132         Args:
    133             host_dir: The host directory to scan.
    134             target_dir: The path from which host_dir is copied.
    135             elf_error_handler: A function that takes 2 arguments
    136                                (target_path, exception). It is called when
    137                                the parser fails to read an ELF file.
    138 
    139         Returns:
    140             List of ElfObject.
    141         """
    142         objs = []
    143         for root_dir, file_name in utils.iterate_files(host_dir):
    144             full_path = os.path.join(root_dir, file_name)
    145             rel_path = os.path.relpath(full_path, host_dir)
    146             target_path = path_utils.JoinTargetPath(
    147                     target_dir, *rel_path.split(os.path.sep));
    148             try:
    149                 elf = elf_parser.ElfParser(full_path)
    150             except elf_parser.ElfError:
    151                 logging.debug("%s is not an ELF file", target_path)
    152                 continue
    153             try:
    154                 deps = elf.listDependencies()
    155             except elf_parser.ElfError as e:
    156                 elf_error_handler(target_path, e)
    157                 continue
    158             finally:
    159                 elf.close()
    160 
    161             logging.info("%s depends on: %s", target_path, ", ".join(deps))
    162             objs.append(self.ElfObject(target_path, elf.bitness, deps))
    163         return objs
    164 
    165     def _isAllowedSpHalDependency(self, lib_name, vndk_sp_names, linkable_libs):
    166         """Checks whether a same-process HAL library dependency is allowed.
    167 
    168         A same-process HAL library is allowed to depend on
    169         - Low-level NDK
    170         - Same-process NDK
    171         - vndk-sp
    172         - Other libraries in vendor/lib[64]
    173 
    174         Args:
    175             lib_name: String. The name of the depended library.
    176             vndk_sp_names: Set of strings. The names of the libraries in
    177                            vndk-sp directory.
    178             linkable_libs: Dictionary. The keys are the names of the libraries
    179                            which can be linked to same-process HAL.
    180 
    181         Returns:
    182             A boolean representing whether the dependency is allowed.
    183         """
    184         if (lib_name in self._LOW_LEVEL_NDK or
    185             lib_name in self._SAME_PROCESS_NDK or
    186             lib_name in vndk_sp_names or
    187             lib_name in linkable_libs):
    188             return True
    189         return False
    190 
    191     def _getTargetVndkSpDir(self, bitness):
    192         """Returns 32/64-bit vndk-sp directory path on target device."""
    193         return getattr(self, "_TARGET_VNDK_SP_DIR_" + str(bitness))
    194 
    195     def _getSpHalLinkPaths(self, bitness):
    196         """Returns 32/64-bit same-process HAL link paths"""
    197         return getattr(self, "_SP_HAL_LINK_PATHS_" + str(bitness))
    198 
    199     def _isInSpHalLinkPaths(self, lib):
    200         """Checks whether a library can be linked to same-process HAL.
    201 
    202         Args:
    203             lib: ElfObject. The library to check.
    204 
    205         Returns:
    206             True if can be linked to same-process HAL; False otherwise.
    207         """
    208         return lib.target_dir in self._getSpHalLinkPaths(lib.bitness)
    209 
    210     def _spHalLinkOrder(self, lib):
    211         """Returns the key for sorting libraries in linker search order.
    212 
    213         Args:
    214             lib: ElfObject.
    215 
    216         Returns:
    217             An integer representing linker search order.
    218         """
    219         link_paths = self._getSpHalLinkPaths(lib.bitness)
    220         for order in range(len(link_paths)):
    221             if lib.target_dir == link_paths[order]:
    222                 return order
    223         order = len(link_paths)
    224         if lib.name in self._LOW_LEVEL_NDK:
    225             return order
    226         order += 1
    227         if lib.name in self._SAME_PROCESS_NDK:
    228             return order
    229         order += 1
    230         return order
    231 
    232     def _dfsDependencies(self, lib, searched, searchable):
    233         """Depth-first-search for library dependencies.
    234 
    235         Args:
    236             lib: ElfObject. The library to search dependencies.
    237             searched: The set of searched libraries.
    238             searchable: The dictionary that maps file names to libraries.
    239         """
    240         if lib in searched:
    241             return
    242         searched.add(lib)
    243         for dep_name in lib.deps:
    244             if dep_name in searchable:
    245                 self._dfsDependencies(
    246                         searchable[dep_name], searched, searchable)
    247 
    248     def _testSpHalDependency(self, bitness, objs):
    249         """Scans same-process HAL dependency on vendor partition.
    250 
    251         Returns:
    252             List of tuples (path, dependency_names). The library with
    253             disallowed dependencies and list of the dependencies.
    254         """
    255         vndk_sp_dir = self._getTargetVndkSpDir(bitness)
    256         vndk_sp_paths = file_utils.FindFiles(self._shell, vndk_sp_dir, "*.so")
    257         vndk_sp_names = set(path_utils.TargetBaseName(x) for x in vndk_sp_paths)
    258         logging.info("%s libraries: %s" % (
    259                 vndk_sp_dir, ", ".join(vndk_sp_names)))
    260         # map file names to libraries which can be linked to same-process HAL
    261         linkable_libs = dict()
    262         for obj in [x for x in objs
    263                     if x.bitness == bitness and self._isInSpHalLinkPaths(x)]:
    264             if obj.name not in linkable_libs:
    265                 linkable_libs[obj.name] = obj
    266             else:
    267                 linkable_libs[obj.name] = min(linkable_libs[obj.name], obj,
    268                                               key=self._spHalLinkOrder)
    269         # find same-process HAL and dependencies
    270         sp_hal_libs = set()
    271         for file_name, obj in linkable_libs.iteritems():
    272             if any([x.match(file_name) for x in self._SAME_PROCESS_HAL]):
    273                 self._dfsDependencies(obj, sp_hal_libs, linkable_libs)
    274         logging.info("%d-bit SP HAL libraries: %s" % (
    275                 bitness, ", ".join([x.name for x in sp_hal_libs])))
    276         # check disallowed dependencies
    277         dep_errors = []
    278         for obj in sp_hal_libs:
    279             disallowed_libs = [x for x in obj.deps
    280                     if not self._isAllowedSpHalDependency(x, vndk_sp_names,
    281                                                           linkable_libs)]
    282             if disallowed_libs:
    283                 dep_errors.append((obj.target_path, disallowed_libs))
    284         return dep_errors
    285 
    286     def testElfDependency(self):
    287         """Scans library/executable dependency on vendor partition."""
    288         read_errors = []
    289         objs = self._loadElfObjects(
    290                 self._temp_dir,
    291                 path_utils.TargetDirName(self._TARGET_VENDOR_DIR),
    292                 lambda p, e: read_errors.append((p, str(e))))
    293 
    294         dep_errors = self._testSpHalDependency(32, objs)
    295         if self._dut.is64Bit:
    296             dep_errors.extend(self._testSpHalDependency(64, objs))
    297         # TODO(hsinyichen): check other vendor libraries
    298 
    299         if read_errors:
    300             logging.error("%d read errors:", len(read_errors))
    301             for x in read_errors:
    302                 logging.error("%s: %s", x[0], x[1])
    303         if dep_errors:
    304             logging.error("%d disallowed dependencies:", len(dep_errors))
    305             for x in dep_errors:
    306                 logging.error("%s: %s", x[0], ", ".join(x[1]))
    307         error_count = len(read_errors) + len(dep_errors)
    308         asserts.assertEqual(error_count, 0,
    309                 "Total number of errors: " + str(error_count))
    310 
    311 
    312 if __name__ == "__main__":
    313     test_runner.main()
    314