summaryrefslogtreecommitdiff
path: root/dependency/VtsVndkDependencyTest.py
blob: f376dbe43d3b49589f8f32f3b06fbc7e8e0edd4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#!/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 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 build/soong/cc/config/global.go
    _LOW_LEVEL_NDK = [
        "ld-android.so",
        "libc.so",
        "libdl.so",
        "liblog.so",
        "libm.so",
        "libz.so"
    ]
    # copied from development/vndk/tools/definition-tool/vndk_definition_tool.py
    _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 = 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()