• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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