blob: 4ccea2af086c5492c7d44493933f83c7ff5f1359 [file] [log] [blame]
#!/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 target_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 development/vndk/tools/definition-tool/vndk_definition_tool.py
_LOW_LEVEL_NDK = [
"libandroid_net.so", "libc.so", "libdl.so", "liblog.so", "libm.so",
"libstdc++.so", "libvndksupport.so", "libz.so"
]
_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 = target_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()