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 test_runner
27from vts.runners.host import utils
28from vts.utils.python.controllers import android_device
29from vts.utils.python.file import file_utils
30from vts.utils.python.os import path_utils
31from vts.testcases.vndk.dependency import elf_parser
32
33
34class 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
312if __name__ == "__main__":
313    test_runner.main()
314