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