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