blob: 9e8e8519ab7fa696c3b0e3a38ee2ce5c9b224369 [file] [log] [blame]
#!/usr/bin/env python3
from __future__ import print_function
import argparse
import collections
import mmap
import os
import re
import stat
import struct
import sys
if sys.version_info >= (3, 0):
from os import makedirs
from mmap import ACCESS_READ, mmap
else:
from mmap import ACCESS_READ, mmap
def makedirs(path, exist_ok):
if exist_ok and os.path.isdir(path):
return
return os.makedirs(path)
class mmap(mmap):
def __enter__(self):
return self
def __exit__(self, exc, value, tb):
self.close()
def __getitem__(self, key):
res = super(mmap, self).__getitem__(key)
if type(key) == int:
return ord(res)
return res
FileNotFoundError = OSError
try:
from sys import intern
except ImportError:
pass
EI_CLASS = 4
EI_DATA = 5
ELFCLASSNONE = 0
ELFCLASS32 = 1
ELFCLASS64 = 2
ELFDATANONE = 0
ELFDATA2LSB = 1
ELFDATA2MSB = 2
DT_NEEDED = 1
DT_RPATH = 15
DT_RUNPATH = 29
SHN_UNDEF = 0
STB_LOCAL = 0
STB_GLOBAL = 1
STB_WEAK = 2
Elf_Hdr = collections.namedtuple(
'Elf_Hdr',
'ei_class ei_data ei_version ei_osabi e_type e_machine e_version ' +
'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum ' +
'e_shentsize e_shnum e_shstridx')
Elf_Shdr = collections.namedtuple(
'Elf_Shdr',
'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info ' +
'sh_addralign sh_entsize')
Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val')
class Elf_Sym(object):
__slots__ = (
'st_name', 'st_value', 'st_size', 'st_info', 'st_other', 'st_shndx'
)
def __init__(self, st_name, st_value, st_size, st_info, st_other, st_shndx):
self.st_name = st_name
self.st_value = st_value
self.st_size = st_size
self.st_info = st_info
self.st_other = st_other
self.st_shndx = st_shndx
def __str__(self):
return ('Elf_Sym(' +
'st_name=' + repr(self.st_name) + ', '
'st_value=' + repr(self.st_value) + ', '
'st_size=' + repr(self.st_size) + ', '
'st_info=' + repr(self.st_info) + ', '
'st_other=' + repr(self.st_other) + ', '
'st_shndx=' + repr(self.st_shndx) + ')')
@staticmethod
def _make(p):
return Elf_Sym(*p)
@property
def st_bind(self):
return (self.st_info >> 4)
def _get_elf_class_name(ei_class):
if ei_class == ELFCLASS32:
return '32'
if ei_class == ELFCLASS64:
return '64'
return 'None'
def _get_elf_data_name(ei_data):
if ei_data == ELFDATA2LSB:
return 'Little-Endian'
if ei_data == ELFDATA2MSB:
return 'Big-Endian'
return 'None'
_ELF_MACHINE_ID_TABLE = {
0: 'EM_NONE',
3: 'EM_386',
8: 'EM_MIPS',
40: 'EM_ARM',
62: 'EM_X86_64',
183: 'EM_AARCH64',
}
def _get_elf_machine_name(e_machine):
return _ELF_MACHINE_ID_TABLE.get(e_machine, str(e_machine))
def _extract_zero_end_slice(buf, offset):
end = offset
try:
while buf[end] != 0:
end += 1
except IndexError:
pass
return buf[offset:end]
if sys.version_info >= (3, 0):
def _extract_zero_end_str(buf, offset):
return intern(_extract_zero_end_slice(buf, offset).decode('utf-8'))
else:
def _extract_zero_end_str(buf, offset):
return intern(_extract_zero_end_slice(buf, offset))
class ELFError(ValueError):
pass
class ELF(object):
def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0,
dt_rpath=None, dt_runpath=None, dt_needed=None,
exported_symbols=None):
self.ei_class = ei_class
self.ei_data = ei_data
self.e_machine = e_machine
self.dt_rpath = dt_rpath
self.dt_runpath = dt_runpath
self.dt_needed = dt_needed if dt_needed is not None else []
self.exported_symbols = \
exported_symbols if exported_symbols is not None else []
def __str__(self):
return ('ELF(' +
'ei_class=' + repr(self.ei_class) + ', ' +
'ei_data=' + repr(self.ei_data) + ', ' +
'e_machine=' + repr(self.e_machine) + ', ' +
'dt_rpath=' + repr(self.dt_rpath) + ', ' +
'dt_runpath=' + repr(self.dt_runpath) + ', ' +
'dt_needed=' + repr(self.dt_needed) + ')')
def dump(self, file=None):
file = file if file is not None else sys.stdout
print('EI_CLASS\t' + _get_elf_class_name(self.ei_class), file=file)
print('EI_DATA\t\t' + _get_elf_data_name(self.ei_data), file=file)
print('E_MACHINE\t' + _get_elf_machine_name(self.e_machine), file=file)
if self.dt_rpath:
print('DT_RPATH\t' + self.dt_rpath, file=file)
if self.dt_runpath:
print('DT_RUNPATH\t' + self.dt_runpath, file=file)
for dt_needed in self.dt_needed:
print('DT_NEEDED\t' + dt_needed, file=file)
for symbol in self.exported_symbols:
print('SYMBOL\t\t' + symbol, file=file)
def dump_exported_symbols(self, file=None):
file = file if file is not None else sys.stdout
for symbol in self.exported_symbols:
print(symbol, file=file)
def _parse_from_buf_internal(self, buf):
# Check ELF ident.
if buf.size() < 8:
raise ELFError('bad ident')
if buf[0:4] != b'\x7fELF':
raise ELFError('bad magic')
self.ei_class = buf[EI_CLASS]
if self.ei_class != ELFCLASS32 and self.ei_class != ELFCLASS64:
raise ELFError('unknown word size')
self.ei_data = buf[EI_DATA]
if self.ei_data != ELFDATA2LSB and self.ei_data != ELFDATA2MSB:
raise ELFError('unknown endianness')
# ELF structure definitions.
endian_fmt = '<' if self.ei_data == ELFDATA2LSB else '>'
if self.ei_class == ELFCLASS32:
elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH'
elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL'
elf_dyn_fmt = endian_fmt + 'lL'
elf_sym_fmt = endian_fmt + 'LLLBBH'
else:
elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH'
elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ'
elf_dyn_fmt = endian_fmt + 'QQ'
elf_sym_fmt = endian_fmt + 'LBBHQQ'
def parse_struct(cls, fmt, offset, error_msg):
try:
return cls._make(struct.unpack_from(fmt, buf, offset))
except struct.error:
raise ELFError(error_msg)
def parse_elf_hdr(offset):
return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header')
def parse_elf_shdr(offset):
return parse_struct(Elf_Shdr, elf_shdr_fmt, offset,
'bad section header')
def parse_elf_dyn(offset):
return parse_struct(Elf_Dyn, elf_dyn_fmt, offset,
'bad .dynamic entry')
if self.ei_class == ELFCLASS32:
def parse_elf_sym(offset):
return parse_struct(Elf_Sym, elf_sym_fmt, offset, 'bad elf sym')
else:
def parse_elf_sym(offset):
try:
p = struct.unpack_from(elf_sym_fmt, buf, offset)
return Elf_Sym(p[0], p[4], p[5], p[1], p[2], p[3])
except struct.error:
raise ELFError('bad elf sym')
def extract_str(offset):
return _extract_zero_end_str(buf, offset)
# Parse ELF header.
header = parse_elf_hdr(0)
self.e_machine = header.e_machine
# Check section header size.
if header.e_shentsize == 0:
raise ELFError('no section header')
# Find .shstrtab section.
shstrtab_shdr_off = \
header.e_shoff + header.e_shstridx * header.e_shentsize
shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off)
shstrtab_off = shstrtab_shdr.sh_offset
# Parse ELF section header.
sections = dict()
header_end = header.e_shoff + header.e_shnum * header.e_shentsize
for shdr_off in range(header.e_shoff, header_end, header.e_shentsize):
shdr = parse_elf_shdr(shdr_off)
name = extract_str(shstrtab_off + shdr.sh_name)
sections[name] = shdr
# Find .dynamic and .dynstr section header.
dynamic_shdr = sections.get('.dynamic')
if not dynamic_shdr:
raise ELFError('no .dynamic section')
dynstr_shdr = sections.get('.dynstr')
if not dynstr_shdr:
raise ELFError('no .dynstr section')
dynamic_off = dynamic_shdr.sh_offset
dynstr_off = dynstr_shdr.sh_offset
# Parse entries in .dynamic section.
assert struct.calcsize(elf_dyn_fmt) == dynamic_shdr.sh_entsize
dynamic_end = dynamic_off + dynamic_shdr.sh_size
for ent_off in range(dynamic_off, dynamic_end, dynamic_shdr.sh_entsize):
ent = parse_elf_dyn(ent_off)
if ent.d_tag == DT_NEEDED:
self.dt_needed.append(extract_str(dynstr_off + ent.d_val))
elif ent.d_tag == DT_RPATH:
self.dt_rpath = extract_str(dynstr_off + ent.d_val)
elif ent.d_tag == DT_RUNPATH:
self.dt_runpath = extract_str(dynstr_off + ent.d_val)
# Parse exported symbols in .dynsym section.
dynsym_shdr = sections.get('.dynsym')
if dynsym_shdr:
exported_symbols = []
dynsym_off = dynsym_shdr.sh_offset
dynsym_end = dynsym_off + dynsym_shdr.sh_size
dynsym_entsize = dynsym_shdr.sh_entsize
for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize):
ent = parse_elf_sym(ent_off)
if ent.st_bind != STB_LOCAL and ent.st_shndx != SHN_UNDEF:
exported_symbols.append(
extract_str(dynstr_off + ent.st_name))
exported_symbols.sort()
self.exported_symbols = exported_symbols
def _parse_from_buf(self, buf):
try:
self._parse_from_buf_internal(buf)
except IndexError:
raise ELFError('bad offset')
def _parse_from_file(self, path):
with open(path, 'rb') as f:
st = os.fstat(f.fileno())
if not st.st_size:
raise ELFError('empty file')
with mmap(f.fileno(), st.st_size, access=ACCESS_READ) as image:
self._parse_from_buf(image)
@staticmethod
def load(path):
elf = ELF()
elf._parse_from_file(path)
return elf
@staticmethod
def loads(buf):
elf = ELF()
elf._parse_from_buf(buf)
return elf
PT_SYSTEM = 0
PT_VENDOR = 1
NUM_PARTITIONS = 2
NDK_LOW_LEVEL = {
'libc.so', 'libstdc++.so', 'libdl.so', 'liblog.so', 'libm.so', 'libz.so',
}
NDK_HIGH_LEVEL = {
'libandroid.so', 'libcamera2ndk.so', 'libEGL.so', 'libGLESv1_CM.so',
'libGLESv2.so', 'libGLESv3.so', 'libjnigraphics.so', 'libmediandk.so',
'libOpenMAXAL.so', 'libOpenSLES.so', 'libvulkan.so',
}
def _is_ndk_lib(path):
lib_name = os.path.basename(path)
return lib_name in NDK_LOW_LEVEL or lib_name in NDK_HIGH_LEVEL
BannedLib = collections.namedtuple(
'BannedLib', ('name', 'reason', 'action',))
BA_WARN = 0
BA_EXCLUDE = 1
class BannedLibDict(object):
def __init__(self):
self.banned_libs = dict()
def add(self, name, reason, action):
self.banned_libs[name] = BannedLib(name, reason, action)
def get(self, name):
return self.banned_libs.get(name, None)
@staticmethod
def create_default():
d = BannedLibDict()
d.add('libbinder.so', 'un-versioned IPC', BA_WARN)
d.add('libselinux.so', 'policydb might be incompatible', BA_WARN)
return d
def is_accessible(path):
try:
mode = os.stat(path).st_mode
return (mode & (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)) != 0
except FileNotFoundError:
return False
def scan_executables(root):
for base, dirs, files in os.walk(root):
for filename in files:
path = os.path.join(base, filename)
if is_accessible(path):
yield path
class GraphNode(object):
def __init__(self, partition, path, elf):
self.partition = partition
self.path = path
self.elf = elf
self.deps = set()
self.users = set()
self.is_ndk = _is_ndk_lib(path)
def add_dep(self, dst):
self.deps.add(dst)
dst.users.add(self)
def sorted_lib_path_list(libs):
libs = [lib.path for lib in libs]
libs.sort()
return libs
class Graph(object):
def __init__(self):
self.lib32 = dict()
self.lib64 = dict()
self.lib_pt = [dict() for i in range(NUM_PARTITIONS)]
def add(self, partition, path, elf):
node = GraphNode(partition, path, elf)
if elf.ei_class == ELFCLASS32:
self.lib32[path] = node
else:
self.lib64[path] = node
self.lib_pt[partition][path] = node
def add_dep(self, src_path, dst_path):
for lib_set in (self.lib32, self.lib64):
src = lib_set.get(src_path)
dst = lib_set.get(dst_path)
if src and dst:
src.add_dep(dst)
def map_path_to_lib(self, path):
for lib_set in (self.lib32, self.lib64):
lib = lib_set.get(path)
if lib:
return lib
return None
def map_paths_to_libs(self, paths, report_error):
result = set()
for path in paths:
lib = self.map_path_to_lib(path)
if not lib:
report_error(path)
continue
result.add(lib)
return result
@staticmethod
def _compile_path_matcher(root, subdirs):
dirs = [os.path.normpath(os.path.join(root, i)) for i in subdirs]
patts = ['(?:' + re.escape(i) + os.sep + ')' for i in dirs]
return re.compile('|'.join(patts))
def add_executables_in_dir(self, partition_name, partition, root,
alter_partition, alter_subdirs):
root = os.path.abspath(root)
prefix_len = len(root) + 1
if alter_subdirs:
alter_patt = Graph._compile_path_matcher(root, alter_subdirs)
for path in scan_executables(root):
try:
elf = ELF.load(path)
except ELFError as e:
continue
short_path = os.path.join('/', partition_name, path[prefix_len:])
if alter_subdirs and alter_patt.match(path):
self.add(alter_partition, short_path, elf)
else:
self.add(partition, short_path, elf)
def load_extra_deps(self, path):
patt = re.compile('([^:]*):\\s*(.*)')
with open(path, 'r') as f:
for line in f:
match = patt.match(line)
if match:
self.add_dep(match.group(1), match.group(2))
def _resolve_deps_lib_set(self, lib_set, system_lib, vendor_lib):
for lib in lib_set.values():
for dt_needed in lib.elf.dt_needed:
candidates = [
dt_needed,
os.path.join(system_lib, dt_needed),
os.path.join(vendor_lib, dt_needed),
]
for candidate in candidates:
dep = lib_set.get(candidate)
if dep:
break
if not dep:
print('warning: {}: Missing needed library: {} Tried: {}'
.format(lib.path, dt_needed, candidates),
file=sys.stderr)
continue
lib.add_dep(dep)
def resolve_deps(self):
self._resolve_deps_lib_set(self.lib32, '/system/lib', '/vendor/lib')
self._resolve_deps_lib_set(self.lib64, '/system/lib64',
'/vendor/lib64')
def compute_vndk_libs(self, generic_refs, banned_libs):
vndk_core = set()
vndk_ext = set()
def collect_lib_with_partition_user(result, lib_set, partition):
for lib in lib_set.values():
for user in lib.users:
if user.partition == partition:
result.add(lib)
break
# Check library usages from vendor to system.
collect_lib_with_partition_user(
vndk_core, self.lib_pt[PT_SYSTEM], PT_VENDOR)
# Check library usages from system to vendor.
collect_lib_with_partition_user(
vndk_ext, self.lib_pt[PT_VENDOR], PT_SYSTEM)
# Remove NDK libraries and banned libraries.
def is_not_vndk(lib):
return lib.is_ndk or banned_libs.get(os.path.basename(lib.path))
def remove_ndk_libs(libs):
return set(lib for lib in libs if not is_not_vndk(lib))
vndk_core = remove_ndk_libs(vndk_core)
vndk_ext = remove_ndk_libs(vndk_ext)
# Compute transitive closure.
def is_not_vndk_indirect(lib):
return is_not_vndk(lib) or lib in vndk_ext
def is_not_vndk_ext(lib):
return is_not_vndk(lib) or lib in vndk_core
vndk_indirect = self.compute_closure(vndk_core, is_not_vndk_indirect)
vndk_indirect -= vndk_core
vndk_ext = self.compute_closure(vndk_ext, is_not_vndk_ext)
# Move extended libraries from vndk_core to vndk_ext.
if generic_refs:
stack = list(vndk_core)
stacked = vndk_core
vndk_core = set()
while stack:
lib = stack.pop()
if generic_refs.is_equivalent_lib(lib):
vndk_core.add(lib)
continue
print('warning: {}: This is a VNDK extension and must be '
'moved to vendor partition.'.format(lib.path),
file=sys.stderr)
# Move the library from vndk_core to vndk_ext.
vndk_ext.add(lib)
for dep in lib.deps:
# Skip NDK or banned libraries.
if is_not_vndk(dep):
continue
# Skip vndk_ext and possibly vndk_core.
if dep in vndk_ext or dep in stacked:
continue
# Promote the dependency from vndk_indirect to vndk_core.
assert dep in vndk_indirect
vndk_indirect.remove(dep)
stack.append(dep)
stacked.add(dep)
return (vndk_core, vndk_indirect, vndk_ext)
@staticmethod
def compute_closure(root_set, is_excluded):
closure = set(root_set)
stack = list(root_set)
while stack:
lib = stack.pop()
for dep in lib.deps:
if is_excluded(dep):
continue
if dep not in closure:
closure.add(dep)
stack.append(dep)
return closure
@staticmethod
def create(system_dirs=None, system_dirs_as_vendor=None, vendor_dirs=None,
vendor_dirs_as_system=None, extra_deps=None):
graph = Graph()
if system_dirs:
for path in system_dirs:
graph.add_executables_in_dir('system', PT_SYSTEM, path,
PT_VENDOR, system_dirs_as_vendor)
if vendor_dirs:
for path in vendor_dirs:
graph.add_executables_in_dir('vendor', PT_VENDOR, path,
PT_SYSTEM, vendor_dirs_as_system)
if extra_deps:
for path in extra_deps:
graph.load_extra_deps(path)
graph.resolve_deps()
return graph
class GenericRefs(object):
def __init__(self):
self.refs = dict()
def _load_from_dir(self, root):
root = os.path.abspath(root)
prefix_len = len(root) + 1
for base, dirnames, filenames in os.walk(root):
for filename in filenames:
if not filename.endswith('.sym'):
continue
path = os.path.join(base, filename)
lib_name = '/' + path[prefix_len:-4]
with open(path, 'r') as f:
self.refs[lib_name] = [line.strip() for line in f]
@staticmethod
def create_from_dir(root):
result = GenericRefs()
result._load_from_dir(root)
return result
def is_equivalent_lib(self, lib):
return self.refs.get(lib.path) == lib.elf.exported_symbols
class Command(object):
def __init__(self, name, help):
self.name = name
self.help = help
def add_argparser_options(self, parser):
pass
def main(self, args):
return 0
class ELFDumpCommand(Command):
def __init__(self):
super(ELFDumpCommand, self).__init__(
'elfdump', help='Dump ELF .dynamic section')
def add_argparser_options(self, parser):
parser.add_argument('path', help='path to an ELF file')
def main(self, args):
try:
ELF.load(args.path).dump()
except ELFError as e:
print('error: {}: Bad ELF file ({})'.format(args.path, e),
file=sys.stderr)
sys.exit(1)
return 0
class CreateGenericRefCommand(Command):
def __init__(self):
super(CreateGenericRefCommand, self).__init__(
'create-generic-ref', help='Create generic references')
def add_argparser_options(self, parser):
parser.add_argument('dir')
parser.add_argument(
'--output', '-o', metavar='PATH', required=True,
help='output directory')
def main(self, args):
root = os.path.abspath(args.dir)
print(root)
prefix_len = len(root) + 1
for path in scan_executables(root):
name = path[prefix_len:]
try:
print('Processing:', name, file=sys.stderr)
elf = ELF.load(path)
out = os.path.join(args.output, name) + '.sym'
makedirs(os.path.dirname(out), exist_ok=True)
with open(out, 'w') as f:
elf.dump_exported_symbols(f)
except ELFError:
pass
return 0
class ELFGraphCommand(Command):
def add_argparser_options(self, parser):
parser.add_argument(
'--load-extra-deps', action='append',
help='load extra module dependencies')
parser.add_argument(
'--system', action='append',
help='path to system partition contents')
parser.add_argument(
'--vendor', action='append',
help='path to vendor partition contents')
parser.add_argument(
'--system-dir-as-vendor', action='append',
help='sub directory of system partition that has vendor files')
parser.add_argument(
'--vendor-dir-as-system', action='append',
help='sub directory of vendor partition that has system files')
class VNDKCommand(ELFGraphCommand):
def __init__(self):
super(VNDKCommand, self).__init__(
'vndk', help='Compute VNDK libraries set')
def add_argparser_options(self, parser):
super(VNDKCommand, self).add_argparser_options(parser)
parser.add_argument(
'--load-generic-refs',
help='compare with generic reference symbols')
parser.add_argument(
'--warn-incorrect-partition', action='store_true',
help='warn about libraries only have cross partition linkages')
parser.add_argument(
'--warn-high-level-ndk-deps', action='store_true',
help='warn about VNDK depends on high-level NDK')
parser.add_argument(
'--warn-banned-vendor-lib-deps', action='store_true',
help='warn when a vendor binaries depends on banned lib')
parser.add_argument(
'--ban-vendor-lib-dep', action='append',
help='library that must not be used by vendor binaries')
def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
for lib in lib_set.values():
if not lib.users:
continue
if all((user.partition != partition for user in lib.users)):
print(error_msg.format(lib.path), file=sys.stderr)
def _warn_incorrect_partition(self, graph):
self._warn_incorrect_partition_lib_set(
graph.lib_pt[PT_VENDOR], PT_VENDOR,
'warning: {}: This is a vendor library with framework-only '
'usages.')
self._warn_incorrect_partition_lib_set(
graph.lib_pt[PT_SYSTEM], PT_SYSTEM,
'warning: {}: This is a framework library with vendor-only '
'usages.')
def _warn_high_level_ndk_deps(self, lib_sets):
for lib_set in lib_sets:
for lib in lib_set:
for dep in lib.deps:
dep_name = os.path.basename(dep.path)
if dep_name in NDK_HIGH_LEVEL:
print('warning: {}: VNDK is using high-level NDK {}.'
.format(lib.path, dep.path), file=sys.stderr)
def _warn_banned_vendor_lib_deps(self, graph, banned_libs):
for lib in graph.lib_pt[PT_VENDOR].values():
for dep in lib.deps:
banned = banned_libs.get(os.path.basename(dep.path))
if banned:
print('warning: {}: Vendor binary depends on banned {} '
'(reason: {})'.format(
lib.path, dep.path, banned.reason),
file=sys.stderr)
def _check_ndk_extensions(self, graph, generic_refs):
for lib_set in (graph.lib32, graph.lib64):
for lib in lib_set.values():
if lib.is_ndk and not generic_refs.is_equivalent_lib(lib):
print('warning: {}: NDK library should not be extended.'
.format(lib.path), file=sys.stderr)
def main(self, args):
# Link ELF objects.
graph = Graph.create(args.system, args.system_dir_as_vendor,
args.vendor, args.vendor_dir_as_system,
args.load_extra_deps)
# Load the generic reference.
generic_refs = None
if args.load_generic_refs:
generic_refs = GenericRefs.create_from_dir(args.load_generic_refs)
self._check_ndk_extensions(graph, generic_refs)
# Create banned libraries.
if not args.ban_vendor_lib_dep:
banned_libs = BannedLibDict.create_default()
else:
banned_libs = BannedLibDict()
for name in args.ban_vendor_lib_dep:
banned_libs.add(name, 'user-banned', BA_WARN)
if args.warn_incorrect_partition:
self._warn_incorrect_partition(graph)
vndk_core, vndk_indirect, vndk_ext = \
graph.compute_vndk_libs(generic_refs, banned_libs)
if args.warn_high_level_ndk_deps:
self._warn_high_level_ndk_deps((vndk_core, vndk_indirect, vndk_ext))
if args.warn_banned_vendor_lib_deps:
self._warn_banned_vendor_lib_deps(graph, banned_libs)
for lib in sorted_lib_path_list(vndk_core):
print('vndk-core:', lib)
for lib in sorted_lib_path_list(vndk_indirect):
print('vndk-indirect:', lib)
for lib in sorted_lib_path_list(vndk_ext):
print('vndk-ext:', lib)
return 0
class DepsCommand(ELFGraphCommand):
def __init__(self):
super(DepsCommand, self).__init__(
'deps', help='Print binary dependencies for debugging')
def add_argparser_options(self, parser):
super(DepsCommand, self).add_argparser_options(parser)
parser.add_argument(
'--revert', action='store_true',
help='print usage dependency')
parser.add_argument(
'--leaf', action='store_true',
help='print binaries without dependencies or usages')
def main(self, args):
graph = Graph.create(args.system, args.system_dir_as_vendor,
args.vendor, args.vendor_dir_as_system,
args.load_extra_deps)
results = []
for partition in range(NUM_PARTITIONS):
for name, lib in graph.lib_pt[partition].items():
assoc_libs = lib.users if args.revert else lib.deps
results.append((name, sorted_lib_path_list(assoc_libs)))
results.sort()
if args.leaf:
for name, deps in results:
if not deps:
print(name)
else:
for name, deps in results:
print(name)
for dep in deps:
print('\t' + dep)
return 0
class DepsClosureCommand(ELFGraphCommand):
def __init__(self):
super(DepsClosureCommand, self).__init__(
'deps-closure', help='Find transitive closure of dependencies')
def add_argparser_options(self, parser):
super(DepsClosureCommand, self).add_argparser_options(parser)
parser.add_argument('lib', nargs='+',
help='root set of the shared libraries')
parser.add_argument('--exclude-lib', action='append', default=[],
help='libraries to be excluded')
parser.add_argument('--exclude-ndk', action='store_true',
help='exclude ndk libraries')
def main(self, args):
graph = Graph.create(args.system, args.system_dir_as_vendor,
args.vendor, args.vendor_dir_as_system,
args.load_extra_deps)
# Find root/excluded libraries by their paths.
def report_error(path):
print('error: no such lib: {}'.format(path), file=sys.stderr)
root_libs = graph.map_paths_to_libs(args.lib, report_error)
excluded_libs = graph.map_paths_to_libs(args.exclude_lib, report_error)
# Compute and print the closure.
if args.exclude_ndk:
def is_excluded_libs(lib):
return lib.is_ndk or lib in excluded_libs
else:
def is_excluded_libs(lib):
return lib in excluded_libs
closure = graph.compute_closure(root_libs, is_excluded_libs)
for lib in sorted_lib_path_list(closure):
print(lib)
return 0
class SpHalCommand(ELFGraphCommand):
def __init__(self):
super(SpHalCommand, self).__init__(
'sp-hal', help='Find transitive closure of same-process HALs')
def add_argparser_options(self, parser):
super(SpHalCommand, self).add_argparser_options(parser)
parser.add_argument('--closure', action='store_true',
help='show the closure')
def main(self, args):
graph = Graph.create(args.system, args.system_dir_as_vendor,
args.vendor, args.vendor_dir_as_system,
args.load_extra_deps)
# Find SP HALs.
name_patterns = (
'^/vendor/.*/libEGL_.*\\.so$',
'^/vendor/.*/libGLESv1_CM_.*\\.so$',
'^/vendor/.*/libGLESv2_.*\\.so$',
'^/vendor/.*/libGLESv3_.*\\.so$',
'^/vendor/.*/vulkan.*\\.so$',
'^/vendor/.*/libRSDriver.*\\.so$',
'^/vendor/.*/libPVRRS\\.so$', # libRSDriver
'^/vendor/.*/gralloc-mapper@\\d+.\\d+-impl\\.so$',
)
patt = re.compile('|'.join('(?:' + p + ')' for p in name_patterns))
# Find root/excluded libraries by their paths.
sp_hals = set()
for lib in graph.lib_pt[PT_VENDOR].values():
if patt.match(lib.path):
sp_hals.add(lib)
# Compute the closure (if specified).
if args.closure:
def is_excluded_libs(lib):
return lib.is_ndk
sp_hals = graph.compute_closure(sp_hals, is_excluded_libs)
# Print the result.
for lib in sorted_lib_path_list(sp_hals):
print(lib)
return 0
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcmd')
subcmds = dict()
def register_subcmd(cmd):
subcmds[cmd.name] = cmd
cmd.add_argparser_options(
subparsers.add_parser(cmd.name, help=cmd.help))
register_subcmd(ELFDumpCommand())
register_subcmd(CreateGenericRefCommand())
register_subcmd(VNDKCommand())
register_subcmd(DepsCommand())
register_subcmd(DepsClosureCommand())
register_subcmd(SpHalCommand())
args = parser.parse_args()
if not args.subcmd:
parser.print_help()
sys.exit(1)
return subcmds[args.subcmd].main(args)
if __name__ == '__main__':
sys.exit(main())