Home | History | Annotate | Download | only in definition-tool
      1 #!/usr/bin/env python3
      2 
      3 from __future__ import print_function
      4 
      5 import argparse
      6 import collections
      7 import copy
      8 import csv
      9 import itertools
     10 import json
     11 import os
     12 import re
     13 import shutil
     14 import stat
     15 import struct
     16 import sys
     17 
     18 
     19 #------------------------------------------------------------------------------
     20 # Python 2 and 3 Compatibility Layer
     21 #------------------------------------------------------------------------------
     22 
     23 if sys.version_info >= (3, 0):
     24     from os import makedirs
     25     from mmap import ACCESS_READ, mmap
     26 else:
     27     from mmap import ACCESS_READ, mmap
     28 
     29     def makedirs(path, exist_ok):
     30         if exist_ok and os.path.isdir(path):
     31             return
     32         return os.makedirs(path)
     33 
     34     class mmap(mmap):
     35         def __enter__(self):
     36             return self
     37 
     38         def __exit__(self, exc, value, tb):
     39             self.close()
     40 
     41         def __getitem__(self, key):
     42             res = super(mmap, self).__getitem__(key)
     43             if type(key) == int:
     44                 return ord(res)
     45             return res
     46 
     47     FileNotFoundError = OSError
     48 
     49 try:
     50     from sys import intern
     51 except ImportError:
     52     pass
     53 
     54 
     55 #------------------------------------------------------------------------------
     56 # Collections
     57 #------------------------------------------------------------------------------
     58 
     59 def defaultnamedtuple(typename, field_names, default):
     60     """Create a namedtuple type with default values.
     61 
     62     This function creates a namedtuple type which will fill in default value
     63     when actual arguments to the constructor were omitted.
     64 
     65     >>> Point = defaultnamedtuple('Point', ['x', 'y'], 0)
     66     >>> Point()
     67     Point(x=0, y=0)
     68     >>> Point(1)
     69     Point(x=1, y=0)
     70     >>> Point(1, 2)
     71     Point(x=1, y=2)
     72     >>> Point(x=1, y=2)
     73     Point(x=1, y=2)
     74     >>> Point(y=2, x=1)
     75     Point(x=1, y=2)
     76 
     77     >>> PermSet = defaultnamedtuple('PermSet', 'allowed disallowed', set())
     78     >>> s = PermSet()
     79     >>> s
     80     PermSet(allowed=set(), disallowed=set())
     81     >>> s.allowed is not s.disallowed
     82     True
     83     >>> PermSet({1})
     84     PermSet(allowed={1}, disallowed=set())
     85     >>> PermSet({1}, {2})
     86     PermSet(allowed={1}, disallowed={2})
     87     """
     88 
     89     if isinstance(field_names, str):
     90         field_names = field_names.replace(',', ' ').split()
     91     field_names = list(map(str, field_names))
     92     num_fields = len(field_names)
     93 
     94     base_cls = collections.namedtuple(typename, field_names)
     95     def __new__(cls, *args, **kwargs):
     96         args = list(args)
     97         for i in range(len(args), num_fields):
     98             arg = kwargs.get(field_names[i])
     99             if arg:
    100                 args.append(arg)
    101             else:
    102                 args.append(copy.copy(default))
    103         return base_cls.__new__(cls, *args)
    104     return type(typename, (base_cls,), {'__new__': __new__})
    105 
    106 
    107 #------------------------------------------------------------------------------
    108 # ELF Parser
    109 #------------------------------------------------------------------------------
    110 
    111 Elf_Hdr = collections.namedtuple(
    112         'Elf_Hdr',
    113         'ei_class ei_data ei_version ei_osabi e_type e_machine e_version '
    114         'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum '
    115         'e_shentsize e_shnum e_shstridx')
    116 
    117 
    118 Elf_Shdr = collections.namedtuple(
    119         'Elf_Shdr',
    120         'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info '
    121         'sh_addralign sh_entsize')
    122 
    123 
    124 Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val')
    125 
    126 
    127 class Elf_Sym(collections.namedtuple(
    128     'ELF_Sym', 'st_name st_value st_size st_info st_other st_shndx')):
    129 
    130     STB_LOCAL = 0
    131     STB_GLOBAL = 1
    132     STB_WEAK = 2
    133 
    134     SHN_UNDEF = 0
    135 
    136     @property
    137     def st_bind(self):
    138         return (self.st_info >> 4)
    139 
    140     @property
    141     def is_local(self):
    142         return self.st_bind == Elf_Sym.STB_LOCAL
    143 
    144     @property
    145     def is_global(self):
    146         return self.st_bind == Elf_Sym.STB_GLOBAL
    147 
    148     @property
    149     def is_weak(self):
    150         return self.st_bind == Elf_Sym.STB_WEAK
    151 
    152     @property
    153     def is_undef(self):
    154         return self.st_shndx == Elf_Sym.SHN_UNDEF
    155 
    156 
    157 class ELFError(ValueError):
    158     pass
    159 
    160 
    161 class ELF(object):
    162     # ELF file format constants.
    163     ELF_MAGIC = b'\x7fELF'
    164 
    165     EI_CLASS = 4
    166     EI_DATA = 5
    167 
    168     ELFCLASSNONE = 0
    169     ELFCLASS32 = 1
    170     ELFCLASS64 = 2
    171 
    172     ELFDATANONE = 0
    173     ELFDATA2LSB = 1
    174     ELFDATA2MSB = 2
    175 
    176     DT_NEEDED = 1
    177     DT_RPATH = 15
    178     DT_RUNPATH = 29
    179 
    180     _ELF_CLASS_NAMES = {
    181         ELFCLASS32: '32',
    182         ELFCLASS64: '64',
    183     }
    184 
    185     _ELF_DATA_NAMES = {
    186         ELFDATA2LSB: 'Little-Endian',
    187         ELFDATA2MSB: 'Big-Endian',
    188     }
    189 
    190     _ELF_MACHINE_IDS = {
    191         0: 'EM_NONE',
    192         3: 'EM_386',
    193         8: 'EM_MIPS',
    194         40: 'EM_ARM',
    195         62: 'EM_X86_64',
    196         183: 'EM_AARCH64',
    197     }
    198 
    199 
    200     @staticmethod
    201     def _dict_find_key_by_value(d, dst):
    202         for key, value in d.items():
    203             if value == dst:
    204                 return key
    205         raise KeyError(dst)
    206 
    207     @staticmethod
    208     def get_ei_class_from_name(name):
    209         return ELF._dict_find_key_by_value(ELF._ELF_CLASS_NAMES, name)
    210 
    211     @staticmethod
    212     def get_ei_data_from_name(name):
    213         return ELF._dict_find_key_by_value(ELF._ELF_DATA_NAMES, name)
    214 
    215     @staticmethod
    216     def get_e_machine_from_name(name):
    217         return ELF._dict_find_key_by_value(ELF._ELF_MACHINE_IDS, name)
    218 
    219 
    220     __slots__ = ('ei_class', 'ei_data', 'e_machine', 'dt_rpath', 'dt_runpath',
    221                  'dt_needed', 'exported_symbols', 'imported_symbols',)
    222 
    223 
    224     def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0,
    225                  dt_rpath=None, dt_runpath=None, dt_needed=None,
    226                  exported_symbols=None, imported_symbols=None):
    227         self.ei_class = ei_class
    228         self.ei_data = ei_data
    229         self.e_machine = e_machine
    230         self.dt_rpath = dt_rpath if dt_rpath is not None else []
    231         self.dt_runpath = dt_runpath if dt_runpath is not None else []
    232         self.dt_needed = dt_needed if dt_needed is not None else []
    233         self.exported_symbols = \
    234                 exported_symbols if exported_symbols is not None else set()
    235         self.imported_symbols = \
    236                 imported_symbols if imported_symbols is not None else set()
    237 
    238     def __repr__(self):
    239         args = (a + '=' + repr(getattr(self, a)) for a in self.__slots__)
    240         return 'ELF(' + ', '.join(args) + ')'
    241 
    242     def __eq__(self, rhs):
    243         return all(getattr(self, a) == getattr(rhs, a) for a in self.__slots__)
    244 
    245     @property
    246     def elf_class_name(self):
    247         return self._ELF_CLASS_NAMES.get(self.ei_class, 'None')
    248 
    249     @property
    250     def elf_data_name(self):
    251         return self._ELF_DATA_NAMES.get(self.ei_data, 'None')
    252 
    253     @property
    254     def elf_machine_name(self):
    255         return self._ELF_MACHINE_IDS.get(self.e_machine, str(self.e_machine))
    256 
    257     @property
    258     def is_32bit(self):
    259         return self.ei_class == ELF.ELFCLASS32
    260 
    261     @property
    262     def is_64bit(self):
    263         return self.ei_class == ELF.ELFCLASS64
    264 
    265     @property
    266     def sorted_exported_symbols(self):
    267         return sorted(list(self.exported_symbols))
    268 
    269     @property
    270     def sorted_imported_symbols(self):
    271         return sorted(list(self.imported_symbols))
    272 
    273     def dump(self, file=None):
    274         """Print parsed ELF information to the file"""
    275         file = file if file is not None else sys.stdout
    276 
    277         print('EI_CLASS\t' + self.elf_class_name, file=file)
    278         print('EI_DATA\t\t' + self.elf_data_name, file=file)
    279         print('E_MACHINE\t' + self.elf_machine_name, file=file)
    280         for dt_rpath in self.dt_rpath:
    281             print('DT_RPATH\t' + dt_rpath, file=file)
    282         for dt_runpath in self.dt_runpath:
    283             print('DT_RUNPATH\t' + dt_runpath, file=file)
    284         for dt_needed in self.dt_needed:
    285             print('DT_NEEDED\t' + dt_needed, file=file)
    286         for symbol in self.sorted_exported_symbols:
    287             print('EXP_SYMBOL\t' + symbol, file=file)
    288         for symbol in self.sorted_imported_symbols:
    289             print('IMP_SYMBOL\t' + symbol, file=file)
    290 
    291     # Extract zero-terminated buffer slice.
    292     def _extract_zero_terminated_buf_slice(self, buf, offset):
    293         """Extract a zero-terminated buffer slice from the given offset"""
    294         end = buf.find(b'\0', offset)
    295         if end == -1:
    296             return buf[offset:]
    297         return buf[offset:end]
    298 
    299     # Extract c-style interned string from the buffer.
    300     if sys.version_info >= (3, 0):
    301         def _extract_zero_terminated_str(self, buf, offset):
    302             """Extract a c-style string from the given buffer and offset"""
    303             buf_slice = self._extract_zero_terminated_buf_slice(buf, offset)
    304             return intern(buf_slice.decode('utf-8'))
    305     else:
    306         def _extract_zero_terminated_str(self, buf, offset):
    307             """Extract a c-style string from the given buffer and offset"""
    308             return intern(self._extract_zero_terminated_buf_slice(buf, offset))
    309 
    310     def _parse_from_buf_internal(self, buf):
    311         """Parse ELF image resides in the buffer"""
    312 
    313         # Check ELF ident.
    314         if buf.size() < 8:
    315             raise ELFError('bad ident')
    316 
    317         if buf[0:4] != ELF.ELF_MAGIC:
    318             raise ELFError('bad magic')
    319 
    320         self.ei_class = buf[ELF.EI_CLASS]
    321         if self.ei_class not in (ELF.ELFCLASS32, ELF.ELFCLASS64):
    322             raise ELFError('unknown word size')
    323 
    324         self.ei_data = buf[ELF.EI_DATA]
    325         if self.ei_data not in (ELF.ELFDATA2LSB, ELF.ELFDATA2MSB):
    326             raise ELFError('unknown endianness')
    327 
    328         # ELF structure definitions.
    329         endian_fmt = '<' if self.ei_data == ELF.ELFDATA2LSB else '>'
    330 
    331         if self.is_32bit:
    332             elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH'
    333             elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL'
    334             elf_dyn_fmt = endian_fmt + 'lL'
    335             elf_sym_fmt = endian_fmt + 'LLLBBH'
    336         else:
    337             elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH'
    338             elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ'
    339             elf_dyn_fmt = endian_fmt + 'QQ'
    340             elf_sym_fmt = endian_fmt + 'LBBHQQ'
    341 
    342         def parse_struct(cls, fmt, offset, error_msg):
    343             try:
    344                 return cls._make(struct.unpack_from(fmt, buf, offset))
    345             except struct.error:
    346                 raise ELFError(error_msg)
    347 
    348         def parse_elf_hdr(offset):
    349             return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header')
    350 
    351         def parse_elf_shdr(offset):
    352             return parse_struct(Elf_Shdr, elf_shdr_fmt, offset,
    353                                 'bad section header')
    354 
    355         def parse_elf_dyn(offset):
    356             return parse_struct(Elf_Dyn, elf_dyn_fmt, offset,
    357                                 'bad .dynamic entry')
    358 
    359         if self.is_32bit:
    360             def parse_elf_sym(offset):
    361                 return parse_struct(Elf_Sym, elf_sym_fmt, offset, 'bad elf sym')
    362         else:
    363             def parse_elf_sym(offset):
    364                 try:
    365                     p = struct.unpack_from(elf_sym_fmt, buf, offset)
    366                     return Elf_Sym(p[0], p[4], p[5], p[1], p[2], p[3])
    367                 except struct.error:
    368                     raise ELFError('bad elf sym')
    369 
    370         def extract_str(offset):
    371             return self._extract_zero_terminated_str(buf, offset)
    372 
    373         # Parse ELF header.
    374         header = parse_elf_hdr(0)
    375         self.e_machine = header.e_machine
    376 
    377         # Check section header size.
    378         if header.e_shentsize == 0:
    379             raise ELFError('no section header')
    380 
    381         # Find .shstrtab section.
    382         shstrtab_shdr_off = \
    383                 header.e_shoff + header.e_shstridx * header.e_shentsize
    384         shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off)
    385         shstrtab_off = shstrtab_shdr.sh_offset
    386 
    387         # Parse ELF section header.
    388         sections = dict()
    389         header_end = header.e_shoff + header.e_shnum * header.e_shentsize
    390         for shdr_off in range(header.e_shoff, header_end, header.e_shentsize):
    391             shdr = parse_elf_shdr(shdr_off)
    392             name = extract_str(shstrtab_off + shdr.sh_name)
    393             sections[name] = shdr
    394 
    395         # Find .dynamic and .dynstr section header.
    396         dynamic_shdr = sections.get('.dynamic')
    397         if not dynamic_shdr:
    398             raise ELFError('no .dynamic section')
    399 
    400         dynstr_shdr = sections.get('.dynstr')
    401         if not dynstr_shdr:
    402             raise ELFError('no .dynstr section')
    403 
    404         dynamic_off = dynamic_shdr.sh_offset
    405         dynstr_off = dynstr_shdr.sh_offset
    406 
    407         # Parse entries in .dynamic section.
    408         assert struct.calcsize(elf_dyn_fmt) == dynamic_shdr.sh_entsize
    409         dynamic_end = dynamic_off + dynamic_shdr.sh_size
    410         for ent_off in range(dynamic_off, dynamic_end, dynamic_shdr.sh_entsize):
    411             ent = parse_elf_dyn(ent_off)
    412             if ent.d_tag == ELF.DT_NEEDED:
    413                 self.dt_needed.append(extract_str(dynstr_off + ent.d_val))
    414             elif ent.d_tag == ELF.DT_RPATH:
    415                 self.dt_rpath.extend(
    416                         extract_str(dynstr_off + ent.d_val).split(':'))
    417             elif ent.d_tag == ELF.DT_RUNPATH:
    418                 self.dt_runpath.extend(
    419                         extract_str(dynstr_off + ent.d_val).split(':'))
    420 
    421         # Parse exported symbols in .dynsym section.
    422         dynsym_shdr = sections.get('.dynsym')
    423         if dynsym_shdr:
    424             exp_symbols = self.exported_symbols
    425             imp_symbols = self.imported_symbols
    426 
    427             dynsym_off = dynsym_shdr.sh_offset
    428             dynsym_end = dynsym_off + dynsym_shdr.sh_size
    429             dynsym_entsize = dynsym_shdr.sh_entsize
    430 
    431             # Skip first symbol entry (null symbol).
    432             dynsym_off += dynsym_entsize
    433 
    434             for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize):
    435                 ent = parse_elf_sym(ent_off)
    436                 symbol_name = extract_str(dynstr_off + ent.st_name)
    437                 if ent.is_undef:
    438                     imp_symbols.add(symbol_name)
    439                 elif not ent.is_local:
    440                     exp_symbols.add(symbol_name)
    441 
    442     def _parse_from_buf(self, buf):
    443         """Parse ELF image resides in the buffer"""
    444         try:
    445             self._parse_from_buf_internal(buf)
    446         except IndexError:
    447             raise ELFError('bad offset')
    448 
    449     def _parse_from_file(self, path):
    450         """Parse ELF image from the file path"""
    451         with open(path, 'rb') as f:
    452             st = os.fstat(f.fileno())
    453             if not st.st_size:
    454                 raise ELFError('empty file')
    455             with mmap(f.fileno(), st.st_size, access=ACCESS_READ) as image:
    456                 self._parse_from_buf(image)
    457 
    458     def _parse_from_dump_lines(self, path, lines):
    459         patt = re.compile('^([A-Za-z_]+)\t+(.*)$')
    460         for line_no, line in enumerate(lines):
    461             match = patt.match(line)
    462             if not match:
    463                 print('error: {}: {}: failed to parse'
    464                         .format(path, line_no + 1), file=sys.stderr)
    465                 continue
    466             key = match.group(1)
    467             value = match.group(2)
    468 
    469             if key == 'EI_CLASS':
    470                 self.ei_class = ELF.get_ei_class_from_name(value)
    471             elif key == 'EI_DATA':
    472                 self.ei_data = ELF.get_ei_data_from_name(value)
    473             elif key == 'E_MACHINE':
    474                 self.e_machine = ELF.get_e_machine_from_name(value)
    475             elif key == 'DT_RPATH':
    476                 self.dt_rpath.append(intern(value))
    477             elif key == 'DT_RUNPATH':
    478                 self.dt_runpath.append(intern(value))
    479             elif key == 'DT_NEEDED':
    480                 self.dt_needed.append(intern(value))
    481             elif key == 'EXP_SYMBOL':
    482                 self.exported_symbols.add(intern(value))
    483             elif key == 'IMP_SYMBOL':
    484                 self.imported_symbols.add(intern(value))
    485             else:
    486                 print('error: {}: {}: unknown tag name: {}'
    487                         .format(path, line_no + 1, key), file=sys.stderr)
    488 
    489     def _parse_from_dump_file(self, path):
    490         """Load information from ELF dump file."""
    491         with open(path, 'r') as f:
    492             self._parse_from_dump_lines(path, f)
    493 
    494     def _parse_from_dump_buf(self, buf):
    495         """Load information from ELF dump buffer."""
    496         self._parse_from_dump_lines('<str:0x{:x}>'.format(id(buf)),
    497                                     buf.splitlines())
    498 
    499     @staticmethod
    500     def load(path):
    501         """Create an ELF instance from the file path"""
    502         elf = ELF()
    503         elf._parse_from_file(path)
    504         return elf
    505 
    506     @staticmethod
    507     def loads(buf):
    508         """Create an ELF instance from the buffer"""
    509         elf = ELF()
    510         elf._parse_from_buf(buf)
    511         return elf
    512 
    513     @staticmethod
    514     def load_dump(path):
    515         """Create an ELF instance from a dump file path"""
    516         elf = ELF()
    517         elf._parse_from_dump_file(path)
    518         return elf
    519 
    520     @staticmethod
    521     def load_dumps(buf):
    522         """Create an ELF instance from a dump file buffer"""
    523         elf = ELF()
    524         elf._parse_from_dump_buf(buf)
    525         return elf
    526 
    527 
    528 #------------------------------------------------------------------------------
    529 # NDK and Banned Libraries
    530 #------------------------------------------------------------------------------
    531 
    532 class NDKLibDict(object):
    533     NOT_NDK = 0
    534     LL_NDK = 1
    535     SP_NDK = 2
    536     HL_NDK = 3
    537 
    538     LL_NDK_LIB_NAMES = (
    539         'libc.so',
    540         'libdl.so',
    541         'liblog.so',
    542         'libm.so',
    543         'libstdc++.so',
    544         'libvndksupport.so',
    545         'libandroid_net.so',
    546         'libz.so',
    547     )
    548 
    549     SP_NDK_LIB_NAMES = (
    550         'libEGL.so',
    551         'libGLESv1_CM.so',
    552         'libGLESv2.so',
    553         'libGLESv3.so',
    554         'libnativewindow.so',
    555         'libsync.so',
    556         'libvulkan.so',
    557     )
    558 
    559     HL_NDK_LIB_NAMES = (
    560         'libOpenMAXAL.so',
    561         'libOpenSLES.so',
    562         'libandroid.so',
    563         'libcamera2ndk.so',
    564         'libjnigraphics.so',
    565         'libmediandk.so',
    566     )
    567 
    568     @staticmethod
    569     def _create_pattern(names):
    570         return '|'.join('(?:^\\/system\\/lib(?:64)?\\/' + re.escape(i) + '$)'
    571                         for i in names)
    572 
    573     @staticmethod
    574     def _compile_path_matcher(names):
    575         return re.compile(NDKLibDict._create_pattern(names))
    576 
    577     @staticmethod
    578     def _compile_multi_path_matcher(name_lists):
    579         patt = '|'.join('(' + NDKLibDict._create_pattern(names) + ')'
    580                         for names in name_lists)
    581         return re.compile(patt)
    582 
    583     def __init__(self):
    584         self.ll_ndk_patterns = self._compile_path_matcher(self.LL_NDK_LIB_NAMES)
    585         self.sp_ndk_patterns = self._compile_path_matcher(self.SP_NDK_LIB_NAMES)
    586         self.hl_ndk_patterns = self._compile_path_matcher(self.HL_NDK_LIB_NAMES)
    587         self.ndk_patterns = self._compile_multi_path_matcher(
    588                 (self.LL_NDK_LIB_NAMES, self.SP_NDK_LIB_NAMES,
    589                  self.HL_NDK_LIB_NAMES))
    590 
    591     def is_ll_ndk(self, path):
    592         return self.ll_ndk_patterns.match(path)
    593 
    594     def is_sp_ndk(self, path):
    595         return self.sp_ndk_patterns.match(path)
    596 
    597     def is_hl_ndk(self, path):
    598         return self.hl_ndk_patterns.match(path)
    599 
    600     def is_ndk(self, path):
    601         return self.ndk_patterns.match(path)
    602 
    603     def classify(self, path):
    604         match = self.ndk_patterns.match(path)
    605         if not match:
    606             return 0
    607         return match.lastindex
    608 
    609 NDK_LIBS = NDKLibDict()
    610 
    611 
    612 BannedLib = collections.namedtuple(
    613         'BannedLib', ('name', 'reason', 'action',))
    614 
    615 BA_WARN = 0
    616 BA_EXCLUDE = 1
    617 
    618 class BannedLibDict(object):
    619     def __init__(self):
    620         self.banned_libs = dict()
    621 
    622     def add(self, name, reason, action):
    623         self.banned_libs[name] = BannedLib(name, reason, action)
    624 
    625     def get(self, name):
    626         return self.banned_libs.get(name)
    627 
    628     def is_banned(self, path):
    629         return self.get(os.path.basename(path))
    630 
    631     @staticmethod
    632     def create_default():
    633         d = BannedLibDict()
    634         d.add('libbinder.so', 'un-versioned IPC', BA_WARN)
    635         d.add('libselinux.so', 'policydb might be incompatible', BA_WARN)
    636         return d
    637 
    638 
    639 #------------------------------------------------------------------------------
    640 # ELF Linker
    641 #------------------------------------------------------------------------------
    642 
    643 def is_accessible(path):
    644     try:
    645         mode = os.stat(path).st_mode
    646         return (mode & (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)) != 0
    647     except FileNotFoundError:
    648         return False
    649 
    650 
    651 def scan_accessible_files(root):
    652     for base, dirs, files in os.walk(root):
    653         for filename in files:
    654             path = os.path.join(base, filename)
    655             if is_accessible(path):
    656                 yield path
    657 
    658 
    659 def scan_elf_files(root):
    660     for path in scan_accessible_files(root):
    661         try:
    662             yield (path, ELF.load(path))
    663         except ELFError:
    664             pass
    665 
    666 
    667 def scan_elf_dump_files(root):
    668     for path in scan_accessible_files(root):
    669         if not path.endswith('.sym'):
    670             continue
    671         yield (path[0:-4], ELF.load_dump(path))
    672 
    673 
    674 PT_SYSTEM = 0
    675 PT_VENDOR = 1
    676 NUM_PARTITIONS = 2
    677 
    678 
    679 SPLibResult = collections.namedtuple(
    680         'SPLibResult',
    681         'sp_hal sp_hal_dep vndk_sp_hal sp_ndk sp_ndk_indirect '
    682         'vndk_sp_both')
    683 
    684 def print_sp_lib(sp_lib, file=sys.stdout):
    685     # SP-NDK
    686     for lib in sorted_lib_path_list(sp_lib.sp_ndk):
    687         print('sp-ndk:', lib, file=file)
    688     for lib in sorted_lib_path_list(sp_lib.sp_ndk_indirect):
    689         print('sp-ndk-indirect:', lib, file=file)
    690 
    691     # SP-HAL
    692     for lib in sorted_lib_path_list(sp_lib.sp_hal):
    693         print('sp-hal:', lib, file=file)
    694     for lib in sorted_lib_path_list(sp_lib.sp_hal_dep):
    695         print('sp-hal-dep:', lib, file=file)
    696     for lib in sorted_lib_path_list(sp_lib.vndk_sp_hal):
    697         print('vndk-sp-hal:', lib, file=file)
    698 
    699     # SP-both
    700     for lib in sorted_lib_path_list(sp_lib.vndk_sp_both):
    701         print('vndk-sp-both:', lib, file=file)
    702 
    703 
    704 class ELFResolver(object):
    705     def __init__(self, lib_set, default_search_path):
    706         self.lib_set = lib_set
    707         self.default_search_path = default_search_path
    708 
    709     def get_candidates(self, name, dt_rpath=None, dt_runpath=None):
    710         if dt_rpath:
    711             for d in dt_rpath:
    712                 yield os.path.join(d, name)
    713         if dt_runpath:
    714             for d in dt_runpath:
    715                 yield os.path.join(d, name)
    716         for d in self.default_search_path:
    717             yield os.path.join(d, name)
    718 
    719     def resolve(self, name, dt_rpath=None, dt_runpath=None):
    720         for path in self.get_candidates(name, dt_rpath, dt_runpath):
    721             try:
    722                 return self.lib_set[path]
    723             except KeyError:
    724                 continue
    725         return None
    726 
    727 
    728 class ELFLinkData(object):
    729     NEEDED = 0  # Dependencies recorded in DT_NEEDED entries.
    730     DLOPEN = 1  # Dependencies introduced by dlopen().
    731 
    732     def __init__(self, partition, path, elf):
    733         self.partition = partition
    734         self.path = path
    735         self.elf = elf
    736         self._deps = (set(), set())
    737         self._users = (set(), set())
    738         self.imported_ext_symbols = collections.defaultdict(set)
    739         self._ndk_classification = NDK_LIBS.classify(path)
    740         self.unresolved_symbols = set()
    741         self.linked_symbols = dict()
    742 
    743     @property
    744     def is_ndk(self):
    745         return self._ndk_classification != NDKLibDict.NOT_NDK
    746 
    747     @property
    748     def is_ll_ndk(self):
    749         return self._ndk_classification == NDKLibDict.LL_NDK
    750 
    751     @property
    752     def is_sp_ndk(self):
    753         return self._ndk_classification == NDKLibDict.SP_NDK
    754 
    755     @property
    756     def is_hl_ndk(self):
    757         return self._ndk_classification == NDKLibDict.HL_NDK
    758 
    759     def add_dep(self, dst, ty):
    760         self._deps[ty].add(dst)
    761         dst._users[ty].add(self)
    762 
    763     def remove_dep(self, dst, ty):
    764         self._deps[ty].remove(dst)
    765         dst._users[ty].remove(self)
    766 
    767     @property
    768     def num_deps(self):
    769         """Get the number of dependencies.  If a library is linked by both
    770         NEEDED and DLOPEN relationship, then it will be counted twice."""
    771         return sum(len(deps) for deps in self._deps)
    772 
    773     @property
    774     def deps(self):
    775         return itertools.chain.from_iterable(self._deps)
    776 
    777     @property
    778     def deps_with_type(self):
    779         dt_deps = zip(self._deps[self.NEEDED], itertools.repeat(self.NEEDED))
    780         dl_deps = zip(self._deps[self.DLOPEN], itertools.repeat(self.DLOPEN))
    781         return itertools.chain(dt_deps, dl_deps)
    782 
    783     @property
    784     def dt_deps(self):
    785         return self._deps[self.NEEDED]
    786 
    787     @property
    788     def dl_deps(self):
    789         return self._deps[self.DLOPEN]
    790 
    791     @property
    792     def num_users(self):
    793         """Get the number of users.  If a library is linked by both NEEDED and
    794         DLOPEN relationship, then it will be counted twice."""
    795         return sum(len(users) for users in self._users)
    796 
    797     @property
    798     def users(self):
    799         return itertools.chain.from_iterable(self._users)
    800 
    801     @property
    802     def users_with_type(self):
    803         dt_users = zip(self._users[self.NEEDED], itertools.repeat(self.NEEDED))
    804         dl_users = zip(self._users[self.DLOPEN], itertools.repeat(self.DLOPEN))
    805         return itertools.chain(dt_users, dl_users)
    806 
    807     @property
    808     def dt_users(self):
    809         return self._users[self.NEEDED]
    810 
    811     @property
    812     def dl_users(self):
    813         return self._users[self.DLOPEN]
    814 
    815     def has_dep(self, dst):
    816         return any(dst in deps for deps in self._deps)
    817 
    818     def has_user(self, dst):
    819         return any(dst in users for users in self._users)
    820 
    821     def is_system_lib(self):
    822         return self.partition == PT_SYSTEM
    823 
    824     def get_dep_linked_symbols(self, dep):
    825         symbols = set()
    826         for symbol, exp_lib in self.linked_symbols.items():
    827             if exp_lib == dep:
    828                 symbols.add(symbol)
    829         return sorted(symbols)
    830 
    831     def __lt__(self, rhs):
    832         return self.path < rhs.path
    833 
    834 
    835 def sorted_lib_path_list(libs):
    836     libs = [lib.path for lib in libs]
    837     libs.sort()
    838     return libs
    839 
    840 _VNDK_RESULT_FIELD_NAMES = (
    841         'll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
    842         'vndk_sp', 'vndk_sp_unused', 'vndk_sp_indirect',
    843         'vndk_sp_indirect_unused', 'vndk_sp_indirect_private', 'vndk',
    844         'vndk_indirect', 'fwk_only', 'fwk_only_rs', 'sp_hal', 'sp_hal_dep',
    845         'vnd_only', 'vndk_ext', 'vndk_sp_ext', 'vndk_sp_indirect_ext',
    846         'extra_vendor_libs')
    847 
    848 VNDKResult = defaultnamedtuple('VNDKResult', _VNDK_RESULT_FIELD_NAMES, set())
    849 
    850 _SIMPLE_VNDK_RESULT_FIELD_NAMES = (
    851         'vndk_sp', 'vndk_sp_ext', 'extra_vendor_libs')
    852 
    853 SimpleVNDKResult = defaultnamedtuple(
    854         'SimpleVNDKResult', _SIMPLE_VNDK_RESULT_FIELD_NAMES, set())
    855 
    856 
    857 class ELFLibDict(defaultnamedtuple('ELFLibDict', ('lib32', 'lib64'), {})):
    858     def get_lib_dict(self, elf_class):
    859         return self[elf_class - 1]
    860 
    861     def add(self, path, lib):
    862         self.get_lib_dict(lib.elf.ei_class)[path] = lib
    863 
    864     def remove(self, lib):
    865         del self.get_lib_dict(lib.elf.ei_class)[lib.path]
    866 
    867     def get(self, path, default=None):
    868         for lib_set in self:
    869             res = lib_set.get(path, None)
    870             if res:
    871                 return res
    872         return default
    873 
    874     def keys(self):
    875         return itertools.chain(self.lib32.keys(), self.lib64.keys())
    876 
    877     def values(self):
    878         return itertools.chain(self.lib32.values(), self.lib64.values())
    879 
    880     def items(self):
    881         return itertools.chain(self.lib32.items(), self.lib64.items())
    882 
    883 
    884 class ELFLinker(object):
    885     def __init__(self):
    886         self.lib_pt = [ELFLibDict() for i in range(NUM_PARTITIONS)]
    887 
    888     def _add_lib_to_lookup_dict(self, lib):
    889         self.lib_pt[lib.partition].add(lib.path, lib)
    890 
    891     def _remove_lib_from_lookup_dict(self, lib):
    892         self.lib_pt[lib.partition].remove(lib)
    893 
    894     def add_lib(self, partition, path, elf):
    895         lib = ELFLinkData(partition, path, elf)
    896         self._add_lib_to_lookup_dict(lib)
    897         return lib
    898 
    899     def rename_lib(self, lib, new_partition, new_path):
    900         self._remove_lib_from_lookup_dict(lib)
    901         lib.path = new_path
    902         lib.partition = new_partition
    903         self._add_lib_to_lookup_dict(lib)
    904 
    905     def add_dep(self, src_path, dst_path, ty):
    906         for elf_class in (ELF.ELFCLASS32, ELF.ELFCLASS64):
    907             src = self.get_lib_in_elf_class(elf_class, src_path)
    908             dst = self.get_lib_in_elf_class(elf_class, dst_path)
    909             if src and dst:
    910                 src.add_dep(dst, ty)
    911                 return
    912         print('error: cannot add dependency from {} to {}.'
    913               .format(src_path, dst_path), file=sys.stderr)
    914 
    915     def get_lib_in_elf_class(self, elf_class, path, default=None):
    916         for partition in range(NUM_PARTITIONS):
    917             res = self.lib_pt[partition].get_lib_dict(elf_class).get(path)
    918             if res:
    919                 return res
    920         return default
    921 
    922     def get_lib(self, path):
    923         for lib_set in self.lib_pt:
    924             lib = lib_set.get(path)
    925             if lib:
    926                 return lib
    927         return None
    928 
    929     def get_libs(self, paths, report_error=None):
    930         result = set()
    931         for path in paths:
    932             lib = self.get_lib(path)
    933             if not lib:
    934                 if report_error is None:
    935                     raise ValueError('path not found ' + path)
    936                 report_error(path)
    937                 continue
    938             result.add(lib)
    939         return result
    940 
    941     def all_libs(self):
    942         for lib_set in self.lib_pt:
    943             for lib in lib_set.values():
    944                 yield lib
    945 
    946     def _compute_lib_dict(self, elf_class):
    947         res = dict()
    948         for lib_pt in self.lib_pt:
    949             res.update(lib_pt.get_lib_dict(elf_class))
    950         return res
    951 
    952     @staticmethod
    953     def _compile_path_matcher(root, subdirs):
    954         dirs = [os.path.normpath(os.path.join(root, i)) for i in subdirs]
    955         patts = ['(?:' + re.escape(i) + os.sep + ')' for i in dirs]
    956         return re.compile('|'.join(patts))
    957 
    958     def add_executables_in_dir(self, partition_name, partition, root,
    959                                alter_partition, alter_subdirs, ignored_subdirs,
    960                                scan_elf_files):
    961         root = os.path.abspath(root)
    962         prefix_len = len(root) + 1
    963 
    964         if alter_subdirs:
    965             alter_patt = ELFLinker._compile_path_matcher(root, alter_subdirs)
    966         if ignored_subdirs:
    967             ignored_patt = ELFLinker._compile_path_matcher(root, ignored_subdirs)
    968 
    969         for path, elf in scan_elf_files(root):
    970             short_path = os.path.join('/', partition_name, path[prefix_len:])
    971             if ignored_subdirs and ignored_patt.match(path):
    972                 continue
    973             if alter_subdirs and alter_patt.match(path):
    974                 self.add_lib(alter_partition, short_path, elf)
    975             else:
    976                 self.add_lib(partition, short_path, elf)
    977 
    978     def load_extra_deps(self, path):
    979         patt = re.compile('([^:]*):\\s*(.*)')
    980         with open(path, 'r') as f:
    981             for line in f:
    982                 match = patt.match(line)
    983                 if match:
    984                     self.add_dep(match.group(1), match.group(2),
    985                                  ELFLinkData.DLOPEN)
    986 
    987     def _find_exported_symbol(self, symbol, libs):
    988         """Find the shared library with the exported symbol."""
    989         for lib in libs:
    990             if symbol in lib.elf.exported_symbols:
    991                 return lib
    992         return None
    993 
    994     def _resolve_lib_imported_symbols(self, lib, imported_libs, generic_refs):
    995         """Resolve the imported symbols in a library."""
    996         for symbol in lib.elf.imported_symbols:
    997             imported_lib = self._find_exported_symbol(symbol, imported_libs)
    998             if not imported_lib:
    999                 lib.unresolved_symbols.add(symbol)
   1000             else:
   1001                 lib.linked_symbols[symbol] = imported_lib
   1002                 if generic_refs:
   1003                     ref_lib = generic_refs.refs.get(imported_lib.path)
   1004                     if not ref_lib or not symbol in ref_lib.exported_symbols:
   1005                         lib.imported_ext_symbols[imported_lib].add(symbol)
   1006 
   1007     def _resolve_lib_dt_needed(self, lib, resolver):
   1008         imported_libs = []
   1009         for dt_needed in lib.elf.dt_needed:
   1010             dep = resolver.resolve(dt_needed, lib.elf.dt_rpath,
   1011                                    lib.elf.dt_runpath)
   1012             if not dep:
   1013                 candidates = list(resolver.get_candidates(
   1014                     dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath))
   1015                 print('warning: {}: Missing needed library: {}  Tried: {}'
   1016                       .format(lib.path, dt_needed, candidates), file=sys.stderr)
   1017                 continue
   1018             lib.add_dep(dep, ELFLinkData.NEEDED)
   1019             imported_libs.append(dep)
   1020         return imported_libs
   1021 
   1022     def _resolve_lib_deps(self, lib, resolver, generic_refs):
   1023         # Resolve DT_NEEDED entries.
   1024         imported_libs = self._resolve_lib_dt_needed(lib, resolver)
   1025 
   1026         if generic_refs:
   1027             for imported_lib in imported_libs:
   1028                 if imported_lib.path not in generic_refs.refs:
   1029                     # Add imported_lib to imported_ext_symbols to make sure
   1030                     # non-AOSP libraries are in the imported_ext_symbols key
   1031                     # set.
   1032                     lib.imported_ext_symbols[imported_lib].update()
   1033 
   1034         # Resolve imported symbols.
   1035         self._resolve_lib_imported_symbols(lib, imported_libs, generic_refs)
   1036 
   1037     def _resolve_lib_set_deps(self, lib_set, resolver, generic_refs):
   1038         for lib in lib_set:
   1039             self._resolve_lib_deps(lib, resolver, generic_refs)
   1040 
   1041     SYSTEM_SEARCH_PATH = (
   1042         '/system/${LIB}',
   1043         '/vendor/${LIB}',
   1044     )
   1045 
   1046     VENDOR_SEARCH_PATH = (
   1047         '/vendor/${LIB}',
   1048         '/vendor/${LIB}/vndk-sp',
   1049         '/system/${LIB}/vndk-sp',
   1050         '/system/${LIB}',  # For degenerated VNDK libs.
   1051     )
   1052 
   1053     VNDK_SP_SEARCH_PATH = (
   1054         '/vendor/${LIB}/vndk-sp',
   1055         '/system/${LIB}/vndk-sp',
   1056         '/vendor/${LIB}',  # To discover missing vndk-sp dependencies.
   1057         '/system/${LIB}',  # To discover missing vndk-sp dependencies.
   1058     )
   1059 
   1060     @staticmethod
   1061     def _subst_search_path(search_path, elf_class):
   1062         lib_dir_name = 'lib' if elf_class == ELF.ELFCLASS32 else 'lib64'
   1063         return [path.replace('${LIB}', lib_dir_name) for path in search_path]
   1064 
   1065     @staticmethod
   1066     def _is_in_vndk_sp_dir(path):
   1067         return os.path.basename(os.path.dirname(path)).startswith('vndk-sp')
   1068 
   1069     def _resolve_elf_class_deps(self, elf_class, generic_refs):
   1070         system_lib_dict = self.lib_pt[PT_SYSTEM].get_lib_dict(elf_class)
   1071         vendor_lib_dict = self.lib_pt[PT_VENDOR].get_lib_dict(elf_class)
   1072         lib_dict = self._compute_lib_dict(elf_class)
   1073 
   1074         # Resolve system libs.
   1075         system_libs = [lib for lib in system_lib_dict.values()
   1076                        if not self._is_in_vndk_sp_dir(lib.path)]
   1077         search_path = self._subst_search_path(
   1078                 self.SYSTEM_SEARCH_PATH, elf_class)
   1079         resolver = ELFResolver(lib_dict, search_path)
   1080         self._resolve_lib_set_deps(system_libs, resolver, generic_refs)
   1081 
   1082         # Resolve vendor libs.
   1083         vendor_libs = [lib for lib in vendor_lib_dict.values()
   1084                        if not self._is_in_vndk_sp_dir(lib.path)]
   1085         search_path = self._subst_search_path(
   1086                 self.VENDOR_SEARCH_PATH, elf_class)
   1087         resolver = ELFResolver(lib_dict, search_path)
   1088         self._resolve_lib_set_deps(vendor_libs, resolver, generic_refs)
   1089 
   1090         # Resolve vndk-sp libs
   1091         vndk_sp = [lib for lib in lib_dict.values()
   1092                    if self._is_in_vndk_sp_dir(lib.path)]
   1093         search_path = self._subst_search_path(
   1094                 self.VNDK_SP_SEARCH_PATH, elf_class)
   1095         resolver = ELFResolver(lib_dict, search_path)
   1096         self._resolve_lib_set_deps(vndk_sp, resolver, generic_refs)
   1097 
   1098     def resolve_deps(self, generic_refs=None):
   1099         self._resolve_elf_class_deps(ELF.ELFCLASS32, generic_refs)
   1100         self._resolve_elf_class_deps(ELF.ELFCLASS64, generic_refs)
   1101 
   1102     def compute_path_matched_lib(self, path_patterns):
   1103         patt = re.compile('|'.join('(?:' + p + ')' for p in path_patterns))
   1104         return set(lib for lib in self.all_libs() if patt.match(lib.path))
   1105 
   1106     def compute_predefined_fwk_only_rs(self):
   1107         """Find all fwk-only-rs libraries."""
   1108         path_patterns = (
   1109             '^/system/lib(?:64)?/(?:vndk-sp/)?libft2\\.so$',
   1110             '^/system/lib(?:64)?/(?:vndk-sp/)?libmediandk\\.so',
   1111         )
   1112         return self.compute_path_matched_lib(path_patterns)
   1113 
   1114     def compute_predefined_vndk_sp(self):
   1115         """Find all vndk-sp libraries."""
   1116         path_patterns = (
   1117             # Visible to SP-HALs
   1118             '^.*/android\\.hardware\\.graphics\\.allocator@2\\.0\\.so$',
   1119             '^.*/android\\.hardware\\.graphics\\.common@1\\.0\\.so$',
   1120             '^.*/android\\.hardware\\.graphics\\.mapper@2\\.0\\.so$',
   1121             '^.*/android\\.hardware\\.renderscript@1\\.0\\.so$',
   1122             '^.*/libRSCpuRef\\.so$',
   1123             '^.*/libRSDriver\\.so$',
   1124             '^.*/libRS_internal\\.so$',
   1125             '^.*/libbase\\.so$',
   1126             '^.*/libbcinfo\\.so$',
   1127             '^.*/libc\\+\\+\\.so$',
   1128             '^.*/libcompiler_rt\\.so$',
   1129             '^.*/libcutils\\.so$',
   1130             '^.*/libhardware\\.so$',
   1131             '^.*/libhidlbase\\.so$',
   1132             '^.*/libhidltransport\\.so$',
   1133             '^.*/libhwbinder\\.so$',
   1134             '^.*/libutils\\.so$',
   1135 
   1136             # Only for o-release
   1137             '^.*/android\\.hidl\\.base@1\\.0\\.so$',
   1138         )
   1139         return self.compute_path_matched_lib(path_patterns)
   1140 
   1141     def compute_predefined_vndk_sp_indirect(self):
   1142         """Find all vndk-sp-indirect libraries."""
   1143         path_patterns = (
   1144             # Invisible to SP-HALs
   1145             '^.*/libbacktrace\\.so$',
   1146             '^.*/libblas\\.so$',
   1147             '^.*/liblzma\\.so$',
   1148             '^.*/libpng\\.so$',
   1149             '^.*/libunwind\\.so$',
   1150         )
   1151         return self.compute_path_matched_lib(path_patterns)
   1152 
   1153     def compute_predefined_sp_hal(self):
   1154         """Find all same-process HALs."""
   1155         path_patterns = (
   1156             # OpenGL-related
   1157             '^/vendor/.*/libEGL_.*\\.so$',
   1158             '^/vendor/.*/libGLES_.*\\.so$',
   1159             '^/vendor/.*/libGLESv1_CM_.*\\.so$',
   1160             '^/vendor/.*/libGLESv2_.*\\.so$',
   1161             '^/vendor/.*/libGLESv3_.*\\.so$',
   1162             # Vulkan
   1163             '^/vendor/.*/vulkan.*\\.so$',
   1164             # libRSDriver
   1165             '^.*/android\\.hardware\\.renderscript@1\\.0-impl\\.so$',
   1166             '^/vendor/.*/libPVRRS\\.so$',
   1167             '^/vendor/.*/libRSDriver.*\\.so$',
   1168             # Gralloc mapper
   1169             '^.*/gralloc\\..*\\.so$',
   1170             '^.*/android\\.hardware\\.graphics\\.mapper@\\d+\\.\\d+-impl\\.so$',
   1171         )
   1172         return self.compute_path_matched_lib(path_patterns)
   1173 
   1174     def compute_sp_ndk(self):
   1175         """Find all SP-NDK libraries."""
   1176         return set(lib for lib in self.all_libs() if lib.is_sp_ndk)
   1177 
   1178     def compute_sp_lib(self, generic_refs):
   1179         def is_ndk(lib):
   1180             return lib.is_ndk
   1181 
   1182         sp_ndk = self.compute_sp_ndk()
   1183         sp_ndk_closure = self.compute_closure(sp_ndk, is_ndk)
   1184         sp_ndk_indirect = sp_ndk_closure - sp_ndk
   1185 
   1186         sp_hal = self.compute_predefined_sp_hal()
   1187         sp_hal_closure = self.compute_closure(sp_hal, is_ndk)
   1188 
   1189         def is_aosp_lib(lib):
   1190             return (not generic_refs or \
   1191                     generic_refs.classify_lib(lib) != GenericRefs.NEW_LIB)
   1192 
   1193         vndk_sp_hal = set()
   1194         sp_hal_dep = set()
   1195         for lib in sp_hal_closure - sp_hal:
   1196             if is_aosp_lib(lib):
   1197                 vndk_sp_hal.add(lib)
   1198             else:
   1199                 sp_hal_dep.add(lib)
   1200 
   1201         vndk_sp_both = sp_ndk_indirect & vndk_sp_hal
   1202         sp_ndk_indirect -= vndk_sp_both
   1203         vndk_sp_hal -= vndk_sp_both
   1204 
   1205         return SPLibResult(sp_hal, sp_hal_dep, vndk_sp_hal, sp_ndk,
   1206                            sp_ndk_indirect, vndk_sp_both)
   1207 
   1208     def _po_sorted(self, lib_set, get_successors):
   1209         result = []
   1210         visited = set()
   1211         def traverse(lib):
   1212             for succ in get_successors(lib):
   1213                 if succ in lib_set and succ not in visited:
   1214                     visited.add(succ)
   1215                     traverse(succ)
   1216             result.append(lib)
   1217         for lib in lib_set:
   1218             if lib not in visited:
   1219                 visited.add(lib)
   1220                 traverse(lib)
   1221         return result
   1222 
   1223     def _deps_po_sorted(self, lib_set):
   1224         return self._po_sorted(lib_set, lambda x: x.deps)
   1225 
   1226     def _users_po_sorted(self, lib_set):
   1227         return self._po_sorted(lib_set, lambda x: x.users)
   1228 
   1229     def normalize_partition_tags(self, sp_hals, generic_refs):
   1230         system_libs = set(self.lib_pt[PT_SYSTEM].values())
   1231         system_libs_po = self._deps_po_sorted(system_libs)
   1232 
   1233         def is_system_lib_or_sp_hal(lib):
   1234             return lib.is_system_lib() or lib in sp_hals
   1235 
   1236         for lib in system_libs_po:
   1237             if all(is_system_lib_or_sp_hal(dep) for dep in lib.deps):
   1238                 # Good system lib.  Do nothing.
   1239                 continue
   1240             if not generic_refs or generic_refs.refs.get(lib.path):
   1241                 # If lib is in AOSP generic reference, then we assume that the
   1242                 # non-SP-HAL dependencies are errors.  Emit errors and remove
   1243                 # the dependencies.
   1244                 for dep in list(lib.dt_deps):
   1245                     if not is_system_lib_or_sp_hal(dep):
   1246                         print('error: {}: system exe/lib must not depend on '
   1247                               'vendor lib {}.  Assume such dependency does '
   1248                               'not exist.'.format(lib.path, dep.path),
   1249                               file=sys.stderr)
   1250                         lib.remove_dep(dep, ELFLinkData.NEEDED)
   1251                 for dep in list(lib.dl_deps):
   1252                     if not is_system_lib_or_sp_hal(dep):
   1253                         print('error: {}: system exe/lib must not dlopen() '
   1254                               'vendor lib {}.  Assume such dependency does '
   1255                               'not exist.'.format(lib.path, dep.path),
   1256                               file=sys.stderr)
   1257                         lib.remove_dep(dep, ELFLinkData.DLOPEN)
   1258             else:
   1259                 # If lib is not in AOSP generic reference, then we assume that
   1260                 # lib must be moved to vendor partition.
   1261                 for dep in lib.deps:
   1262                     if not is_system_lib_or_sp_hal(dep):
   1263                         print('warning: {}: system exe/lib must not depend on '
   1264                               'vendor lib {}.  Assuming {} should be placed in '
   1265                               'vendor partition.'
   1266                               .format(lib.path, dep.path, lib.path),
   1267                               file=sys.stderr)
   1268                 new_path = lib.path.replace('/system/', '/vendor/')
   1269                 self.rename_lib(lib, PT_VENDOR, new_path)
   1270 
   1271     @staticmethod
   1272     def _parse_action_on_ineligible_lib(arg):
   1273         follow = False
   1274         warn = False
   1275         for flag in arg.split(','):
   1276             if flag == 'follow':
   1277                 follow = True
   1278             elif flag == 'warn':
   1279                 warn = True
   1280             elif flag == 'ignore':
   1281                 continue
   1282             else:
   1283                 raise ValueError('unknown action \"{}\"'.format(flag))
   1284         return (follow, warn)
   1285 
   1286     def compute_degenerated_vndk(self, generic_refs, tagged_paths=None,
   1287                                  action_ineligible_vndk_sp='warn',
   1288                                  action_ineligible_vndk='warn'):
   1289         # Find LL-NDK and SP-NDK libs.
   1290         ll_ndk = set(lib for lib in self.all_libs() if lib.is_ll_ndk)
   1291         sp_ndk = set(lib for lib in self.all_libs() if lib.is_sp_ndk)
   1292 
   1293         # Find SP-HAL libs.
   1294         sp_hal = self.compute_predefined_sp_hal()
   1295 
   1296         # Normalize partition tags.  We expect many violations from the
   1297         # pre-Treble world.  Guess a resolution for the incorrect partition
   1298         # tag.
   1299         self.normalize_partition_tags(sp_hal, generic_refs)
   1300 
   1301         # Find SP-HAL-Dep libs.
   1302         def is_aosp_lib(lib):
   1303             if not generic_refs:
   1304                 # If generic reference is not available, then assume all system
   1305                 # libs are AOSP libs.
   1306                 return lib.partition == PT_SYSTEM
   1307             return generic_refs.has_same_name_lib(lib)
   1308 
   1309         def is_not_sp_hal_dep(lib):
   1310             if lib.is_ll_ndk or lib.is_sp_ndk or lib in sp_hal:
   1311                 return True
   1312             return is_aosp_lib(lib)
   1313 
   1314         sp_hal_dep = self.compute_closure(sp_hal, is_not_sp_hal_dep)
   1315         sp_hal_dep -= sp_hal
   1316 
   1317         # Find FWK-ONLY-RS libs.
   1318         fwk_only_rs = self.compute_predefined_fwk_only_rs()
   1319 
   1320         # Find VNDK-SP libs.
   1321         def is_not_vndk_sp(lib):
   1322             return lib.is_ll_ndk or lib.is_sp_ndk or lib in sp_hal or \
   1323                    lib in sp_hal_dep
   1324 
   1325         follow_ineligible_vndk_sp, warn_ineligible_vndk_sp = \
   1326                 self._parse_action_on_ineligible_lib(action_ineligible_vndk_sp)
   1327         predefined_vndk_sp = self.compute_predefined_vndk_sp()
   1328         vndk_sp = set()
   1329         for lib in itertools.chain(sp_hal, sp_hal_dep):
   1330             for dep in lib.deps:
   1331                 if is_not_vndk_sp(dep):
   1332                     continue
   1333                 if dep in predefined_vndk_sp:
   1334                     vndk_sp.add(dep)
   1335                     continue
   1336                 if warn_ineligible_vndk_sp:
   1337                     print('error: SP-HAL {} depends on non vndk-sp '
   1338                           'library {}.'.format(lib.path, dep.path),
   1339                           file=sys.stderr)
   1340                 if follow_ineligible_vndk_sp:
   1341                     vndk_sp.add(dep)
   1342 
   1343         # Find VNDK-SP-Indirect libs.
   1344         def is_not_vndk_sp_indirect(lib):
   1345             return lib.is_ll_ndk or lib.is_sp_ndk or lib in vndk_sp or \
   1346                    lib in fwk_only_rs
   1347 
   1348         vndk_sp_indirect = self.compute_closure(
   1349                 vndk_sp, is_not_vndk_sp_indirect)
   1350         vndk_sp_indirect -= vndk_sp
   1351 
   1352         # Find unused predefined VNDK-SP libs.
   1353         vndk_sp_unused = set(lib for lib in predefined_vndk_sp
   1354                              if self._is_in_vndk_sp_dir(lib.path))
   1355         vndk_sp_unused -= vndk_sp
   1356         vndk_sp_unused -= vndk_sp_indirect
   1357 
   1358         # Find dependencies of unused predefined VNDK-SP libs.
   1359         def is_not_vndk_sp_indirect_unused(lib):
   1360             return is_not_vndk_sp_indirect(lib) or lib in vndk_sp_indirect
   1361         vndk_sp_indirect_unused = self.compute_closure(
   1362                 vndk_sp_unused, is_not_vndk_sp_indirect_unused)
   1363         vndk_sp_indirect_unused -= vndk_sp_unused
   1364 
   1365         # TODO: Compute VNDK-SP-Indirect-Private.
   1366         vndk_sp_indirect_private = set()
   1367 
   1368         # Define helper functions for vndk_sp sets.
   1369         def is_vndk_sp_public(lib):
   1370             return lib in vndk_sp or lib in vndk_sp_unused or \
   1371                    lib in vndk_sp_indirect or \
   1372                    lib in vndk_sp_indirect_unused
   1373 
   1374         def is_vndk_sp(lib):
   1375             return is_vndk_sp_public(lib) or lib in vndk_sp_indirect_private
   1376 
   1377         def is_vndk_sp_unused(lib):
   1378             return lib in vndk_sp_unused or lib in vndk_sp_indirect_unused
   1379 
   1380         def relabel_vndk_sp_as_used(lib):
   1381             assert is_vndk_sp_unused(lib)
   1382 
   1383             if lib in vndk_sp_unused:
   1384                 vndk_sp_unused.remove(lib)
   1385                 vndk_sp.add(lib)
   1386             else:
   1387                 vndk_sp_indirect_unused.remove(lib)
   1388                 vndk_sp_indirect.add(lib)
   1389 
   1390             closure = self.compute_closure({lib}, is_not_vndk_sp_indirect)
   1391             closure -= vndk_sp
   1392             vndk_sp_indirect_unused.difference_update(closure)
   1393             vndk_sp_indirect.update(closure)
   1394 
   1395         # Find VNDK-SP-Ext libs.
   1396         vndk_sp_ext = set()
   1397         def collect_vndk_ext(libs):
   1398             result = set()
   1399             for lib in libs:
   1400                 for dep in lib.imported_ext_symbols:
   1401                     if dep in vndk_sp and dep not in vndk_sp_ext:
   1402                         result.add(dep)
   1403             return result
   1404 
   1405         candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
   1406         while candidates:
   1407             vndk_sp_ext |= candidates
   1408             candidates = collect_vndk_ext(candidates)
   1409 
   1410         # Find VNDK-SP-Indirect-Ext libs.
   1411         predefined_vndk_sp_indirect = self.compute_predefined_vndk_sp_indirect()
   1412         vndk_sp_indirect_ext = set()
   1413         def collect_vndk_sp_indirect_ext(libs):
   1414             result = set()
   1415             for lib in libs:
   1416                 exts = set(lib.imported_ext_symbols.keys())
   1417                 for dep in lib.deps:
   1418                     if not is_vndk_sp_public(dep):
   1419                         continue
   1420                     if dep in vndk_sp_ext or dep in vndk_sp_indirect_ext:
   1421                         continue
   1422                     # If lib is using extended definition from deps, then we
   1423                     # have to make a copy of dep.
   1424                     if dep in exts:
   1425                         result.add(dep)
   1426                         continue
   1427                     # If lib is using non-predefined VNDK-SP-Indirect, then we
   1428                     # have to make a copy of dep.
   1429                     if dep not in predefined_vndk_sp and \
   1430                             dep not in predefined_vndk_sp_indirect:
   1431                         result.add(dep)
   1432                         continue
   1433             return result
   1434 
   1435         def is_not_vndk_sp_indirect(lib):
   1436             return lib.is_ll_ndk or lib.is_sp_ndk or lib in vndk_sp or \
   1437                    lib in fwk_only_rs
   1438 
   1439         candidates = collect_vndk_sp_indirect_ext(vndk_sp_ext)
   1440         while candidates:
   1441             vndk_sp_indirect_ext |= candidates
   1442             candidates = collect_vndk_sp_indirect_ext(candidates)
   1443 
   1444         # Find VNDK libs (a.k.a. system shared libs directly used by vendor
   1445         # partition.)
   1446         def is_not_vndk(lib):
   1447             if lib.is_ll_ndk or lib.is_sp_ndk or is_vndk_sp_public(lib) or \
   1448                lib in fwk_only_rs:
   1449                 return True
   1450             return lib.partition != PT_SYSTEM
   1451 
   1452         def is_eligible_lib_access(lib, dep):
   1453             return not tagged_paths or \
   1454                     tagged_paths.is_path_visible(lib.path, dep.path)
   1455 
   1456         follow_ineligible_vndk, warn_ineligible_vndk = \
   1457                 self._parse_action_on_ineligible_lib(action_ineligible_vndk)
   1458         vndk = set()
   1459         extra_vendor_libs = set()
   1460         def collect_vndk(vendor_libs):
   1461             next_vendor_libs = set()
   1462             for lib in vendor_libs:
   1463                 for dep in lib.deps:
   1464                     if is_vndk_sp_unused(dep):
   1465                         relabel_vndk_sp_as_used(dep)
   1466                         continue
   1467                     if is_not_vndk(dep):
   1468                         continue
   1469                     if not is_aosp_lib(dep):
   1470                         # The dependency should be copied into vendor partition
   1471                         # as an extra vendor lib.
   1472                         if dep not in extra_vendor_libs:
   1473                             next_vendor_libs.add(dep)
   1474                             extra_vendor_libs.add(dep)
   1475                         continue
   1476                     if is_eligible_lib_access(lib, dep):
   1477                         vndk.add(dep)
   1478                         continue
   1479                     if warn_ineligible_vndk:
   1480                         print('warning: vendor lib/exe {} depends on '
   1481                               'ineligible framework shared lib {}.'
   1482                               .format(lib.path, dep.path), file=sys.stderr)
   1483                     if follow_ineligible_vndk:
   1484                         vndk.add(dep)
   1485             return next_vendor_libs
   1486 
   1487         candidates = collect_vndk(self.lib_pt[PT_VENDOR].values())
   1488         while candidates:
   1489             candidates = collect_vndk(candidates)
   1490 
   1491         vndk_indirect = self.compute_closure(vndk, is_not_vndk)
   1492         vndk_indirect -= vndk
   1493 
   1494         def is_vndk(lib):
   1495             return lib in vndk or lib in vndk_indirect
   1496 
   1497         # Find VNDK-EXT libs (VNDK libs with extended definitions and the
   1498         # extended definitions are used by the vendor modules (including
   1499         # extra_vendor_libs).
   1500 
   1501         # FIXME: DAUX libraries won't be found by the following algorithm.
   1502         vndk_ext = set()
   1503 
   1504         def collect_vndk_ext(libs):
   1505             result = set()
   1506             for lib in libs:
   1507                 for dep in lib.imported_ext_symbols:
   1508                     if dep in vndk and dep not in vndk_ext:
   1509                         result.add(dep)
   1510             return result
   1511 
   1512         candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
   1513         candidates |= collect_vndk_ext(extra_vendor_libs)
   1514 
   1515         while candidates:
   1516             vndk_ext |= candidates
   1517             candidates = collect_vndk_ext(candidates)
   1518 
   1519         # Compute LL-NDK-Indirect and SP-NDK-Indirect.
   1520         def is_not_ll_ndk_indirect(lib):
   1521             return lib.is_ll_ndk or is_vndk_sp(lib) or is_vndk(lib)
   1522 
   1523         ll_ndk_indirect = self.compute_closure(ll_ndk, is_not_ll_ndk_indirect)
   1524         ll_ndk_indirect -= ll_ndk
   1525 
   1526         def is_not_sp_ndk_indirect(lib):
   1527             return lib.is_ll_ndk or lib.is_sp_ndk or lib in ll_ndk_indirect or \
   1528                    is_vndk_sp(lib) or is_vndk(lib)
   1529 
   1530         sp_ndk_indirect = self.compute_closure(sp_ndk, is_not_sp_ndk_indirect)
   1531         sp_ndk_indirect -= sp_ndk
   1532 
   1533         # Return the VNDK classifications.
   1534         return VNDKResult(
   1535                 ll_ndk=ll_ndk,
   1536                 ll_ndk_indirect=ll_ndk_indirect,
   1537                 sp_ndk=sp_ndk,
   1538                 sp_ndk_indirect=sp_ndk_indirect,
   1539                 vndk_sp=vndk_sp,
   1540                 vndk_sp_indirect=vndk_sp_indirect,
   1541                 # vndk_sp_indirect_private=vndk_sp_indirect_private,
   1542                 vndk_sp_unused=vndk_sp_unused,
   1543                 vndk_sp_indirect_unused=vndk_sp_indirect_unused,
   1544                 vndk=vndk,
   1545                 vndk_indirect=vndk_indirect,
   1546                 # fwk_only=fwk_only,
   1547                 fwk_only_rs=fwk_only_rs,
   1548                 sp_hal=sp_hal,
   1549                 sp_hal_dep=sp_hal_dep,
   1550                 # vnd_only=vnd_only,
   1551                 vndk_ext=vndk_ext,
   1552                 vndk_sp_ext=vndk_sp_ext,
   1553                 vndk_sp_indirect_ext=vndk_sp_indirect_ext,
   1554                 extra_vendor_libs=extra_vendor_libs)
   1555 
   1556     def compute_vndk_cap(self, banned_libs):
   1557         # ELF files on vendor partitions are banned unconditionally.  ELF files
   1558         # on the system partition are banned if their file extensions are not
   1559         # '.so' or their file names are listed in banned_libs.  LL-NDK and
   1560         # SP-NDK libraries are treated as a special case which will not be
   1561         # considered as banned libraries at the moment.
   1562         def is_banned(lib):
   1563             if lib.is_ndk:
   1564                 return lib.is_hl_ndk
   1565             return (banned_libs.is_banned(lib.path) or
   1566                     not lib.is_system_lib() or
   1567                     not lib.path.endswith('.so'))
   1568 
   1569         # Find all libraries that are banned.
   1570         banned_set = set()
   1571         for lib_set in self.lib_pt:
   1572             for lib in lib_set.values():
   1573                 if is_banned(lib):
   1574                     banned_set.add(lib)
   1575 
   1576         # Find the transitive closure of the banned libraries.
   1577         stack = list(banned_set)
   1578         while stack:
   1579             lib = stack.pop()
   1580             for user in lib.users:
   1581                 if not user.is_ndk and user not in banned_set:
   1582                     banned_set.add(user)
   1583                     stack.append(user)
   1584 
   1585         # Find the non-NDK non-banned libraries.
   1586         vndk_cap = set()
   1587         for lib in self.lib_pt[PT_SYSTEM].values():
   1588             if not lib.is_ndk and lib not in banned_set:
   1589                 vndk_cap.add(lib)
   1590 
   1591         return vndk_cap
   1592 
   1593     @staticmethod
   1594     def compute_closure(root_set, is_excluded):
   1595         closure = set(root_set)
   1596         stack = list(root_set)
   1597         while stack:
   1598             lib = stack.pop()
   1599             for dep in lib.deps:
   1600                 if is_excluded(dep):
   1601                     continue
   1602                 if dep not in closure:
   1603                     closure.add(dep)
   1604                     stack.append(dep)
   1605         return closure
   1606 
   1607     @staticmethod
   1608     def _create_internal(scan_elf_files, system_dirs, system_dirs_as_vendor,
   1609                          system_dirs_ignored, vendor_dirs,
   1610                          vendor_dirs_as_system, vendor_dirs_ignored,
   1611                          extra_deps, generic_refs):
   1612         graph = ELFLinker()
   1613 
   1614         if system_dirs:
   1615             for path in system_dirs:
   1616                 graph.add_executables_in_dir('system', PT_SYSTEM, path,
   1617                                              PT_VENDOR, system_dirs_as_vendor,
   1618                                              system_dirs_ignored,
   1619                                              scan_elf_files)
   1620 
   1621         if vendor_dirs:
   1622             for path in vendor_dirs:
   1623                 graph.add_executables_in_dir('vendor', PT_VENDOR, path,
   1624                                              PT_SYSTEM, vendor_dirs_as_system,
   1625                                              vendor_dirs_ignored,
   1626                                              scan_elf_files)
   1627 
   1628         if extra_deps:
   1629             for path in extra_deps:
   1630                 graph.load_extra_deps(path)
   1631 
   1632         graph.resolve_deps(generic_refs)
   1633 
   1634         return graph
   1635 
   1636     @staticmethod
   1637     def create(system_dirs=None, system_dirs_as_vendor=None,
   1638                system_dirs_ignored=None, vendor_dirs=None,
   1639                vendor_dirs_as_system=None, vendor_dirs_ignored=None,
   1640                extra_deps=None, generic_refs=None):
   1641         return ELFLinker._create_internal(
   1642                 scan_elf_files, system_dirs, system_dirs_as_vendor,
   1643                 system_dirs_ignored, vendor_dirs, vendor_dirs_as_system,
   1644                 vendor_dirs_ignored, extra_deps, generic_refs)
   1645 
   1646     @staticmethod
   1647     def create_from_dump(system_dirs=None, system_dirs_as_vendor=None,
   1648                          vendor_dirs=None, vendor_dirs_as_system=None,
   1649                          extra_deps=None, generic_refs=None):
   1650         return ELFLinker._create_internal(
   1651                 scan_elf_dump_files, system_dirs, system_dirs_as_vendor,
   1652                 vendor_dirs, vendor_dirs_as_system, extra_deps, generic_refs)
   1653 
   1654 
   1655 #------------------------------------------------------------------------------
   1656 # Generic Reference
   1657 #------------------------------------------------------------------------------
   1658 
   1659 class GenericRefs(object):
   1660     NEW_LIB = 0
   1661     EXPORT_EQUAL = 1
   1662     EXPORT_SUPER_SET = 2
   1663     MODIFIED = 3
   1664 
   1665     def __init__(self):
   1666         self.refs = dict()
   1667         self._lib_names = set()
   1668 
   1669     def add(self, path, elf):
   1670         self.refs[path] = elf
   1671         self._lib_names.add(os.path.basename(path))
   1672 
   1673     def _load_from_sym_dir(self, root):
   1674         root = os.path.abspath(root)
   1675         prefix_len = len(root) + 1
   1676         for base, dirnames, filenames in os.walk(root):
   1677             for filename in filenames:
   1678                 if not filename.endswith('.sym'):
   1679                     continue
   1680                 path = os.path.join(base, filename)
   1681                 lib_path = '/' + path[prefix_len:-4]
   1682                 with open(path, 'r') as f:
   1683                     self.add(lib_path, ELF.load_dump(path))
   1684 
   1685     @staticmethod
   1686     def create_from_sym_dir(root):
   1687         result = GenericRefs()
   1688         result._load_from_sym_dir(root)
   1689         return result
   1690 
   1691     def _load_from_image_dir(self, root, prefix):
   1692         root = os.path.abspath(root)
   1693         root_len = len(root) + 1
   1694         for path, elf in scan_elf_files(root):
   1695             self.add(os.path.join(prefix, path[root_len:]), elf)
   1696 
   1697     @staticmethod
   1698     def create_from_image_dir(root, prefix):
   1699         result = GenericRefs()
   1700         result._load_from_image_dir(root, prefix)
   1701         return result
   1702 
   1703     def classify_lib(self, lib):
   1704         ref_lib = self.refs.get(lib.path)
   1705         if not ref_lib:
   1706             return GenericRefs.NEW_LIB
   1707         exported_symbols = lib.elf.exported_symbols
   1708         if exported_symbols == ref_lib.exported_symbols:
   1709             return GenericRefs.EXPORT_EQUAL
   1710         if exported_symbols > ref_lib.exported_symbols:
   1711             return GenericRefs.EXPORT_SUPER_SET
   1712         return GenericRefs.MODIFIED
   1713 
   1714     def is_equivalent_lib(self, lib):
   1715         return self.classify_lib(lib) == GenericRefs.EXPORT_EQUAL
   1716 
   1717     def has_same_name_lib(self, lib):
   1718         return os.path.basename(lib.path) in self._lib_names
   1719 
   1720 
   1721 #------------------------------------------------------------------------------
   1722 # Commands
   1723 #------------------------------------------------------------------------------
   1724 
   1725 class Command(object):
   1726     def __init__(self, name, help):
   1727         self.name = name
   1728         self.help = help
   1729 
   1730     def add_argparser_options(self, parser):
   1731         pass
   1732 
   1733     def main(self, args):
   1734         return 0
   1735 
   1736 
   1737 class ELFDumpCommand(Command):
   1738     def __init__(self):
   1739         super(ELFDumpCommand, self).__init__(
   1740                 'elfdump', help='Dump ELF .dynamic section')
   1741 
   1742     def add_argparser_options(self, parser):
   1743         parser.add_argument('path', help='path to an ELF file')
   1744 
   1745     def main(self, args):
   1746         try:
   1747             ELF.load(args.path).dump()
   1748         except ELFError as e:
   1749             print('error: {}: Bad ELF file ({})'.format(args.path, e),
   1750                   file=sys.stderr)
   1751             sys.exit(1)
   1752         return 0
   1753 
   1754 
   1755 class CreateGenericRefCommand(Command):
   1756     def __init__(self):
   1757         super(CreateGenericRefCommand, self).__init__(
   1758                 'create-generic-ref', help='Create generic references')
   1759 
   1760     def add_argparser_options(self, parser):
   1761         parser.add_argument('dir')
   1762 
   1763         parser.add_argument(
   1764                 '--output', '-o', metavar='PATH', required=True,
   1765                 help='output directory')
   1766 
   1767     def main(self, args):
   1768         root = os.path.abspath(args.dir)
   1769         print(root)
   1770         prefix_len = len(root) + 1
   1771         for path, elf in scan_elf_files(root):
   1772             name = path[prefix_len:]
   1773             print('Processing:', name, file=sys.stderr)
   1774             out = os.path.join(args.output, name) + '.sym'
   1775             makedirs(os.path.dirname(out), exist_ok=True)
   1776             with open(out, 'w') as f:
   1777                 elf.dump(f)
   1778         return 0
   1779 
   1780 
   1781 class ELFGraphCommand(Command):
   1782     def add_argparser_options(self, parser):
   1783         parser.add_argument(
   1784                 '--load-extra-deps', action='append',
   1785                 help='load extra module dependencies')
   1786 
   1787         parser.add_argument(
   1788                 '--system', action='append',
   1789                 help='path to system partition contents')
   1790 
   1791         parser.add_argument(
   1792                 '--vendor', action='append',
   1793                 help='path to vendor partition contents')
   1794 
   1795         parser.add_argument(
   1796                 '--system-dir-as-vendor', action='append',
   1797                 help='sub directory of system partition that has vendor files')
   1798 
   1799         parser.add_argument(
   1800                 '--system-dir-ignored', action='append',
   1801                 help='sub directory of system partition that must be ignored')
   1802 
   1803         parser.add_argument(
   1804                 '--vendor-dir-as-system', action='append',
   1805                 help='sub directory of vendor partition that has system files')
   1806 
   1807         parser.add_argument(
   1808                 '--vendor-dir-ignored', action='append',
   1809                 help='sub directory of vendor partition that must be ignored')
   1810 
   1811         parser.add_argument(
   1812                 '--load-generic-refs',
   1813                 help='compare with generic reference symbols')
   1814 
   1815         parser.add_argument(
   1816                 '--aosp-system',
   1817                 help='compare with AOSP generic system image directory')
   1818 
   1819     def get_generic_refs_from_args(self, args):
   1820         if args.load_generic_refs:
   1821             return GenericRefs.create_from_sym_dir(args.load_generic_refs)
   1822         if args.aosp_system:
   1823             return GenericRefs.create_from_image_dir(args.aosp_system,
   1824                                                      '/system')
   1825         return None
   1826 
   1827     def _check_arg_dir_exists(self, arg_name, dirs):
   1828         for path in dirs:
   1829             if not os.path.exists(path):
   1830                 print('error: Failed to find the directory "{}" specified in {}'
   1831                         .format(path, arg_name), file=sys.stderr)
   1832                 sys.exit(1)
   1833             if not os.path.isdir(path):
   1834                 print('error: Path "{}" specified in {} is not a directory'
   1835                         .format(path, arg_name), file=sys.stderr)
   1836                 sys.exit(1)
   1837 
   1838     def check_dirs_from_args(self, args):
   1839         self._check_arg_dir_exists('--system', args.system)
   1840         self._check_arg_dir_exists('--vendor', args.vendor)
   1841 
   1842     def create_from_args(self, args):
   1843         self.check_dirs_from_args(args)
   1844 
   1845         generic_refs = self.get_generic_refs_from_args(args)
   1846 
   1847         graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
   1848                                  args.system_dir_ignored,
   1849                                  args.vendor, args.vendor_dir_as_system,
   1850                                  args.vendor_dir_ignored,
   1851                                  args.load_extra_deps,
   1852                                  generic_refs=generic_refs)
   1853 
   1854         return (generic_refs, graph)
   1855 
   1856 
   1857 class VNDKCommandBase(ELFGraphCommand):
   1858     def add_argparser_options(self, parser):
   1859         super(VNDKCommandBase, self).add_argparser_options(parser)
   1860 
   1861         parser.add_argument('--no-default-dlopen-deps', action='store_true',
   1862                 help='do not add default dlopen dependencies')
   1863 
   1864         parser.add_argument('--tag-file', help='lib tag file')
   1865 
   1866         parser.add_argument(
   1867                 '--action-ineligible-vndk-sp', default='warn',
   1868                 help='action when a sp-hal uses non-vndk-sp libs '
   1869                      '(option: follow,warn,ignore)')
   1870 
   1871         parser.add_argument(
   1872                 '--action-ineligible-vndk', default='warn',
   1873                 help='action when a vendor lib/exe uses fwk-only libs '
   1874                      '(option: follow,warn,ignore)')
   1875 
   1876     def create_from_args(self, args):
   1877         """Create all essential data structures for VNDK computation."""
   1878 
   1879         generic_refs, graph = \
   1880                 super(VNDKCommandBase, self).create_from_args(args)
   1881 
   1882         if not args.no_default_dlopen_deps:
   1883             script_dir = os.path.dirname(os.path.abspath(__file__))
   1884             minimum_dlopen_deps = os.path.join(script_dir, 'datasets',
   1885                                                'minimum_dlopen_deps.txt')
   1886             graph.load_extra_deps(minimum_dlopen_deps)
   1887 
   1888         if args.tag_file:
   1889             tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
   1890         else:
   1891             tagged_paths = None
   1892 
   1893         return (generic_refs, graph, tagged_paths)
   1894 
   1895 
   1896 class VNDKCommand(VNDKCommandBase):
   1897     def __init__(self):
   1898         super(VNDKCommand, self).__init__(
   1899                 'vndk', help='Compute VNDK libraries set')
   1900 
   1901     def add_argparser_options(self, parser):
   1902         super(VNDKCommand, self).add_argparser_options(parser)
   1903 
   1904         parser.add_argument(
   1905                 '--warn-incorrect-partition', action='store_true',
   1906                 help='warn about libraries only have cross partition linkages')
   1907 
   1908         parser.add_argument(
   1909                 '--full', action='store_true',
   1910                 help='print all classification')
   1911 
   1912         parser.add_argument(
   1913                 '--output-format', default='tag',
   1914                 help='output format for vndk classification')
   1915 
   1916     def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
   1917         for lib in lib_set.values():
   1918             if not lib.num_users:
   1919                 continue
   1920             if all((user.partition != partition for user in lib.users)):
   1921                 print(error_msg.format(lib.path), file=sys.stderr)
   1922 
   1923     def _warn_incorrect_partition(self, graph):
   1924         self._warn_incorrect_partition_lib_set(
   1925                 graph.lib_pt[PT_VENDOR], PT_VENDOR,
   1926                 'warning: {}: This is a vendor library with framework-only '
   1927                 'usages.')
   1928 
   1929         self._warn_incorrect_partition_lib_set(
   1930                 graph.lib_pt[PT_SYSTEM], PT_SYSTEM,
   1931                 'warning: {}: This is a framework library with vendor-only '
   1932                 'usages.')
   1933 
   1934     def _check_ndk_extensions(self, graph, generic_refs):
   1935         for lib_set in graph.lib_pt:
   1936             for lib in lib_set.values():
   1937                 if lib.is_ndk and not generic_refs.is_equivalent_lib(lib):
   1938                     print('warning: {}: NDK library should not be extended.'
   1939                             .format(lib.path), file=sys.stderr)
   1940 
   1941     @staticmethod
   1942     def _extract_simple_vndk_result(vndk_result):
   1943         field_name_tags = [
   1944             ('vndk_sp', 'vndk_sp'),
   1945             ('vndk_sp_unused', 'vndk_sp'),
   1946             ('vndk_sp_indirect', 'vndk_sp'),
   1947             ('vndk_sp_indirect_unused', 'vndk_sp'),
   1948             ('vndk_sp_indirect_private', 'vndk_sp'),
   1949 
   1950             ('vndk_sp_ext', 'vndk_sp_ext'),
   1951             ('vndk_sp_indirect_ext', 'vndk_sp_ext'),
   1952 
   1953             ('vndk_ext', 'extra_vendor_libs'),
   1954             ('extra_vendor_libs', 'extra_vendor_libs'),
   1955         ]
   1956         results = SimpleVNDKResult()
   1957         for field_name, tag in field_name_tags:
   1958             getattr(results, tag).update(getattr(vndk_result, field_name))
   1959         return results
   1960 
   1961     def _print_tags(self, vndk_lib, full, file=sys.stdout):
   1962         if full:
   1963             result_tags = _VNDK_RESULT_FIELD_NAMES
   1964             results = vndk_lib
   1965         else:
   1966             # Simplified VNDK output with only three sets.
   1967             result_tags = _SIMPLE_VNDK_RESULT_FIELD_NAMES
   1968             results = self._extract_simple_vndk_result(vndk_lib)
   1969 
   1970         for tag in result_tags:
   1971             libs = getattr(results, tag)
   1972             tag += ':'
   1973             for lib in sorted_lib_path_list(libs):
   1974                 print(tag, lib, file=file)
   1975 
   1976     def _print_make(self, vndk_lib, file=sys.stdout):
   1977         def get_module_name(path):
   1978             name = os.path.basename(path)
   1979             root, ext = os.path.splitext(name)
   1980             return root
   1981 
   1982         def get_module_names(lib_set):
   1983             return sorted({ get_module_name(lib.path) for lib in lib_set })
   1984 
   1985         results = self._extract_simple_vndk_result(vndk_lib)
   1986         vndk_sp = get_module_names(results.vndk_sp)
   1987         vndk_sp_ext = get_module_names(results.vndk_sp_ext)
   1988         extra_vendor_libs= get_module_names(results.extra_vendor_libs)
   1989 
   1990         def format_module_names(module_names):
   1991             return '\\\n    ' +  ' \\\n    '.join(module_names)
   1992 
   1993         script_dir = os.path.dirname(os.path.abspath(__file__))
   1994         template_path = os.path.join(script_dir, 'templates', 'vndk.txt')
   1995         with open(template_path, 'r') as f:
   1996             template = f.read()
   1997 
   1998         template = template.replace('##_VNDK_SP_##',
   1999                                     format_module_names(vndk_sp))
   2000         template = template.replace('##_VNDK_SP_EXT_##',
   2001                                     format_module_names(vndk_sp_ext))
   2002         template = template.replace('##_EXTRA_VENDOR_LIBS_##',
   2003                                     format_module_names(extra_vendor_libs))
   2004 
   2005         file.write(template)
   2006 
   2007     def main(self, args):
   2008         generic_refs, graph, tagged_paths = self.create_from_args(args)
   2009 
   2010         # Check the API extensions to NDK libraries.
   2011         if generic_refs:
   2012             self._check_ndk_extensions(graph, generic_refs)
   2013 
   2014         if args.warn_incorrect_partition:
   2015             self._warn_incorrect_partition(graph)
   2016 
   2017         # Compute vndk heuristics.
   2018         vndk_lib = graph.compute_degenerated_vndk(
   2019                 generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
   2020                 args.action_ineligible_vndk)
   2021 
   2022         # Print results.
   2023         if args.output_format == 'make':
   2024             self._print_make(vndk_lib)
   2025         else:
   2026             self._print_tags(vndk_lib, args.full)
   2027 
   2028         return 0
   2029 
   2030 
   2031 class DepsInsightCommand(VNDKCommandBase):
   2032     def __init__(self):
   2033         super(DepsInsightCommand, self).__init__(
   2034                 'deps-insight', help='Generate HTML to show dependencies')
   2035 
   2036     def add_argparser_options(self, parser):
   2037         super(DepsInsightCommand, self).add_argparser_options(parser)
   2038 
   2039         parser.add_argument(
   2040                 '--output', '-o', help='output directory')
   2041 
   2042     def main(self, args):
   2043         generic_refs, graph, tagged_paths = self.create_from_args(args)
   2044 
   2045         # Compute vndk heuristics.
   2046         vndk_lib = graph.compute_degenerated_vndk(
   2047                 generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
   2048                 args.action_ineligible_vndk)
   2049 
   2050         # Serialize data.
   2051         strs = []
   2052         strs_dict = dict()
   2053 
   2054         libs = list(graph.all_libs())
   2055         libs.sort(key=lambda lib: lib.path)
   2056         libs_dict = {lib: i for i, lib in enumerate(libs)}
   2057 
   2058         def get_str_idx(s):
   2059             try:
   2060                 return strs_dict[s]
   2061             except KeyError:
   2062                 idx = len(strs)
   2063                 strs_dict[s] = idx
   2064                 strs.append(s)
   2065                 return idx
   2066 
   2067         def collect_path_sorted_lib_idxs(libs):
   2068             return [libs_dict[lib] for lib in sorted(libs)]
   2069 
   2070         def collect_deps(lib):
   2071             queue = list(lib.deps)
   2072             visited = set(queue)
   2073             visited.add(lib)
   2074             deps = []
   2075 
   2076             # Traverse dependencies with breadth-first search.
   2077             while queue:
   2078                 # Collect dependencies for next queue.
   2079                 next_queue = []
   2080                 for lib in queue:
   2081                     for dep in lib.deps:
   2082                         if dep not in visited:
   2083                             next_queue.append(dep)
   2084                             visited.add(dep)
   2085 
   2086                 # Append current queue to result.
   2087                 deps.append(collect_path_sorted_lib_idxs(queue))
   2088 
   2089                 queue = next_queue
   2090 
   2091             return deps
   2092 
   2093         def collect_tags(lib):
   2094             tags = []
   2095             for field_name in _VNDK_RESULT_FIELD_NAMES:
   2096                 if lib in getattr(vndk_lib, field_name):
   2097                     tags.append(get_str_idx(field_name))
   2098             return tags
   2099 
   2100         mods = []
   2101         for lib in libs:
   2102             mods.append([get_str_idx(lib.path),
   2103                          32 if lib.elf.is_32bit else 64,
   2104                          collect_tags(lib),
   2105                          collect_deps(lib),
   2106                          collect_path_sorted_lib_idxs(lib.users)])
   2107 
   2108         # Generate output files.
   2109         makedirs(args.output, exist_ok=True)
   2110         script_dir = os.path.dirname(os.path.abspath(__file__))
   2111         for name in ('index.html', 'insight.css', 'insight.js'):
   2112             shutil.copyfile(os.path.join(script_dir, 'assets', name),
   2113                             os.path.join(args.output, name))
   2114 
   2115         with open(os.path.join(args.output, 'insight-data.js'), 'w') as f:
   2116             f.write('''(function () {
   2117     var strs = ''' + json.dumps(strs) + ''';
   2118     var mods = ''' + json.dumps(mods) + ''';
   2119     insight.init(document, strs, mods);
   2120 })();''')
   2121 
   2122         return 0
   2123 
   2124 
   2125 class VNDKCapCommand(ELFGraphCommand):
   2126     def __init__(self):
   2127         super(VNDKCapCommand, self).__init__(
   2128                 'vndk-cap', help='Compute VNDK set upper bound')
   2129 
   2130     def add_argparser_options(self, parser):
   2131         super(VNDKCapCommand, self).add_argparser_options(parser)
   2132 
   2133     def main(self, args):
   2134         generic_refs, graph = self.create_from_args(args)
   2135 
   2136         banned_libs = BannedLibDict.create_default()
   2137 
   2138         vndk_cap = graph.compute_vndk_cap(banned_libs)
   2139 
   2140         for lib in sorted_lib_path_list(vndk_cap):
   2141             print(lib)
   2142 
   2143 
   2144 class DepsCommand(ELFGraphCommand):
   2145     def __init__(self):
   2146         super(DepsCommand, self).__init__(
   2147                 'deps', help='Print binary dependencies for debugging')
   2148 
   2149     def add_argparser_options(self, parser):
   2150         super(DepsCommand, self).add_argparser_options(parser)
   2151 
   2152         parser.add_argument(
   2153                 '--revert', action='store_true',
   2154                 help='print usage dependency')
   2155 
   2156         parser.add_argument(
   2157                 '--leaf', action='store_true',
   2158                 help='print binaries without dependencies or usages')
   2159 
   2160         parser.add_argument(
   2161                 '--symbols', action='store_true',
   2162                 help='print symbols')
   2163 
   2164     def main(self, args):
   2165         generic_refs, graph = self.create_from_args(args)
   2166 
   2167         results = []
   2168         for partition in range(NUM_PARTITIONS):
   2169             for name, lib in graph.lib_pt[partition].items():
   2170                 if args.symbols:
   2171                     def collect_symbols(user, definer):
   2172                         return user.get_dep_linked_symbols(definer)
   2173                 else:
   2174                     def collect_symbols(user, definer):
   2175                         return ()
   2176 
   2177                 data = []
   2178                 if args.revert:
   2179                     for assoc_lib in sorted(lib.users):
   2180                         data.append((assoc_lib.path,
   2181                                      collect_symbols(assoc_lib, lib)))
   2182                 else:
   2183                     for assoc_lib in sorted(lib.deps):
   2184                         data.append((assoc_lib.path,
   2185                                      collect_symbols(lib, assoc_lib)))
   2186                 results.append((name, data))
   2187         results.sort()
   2188 
   2189         if args.leaf:
   2190             for name, deps in results:
   2191                 if not deps:
   2192                     print(name)
   2193         else:
   2194             for name, assoc_libs in results:
   2195                 print(name)
   2196                 for assoc_lib, symbols in assoc_libs:
   2197                     print('\t' + assoc_lib)
   2198                     for symbol in symbols:
   2199                         print('\t\t' + symbol)
   2200         return 0
   2201 
   2202 
   2203 class DepsClosureCommand(ELFGraphCommand):
   2204     def __init__(self):
   2205         super(DepsClosureCommand, self).__init__(
   2206                 'deps-closure', help='Find transitive closure of dependencies')
   2207 
   2208     def add_argparser_options(self, parser):
   2209         super(DepsClosureCommand, self).add_argparser_options(parser)
   2210 
   2211         parser.add_argument('lib', nargs='+',
   2212                             help='root set of the shared libraries')
   2213 
   2214         parser.add_argument('--exclude-lib', action='append', default=[],
   2215                             help='libraries to be excluded')
   2216 
   2217         parser.add_argument('--exclude-ndk', action='store_true',
   2218                             help='exclude ndk libraries')
   2219 
   2220     def main(self, args):
   2221         generic_refs, graph = self.create_from_args(args)
   2222 
   2223         # Find root/excluded libraries by their paths.
   2224         def report_error(path):
   2225             print('error: no such lib: {}'.format(path), file=sys.stderr)
   2226         root_libs = graph.get_libs(args.lib, report_error)
   2227         excluded_libs = graph.get_libs(args.exclude_lib, report_error)
   2228 
   2229         # Compute and print the closure.
   2230         if args.exclude_ndk:
   2231             def is_excluded_libs(lib):
   2232                 return lib.is_ndk or lib in excluded_libs
   2233         else:
   2234             def is_excluded_libs(lib):
   2235                 return lib in excluded_libs
   2236 
   2237         closure = graph.compute_closure(root_libs, is_excluded_libs)
   2238         for lib in sorted_lib_path_list(closure):
   2239             print(lib)
   2240         return 0
   2241 
   2242 
   2243 class TaggedDict(object):
   2244     TAGS = {
   2245         'll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
   2246         'vndk_sp', 'vndk_sp_indirect', 'vndk_sp_indirect_private',
   2247         'vndk',
   2248         'fwk_only', 'fwk_only_rs',
   2249         'sp_hal', 'sp_hal_dep',
   2250         'vnd_only',
   2251         'remove',
   2252     }
   2253 
   2254     _TAG_ALIASES = {
   2255         'hl_ndk': 'fwk_only',  # Treat HL-NDK as FWK-ONLY.
   2256         'vndk_indirect': 'vndk',  # Legacy
   2257         'vndk_sp_hal': 'vndk_sp',  # Legacy
   2258         'vndk_sp_both': 'vndk_sp',  # Legacy
   2259     }
   2260 
   2261     @classmethod
   2262     def _normalize_tag(cls, tag):
   2263         tag = tag.lower().replace('-', '_')
   2264         tag = cls._TAG_ALIASES.get(tag, tag)
   2265         if tag not in cls.TAGS:
   2266             raise ValueError('unknown lib tag ' + tag)
   2267         return tag
   2268 
   2269     _LL_NDK_VIS = {'ll_ndk', 'll_ndk_indirect'}
   2270     _SP_NDK_VIS = {'ll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect'}
   2271     _VNDK_SP_VIS = {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect',
   2272                     'vndk_sp_indirect_private', 'fwk_only_rs'}
   2273     _FWK_ONLY_VIS = {'ll_ndk', 'll_ndk_indirect', 'sp_ndk', 'sp_ndk_indirect',
   2274                      'vndk_sp', 'vndk_sp_indirect', 'vndk_sp_indirect_private',
   2275                      'vndk', 'fwk_only', 'fwk_only_rs', 'sp_hal'}
   2276     _SP_HAL_VIS = {'ll_ndk', 'sp_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
   2277 
   2278     _TAG_VISIBILITY = {
   2279         'll_ndk': _LL_NDK_VIS,
   2280         'll_ndk_indirect': _LL_NDK_VIS,
   2281         'sp_ndk': _SP_NDK_VIS,
   2282         'sp_ndk_indirect': _SP_NDK_VIS,
   2283 
   2284         'vndk_sp': _VNDK_SP_VIS,
   2285         'vndk_sp_indirect': _VNDK_SP_VIS,
   2286         'vndk_sp_indirect_private': _VNDK_SP_VIS,
   2287 
   2288         'vndk': {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect', 'vndk'},
   2289 
   2290         'fwk_only': _FWK_ONLY_VIS,
   2291         'fwk_only_rs': _FWK_ONLY_VIS,
   2292 
   2293         'sp_hal': _SP_HAL_VIS,
   2294         'sp_hal_dep': _SP_HAL_VIS,
   2295 
   2296         'vnd_only': {'ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk_sp_indirect',
   2297                      'vndk', 'sp_hal', 'sp_hal_dep', 'vnd_only'},
   2298 
   2299         'remove': set(),
   2300     }
   2301 
   2302     del _LL_NDK_VIS, _SP_NDK_VIS, _VNDK_SP_VIS, _FWK_ONLY_VIS, _SP_HAL_VIS
   2303 
   2304     @classmethod
   2305     def is_tag_visible(cls, from_tag, to_tag):
   2306         return to_tag in cls._TAG_VISIBILITY[from_tag]
   2307 
   2308     def __init__(self):
   2309         self._path_tag = dict()
   2310         for tag in self.TAGS:
   2311             setattr(self, tag, set())
   2312 
   2313     def add(self, tag, lib):
   2314         lib_set = getattr(self, tag)
   2315         lib_set.add(lib)
   2316         self._path_tag[lib] = tag
   2317 
   2318     def get_path_tag(self, lib):
   2319         try:
   2320             return self._path_tag[lib]
   2321         except KeyError:
   2322             return self.get_path_tag_default(lib)
   2323 
   2324     def get_path_tag_default(self, lib):
   2325         raise NotImplementedError()
   2326 
   2327     def is_path_visible(self, from_lib, to_lib):
   2328         return self.is_tag_visible(self.get_path_tag(from_lib),
   2329                                    self.get_path_tag(to_lib))
   2330 
   2331 
   2332 class TaggedPathDict(TaggedDict):
   2333     def load_from_csv(self, fp):
   2334         reader = csv.reader(fp)
   2335 
   2336         # Read first row and check the existence of the header.
   2337         try:
   2338             row = next(reader)
   2339         except StopIteration:
   2340             return
   2341 
   2342         try:
   2343             path_col = row.index('Path')
   2344             tag_col = row.index('Tag')
   2345         except ValueError:
   2346             path_col = 0
   2347             tag_col = 1
   2348             self.add(self._normalize_tag(row[tag_col]), row[path_col])
   2349 
   2350         # Read the rest of rows.
   2351         for row in reader:
   2352             self.add(self._normalize_tag(row[tag_col]), row[path_col])
   2353 
   2354     @staticmethod
   2355     def create_from_csv(fp):
   2356         d = TaggedPathDict()
   2357         d.load_from_csv(fp)
   2358         return d
   2359 
   2360     @staticmethod
   2361     def create_from_csv_path(path):
   2362         with open(path, 'r') as fp:
   2363             return TaggedPathDict.create_from_csv(fp)
   2364 
   2365     @staticmethod
   2366     def _enumerate_paths(pattern):
   2367         if '${LIB}' in pattern:
   2368             yield pattern.replace('${LIB}', 'lib')
   2369             yield pattern.replace('${LIB}', 'lib64')
   2370         else:
   2371             yield pattern
   2372 
   2373     def add(self, tag, path):
   2374         for path in self._enumerate_paths(path):
   2375             super(TaggedPathDict, self).add(tag, path)
   2376 
   2377     def get_path_tag_default(self, path):
   2378         return 'vnd_only' if path.startswith('/vendor') else 'fwk_only'
   2379 
   2380 
   2381 class TaggedLibDict(TaggedDict):
   2382     @staticmethod
   2383     def create_from_graph(graph, tagged_paths, generic_refs=None):
   2384         d = TaggedLibDict()
   2385 
   2386         for lib in graph.lib_pt[PT_SYSTEM].values():
   2387             d.add(tagged_paths.get_path_tag(lib.path), lib)
   2388 
   2389         sp_lib = graph.compute_sp_lib(generic_refs)
   2390         for lib in graph.lib_pt[PT_VENDOR].values():
   2391             if lib in sp_lib.sp_hal:
   2392                 d.add('sp_hal', lib)
   2393             elif lib in sp_lib.sp_hal_dep:
   2394                 d.add('sp_hal_dep', lib)
   2395             else:
   2396                 d.add('vnd_only', lib)
   2397         return d
   2398 
   2399     def get_path_tag_default(self, lib):
   2400         return 'vnd_only' if lib.path.startswith('/vendor') else 'fwk_only'
   2401 
   2402 
   2403 class ModuleInfo(object):
   2404     def __init__(self, module_info_path=None):
   2405         if not module_info_path:
   2406             self.json = dict()
   2407         else:
   2408             with open(module_info_path, 'r') as f:
   2409                 self.json = json.load(f)
   2410 
   2411     def get_module_path(self, installed_path):
   2412         for name, module in  self.json.items():
   2413             if any(path.endswith(installed_path)
   2414                    for path in module['installed']):
   2415                 return module['path']
   2416         return []
   2417 
   2418 
   2419 class CheckDepCommand(ELFGraphCommand):
   2420     def __init__(self):
   2421         super(CheckDepCommand, self).__init__(
   2422                 'check-dep', help='Check the eligible dependencies')
   2423 
   2424     def add_argparser_options(self, parser):
   2425         super(CheckDepCommand, self).add_argparser_options(parser)
   2426 
   2427         parser.add_argument('--tag-file', required=True)
   2428 
   2429         parser.add_argument('--module-info')
   2430 
   2431     @staticmethod
   2432     def _dump_dep(lib, bad_deps, module_info):
   2433         print(lib.path)
   2434         for module_path in sorted(module_info.get_module_path(lib.path)):
   2435             print('\tMODULE_PATH:', module_path)
   2436         for dep in sorted(bad_deps):
   2437             print('\t' + dep.path)
   2438             for symbol in lib.get_dep_linked_symbols(dep):
   2439                 print('\t\t' + symbol)
   2440 
   2441     def _check_eligible_vndk_dep(self, graph, tagged_libs, module_info):
   2442         """Check whether eligible sets are self-contained."""
   2443         num_errors = 0
   2444 
   2445         indirect_libs = (tagged_libs.ll_ndk_indirect | \
   2446                          tagged_libs.sp_ndk_indirect | \
   2447                          tagged_libs.vndk_sp_indirect_private | \
   2448                          tagged_libs.fwk_only_rs)
   2449 
   2450         eligible_libs = (tagged_libs.ll_ndk | tagged_libs.sp_ndk | \
   2451                          tagged_libs.vndk_sp | tagged_libs.vndk_sp_indirect | \
   2452                          tagged_libs.vndk)
   2453 
   2454         # Check eligible vndk is self-contained.
   2455         for lib in sorted(eligible_libs):
   2456             bad_deps = []
   2457             for dep in lib.deps:
   2458                 if dep not in eligible_libs and dep not in indirect_libs:
   2459                     print('error: eligible lib "{}" should not depend on '
   2460                           'non-eligible lib "{}".'.format(lib.path, dep.path),
   2461                           file=sys.stderr)
   2462                     bad_deps.append(dep)
   2463                     num_errors += 1
   2464             if bad_deps:
   2465                 self._dump_dep(lib, bad_deps, module_info)
   2466 
   2467         # Check the libbinder dependencies.
   2468         for lib in sorted(eligible_libs):
   2469             bad_deps = []
   2470             for dep in lib.deps:
   2471                 if os.path.basename(dep.path) == 'libbinder.so':
   2472                     print('error: eligible lib "{}" should not depend on '
   2473                           'libbinder.so.'.format(lib.path), file=sys.stderr)
   2474                     bad_deps.append(dep)
   2475                     num_errors += 1
   2476             if bad_deps:
   2477                 self._dump_dep(lib, bad_deps, module_info)
   2478 
   2479         return num_errors
   2480 
   2481     def _check_vendor_dep(self, graph, tagged_libs, module_info):
   2482         """Check whether vendor libs are depending on non-eligible libs."""
   2483         num_errors = 0
   2484 
   2485         vendor_libs = set(graph.lib_pt[PT_VENDOR].values())
   2486 
   2487         eligible_libs = (tagged_libs.ll_ndk | tagged_libs.sp_ndk | \
   2488                          tagged_libs.vndk_sp | tagged_libs.vndk_sp_indirect | \
   2489                          tagged_libs.vndk)
   2490 
   2491         for lib in sorted(vendor_libs):
   2492             bad_deps = []
   2493             for dep in lib.deps:
   2494                 if dep not in vendor_libs and dep not in eligible_libs:
   2495                     print('error: vendor lib "{}" depends on non-eligible '
   2496                           'lib "{}".'.format(lib.path, dep.path),
   2497                           file=sys.stderr)
   2498                     bad_deps.append(dep)
   2499                     num_errors += 1
   2500             if bad_deps:
   2501                 self._dump_dep(lib, bad_deps, module_info)
   2502 
   2503         return num_errors
   2504 
   2505     def main(self, args):
   2506         generic_refs, graph = self.create_from_args(args)
   2507 
   2508         tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
   2509         tagged_libs = TaggedLibDict.create_from_graph(graph, tagged_paths)
   2510 
   2511         module_info = ModuleInfo(args.module_info)
   2512 
   2513         num_errors = self._check_eligible_vndk_dep(graph, tagged_libs,
   2514                                                    module_info)
   2515         num_errors += self._check_vendor_dep(graph, tagged_libs, module_info)
   2516 
   2517         return 0 if num_errors == 0 else 1
   2518 
   2519 
   2520 class DepGraphCommand(ELFGraphCommand):
   2521     def __init__(self):
   2522         super(DepGraphCommand, self).__init__(
   2523                 'dep-graph', help='Show the eligible dependencies graph')
   2524 
   2525     def add_argparser_options(self, parser):
   2526         super(DepGraphCommand, self).add_argparser_options(parser)
   2527 
   2528         parser.add_argument('--tag-file', required=True)
   2529         parser.add_argument('--output', '-o', help='output directory')
   2530 
   2531     def _get_tag_from_lib(self, lib, tagged_paths):
   2532         tag_hierarchy = dict()
   2533         for tag in TaggedPathDict.TAGS:
   2534             if tag in {'sp_hal', 'sp_hal_dep', 'vnd_only'}:
   2535                 tag_hierarchy[tag] = 'vendor.private.{}'.format(tag)
   2536             else:
   2537                 vendor_visible = TaggedPathDict.is_tag_visible('vnd_only', tag)
   2538                 pub = 'public' if vendor_visible else 'private'
   2539                 tag_hierarchy[tag] = 'system.{}.{}'.format(pub, tag)
   2540 
   2541         return tag_hierarchy[tagged_paths.get_path_tag(lib.path)]
   2542 
   2543     def _check_if_allowed(self, my_tag, other_tag):
   2544         my = my_tag.split('.')
   2545         other = other_tag.split('.')
   2546         if my[0] == 'system' and other[0] == 'vendor':
   2547             return False
   2548         if my[0] == 'vendor' and other[0] == 'system' \
   2549                              and other[1] == 'private':
   2550             return False
   2551         return True
   2552 
   2553     def _get_dep_graph(self, graph, tagged_paths):
   2554         data = []
   2555         violate_libs = dict()
   2556         system_libs = graph.lib_pt[PT_SYSTEM].values()
   2557         vendor_libs = graph.lib_pt[PT_VENDOR].values()
   2558         for lib in itertools.chain(system_libs, vendor_libs):
   2559             tag = self._get_tag_from_lib(lib, tagged_paths)
   2560             violate_count = 0
   2561             lib_item = {
   2562                 'name': lib.path,
   2563                 'tag': tag,
   2564                 'depends': [],
   2565                 'violates': [],
   2566             }
   2567             for dep in lib.deps:
   2568                 if self._check_if_allowed(tag,
   2569                         self._get_tag_from_lib(dep, tagged_paths)):
   2570                     lib_item['depends'].append(dep.path)
   2571                 else:
   2572                     lib_item['violates'].append(dep.path)
   2573                     violate_count += 1;
   2574             lib_item['violate_count'] = violate_count
   2575             if violate_count > 0:
   2576                 if not tag in violate_libs:
   2577                     violate_libs[tag] = []
   2578                 violate_libs[tag].append((lib.path, violate_count))
   2579             data.append(lib_item)
   2580         return data, violate_libs
   2581 
   2582     def main(self, args):
   2583         generic_refs, graph = self.create_from_args(args)
   2584 
   2585         tagged_paths = TaggedPathDict.create_from_csv_path(args.tag_file)
   2586         data, violate_libs = self._get_dep_graph(graph, tagged_paths)
   2587         data.sort(key=lambda lib_item: (lib_item['tag'],
   2588                                         lib_item['violate_count']))
   2589         for libs in violate_libs.values():
   2590             libs.sort(key=lambda libs: libs[1], reverse=True)
   2591 
   2592         makedirs(args.output, exist_ok=True)
   2593         script_dir = os.path.dirname(os.path.abspath(__file__))
   2594         for name in ('index.html', 'dep-graph.js', 'dep-graph.css'):
   2595             shutil.copyfile(os.path.join(script_dir, 'assets/visual', name),
   2596                             os.path.join(args.output, name))
   2597         with open(os.path.join(args.output, 'dep-data.js'), 'w') as f:
   2598             f.write('var violatedLibs = ' + json.dumps(violate_libs) +
   2599                     '\nvar depData = ' + json.dumps(data) + ';')
   2600 
   2601         return 0
   2602 
   2603 
   2604 class VNDKSPCommand(ELFGraphCommand):
   2605     def __init__(self):
   2606         super(VNDKSPCommand, self).__init__(
   2607                 'vndk-sp', help='List pre-defined VNDK-SP')
   2608 
   2609     def add_argparser_options(self, parser):
   2610         super(VNDKSPCommand, self).add_argparser_options(parser)
   2611 
   2612     def main(self, args):
   2613         generic_refs, graph = self.create_from_args(args)
   2614 
   2615         vndk_sp = graph.compute_predefined_vndk_sp()
   2616         for lib in sorted_lib_path_list(vndk_sp):
   2617             print('vndk-sp:', lib)
   2618         vndk_sp_indirect = graph.compute_predefined_vndk_sp_indirect()
   2619         for lib in sorted_lib_path_list(vndk_sp_indirect):
   2620             print('vndk-sp-indirect:', lib)
   2621         return 0
   2622 
   2623 
   2624 class SpLibCommand(ELFGraphCommand):
   2625     def __init__(self):
   2626         super(SpLibCommand, self).__init__(
   2627                 'sp-lib', help='Define sp-ndk, sp-hal, and vndk-sp')
   2628 
   2629     def add_argparser_options(self, parser):
   2630         super(SpLibCommand, self).add_argparser_options(parser)
   2631 
   2632     def main(self, args):
   2633         generic_refs, graph = self.create_from_args(args)
   2634         print_sp_lib(graph.compute_sp_lib(generic_refs))
   2635         return 0
   2636 
   2637 
   2638 def main():
   2639     parser = argparse.ArgumentParser()
   2640     subparsers = parser.add_subparsers(dest='subcmd')
   2641     subcmds = dict()
   2642 
   2643     def register_subcmd(cmd):
   2644         subcmds[cmd.name] = cmd
   2645         cmd.add_argparser_options(
   2646                 subparsers.add_parser(cmd.name, help=cmd.help))
   2647 
   2648     register_subcmd(ELFDumpCommand())
   2649     register_subcmd(CreateGenericRefCommand())
   2650     register_subcmd(VNDKCommand())
   2651     register_subcmd(VNDKCapCommand())
   2652     register_subcmd(DepsCommand())
   2653     register_subcmd(DepsClosureCommand())
   2654     register_subcmd(DepsInsightCommand())
   2655     register_subcmd(CheckDepCommand())
   2656     register_subcmd(DepGraphCommand())
   2657     register_subcmd(SpLibCommand())
   2658     register_subcmd(VNDKSPCommand())
   2659 
   2660     args = parser.parse_args()
   2661     if not args.subcmd:
   2662         parser.print_help()
   2663         sys.exit(1)
   2664     return subcmds[args.subcmd].main(args)
   2665 
   2666 if __name__ == '__main__':
   2667     sys.exit(main())
   2668