| #!/usr/bin/env python3 |
| |
| from __future__ import print_function |
| |
| import argparse |
| import collections |
| import itertools |
| import json |
| import os |
| import re |
| import shutil |
| import stat |
| import struct |
| import sys |
| |
| |
| #------------------------------------------------------------------------------ |
| # Python 2 and 3 Compatibility Layer |
| #------------------------------------------------------------------------------ |
| |
| 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 |
| |
| |
| #------------------------------------------------------------------------------ |
| # ELF Parser |
| #------------------------------------------------------------------------------ |
| |
| 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(collections.namedtuple( |
| 'ELF_Sym', 'st_name st_value st_size st_info st_other st_shndx')): |
| |
| STB_LOCAL = 0 |
| STB_GLOBAL = 1 |
| STB_WEAK = 2 |
| |
| SHN_UNDEF = 0 |
| |
| @property |
| def st_bind(self): |
| return (self.st_info >> 4) |
| |
| @property |
| def is_local(self): |
| return self.st_bind == Elf_Sym.STB_LOCAL |
| |
| @property |
| def is_global(self): |
| return self.st_bind == Elf_Sym.STB_GLOBAL |
| |
| @property |
| def is_weak(self): |
| return self.st_bind == Elf_Sym.STB_WEAK |
| |
| @property |
| def is_undef(self): |
| return self.st_shndx == Elf_Sym.SHN_UNDEF |
| |
| |
| class ELFError(ValueError): |
| pass |
| |
| |
| class ELF(object): |
| # ELF file format constants. |
| ELF_MAGIC = b'\x7fELF' |
| |
| 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 |
| |
| _ELF_CLASS_NAMES = { |
| ELFCLASS32: '32', |
| ELFCLASS64: '64', |
| } |
| |
| _ELF_DATA_NAMES = { |
| ELFDATA2LSB: 'Little-Endian', |
| ELFDATA2MSB: 'Big-Endian', |
| } |
| |
| _ELF_MACHINE_IDS = { |
| 0: 'EM_NONE', |
| 3: 'EM_386', |
| 8: 'EM_MIPS', |
| 40: 'EM_ARM', |
| 62: 'EM_X86_64', |
| 183: 'EM_AARCH64', |
| } |
| |
| |
| @staticmethod |
| def _dict_find_key_by_value(d, dst): |
| for key, value in d.items(): |
| if value == dst: |
| return key |
| raise KeyError(dst) |
| |
| @staticmethod |
| def get_ei_class_from_name(name): |
| return ELF._dict_find_key_by_value(ELF._ELF_CLASS_NAMES, name) |
| |
| @staticmethod |
| def get_ei_data_from_name(name): |
| return ELF._dict_find_key_by_value(ELF._ELF_DATA_NAMES, name) |
| |
| @staticmethod |
| def get_e_machine_from_name(name): |
| return ELF._dict_find_key_by_value(ELF._ELF_MACHINE_IDS, name) |
| |
| |
| __slots__ = ('ei_class', 'ei_data', 'e_machine', 'dt_rpath', 'dt_runpath', |
| 'dt_needed', 'exported_symbols', 'imported_symbols',) |
| |
| |
| def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0, |
| dt_rpath=None, dt_runpath=None, dt_needed=None, |
| exported_symbols=None, imported_symbols=None): |
| self.ei_class = ei_class |
| self.ei_data = ei_data |
| self.e_machine = e_machine |
| self.dt_rpath = dt_rpath if dt_rpath is not None else [] |
| self.dt_runpath = dt_runpath if dt_runpath is not None else [] |
| self.dt_needed = dt_needed if dt_needed is not None else [] |
| self.exported_symbols = \ |
| exported_symbols if exported_symbols is not None else set() |
| self.imported_symbols = \ |
| imported_symbols if imported_symbols is not None else set() |
| |
| def __repr__(self): |
| args = (a + '=' + repr(getattr(self, a)) for a in self.__slots__) |
| return 'ELF(' + ', '.join(args) + ')' |
| |
| def __eq__(self, rhs): |
| return all(getattr(self, a) == getattr(rhs, a) for a in self.__slots__) |
| |
| @property |
| def elf_class_name(self): |
| return self._ELF_CLASS_NAMES.get(self.ei_class, 'None') |
| |
| @property |
| def elf_data_name(self): |
| return self._ELF_DATA_NAMES.get(self.ei_data, 'None') |
| |
| @property |
| def elf_machine_name(self): |
| return self._ELF_MACHINE_IDS.get(self.e_machine, str(self.e_machine)) |
| |
| @property |
| def is_32bit(self): |
| return self.ei_class == ELF.ELFCLASS32 |
| |
| @property |
| def is_64bit(self): |
| return self.ei_class == ELF.ELFCLASS64 |
| |
| @property |
| def sorted_exported_symbols(self): |
| return sorted(list(self.exported_symbols)) |
| |
| @property |
| def sorted_imported_symbols(self): |
| return sorted(list(self.imported_symbols)) |
| |
| def dump(self, file=None): |
| """Print parsed ELF information to the file""" |
| file = file if file is not None else sys.stdout |
| |
| print('EI_CLASS\t' + self.elf_class_name, file=file) |
| print('EI_DATA\t\t' + self.elf_data_name, file=file) |
| print('E_MACHINE\t' + self.elf_machine_name, file=file) |
| for dt_rpath in self.dt_rpath: |
| print('DT_RPATH\t' + dt_rpath, file=file) |
| for dt_runpath in self.dt_runpath: |
| print('DT_RUNPATH\t' + dt_runpath, file=file) |
| for dt_needed in self.dt_needed: |
| print('DT_NEEDED\t' + dt_needed, file=file) |
| for symbol in self.sorted_exported_symbols: |
| print('EXP_SYMBOL\t' + symbol, file=file) |
| for symbol in self.sorted_imported_symbols: |
| print('IMP_SYMBOL\t' + symbol, file=file) |
| |
| # Extract zero-terminated buffer slice. |
| def _extract_zero_terminated_buf_slice(self, buf, offset): |
| """Extract a zero-terminated buffer slice from the given offset""" |
| end = offset |
| try: |
| while buf[end] != 0: |
| end += 1 |
| except IndexError: |
| pass |
| return buf[offset:end] |
| |
| # Extract c-style interned string from the buffer. |
| if sys.version_info >= (3, 0): |
| def _extract_zero_terminated_str(self, buf, offset): |
| """Extract a c-style string from the given buffer and offset""" |
| buf_slice = self._extract_zero_terminated_buf_slice(buf, offset) |
| return intern(buf_slice.decode('utf-8')) |
| else: |
| def _extract_zero_terminated_str(self, buf, offset): |
| """Extract a c-style string from the given buffer and offset""" |
| return intern(self._extract_zero_terminated_buf_slice(buf, offset)) |
| |
| def _parse_from_buf_internal(self, buf): |
| """Parse ELF image resides in the buffer""" |
| |
| # Check ELF ident. |
| if buf.size() < 8: |
| raise ELFError('bad ident') |
| |
| if buf[0:4] != ELF.ELF_MAGIC: |
| raise ELFError('bad magic') |
| |
| self.ei_class = buf[ELF.EI_CLASS] |
| if self.ei_class not in (ELF.ELFCLASS32, ELF.ELFCLASS64): |
| raise ELFError('unknown word size') |
| |
| self.ei_data = buf[ELF.EI_DATA] |
| if self.ei_data not in (ELF.ELFDATA2LSB, ELF.ELFDATA2MSB): |
| raise ELFError('unknown endianness') |
| |
| # ELF structure definitions. |
| endian_fmt = '<' if self.ei_data == ELF.ELFDATA2LSB else '>' |
| |
| if self.is_32bit: |
| 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.is_32bit: |
| 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 self._extract_zero_terminated_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 == ELF.DT_NEEDED: |
| self.dt_needed.append(extract_str(dynstr_off + ent.d_val)) |
| elif ent.d_tag == ELF.DT_RPATH: |
| self.dt_rpath.extend( |
| extract_str(dynstr_off + ent.d_val).split(':')) |
| elif ent.d_tag == ELF.DT_RUNPATH: |
| self.dt_runpath.extend( |
| extract_str(dynstr_off + ent.d_val).split(':')) |
| |
| # Parse exported symbols in .dynsym section. |
| dynsym_shdr = sections.get('.dynsym') |
| if dynsym_shdr: |
| exp_symbols = self.exported_symbols |
| imp_symbols = self.imported_symbols |
| |
| dynsym_off = dynsym_shdr.sh_offset |
| dynsym_end = dynsym_off + dynsym_shdr.sh_size |
| dynsym_entsize = dynsym_shdr.sh_entsize |
| |
| # Skip first symbol entry (null symbol). |
| dynsym_off += dynsym_entsize |
| |
| for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize): |
| ent = parse_elf_sym(ent_off) |
| symbol_name = extract_str(dynstr_off + ent.st_name) |
| if ent.is_undef: |
| imp_symbols.add(symbol_name) |
| elif not ent.is_local: |
| exp_symbols.add(symbol_name) |
| |
| def _parse_from_buf(self, buf): |
| """Parse ELF image resides in the buffer""" |
| try: |
| self._parse_from_buf_internal(buf) |
| except IndexError: |
| raise ELFError('bad offset') |
| |
| def _parse_from_file(self, path): |
| """Parse ELF image from the file 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) |
| |
| def _parse_from_dump_lines(self, path, lines): |
| patt = re.compile('^([A-Za-z_]+)\t+(.*)$') |
| for line_no, line in enumerate(lines): |
| match = patt.match(line) |
| if not match: |
| print('error: {}: {}: failed to parse' |
| .format(path, line_no + 1), file=sys.stderr) |
| continue |
| key = match.group(1) |
| value = match.group(2) |
| |
| if key == 'EI_CLASS': |
| self.ei_class = ELF.get_ei_class_from_name(value) |
| elif key == 'EI_DATA': |
| self.ei_data = ELF.get_ei_data_from_name(value) |
| elif key == 'E_MACHINE': |
| self.e_machine = ELF.get_e_machine_from_name(value) |
| elif key == 'DT_RPATH': |
| self.dt_rpath.append(intern(value)) |
| elif key == 'DT_RUNPATH': |
| self.dt_runpath.append(intern(value)) |
| elif key == 'DT_NEEDED': |
| self.dt_needed.append(intern(value)) |
| elif key == 'EXP_SYMBOL': |
| self.exported_symbols.add(intern(value)) |
| elif key == 'IMP_SYMBOL': |
| self.imported_symbols.add(intern(value)) |
| else: |
| print('error: {}: {}: unknown tag name: {}' |
| .format(path, line_no + 1, key), file=sys.stderr) |
| |
| def _parse_from_dump_file(self, path): |
| """Load information from ELF dump file.""" |
| with open(path, 'r') as f: |
| self._parse_from_dump_lines(path, f) |
| |
| def _parse_from_dump_buf(self, buf): |
| """Load information from ELF dump buffer.""" |
| self._parse_from_dump_lines('<str:0x{:x}>'.format(id(buf)), |
| buf.splitlines()) |
| |
| @staticmethod |
| def load(path): |
| """Create an ELF instance from the file path""" |
| elf = ELF() |
| elf._parse_from_file(path) |
| return elf |
| |
| @staticmethod |
| def loads(buf): |
| """Create an ELF instance from the buffer""" |
| elf = ELF() |
| elf._parse_from_buf(buf) |
| return elf |
| |
| @staticmethod |
| def load_dump(path): |
| """Create an ELF instance from a dump file path""" |
| elf = ELF() |
| elf._parse_from_dump_file(path) |
| return elf |
| |
| @staticmethod |
| def load_dumps(buf): |
| """Create an ELF instance from a dump file buffer""" |
| elf = ELF() |
| elf._parse_from_dump_buf(buf) |
| return elf |
| |
| |
| #------------------------------------------------------------------------------ |
| # NDK and Banned Libraries |
| #------------------------------------------------------------------------------ |
| |
| class NDKLibDict(object): |
| NOT_NDK = 0 |
| LL_NDK = 1 |
| SP_NDK = 2 |
| HL_NDK = 3 |
| |
| LL_NDK_LIB_NAMES = ( |
| 'libc.so', |
| 'libdl.so', |
| 'liblog.so', |
| 'libm.so', |
| 'libstdc++.so', |
| 'libz.so', |
| ) |
| |
| SP_NDK_LIB_NAMES = ( |
| 'libEGL.so', |
| 'libGLESv1_CM.so', |
| 'libGLESv2.so', |
| 'libGLESv3.so', |
| 'libvulkan.so', |
| ) |
| |
| HL_NDK_LIB_NAMES = ( |
| 'libOpenMAXAL.so', |
| 'libOpenSLES.so', |
| 'libandroid.so', |
| 'libcamera2ndk.so', |
| 'libjnigraphics.so', |
| 'libmediandk.so', |
| ) |
| |
| @staticmethod |
| def _create_pattern(names): |
| return '|'.join('(?:^\\/system\\/lib(?:64)?\\/' + re.escape(i) + '$)' |
| for i in names) |
| |
| @staticmethod |
| def _compile_path_matcher(names): |
| return re.compile(NDKLibDict._create_pattern(names)) |
| |
| @staticmethod |
| def _compile_multi_path_matcher(name_lists): |
| patt = '|'.join('(' + NDKLibDict._create_pattern(names) + ')' |
| for names in name_lists) |
| return re.compile(patt) |
| |
| def __init__(self): |
| self.ll_ndk_patterns = self._compile_path_matcher(self.LL_NDK_LIB_NAMES) |
| self.sp_ndk_patterns = self._compile_path_matcher(self.SP_NDK_LIB_NAMES) |
| self.hl_ndk_patterns = self._compile_path_matcher(self.HL_NDK_LIB_NAMES) |
| self.ndk_patterns = self._compile_multi_path_matcher( |
| (self.LL_NDK_LIB_NAMES, self.SP_NDK_LIB_NAMES, |
| self.HL_NDK_LIB_NAMES)) |
| |
| def is_ll_ndk(self, path): |
| return self.ll_ndk_patterns.match(path) |
| |
| def is_sp_ndk(self, path): |
| return self.sp_ndk_patterns.match(path) |
| |
| def is_hl_ndk(self, path): |
| return self.hl_ndk_patterns.match(path) |
| |
| def is_ndk(self, path): |
| return self.ndk_patterns.match(path) |
| |
| def classify(self, path): |
| match = self.ndk_patterns.match(path) |
| if not match: |
| return 0 |
| return match.lastindex |
| |
| NDK_LIBS = NDKLibDict() |
| |
| |
| 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) |
| |
| def is_banned(self, path): |
| return self.get(os.path.basename(path)) |
| |
| @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 |
| |
| |
| #------------------------------------------------------------------------------ |
| # ELF Linker |
| #------------------------------------------------------------------------------ |
| |
| 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_accessible_files(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 |
| |
| |
| def scan_elf_files(root): |
| for path in scan_accessible_files(root): |
| try: |
| yield (path, ELF.load(path)) |
| except ELFError: |
| pass |
| |
| |
| def scan_elf_dump_files(root): |
| for path in scan_accessible_files(root): |
| if not path.endswith('.sym'): |
| continue |
| yield (path[0:-4], ELF.load_dump(path)) |
| |
| |
| PT_SYSTEM = 0 |
| PT_VENDOR = 1 |
| NUM_PARTITIONS = 2 |
| |
| |
| VNDKResult = collections.namedtuple( |
| 'VNDKResult', |
| 'sp_hal sp_hal_dep sp_hal_vndk_stable sp_ndk sp_ndk_vndk_stable ' |
| 'sp_both_vndk_stable ' |
| 'extra_vendor_lib vndk_core vndk_indirect vndk_fwk_ext vndk_vnd_ext') |
| |
| def print_vndk_lib(vndk_lib, file=sys.stdout): |
| # SP-NDK and SP-HAL |
| print_sp_lib(vndk_lib, file=file) |
| |
| # VNDK (framework) |
| for lib in sorted_lib_path_list(vndk_lib.vndk_core): |
| print('vndk-core:', lib, file=file) |
| for lib in sorted_lib_path_list(vndk_lib.vndk_indirect): |
| print('vndk-indirect:', lib, file=file) |
| for lib in sorted_lib_path_list(vndk_lib.vndk_fwk_ext): |
| print('vndk-fwk-ext:', lib, file=file) |
| |
| # VNDK (vendor) |
| for lib in sorted_lib_path_list(vndk_lib.vndk_vnd_ext): |
| print('vndk-vnd-ext:', lib, file=file) |
| for lib in sorted_lib_path_list(vndk_lib.extra_vendor_lib): |
| print('extra-vendor-lib:', lib, file=file) |
| |
| |
| SPLibResult = collections.namedtuple( |
| 'SPLibResult', |
| 'sp_hal sp_hal_dep sp_hal_vndk_stable sp_ndk sp_ndk_vndk_stable ' |
| 'sp_both_vndk_stable') |
| |
| def print_sp_lib(sp_lib, file=sys.stdout): |
| # SP-NDK |
| for lib in sorted_lib_path_list(sp_lib.sp_ndk): |
| print('sp-ndk:', lib, file=file) |
| for lib in sorted_lib_path_list(sp_lib.sp_ndk_vndk_stable): |
| print('sp-ndk-vndk-stable:', lib, file=file) |
| |
| # SP-HAL |
| for lib in sorted_lib_path_list(sp_lib.sp_hal): |
| print('sp-hal:', lib, file=file) |
| for lib in sorted_lib_path_list(sp_lib.sp_hal_dep): |
| print('sp-hal-dep:', lib, file=file) |
| for lib in sorted_lib_path_list(sp_lib.sp_hal_vndk_stable): |
| print('sp-hal-vndk-stable:', lib, file=file) |
| |
| # SP-both |
| for lib in sorted_lib_path_list(sp_lib.sp_both_vndk_stable): |
| print('sp-both-vndk-stable:', lib, file=file) |
| |
| |
| class ELFResolver(object): |
| def __init__(self, lib_set, default_search_path): |
| self.lib_set = lib_set |
| self.default_search_path = default_search_path |
| |
| def get_candidates(self, name, dt_rpath=None, dt_runpath=None): |
| if dt_rpath: |
| for d in dt_rpath: |
| yield os.path.join(d, name) |
| if dt_runpath: |
| for d in dt_runpath: |
| yield os.path.join(d, name) |
| for d in self.default_search_path: |
| yield os.path.join(d, name) |
| |
| def resolve(self, name, dt_rpath=None, dt_runpath=None): |
| for path in self.get_candidates(name, dt_rpath, dt_runpath): |
| try: |
| return self.lib_set[path] |
| except KeyError: |
| continue |
| return None |
| |
| |
| class ELFLinkData(object): |
| NEEDED = 0 # Dependencies recorded in DT_NEEDED entries. |
| DLOPEN = 1 # Dependencies introduced by dlopen(). |
| |
| def __init__(self, partition, path, elf): |
| self.partition = partition |
| self.path = path |
| self.elf = elf |
| self._deps = (set(), set()) |
| self._users = (set(), set()) |
| self.imported_ext_symbols = collections.defaultdict(set) |
| self._ndk_classification = NDK_LIBS.classify(path) |
| self.unresolved_symbols = set() |
| self.linked_symbols = dict() |
| |
| @property |
| def is_ndk(self): |
| return self._ndk_classification != NDKLibDict.NOT_NDK |
| |
| @property |
| def is_ll_ndk(self): |
| return self._ndk_classification == NDKLibDict.LL_NDK |
| |
| @property |
| def is_sp_ndk(self): |
| return self._ndk_classification == NDKLibDict.SP_NDK |
| |
| @property |
| def is_hl_ndk(self): |
| return self._ndk_classification == NDKLibDict.HL_NDK |
| |
| def add_dep(self, dst, ty): |
| self._deps[ty].add(dst) |
| dst._users[ty].add(self) |
| |
| def remove_dep(self, dst, ty): |
| self._deps[ty].remove(dst) |
| dst._users[ty].remove(self) |
| |
| @property |
| def num_deps(self): |
| """Get the number of dependencies. If a library is linked by both |
| NEEDED and DLOPEN relationship, then it will be counted twice.""" |
| return sum(len(deps) for deps in self._deps) |
| |
| @property |
| def deps(self): |
| return itertools.chain.from_iterable(self._deps) |
| |
| @property |
| def deps_with_type(self): |
| dt_deps = zip(self._deps[self.NEEDED], itertools.repeat(self.NEEDED)) |
| dl_deps = zip(self._deps[self.DLOPEN], itertools.repeat(self.DLOPEN)) |
| return itertools.chain(dt_deps, dl_deps) |
| |
| @property |
| def dt_deps(self): |
| return self._deps[self.NEEDED] |
| |
| @property |
| def dl_deps(self): |
| return self._deps[self.DLOPEN] |
| |
| @property |
| def num_users(self): |
| """Get the number of users. If a library is linked by both NEEDED and |
| DLOPEN relationship, then it will be counted twice.""" |
| return sum(len(users) for users in self._users) |
| |
| @property |
| def users(self): |
| return itertools.chain.from_iterable(self._users) |
| |
| @property |
| def users_with_type(self): |
| dt_users = zip(self._users[self.NEEDED], itertools.repeat(self.NEEDED)) |
| dl_users = zip(self._users[self.DLOPEN], itertools.repeat(self.DLOPEN)) |
| return itertools.chain(dt_users, dl_users) |
| |
| @property |
| def dt_users(self): |
| return self._users[self.NEEDED] |
| |
| @property |
| def dl_users(self): |
| return self._users[self.DLOPEN] |
| |
| def has_dep(self, dst): |
| return any(dst in deps for deps in self._deps) |
| |
| def has_user(self, dst): |
| return any(dst in users for users in self._users) |
| |
| def is_system_lib(self): |
| return self.partition == PT_SYSTEM |
| |
| |
| def sorted_lib_path_list(libs): |
| libs = [lib.path for lib in libs] |
| libs.sort() |
| return libs |
| |
| |
| class ELFLinker(object): |
| LIB32_SEARCH_PATH = ( |
| '/system/lib', |
| '/system/lib/vndk', |
| '/system/lib/vndk-ext', |
| '/vendor/lib', |
| ) |
| |
| LIB64_SEARCH_PATH = ( |
| '/system/lib64', |
| '/system/lib64/vndk', |
| '/system/lib64/vndk-ext', |
| '/vendor/lib64', |
| ) |
| |
| |
| def __init__(self): |
| self.lib32 = dict() |
| self.lib64 = dict() |
| self.lib_pt = [dict() for i in range(NUM_PARTITIONS)] |
| |
| self.lib32_resolver = ELFResolver(self.lib32, self.LIB32_SEARCH_PATH) |
| self.lib64_resolver = ELFResolver(self.lib64, self.LIB64_SEARCH_PATH) |
| |
| def _add_lib_to_lookup_dict(self, lib): |
| if lib.elf.is_32bit: |
| self.lib32[lib.path] = lib |
| else: |
| self.lib64[lib.path] = lib |
| self.lib_pt[lib.partition][lib.path] = lib |
| |
| def _remove_lib_from_lookup_dict(self, lib): |
| if lib.elf.is_32bit: |
| del self.lib32[lib.path] |
| else: |
| del self.lib64[lib.path] |
| del self.lib_pt[lib.partition][lib.path] |
| |
| def add_lib(self, partition, path, elf): |
| lib = ELFLinkData(partition, path, elf) |
| self._add_lib_to_lookup_dict(lib) |
| return lib |
| |
| def rename_lib(self, lib, new_partition, new_path): |
| self._remove_lib_from_lookup_dict(lib) |
| lib.path = new_path |
| lib.partition = new_partition |
| self._add_lib_to_lookup_dict(lib) |
| |
| def add_dep(self, src_path, dst_path, ty): |
| 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, ty) |
| return |
| print('error: cannot add dependency from {} to {}.' |
| .format(src_path, dst_path), file=sys.stderr) |
| |
| def get_lib(self, path): |
| for lib_set in (self.lib32, self.lib64): |
| lib = lib_set.get(path) |
| if lib: |
| return lib |
| return None |
| |
| def get_libs(self, paths, report_error): |
| result = set() |
| for path in paths: |
| lib = self.get_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, scan_elf_files): |
| root = os.path.abspath(root) |
| prefix_len = len(root) + 1 |
| |
| if alter_subdirs: |
| alter_patt = ELFLinker._compile_path_matcher(root, alter_subdirs) |
| |
| for path, elf in scan_elf_files(root): |
| short_path = os.path.join('/', partition_name, path[prefix_len:]) |
| if alter_subdirs and alter_patt.match(path): |
| self.add_lib(alter_partition, short_path, elf) |
| else: |
| self.add_lib(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), |
| ELFLinkData.DLOPEN) |
| |
| def _find_exported_symbol(self, symbol, libs): |
| """Find the shared library with the exported symbol.""" |
| for lib in libs: |
| if symbol in lib.elf.exported_symbols: |
| return lib |
| return None |
| |
| def _resolve_lib_imported_symbols(self, lib, imported_libs, generic_refs): |
| """Resolve the imported symbols in a library.""" |
| for symbol in lib.elf.imported_symbols: |
| imported_lib = self._find_exported_symbol(symbol, imported_libs) |
| if not imported_lib: |
| lib.unresolved_symbols.add(symbol) |
| else: |
| lib.linked_symbols[symbol] = imported_lib |
| if generic_refs: |
| ref_lib = generic_refs.refs.get(imported_lib.path) |
| if not ref_lib or not symbol in ref_lib.exported_symbols: |
| lib.imported_ext_symbols[imported_lib].add(symbol) |
| |
| def _resolve_lib_dt_needed(self, lib, resolver): |
| imported_libs = [] |
| for dt_needed in lib.elf.dt_needed: |
| dep = resolver.resolve(dt_needed, lib.elf.dt_rpath, |
| lib.elf.dt_runpath) |
| if not dep: |
| candidates = list(resolver.get_candidates( |
| dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath)) |
| print('warning: {}: Missing needed library: {} Tried: {}' |
| .format(lib.path, dt_needed, candidates), file=sys.stderr) |
| continue |
| lib.add_dep(dep, ELFLinkData.NEEDED) |
| imported_libs.append(dep) |
| return imported_libs |
| |
| def _resolve_lib_deps(self, lib, resolver, generic_refs): |
| # Resolve DT_NEEDED entries. |
| imported_libs = self._resolve_lib_dt_needed(lib, resolver) |
| |
| if generic_refs: |
| for imported_lib in imported_libs: |
| if imported_lib.path not in generic_refs.refs: |
| # Add imported_lib to imported_ext_symbols to make sure |
| # non-AOSP libraries are in the imported_ext_symbols key |
| # set. |
| lib.imported_ext_symbols[imported_lib].update() |
| |
| # Resolve imported symbols. |
| self._resolve_lib_imported_symbols(lib, imported_libs, generic_refs) |
| |
| def _resolve_lib_set_deps(self, lib_set, resolver, generic_refs): |
| for lib in lib_set.values(): |
| self._resolve_lib_deps(lib, resolver, generic_refs) |
| |
| def resolve_deps(self, generic_refs=None): |
| self._resolve_lib_set_deps( |
| self.lib32, self.lib32_resolver, generic_refs) |
| self._resolve_lib_set_deps( |
| self.lib64, self.lib64_resolver, generic_refs) |
| |
| def all_lib(self): |
| for lib_set in self.lib_pt: |
| for lib in lib_set.values(): |
| yield lib |
| |
| def compute_path_matched_lib(self, path_patterns): |
| patt = re.compile('|'.join('(?:' + p + ')' for p in path_patterns)) |
| return set(lib for lib in self.all_lib() if patt.match(lib.path)) |
| |
| def compute_predefined_vndk_stable(self): |
| """Find all vndk stable libraries.""" |
| |
| path_patterns = ( |
| # SP-HAL VNDK-stable |
| '^.*/libhidlmemory\\.so$', |
| |
| # SP-NDK VNDK-stable |
| '^.*/android\\.hardware\\.graphics\\.allocator@2\\.0\\.so$', |
| '^.*/android\\.hardware\\.graphics\\.common@1\\.0\\.so$', |
| '^.*/android\\.hardware\\.graphics\\.mapper@2\\.0\\.so$', |
| '^.*/android\\.hidl\\.base@1\\.0\\.so$', |
| '^.*/libcutils\\.so$', |
| '^.*/libhidl-gen-utils\\.so$', |
| '^.*/libhidlbase\\.so$', |
| '^.*/libhidltransport\\.so$', |
| '^.*/libhwbinder\\.so$', |
| '^.*/liblzma\\.so$', |
| '^.*/libnativewindow\\.so$', |
| '^.*/libsync\\.so$', |
| |
| # SP-NDK VNDK-stable (should to be removed) |
| '^.*/libbacktrace\\.so$', |
| '^.*/libbase\\.so$', |
| '^.*/libc\\+\\+\\.so$', |
| '^.*/libunwind\\.so$', |
| '^.*/libziparchive\\.so$', |
| |
| # SP-NDK dependencies (SP-NDK only) |
| '^.*/libui\\.so$', |
| '^.*/libutils\\.so$', |
| |
| # Bad vndk-stable (must be removed) |
| '^.*/libhardware\\.so$', |
| '^.*/libnativeloader\\.so$', |
| '^.*/libvintf\\.so$', |
| ) |
| |
| return self.compute_path_matched_lib(path_patterns) |
| |
| def compute_predefined_sp_hal(self): |
| """Find all same-process HALs.""" |
| |
| path_patterns = ( |
| # OpenGL-related |
| '^/vendor/.*/libEGL_.*\\.so$', |
| '^/vendor/.*/libGLES.*\\.so$', |
| '^/vendor/.*/libGLESv1_CM_.*\\.so$', |
| '^/vendor/.*/libGLESv2_.*\\.so$', |
| '^/vendor/.*/libGLESv3_.*\\.so$', |
| # Vulkan |
| '^/vendor/.*/vulkan.*\\.so$', |
| # libRSDriver |
| '^/vendor/.*/libRSDriver.*\\.so$', |
| '^/vendor/.*/libPVRRS\\.so$', |
| # Gralloc mapper |
| '^.*/gralloc\\..*\\.so$', |
| '^.*/android\\.hardware\\.graphics\\.mapper@\\d+\\.\\d+-impl\\.so$', |
| ) |
| |
| return self.compute_path_matched_lib(path_patterns) |
| |
| def compute_sp_ndk(self): |
| """Find all SP-NDK libraries.""" |
| return set(lib for lib in self.all_lib() if lib.is_sp_ndk) |
| |
| def compute_sp_lib(self, generic_refs): |
| def is_ndk(lib): |
| return lib.is_ndk |
| |
| sp_ndk = self.compute_sp_ndk() |
| sp_ndk_closure = self.compute_closure(sp_ndk, is_ndk) |
| sp_ndk_vndk_stable = sp_ndk_closure - sp_ndk |
| |
| sp_hal = self.compute_predefined_sp_hal() |
| sp_hal_closure = self.compute_closure(sp_hal, is_ndk) |
| |
| def is_aosp_lib(lib): |
| return (not generic_refs or \ |
| generic_refs.classify_lib(lib) != GenericRefs.NEW_LIB) |
| |
| sp_hal_vndk_stable = set() |
| sp_hal_dep = set() |
| for lib in sp_hal_closure - sp_hal: |
| if is_aosp_lib(lib): |
| sp_hal_vndk_stable.add(lib) |
| else: |
| sp_hal_dep.add(lib) |
| |
| sp_both_vndk_stable = sp_ndk_vndk_stable & sp_hal_vndk_stable |
| sp_ndk_vndk_stable -= sp_both_vndk_stable |
| sp_hal_vndk_stable -= sp_both_vndk_stable |
| |
| return SPLibResult(sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk, |
| sp_ndk_vndk_stable, sp_both_vndk_stable) |
| |
| def _po_component_sorted(self, lib_set, get_successors, |
| get_strong_successors): |
| result = [] |
| |
| idx_dict = {} |
| idx_counter = 0 |
| has_scc = set() |
| |
| s = [] |
| p = [] |
| |
| def traverse(v): |
| idx_dict[v] = len(idx_dict) |
| |
| s.append(v) |
| p.append(v) |
| |
| for succ in get_successors(v): |
| if succ not in lib_set: |
| continue |
| succ_idx = idx_dict.get(succ) |
| if succ_idx is None: |
| traverse(succ) |
| elif succ not in has_scc: |
| while idx_dict[p[-1]] > succ_idx: |
| p.pop() |
| |
| if p[-1] is v: |
| scc = set() |
| while True: |
| w = s.pop() |
| scc.add(w) |
| has_scc.add(w) |
| if w is v: |
| break |
| p.pop() |
| result.append(self._po_sorted(scc, get_strong_successors)) |
| |
| for v in lib_set: |
| if v not in idx_dict: |
| traverse(v) |
| |
| return result |
| |
| def _po_sorted(self, lib_set, get_successors): |
| result = [] |
| visited = set() |
| def traverse(lib): |
| for succ in get_successors(lib): |
| if succ in lib_set and succ not in visited: |
| visited.add(succ) |
| traverse(succ) |
| result.append(lib) |
| for lib in lib_set: |
| if lib not in visited: |
| visited.add(lib) |
| traverse(lib) |
| return result |
| |
| def _deps_po_sorted(self, lib_set): |
| return self._po_sorted(lib_set, lambda x: x.deps) |
| |
| def _users_po_sorted(self, lib_set): |
| return self._po_sorted(lib_set, lambda x: x.users) |
| |
| def normalize_partition_tags(self, sp_hals, generic_refs): |
| system_libs_po = self._deps_po_sorted(self.lib_pt[PT_SYSTEM].values()) |
| system_libs = self.lib_pt[PT_SYSTEM] |
| vendor_libs = self.lib_pt[PT_VENDOR] |
| |
| def is_system_lib_or_sp_hal(lib): |
| return lib.is_system_lib() or lib in sp_hals |
| |
| for lib in system_libs_po: |
| if all(is_system_lib_or_sp_hal(dep) for dep in lib.deps): |
| # Good system lib. Do nothing. |
| continue |
| if not generic_refs or generic_refs.refs.get(lib.path): |
| # If lib is in AOSP generic reference, then we assume that the |
| # non-SP-HAL dependencies are errors. Emit errors and remove |
| # the dependencies. |
| for dep in list(lib.dt_deps): |
| if not is_system_lib_or_sp_hal(dep): |
| print('error: {}: system exe/lib must not depend on ' |
| 'vendor lib {}. Assume such dependency does ' |
| 'not exist.'.format(lib.path, dep.path), |
| file=sys.stderr) |
| lib.remove_dep(dep, ELFLinkData.NEEDED) |
| for dep in list(lib.dl_deps): |
| if not is_system_lib_or_sp_hal(dep): |
| print('error: {}: system exe/lib must not dlopen() ' |
| 'vendor lib {}. Assume such dependency does ' |
| 'not exist.'.format(lib.path, dep.path), |
| file=sys.stderr) |
| lib.remove_dep(dep, ELFLinkData.DLOPEN) |
| else: |
| # If lib is not in AOSP generic reference, then we assume that |
| # lib must be moved to vendor partition. |
| for dep in lib.deps: |
| if not is_system_lib_or_sp_hal(dep): |
| print('warning: {}: system exe/lib must not depend on ' |
| 'vendor lib {}. Assuming {} should be placed in ' |
| 'vendor partition.' |
| .format(lib.path, dep.path, lib.path), |
| file=sys.stderr) |
| lib.partition = PT_VENDOR |
| vendor_libs[lib.path] = lib |
| del system_libs[lib.path] |
| |
| def find_existing_vndk(self): |
| def collect_libs_with_path_pattern(pattern): |
| result = set() |
| pattern = re.compile(pattern) |
| for lib_set in (self.lib32.values(), self.lib64.values()): |
| for lib in lib_set: |
| if pattern.match(lib.path): |
| result.add(lib) |
| return result |
| |
| vndk_core = collect_libs_with_path_pattern( |
| '^/system/lib(?:64)?/vndk(?:-\\d+)?/') |
| vndk_fwk_ext = collect_libs_with_path_pattern( |
| '^/system/lib(?:64)?/vndk(?:-\\d+)?-ext?/') |
| vndk_vnd_ext = collect_libs_with_path_pattern( |
| '^/vendor/lib(?:64)?/vndk(?:-\\d+)?-ext?/') |
| |
| return (vndk_core, vndk_fwk_ext, vndk_vnd_ext) |
| |
| def compute_vndk(self, vndk_customized_for_system, |
| vndk_customized_for_vendor, generic_refs, banned_libs): |
| return self._compute_vndk( |
| self.compute_sp_lib(generic_refs), vndk_customized_for_system, |
| vndk_customized_for_vendor, generic_refs, banned_libs) |
| |
| def _compute_vndk(self, sp_lib, vndk_customized_for_system, |
| vndk_customized_for_vendor, generic_refs, banned_libs): |
| # Compute sp-hal and vndk-stable. |
| vndk_stable = sp_lib.sp_hal_vndk_stable | sp_lib.sp_ndk_vndk_stable | \ |
| sp_lib.sp_both_vndk_stable |
| sp_hal_closure = sp_lib.sp_hal | sp_lib.sp_hal_dep |
| |
| # Normalize partition tags. We expect many violations from the |
| # pre-Treble world. Guess a resolution for the incorrect partition |
| # tag. |
| self.normalize_partition_tags(sp_lib.sp_hal, generic_refs) |
| |
| # ELF resolvers. |
| VNDK_CORE_SEARCH_PATH32 = ( |
| '/system/lib/vndk', |
| '/system/lib', |
| ) |
| |
| VNDK_CORE_SEARCH_PATH64 = ( |
| '/system/lib64/vndk', |
| '/system/lib64', |
| ) |
| |
| VENDOR_SEARCH_PATH32 = ( |
| '/system/lib/vndk', |
| '/vendor/lib', |
| |
| # FIXME: Remove following line after we fixed vndk-stable |
| # resolution. |
| '/system/lib', |
| ) |
| |
| VENDOR_SEARCH_PATH64 = ( |
| '/system/lib64/vndk', |
| '/vendor/lib64', |
| |
| # FIXME: Remove following line after we fixed vndk-stable |
| # resolution. |
| '/system/lib64', |
| ) |
| |
| vndk_core_resolver32 = ELFResolver(self.lib32, VNDK_CORE_SEARCH_PATH32) |
| vndk_core_resolver64 = ELFResolver(self.lib64, VNDK_CORE_SEARCH_PATH64) |
| vendor_resolver32 = ELFResolver(self.lib32, VENDOR_SEARCH_PATH32) |
| vendor_resolver64 = ELFResolver(self.lib64, VENDOR_SEARCH_PATH64) |
| |
| # Collect existing VNDK libraries. |
| vndk_core, vndk_fwk_ext, vndk_vnd_ext = self.find_existing_vndk() |
| |
| # Collect VNDK candidates. |
| def is_not_vndk(lib): |
| return (lib.is_ndk or banned_libs.is_banned(lib.path) or |
| (lib in sp_hal_closure) or (lib in vndk_stable)) |
| |
| def collect_libs_with_partition_user(lib_set, partition): |
| result = set() |
| for lib in lib_set: |
| if is_not_vndk(lib): |
| continue |
| if any(user.partition == partition for user in lib.users): |
| result.add(lib) |
| return result |
| |
| vndk_candidates = collect_libs_with_partition_user( |
| self.lib_pt[PT_SYSTEM].values(), PT_VENDOR) |
| |
| vndk_visited = set(vndk_candidates) |
| |
| # Sets for missing libraries. |
| extra_vendor_lib = set() |
| |
| def get_vndk_core_lib_name(lib): |
| lib_name = os.path.basename(lib.path) |
| lib_dir_name = 'lib' if lib.elf.is_32bit else 'lib64' |
| return os.path.join('/system', lib_dir_name, 'vndk', lib_name) |
| |
| def get_vndk_fwk_ext_lib_name(lib): |
| lib_name = os.path.basename(lib.path) |
| lib_dir_name = 'lib' if lib.elf.is_32bit else 'lib64' |
| return os.path.join('/system', lib_dir_name, 'vndk-ext', lib_name) |
| |
| def get_vndk_vnd_ext_lib_name(lib): |
| lib_name = os.path.basename(lib.path) |
| lib_dir_name = 'lib' if lib.elf.is_32bit else 'lib64' |
| return os.path.join('/vendor', lib_dir_name, 'vndk-ext', lib_name) |
| |
| def is_valid_vndk_core_dep(path): |
| d = os.path.dirname(path) |
| return (d == '/system/lib' or d == '/system/lib64' or |
| d == '/system/lib/vndk' or d == '/system/lib64/vndk') |
| |
| def add_generic_lib_to_vndk_core(lib): |
| """Add a library to vndk-core.""" |
| elf = generic_refs.refs[lib.path] |
| |
| # Create new vndk-core lib from generic reference. |
| vndk_core_lib_path = get_vndk_core_lib_name(lib) |
| vndk_core_lib = self.add_lib(PT_SYSTEM, vndk_core_lib_path, elf) |
| |
| # Resovle the library dependencies. |
| resolver = vndk_core_resolver32 if lib.elf.is_32bit else \ |
| vndk_core_resolver64 |
| self._resolve_lib_deps(vndk_core_lib, resolver, generic_refs) |
| |
| assert all(is_valid_vndk_core_dep(dep.path) |
| for dep in vndk_core_lib.deps) |
| |
| # Add vndk-core to the set. |
| vndk_core.add(vndk_core_lib) |
| return vndk_core_lib |
| |
| def add_to_vndk_core(lib): |
| self.rename_lib(lib, PT_SYSTEM, get_vndk_core_lib_name(lib)) |
| vndk_core.add(lib) |
| |
| # Compute vndk-core, vndk-fwk-ext and vndk-vnd-ext. |
| if not generic_refs: |
| for lib in vndk_candidates: |
| add_to_vndk_core(lib) |
| else: |
| while vndk_candidates: |
| if __debug__: |
| # Loop invariant: These set should be pairwise independent. |
| # Each VNDK libraries should have their ELFLinkData |
| # instance. |
| assert not (vndk_core & vndk_fwk_ext) |
| assert not (vndk_core & vndk_vnd_ext) |
| assert not (vndk_fwk_ext & vndk_vnd_ext) |
| |
| # Loop invariant: The library names in vndk_fwk_ext and |
| # vndk_vnd_ext must exist in vndk_core as well. |
| vndk_core_lib_names = \ |
| set(os.path.basename(x.path) for x in vndk_core) |
| vndk_fwk_ext_lib_names = \ |
| set(os.path.basename(x.path) for x in vndk_fwk_ext) |
| vndk_vnd_ext_lib_names = \ |
| set(os.path.basename(x.path) for x in vndk_vnd_ext) |
| assert vndk_fwk_ext_lib_names <= vndk_core_lib_names |
| assert vndk_vnd_ext_lib_names <= vndk_core_lib_names |
| |
| prev_vndk_candidates = vndk_candidates |
| vndk_candidates = set() |
| |
| def replace_linked_lib(user, old_lib, new_lib, dep_type): |
| user.remove_dep(old_lib, dep_type) |
| user.add_dep(new_lib, dep_type) |
| for symbol, imported_lib in user.linked_symbols.items(): |
| if imported_lib == old_lib: |
| user.linked_symbols[symbol] = new_lib |
| |
| def replace_generic_lib_usages(lib, generic_lib): |
| for user, dep_type in list(lib.users_with_type): |
| if lib not in user.imported_ext_symbols: |
| replace_linked_lib(user, lib, generic_lib, dep_type) |
| |
| def add_to_vndk_fwk_ext(lib, generic_lib): |
| self.rename_lib(lib, PT_SYSTEM, |
| get_vndk_fwk_ext_lib_name(lib)) |
| vndk_fwk_ext.add(lib) |
| replace_generic_lib_usages(lib, generic_lib) |
| |
| def add_to_vndk_vnd_ext(lib, generic_lib): |
| """Add a library to vndk-vnd-ext.""" |
| |
| replace_generic_lib_usages(lib, generic_lib) |
| |
| # Create a new vndk-vnd-ext library. |
| vndk_vnd_ext_lib = self.add_lib( |
| PT_VENDOR, get_vndk_vnd_ext_lib_name(lib), lib.elf) |
| |
| # Vendor libraries should link to vndk_vnd_ext_lib instead. |
| for user, dep_type in list(lib.users_with_type): |
| if not user.is_system_lib(): |
| replace_linked_lib(user, lib, vndk_vnd_ext_lib, |
| dep_type) |
| |
| # Resolve the dependencies. In order to find more |
| # dependencies from vendor partition to system partition, |
| # continue to resolve dependencies with the global |
| # ELFResolver. |
| resolver = self.lib32_resolver if lib.elf.is_32bit else \ |
| self.lib64_resolver |
| self._resolve_lib_deps(vndk_vnd_ext_lib, resolver, |
| generic_refs) |
| |
| add_deps_to_vndk_candidate(vndk_vnd_ext_lib) |
| |
| vndk_vnd_ext.add(vndk_vnd_ext_lib) |
| |
| def add_to_vndk_candidate(lib): |
| if is_not_vndk(lib): |
| return |
| if lib not in vndk_visited: |
| vndk_candidates.add(lib) |
| vndk_visited.add(lib) |
| |
| def add_deps_to_vndk_candidate(lib): |
| for dep in lib.deps: |
| if dep.is_system_lib(): |
| add_to_vndk_candidate(dep) |
| |
| # Remove non-AOSP libraries. |
| vndk_extended_candidates = set() |
| vndk_customized_candidates = set() |
| for lib in prev_vndk_candidates: |
| category = generic_refs.classify_lib(lib) |
| if category == GenericRefs.NEW_LIB: |
| extra_vendor_lib.add(lib) |
| add_deps_to_vndk_candidate(lib) |
| elif category == GenericRefs.EXPORT_EQUAL: |
| vndk_customized_candidates.add(lib) |
| elif category == GenericRefs.EXPORT_SUPER_SET: |
| vndk_extended_candidates.add(lib) |
| else: |
| print('error: {}: vndk library must not be modified.' |
| .format(lib.path), file=sys.stderr) |
| |
| # Classify VNDK customized candidates. |
| for lib in vndk_customized_candidates: |
| if not lib.imported_ext_symbols: |
| # Inward-customized VNDK-core libraries. |
| add_to_vndk_core(lib) |
| else: |
| # Outward-customized VNDK libraries. |
| generic_lib = add_generic_lib_to_vndk_core(lib) |
| if lib in vndk_customized_for_system: |
| add_to_vndk_fwk_ext(lib, generic_lib) |
| if lib in vndk_customized_for_vendor: |
| add_to_vndk_vnd_ext(lib, generic_lib) |
| |
| # Compute VNDK extension candidates. |
| for lib in self._users_po_sorted(vndk_extended_candidates): |
| # Check the users of the extended exported symbols. |
| has_system_users = False |
| has_vendor_users = False |
| for user in lib.users: |
| if lib in user.imported_ext_symbols: |
| if user.is_system_lib(): |
| has_system_users = True |
| else: |
| has_vendor_users = True |
| if has_system_users and has_vendor_users: |
| break |
| |
| generic_lib = add_generic_lib_to_vndk_core(lib) |
| if has_system_users: |
| add_to_vndk_fwk_ext(lib, generic_lib) |
| if has_vendor_users: |
| add_to_vndk_vnd_ext(lib, generic_lib) |
| |
| # Compute the closure of the VNDK libs. |
| visited_libs = set(vndk_core) |
| processed_paths = set(lib.path for lib in vndk_core) |
| stack = [] |
| |
| def add_vndk_core_deps_to_stack(lib): |
| for dep in lib.deps: |
| if is_not_vndk(dep): |
| continue |
| if dep not in visited_libs: |
| stack.append(dep) |
| visited_libs.add(dep) |
| |
| for lib in vndk_core: |
| add_vndk_core_deps_to_stack(lib) |
| |
| while stack: |
| lib = stack.pop() |
| |
| vndk_lib_path = get_vndk_core_lib_name(lib) |
| if vndk_lib_path in processed_paths: |
| continue |
| |
| processed_paths.add(vndk_lib_path) |
| |
| if lib.imported_ext_symbols or \ |
| (generic_refs and not generic_refs.is_equivalent_lib(lib)): |
| generic_lib = add_generic_lib_to_vndk_core(lib) |
| add_vndk_core_deps_to_stack(generic_lib) |
| add_to_vndk_fwk_ext(lib, generic_lib) |
| else: |
| add_to_vndk_core(lib) |
| add_vndk_core_deps_to_stack(lib) |
| |
| # Truncate all vendor libs and resolve it again. |
| for lib in self.lib_pt[PT_VENDOR].values(): |
| lib._deps = (set(), set()) |
| lib._users = (set(), set()) |
| lib.imported_ext_symbols = collections.defaultdict(set) |
| lib.unresolved_symbols = set() |
| lib.linked_symbols = dict() |
| |
| for lib in self.lib_pt[PT_VENDOR].values(): |
| resolver = vendor_resolver32 if lib.elf.is_32bit else \ |
| vendor_resolver64 |
| self._resolve_lib_deps(lib, resolver, generic_refs) |
| |
| # Separate vndk-core and vndk-indirect. |
| vndk_core_indirect = vndk_core |
| vndk_core = set() |
| vndk_indirect = set() |
| for lib in vndk_core_indirect: |
| if any(not user.is_system_lib() for user in lib.users): |
| vndk_core.add(lib) |
| else: |
| vndk_indirect.add(lib) |
| |
| return VNDKResult( |
| sp_lib.sp_hal, sp_lib.sp_hal_dep, sp_lib.sp_hal_vndk_stable, |
| sp_lib.sp_ndk, sp_lib.sp_ndk_vndk_stable, |
| sp_lib.sp_both_vndk_stable, |
| extra_vendor_lib, vndk_core, vndk_indirect, |
| vndk_fwk_ext, vndk_vnd_ext) |
| |
| def compute_vndk_cap(self, banned_libs): |
| # ELF files on vendor partitions are banned unconditionally. ELF files |
| # on the system partition are banned if their file extensions are not |
| # '.so' or their file names are listed in banned_libs. LL-NDK and |
| # SP-NDK libraries are treated as a special case which will not be |
| # considered as banned libraries at the moment. |
| def is_banned(lib): |
| if lib.is_ndk: |
| return lib.is_hl_ndk |
| return (banned_libs.is_banned(lib.path) or |
| not lib.is_system_lib() or |
| not lib.path.endswith('.so')) |
| |
| # Find all libraries that are banned. |
| banned_set = set() |
| for lib_set in self.lib_pt: |
| for lib in lib_set.values(): |
| if is_banned(lib): |
| banned_set.add(lib) |
| |
| # Find the transitive closure of the banned libraries. |
| stack = list(banned_set) |
| while stack: |
| lib = stack.pop() |
| for user in lib.users: |
| if not user.is_ndk and user not in banned_set: |
| banned_set.add(user) |
| stack.append(user) |
| |
| # Find the non-NDK non-banned libraries. |
| vndk_cap = set() |
| for lib in self.lib_pt[PT_SYSTEM].values(): |
| if not lib.is_ndk and lib not in banned_set: |
| vndk_cap.add(lib) |
| |
| return vndk_cap |
| |
| @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_internal(scan_elf_files, system_dirs, system_dirs_as_vendor, |
| vendor_dirs, vendor_dirs_as_system, extra_deps, |
| generic_refs): |
| graph = ELFLinker() |
| |
| if system_dirs: |
| for path in system_dirs: |
| graph.add_executables_in_dir('system', PT_SYSTEM, path, |
| PT_VENDOR, system_dirs_as_vendor, |
| scan_elf_files) |
| |
| if vendor_dirs: |
| for path in vendor_dirs: |
| graph.add_executables_in_dir('vendor', PT_VENDOR, path, |
| PT_SYSTEM, vendor_dirs_as_system, |
| scan_elf_files) |
| |
| if extra_deps: |
| for path in extra_deps: |
| graph.load_extra_deps(path) |
| |
| graph.resolve_deps(generic_refs) |
| |
| return graph |
| |
| @staticmethod |
| def create(system_dirs=None, system_dirs_as_vendor=None, vendor_dirs=None, |
| vendor_dirs_as_system=None, extra_deps=None, generic_refs=None): |
| return ELFLinker._create_internal( |
| scan_elf_files, system_dirs, system_dirs_as_vendor, vendor_dirs, |
| vendor_dirs_as_system, extra_deps, generic_refs) |
| |
| @staticmethod |
| def create_from_dump(system_dirs=None, system_dirs_as_vendor=None, |
| vendor_dirs=None, vendor_dirs_as_system=None, |
| extra_deps=None, generic_refs=None): |
| return ELFLinker._create_internal( |
| scan_elf_dump_files, system_dirs, system_dirs_as_vendor, |
| vendor_dirs, vendor_dirs_as_system, extra_deps, generic_refs) |
| |
| |
| #------------------------------------------------------------------------------ |
| # Generic Reference |
| #------------------------------------------------------------------------------ |
| |
| class GenericRefs(object): |
| NEW_LIB = 0 |
| EXPORT_EQUAL = 1 |
| EXPORT_SUPER_SET = 2 |
| MODIFIED = 3 |
| |
| def __init__(self): |
| self.refs = dict() |
| |
| def add(self, name, elf): |
| self.refs[name] = elf |
| |
| 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.add(lib_name, ELF.load_dump(path)) |
| |
| @staticmethod |
| def create_from_dir(root): |
| result = GenericRefs() |
| result._load_from_dir(root) |
| return result |
| |
| def classify_lib(self, lib): |
| ref_lib = self.refs.get(lib.path) |
| if not ref_lib: |
| return GenericRefs.NEW_LIB |
| exported_symbols = lib.elf.exported_symbols |
| if exported_symbols == ref_lib.exported_symbols: |
| return GenericRefs.EXPORT_EQUAL |
| if exported_symbols > ref_lib.exported_symbols: |
| return GenericRefs.EXPORT_SUPER_SET |
| return GenericRefs.MODIFIED |
| |
| def is_equivalent_lib(self, lib): |
| return self.classify_lib(lib) == GenericRefs.EXPORT_EQUAL |
| |
| |
| #------------------------------------------------------------------------------ |
| # Commands |
| #------------------------------------------------------------------------------ |
| |
| 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, elf in scan_elf_files(root): |
| name = path[prefix_len:] |
| print('Processing:', name, file=sys.stderr) |
| out = os.path.join(args.output, name) + '.sym' |
| makedirs(os.path.dirname(out), exist_ok=True) |
| with open(out, 'w') as f: |
| elf.dump(f) |
| 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 VNDKCommandBase(ELFGraphCommand): |
| def add_argparser_options(self, parser): |
| super(VNDKCommandBase, self).add_argparser_options(parser) |
| |
| parser.add_argument( |
| '--load-generic-refs', |
| help='compare with generic reference symbols') |
| |
| parser.add_argument( |
| '--ban-vendor-lib-dep', action='append', |
| help='library that must not be used by vendor binaries') |
| |
| parser.add_argument( |
| '--outward-customization-default-partition', default='system', |
| help='default partition for outward customized vndk libs') |
| |
| parser.add_argument( |
| '--outward-customization-for-system', action='append', |
| help='outward customized vndk for system partition') |
| |
| parser.add_argument( |
| '--outward-customization-for-vendor', action='append', |
| help='outward customized vndk for vendor partition') |
| |
| def _check_arg_dir_exists(self, arg_name, dirs): |
| for path in dirs: |
| if not os.path.exists(path): |
| print('error: Failed to find the directory "{}" specified in {}' |
| .format(path, arg_name), file=sys.stderr) |
| sys.exit(1) |
| if not os.path.isdir(path): |
| print('error: Path "{}" specified in {} is not a directory' |
| .format(path, arg_name), file=sys.stderr) |
| sys.exit(1) |
| |
| def check_dirs_from_args(self, args): |
| self._check_arg_dir_exists('--system', args.system) |
| self._check_arg_dir_exists('--vendor', args.vendor) |
| |
| def _get_generic_refs_from_args(self, args): |
| if not args.load_generic_refs: |
| return None |
| return GenericRefs.create_from_dir(args.load_generic_refs) |
| |
| def _get_banned_libs_from_args(self, args): |
| if not args.ban_vendor_lib_dep: |
| return BannedLibDict.create_default() |
| |
| banned_libs = BannedLibDict() |
| for name in args.ban_vendor_lib_dep: |
| banned_libs.add(name, 'user-banned', BA_WARN) |
| return banned_libs |
| |
| def _get_outward_customized_sets_from_args(self, args, graph): |
| vndk_customized_for_system = set() |
| vndk_customized_for_vendor = set() |
| system_libs = graph.lib_pt[PT_SYSTEM].values() |
| |
| if args.outward_customization_default_partition in {'system', 'both'}: |
| vndk_customized_for_system.update(system_libs) |
| |
| if args.outward_customization_default_partition in {'vendor', 'both'}: |
| vndk_customized_for_vendor.update(system_libs) |
| |
| if args.outward_customization_for_system: |
| vndk_customized_for_system.update( |
| graph.get_libs( |
| args.outward_customization_for_system, lambda x: None)) |
| |
| if args.outward_customization_for_vendor: |
| vndk_customized_for_vendor.update( |
| graph.get_libs( |
| args.outward_customization_for_vendor, lambda x: None)) |
| return (vndk_customized_for_system, vndk_customized_for_vendor) |
| |
| def create_from_args(self, args): |
| """Create all essential data structures for VNDK computation.""" |
| |
| self.check_dirs_from_args(args) |
| |
| generic_refs = self._get_generic_refs_from_args(args) |
| banned_libs = self._get_banned_libs_from_args(args) |
| |
| graph = ELFLinker.create(args.system, args.system_dir_as_vendor, |
| args.vendor, args.vendor_dir_as_system, |
| args.load_extra_deps, |
| generic_refs=generic_refs) |
| |
| vndk_customized_for_system, vndk_customized_for_vendor = \ |
| self._get_outward_customized_sets_from_args(args, graph) |
| |
| return (generic_refs, banned_libs, graph, vndk_customized_for_system, |
| vndk_customized_for_vendor) |
| |
| |
| class VNDKCommand(VNDKCommandBase): |
| 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( |
| '--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') |
| |
| |
| def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg): |
| for lib in lib_set.values(): |
| if not lib.num_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: |
| if dep.is_hl_ndk: |
| 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.is_banned(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): |
| generic_refs, banned_libs, graph, vndk_customized_for_system, \ |
| vndk_customized_for_vendor = self.create_from_args(args) |
| |
| # Check the API extensions to NDK libraries. |
| if generic_refs: |
| self._check_ndk_extensions(graph, generic_refs) |
| |
| if args.warn_incorrect_partition: |
| self._warn_incorrect_partition(graph) |
| |
| if args.warn_banned_vendor_lib_deps: |
| self._warn_banned_vendor_lib_deps(graph, banned_libs) |
| |
| # Compute vndk heuristics. |
| vndk_lib = graph.compute_vndk(vndk_customized_for_system, |
| vndk_customized_for_vendor, generic_refs, |
| banned_libs) |
| |
| if args.warn_high_level_ndk_deps: |
| self._warn_high_level_ndk_deps( |
| (vndk_lib.vndk_core, vndk_lib.vndk_indirect, |
| vndk_lib.vndk_fwk_ext, vndk_lib.vndk_vnd_ext)) |
| |
| # Print results. |
| print_vndk_lib(vndk_lib) |
| return 0 |
| |
| |
| class DepsInsightCommand(VNDKCommandBase): |
| def __init__(self): |
| super(DepsInsightCommand, self).__init__( |
| 'deps-insight', help='Generate HTML to show dependencies') |
| |
| def add_argparser_options(self, parser): |
| super(DepsInsightCommand, self).add_argparser_options(parser) |
| |
| parser.add_argument( |
| '--output', '-o', help='output directory') |
| |
| def main(self, args): |
| generic_refs, banned_libs, graph, vndk_customized_for_system, \ |
| vndk_customized_for_vendor = self.create_from_args(args) |
| |
| # Compute vndk heuristics. |
| vndk_lib = graph.compute_vndk(vndk_customized_for_system, |
| vndk_customized_for_vendor, generic_refs, |
| banned_libs) |
| |
| # Serialize data. |
| strs = [] |
| strs_dict = dict() |
| |
| libs = list(graph.lib32.values()) + list(graph.lib64.values()) |
| libs.sort(key=lambda lib: lib.path) |
| libs_dict = {lib: i for i, lib in enumerate(libs)} |
| |
| def get_str_idx(s): |
| try: |
| return strs_dict[s] |
| except KeyError: |
| idx = len(strs) |
| strs_dict[s] = idx |
| strs.append(s) |
| return idx |
| |
| def collect_path_sorted_lib_idxs(libs): |
| libs = sorted(libs, key=lambda lib: lib.path) |
| return [libs_dict[lib] for lib in libs] |
| |
| def collect_deps(lib): |
| queue = list(lib.deps) |
| visited = set(queue) |
| visited.add(lib) |
| deps = [] |
| |
| # Traverse dependencies with breadth-first search. |
| while queue: |
| # Collect dependencies for next queue. |
| next_queue = [] |
| for lib in queue: |
| for dep in lib.deps: |
| if dep not in visited: |
| next_queue.append(dep) |
| visited.add(dep) |
| |
| # Append current queue to result. |
| deps.append(collect_path_sorted_lib_idxs(queue)) |
| |
| queue = next_queue |
| |
| return deps |
| |
| def collect_tags(lib): |
| tags = [] |
| if lib.is_ll_ndk: |
| tags.append(get_str_idx('ll-ndk')) |
| if lib.is_sp_ndk: |
| tags.append(get_str_idx('sp-ndk')) |
| if lib.is_hl_ndk: |
| tags.append(get_str_idx('hl-ndk')) |
| |
| if lib in vndk_lib.sp_hal: |
| tags.append(get_str_idx('sp-hal')) |
| if lib in vndk_lib.sp_hal_dep: |
| tags.append(get_str_idx('sp-hal-dep')) |
| if lib in vndk_lib.sp_hal_vndk_stable: |
| tags.append(get_str_idx('sp-hal-vndk-stable')) |
| |
| if lib in vndk_lib.sp_ndk_vndk_stable: |
| tags.append(get_str_idx('sp-ndk-vndk-stable')) |
| if lib in vndk_lib.sp_both_vndk_stable: |
| tags.append(get_str_idx('sp-both-vndk-stable')) |
| |
| if lib in vndk_lib.vndk_core: |
| tags.append(get_str_idx('vndk-core')) |
| if lib in vndk_lib.vndk_indirect: |
| tags.append(get_str_idx('vndk-indirect')) |
| if lib in vndk_lib.vndk_fwk_ext: |
| tags.append(get_str_idx('vndk-fwk-ext')) |
| if lib in vndk_lib.vndk_vnd_ext: |
| tags.append(get_str_idx('vndk-vnd-ext')) |
| if lib in vndk_lib.extra_vendor_lib: |
| tags.append(get_str_idx('extra-vendor-lib')) |
| return tags |
| |
| mods = [] |
| for lib in libs: |
| mods.append([get_str_idx(lib.path), |
| 32 if lib.elf.is_32bit else 64, |
| collect_tags(lib), |
| collect_deps(lib), |
| collect_path_sorted_lib_idxs(lib.users)]) |
| |
| # Generate output files. |
| makedirs(args.output, exist_ok=True) |
| script_dir = os.path.dirname(os.path.abspath(__file__)) |
| for name in ('index.html', 'insight.css', 'insight.js'): |
| shutil.copyfile(os.path.join(script_dir, 'assets', name), |
| os.path.join(args.output, name)) |
| |
| with open(os.path.join(args.output, 'insight-data.js'), 'w') as f: |
| f.write('''(function () { |
| var strs = ''' + json.dumps(strs) + '''; |
| var mods = ''' + json.dumps(mods) + '''; |
| insight.init(document, strs, mods); |
| })();''') |
| |
| return 0 |
| |
| |
| class VNDKCapCommand(ELFGraphCommand): |
| def __init__(self): |
| super(VNDKCapCommand, self).__init__( |
| 'vndk-cap', help='Compute VNDK set upper bound') |
| |
| def add_argparser_options(self, parser): |
| super(VNDKCapCommand, self).add_argparser_options(parser) |
| |
| def main(self, args): |
| graph = ELFLinker.create(args.system, args.system_dir_as_vendor, |
| args.vendor, args.vendor_dir_as_system, |
| args.load_extra_deps) |
| |
| banned_libs = BannedLibDict.create_default() |
| |
| vndk_cap = graph.compute_vndk_cap(banned_libs) |
| |
| for lib in sorted_lib_path_list(vndk_cap): |
| print(lib) |
| |
| |
| 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') |
| |
| parser.add_argument( |
| '--symbols', action='store_true', |
| help='print symbols') |
| |
| def main(self, args): |
| graph = ELFLinker.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(): |
| if not args.symbols: |
| def collect_symbols(user, definer): |
| return () |
| else: |
| def collect_symbols(user, definer): |
| symbols = set() |
| for symbol, exp_lib in user.linked_symbols.items(): |
| if exp_lib == definer: |
| symbols.add(symbol) |
| return sorted(symbols) |
| |
| data = [] |
| if args.revert: |
| for assoc_lib in sorted(lib.users, key=lambda x: x.path): |
| data.append((assoc_lib.path, |
| collect_symbols(assoc_lib, lib))) |
| else: |
| for assoc_lib in sorted(lib.deps, key=lambda x: x.path): |
| data.append((assoc_lib.path, |
| collect_symbols(lib, assoc_lib))) |
| results.append((name, data)) |
| results.sort() |
| |
| if args.leaf: |
| for name, deps in results: |
| if not deps: |
| print(name) |
| else: |
| for name, assoc_libs in results: |
| print(name) |
| for assoc_lib, symbols in assoc_libs: |
| print('\t' + assoc_lib) |
| for symbol in symbols: |
| print('\t\t' + symbol) |
| 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 = ELFLinker.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.get_libs(args.lib, report_error) |
| excluded_libs = graph.get_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 VNDKStableCommand(ELFGraphCommand): |
| def __init__(self): |
| super(VNDKStableCommand, self).__init__( |
| 'vndk-stable', help='List pre-defined VNDK stable') |
| |
| def add_argparser_options(self, parser): |
| super(VNDKStableCommand, self).add_argparser_options(parser) |
| |
| def main(self, args): |
| graph = ELFLinker.create(args.system, args.system_dir_as_vendor, |
| args.vendor, args.vendor_dir_as_system, |
| args.load_extra_deps) |
| |
| vndk_stable = graph.compute_predefined_vndk_stable() |
| for lib in sorted_lib_path_list(vndk_stable): |
| print(lib) |
| return 0 |
| |
| |
| class SpLibCommand(ELFGraphCommand): |
| def __init__(self): |
| super(SpLibCommand, self).__init__( |
| 'sp-lib', help='Define sp-ndk, sp-hal, and vndk-stable') |
| |
| def add_argparser_options(self, parser): |
| super(SpLibCommand, self).add_argparser_options(parser) |
| |
| parser.add_argument( |
| '--load-generic-refs', |
| help='compare with generic reference symbols') |
| |
| def main(self, args): |
| generic_refs = None |
| if args.load_generic_refs: |
| generic_refs = GenericRefs.create_from_dir(args.load_generic_refs) |
| |
| graph = ELFLinker.create(args.system, args.system_dir_as_vendor, |
| args.vendor, args.vendor_dir_as_system, |
| args.load_extra_deps) |
| |
| print_sp_lib(graph.compute_sp_lib(generic_refs)) |
| 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(VNDKCapCommand()) |
| register_subcmd(DepsCommand()) |
| register_subcmd(DepsClosureCommand()) |
| register_subcmd(DepsInsightCommand()) |
| register_subcmd(SpLibCommand()) |
| register_subcmd(VNDKStableCommand()) |
| |
| 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()) |