1#!/usr/bin/env python3
2
3from __future__ import print_function
4
5import argparse
6import codecs
7import collections
8import copy
9import csv
10import io
11import itertools
12import json
13import os
14import posixpath
15import re
16import shutil
17import stat
18import struct
19import subprocess
20import sys
21import zipfile
22
23
24#------------------------------------------------------------------------------
25# Python 2 and 3 Compatibility Layer
26#------------------------------------------------------------------------------
27
28if sys.version_info >= (3, 0):
29    from os import makedirs
30    from mmap import ACCESS_READ, mmap
31
32    def get_py3_bytes(buf):
33        return buf
34
35    create_chr = chr
36    enumerate_bytes = enumerate
37else:
38    from mmap import ACCESS_READ, mmap
39
40    def makedirs(path, exist_ok):
41        if exist_ok and os.path.isdir(path):
42            return
43        os.makedirs(path)
44
45    class mmap(mmap):
46        def __enter__(self):
47            return self
48
49        def __exit__(self, exc, value, tb):
50            self.close()
51
52        def __getitem__(self, key):
53            res = super(mmap, self).__getitem__(key)
54            if isinstance(key, int):
55                return ord(res)
56            return res
57
58    class Py3Bytes(bytes):
59        def __getitem__(self, key):
60            res = super(Py3Bytes, self).__getitem__(key)
61            if isinstance(key, int):
62                return ord(res)
63            return Py3Bytes(res)
64
65    def get_py3_bytes(buf):
66        return Py3Bytes(buf)
67
68    create_chr = unichr
69
70    def enumerate_bytes(iterable):
71        for i, byte in enumerate(iterable):
72            yield (i, ord(byte))
73
74    FileNotFoundError = EnvironmentError
75
76try:
77    from sys import intern
78except ImportError:
79    pass
80
81try:
82    from tempfile import TemporaryDirectory
83except ImportError:
84    import shutil
85    import tempfile
86
87    class TemporaryDirectory(object):
88        def __init__(self, suffix='', prefix='tmp', dir=None):
89            # pylint: disable=redefined-builtin
90            self.name = tempfile.mkdtemp(suffix, prefix, dir)
91
92        def __del__(self):
93            self.cleanup()
94
95        def __enter__(self):
96            return self.name
97
98        def __exit__(self, exc, value, tb):
99            self.cleanup()
100
101        def cleanup(self):
102            if self.name:
103                shutil.rmtree(self.name)
104                self.name = None
105
106try:
107    from os import scandir
108except ImportError:
109    import stat
110    import os
111
112    class DirEntry(object):
113        def __init__(self, name, path):
114            self.name = name
115            self.path = path
116            self._stat = None
117            self._lstat = None
118
119        @staticmethod
120        def _stat_impl(path, follow_symlinks):
121            return os.stat(path) if follow_symlinks else os.lstat(path)
122
123        def stat(self, follow_symlinks=True):
124            attr = '_stat' if follow_symlinks else '_lstat'
125            stat_res = getattr(self, attr)
126            if stat_res is None:
127                stat_res = self._stat_impl(self.path, follow_symlinks)
128                setattr(self, attr, stat_res)
129            return stat_res
130
131        def is_dir(self, follow_symlinks=True):
132            try:
133                return stat.S_ISDIR(self.stat(follow_symlinks).st_mode)
134            except EnvironmentError:
135                return False
136
137        def is_file(self, follow_symlinks=True):
138            try:
139                return stat.S_ISREG(self.stat(follow_symlinks).st_mode)
140            except EnvironmentError:
141                return False
142
143        def is_symlink(self):
144            return stat.S_ISLNK(self.stat(follow_symlinks=False).st_mode)
145
146    def scandir(path):
147        for name in os.listdir(path):
148            yield DirEntry(name, os.path.join(path, name))
149
150
151#------------------------------------------------------------------------------
152# Print Function
153#------------------------------------------------------------------------------
154
155def print_sb(*args, **kwargs):
156    """A print function that supports both str and bytes."""
157    sep = kwargs.get('sep', ' ')
158    end = kwargs.get('end', '\n')
159    out_file = kwargs.get('file', sys.stdout)
160    for i, arg in enumerate(args):
161        if i > 0:
162            out_file.write(sep)
163        if isinstance(arg, str):
164            out_file.write(arg)
165        elif isinstance(arg, bytes):
166            out_file.flush()
167            out_file.buffer.write(arg)
168            out_file.flush()
169        else:
170            out_file.write(str(arg))
171    out_file.write(end)
172
173
174#------------------------------------------------------------------------------
175# Modified UTF-8 Encoder and Decoder
176#------------------------------------------------------------------------------
177
178class UnicodeSurrogateDecodeError(UnicodeDecodeError):
179    pass
180
181
182def encode_mutf8(input, errors='strict'):
183    i = 0
184    res = io.BytesIO()
185
186    for i, char in enumerate(input):
187        code = ord(char)
188        if code == 0x00:
189            res.write(b'\xc0\x80')
190        elif code < 0x80:
191            res.write(bytearray((code,)))
192        elif code < 0x800:
193            res.write(bytearray((0xc0 | (code >> 6), 0x80 | (code & 0x3f))))
194        elif code < 0x10000:
195            res.write(bytearray((0xe0 | (code >> 12),
196                                 0x80 | ((code >> 6) & 0x3f),
197                                 0x80 | (code & 0x3f))))
198        elif code < 0x110000:
199            code -= 0x10000
200            code_hi = 0xd800 + (code >> 10)
201            code_lo = 0xdc00 + (code & 0x3ff)
202            res.write(bytearray((0xe0 | (code_hi >> 12),
203                                 0x80 | ((code_hi >> 6) & 0x3f),
204                                 0x80 | (code_hi & 0x3f),
205                                 0xe0 | (code_lo >> 12),
206                                 0x80 | ((code_lo >> 6) & 0x3f),
207                                 0x80 | (code_lo & 0x3f))))
208        else:
209            raise UnicodeEncodeError('mutf-8', input, i, i + 1,
210                                     'illegal code point')
211
212    return (res.getvalue(), i)
213
214
215def decode_mutf8(input, errors='strict'):
216    res = io.StringIO()
217
218    num_next = 0
219
220    i = 0
221    code = 0
222    start = 0
223
224    code_surrogate = None
225    start_surrogate = None
226
227    def raise_error(start, reason):
228        raise UnicodeDecodeError('mutf-8', input, start, i + 1, reason)
229
230    def raise_surrogate_error(start, reason):
231        raise UnicodeSurrogateDecodeError(
232            'mutf-8', input, start, i + 1, reason)
233
234    for i, byte in enumerate_bytes(input):
235        if (byte & 0x80) == 0x00:
236            if num_next > 0:
237                raise_error(start, 'invalid continuation byte')
238            num_next = 0
239            code = byte
240            start = i
241        elif (byte & 0xc0) == 0x80:
242            if num_next < 1:
243                raise_error(start, 'invalid start byte')
244            num_next -= 1
245            code = (code << 6) | (byte & 0x3f)
246        elif (byte & 0xe0) == 0xc0:
247            if num_next > 0:
248                raise_error(start, 'invalid continuation byte')
249            num_next = 1
250            code = byte & 0x1f
251            start = i
252        elif (byte & 0xf0) == 0xe0:
253            if num_next > 0:
254                raise_error(start, 'invalid continuation byte')
255            num_next = 2
256            code = byte & 0x0f
257            start = i
258        else:
259            raise_error(i, 'invalid start byte')
260
261        if num_next == 0:
262            if 0xd800 <= code <= 0xdbff:  # High surrogate
263                if code_surrogate is not None:
264                    raise_surrogate_error(
265                        start_surrogate, 'invalid high surrogate')
266                code_surrogate = code
267                start_surrogate = start
268                continue
269
270            if 0xdc00 <= code <= 0xdfff:  # Low surrogate
271                if code_surrogate is None:
272                    raise_surrogate_error(start, 'invalid low surrogate')
273                code = (((code_surrogate & 0x3f) << 10) |
274                        (code & 0x3f) + 0x10000)
275                code_surrogate = None
276                start_surrogate = None
277            elif code_surrogate is not None:
278                if errors == 'ignore':
279                    res.write(create_chr(code_surrogate))
280                    code_surrogate = None
281                    start_surrogate = None
282                else:
283                    raise_surrogate_error(start_surrogate, 'illegal surrogate')
284
285            res.write(create_chr(code))
286
287    # Check the unexpected end of input
288    if num_next > 0:
289        raise_error(start, 'unexpected end')
290    if code_surrogate is not None:
291        raise_surrogate_error(start_surrogate, 'unexpected end')
292
293    return (res.getvalue(), i)
294
295
296def probe_mutf8(name):
297    if name == 'mutf-8':
298        return codecs.CodecInfo(encode_mutf8, decode_mutf8)
299    return None
300
301codecs.register(probe_mutf8)
302
303
304#------------------------------------------------------------------------------
305# Collections
306#------------------------------------------------------------------------------
307
308def defaultnamedtuple(typename, field_names, default):
309    """Create a namedtuple type with default values.
310
311    This function creates a namedtuple type which will fill in default value
312    when actual arguments to the constructor were omitted.
313
314    >>> Point = defaultnamedtuple('Point', ['x', 'y'], 0)
315    >>> Point()
316    Point(x=0, y=0)
317    >>> Point(1)
318    Point(x=1, y=0)
319    >>> Point(1, 2)
320    Point(x=1, y=2)
321    >>> Point(x=1, y=2)
322    Point(x=1, y=2)
323    >>> Point(y=2, x=1)
324    Point(x=1, y=2)
325
326    >>> PermSet = defaultnamedtuple('PermSet', 'allowed disallowed', set())
327    >>> s = PermSet()
328    >>> s
329    PermSet(allowed=set(), disallowed=set())
330    >>> s.allowed is not s.disallowed
331    True
332    >>> PermSet({1})
333    PermSet(allowed={1}, disallowed=set())
334    >>> PermSet({1}, {2})
335    PermSet(allowed={1}, disallowed={2})
336    """
337
338    if isinstance(field_names, str):
339        field_names = field_names.replace(',', ' ').split()
340    field_names = list(map(str, field_names))
341    num_fields = len(field_names)
342
343    base_cls = collections.namedtuple(typename, field_names)
344    def __new__(cls, *args, **kwargs):
345        args = list(args)
346        for i in range(len(args), num_fields):
347            arg = kwargs.get(field_names[i])
348            if arg:
349                args.append(arg)
350            else:
351                args.append(copy.copy(default))
352        return base_cls.__new__(cls, *args)
353    return type(typename, (base_cls,), {'__new__': __new__})
354
355
356def create_struct(name, fields):
357    """Create a namedtuple with unpack_from() function.
358    >>> Point = create_struct('Point', [('x', 'I'), ('y', 'I')])
359    >>> pt = Point.unpack_from(b'\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00', 0)
360    >>> pt.x
361    0
362    >>> pt.y
363    1
364    """
365    field_names = [name for name, ty in fields]
366    cls = collections.namedtuple(name, field_names)
367    cls.struct_fmt = ''.join(ty for name, ty in fields)
368    cls.struct_size = struct.calcsize(cls.struct_fmt)
369    def unpack_from(cls, buf, offset=0):
370        unpacked = struct.unpack_from(cls.struct_fmt, buf, offset)
371        return cls.__new__(cls, *unpacked)
372    cls.unpack_from = classmethod(unpack_from)
373    return cls
374
375
376#------------------------------------------------------------------------------
377# ELF Parser
378#------------------------------------------------------------------------------
379
380Elf_Hdr = collections.namedtuple(
381    'Elf_Hdr',
382    'ei_class ei_data ei_version ei_osabi e_type e_machine e_version '
383    'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum '
384    'e_shentsize e_shnum e_shstridx')
385
386
387Elf_Shdr = collections.namedtuple(
388    'Elf_Shdr',
389    'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info '
390    'sh_addralign sh_entsize')
391
392
393Elf_Phdr = collections.namedtuple(
394    'Elf_Phdr',
395    'p_type p_offset p_vaddr p_paddr p_filesz p_memsz p_flags p_align')
396
397
398Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val')
399
400
401class Elf_Sym(collections.namedtuple(
402        'ELF_Sym', 'st_name st_value st_size st_info st_other st_shndx')):
403
404    STB_LOCAL = 0
405    STB_GLOBAL = 1
406    STB_WEAK = 2
407
408    SHN_UNDEF = 0
409
410
411    @property
412    def st_bind(self):
413        return self.st_info >> 4
414
415
416    @property
417    def is_local(self):
418        return self.st_bind == Elf_Sym.STB_LOCAL
419
420
421    @property
422    def is_global(self):
423        return self.st_bind == Elf_Sym.STB_GLOBAL
424
425
426    @property
427    def is_weak(self):
428        return self.st_bind == Elf_Sym.STB_WEAK
429
430
431    @property
432    def is_undef(self):
433        return self.st_shndx == Elf_Sym.SHN_UNDEF
434
435
436class ELFError(ValueError):
437    pass
438
439
440class ELF(object):
441    # ELF file format constants.
442    ELF_MAGIC = b'\x7fELF'
443
444    EI_CLASS = 4
445    EI_DATA = 5
446
447    ELFCLASSNONE = 0
448    ELFCLASS32 = 1
449    ELFCLASS64 = 2
450
451    ELFDATANONE = 0
452    ELFDATA2LSB = 1
453    ELFDATA2MSB = 2
454
455    PT_LOAD = 1
456
457    PF_X = 1
458    PF_W = 2
459    PF_R = 4
460
461    DT_NEEDED = 1
462    DT_RPATH = 15
463    DT_RUNPATH = 29
464
465    _ELF_CLASS_NAMES = {
466        ELFCLASS32: '32',
467        ELFCLASS64: '64',
468    }
469
470    _ELF_DATA_NAMES = {
471        ELFDATA2LSB: 'Little-Endian',
472        ELFDATA2MSB: 'Big-Endian',
473    }
474
475    EM_NONE = 0
476    EM_386 = 3
477    EM_MIPS = 8
478    EM_ARM = 40
479    EM_X86_64 = 62
480    EM_AARCH64 = 183
481
482
483    def _create_elf_machines(d):
484        elf_machine_ids = {}
485        for key, value in d.items():
486            if key.startswith('EM_'):
487                elf_machine_ids[value] = key
488        return elf_machine_ids
489
490    ELF_MACHINES = _create_elf_machines(locals())
491
492    del _create_elf_machines
493
494
495    @staticmethod
496    def _dict_find_key_by_value(d, dst):
497        for key, value in d.items():
498            if value == dst:
499                return key
500        raise KeyError(dst)
501
502
503    @staticmethod
504    def get_ei_class_from_name(name):
505        return ELF._dict_find_key_by_value(ELF._ELF_CLASS_NAMES, name)
506
507
508    @staticmethod
509    def get_ei_data_from_name(name):
510        return ELF._dict_find_key_by_value(ELF._ELF_DATA_NAMES, name)
511
512
513    @staticmethod
514    def get_e_machine_from_name(name):
515        return ELF._dict_find_key_by_value(ELF.ELF_MACHINES, name)
516
517
518    __slots__ = ('ei_class', 'ei_data', 'e_machine', 'dt_rpath', 'dt_runpath',
519                 'dt_needed', 'exported_symbols', 'imported_symbols',
520                 'file_size', 'ro_seg_file_size', 'ro_seg_mem_size',
521                 'rw_seg_file_size', 'rw_seg_mem_size',)
522
523
524    def __init__(self, ei_class=ELFCLASSNONE, ei_data=ELFDATANONE, e_machine=0,
525                 dt_rpath=None, dt_runpath=None, dt_needed=None,
526                 exported_symbols=None, imported_symbols=None,
527                 file_size=0, ro_seg_file_size=0, ro_seg_mem_size=0,
528                 rw_seg_file_size=0, rw_seg_mem_size=0):
529        self.ei_class = ei_class
530        self.ei_data = ei_data
531        self.e_machine = e_machine
532        self.dt_rpath = dt_rpath if dt_rpath is not None else []
533        self.dt_runpath = dt_runpath if dt_runpath is not None else []
534        self.dt_needed = dt_needed if dt_needed is not None else []
535        self.exported_symbols = \
536            exported_symbols if exported_symbols is not None else set()
537        self.imported_symbols = \
538            imported_symbols if imported_symbols is not None else set()
539        self.file_size = file_size
540        self.ro_seg_file_size = ro_seg_file_size
541        self.ro_seg_mem_size = ro_seg_mem_size
542        self.rw_seg_file_size = rw_seg_file_size
543        self.rw_seg_mem_size = rw_seg_mem_size
544
545
546    def __repr__(self):
547        args = (a + '=' + repr(getattr(self, a)) for a in self.__slots__)
548        return 'ELF(' + ', '.join(args) + ')'
549
550
551    def __eq__(self, rhs):
552        return all(getattr(self, a) == getattr(rhs, a) for a in self.__slots__)
553
554
555    @property
556    def elf_class_name(self):
557        return self._ELF_CLASS_NAMES.get(self.ei_class, 'None')
558
559
560    @property
561    def elf_data_name(self):
562        return self._ELF_DATA_NAMES.get(self.ei_data, 'None')
563
564
565    @property
566    def elf_machine_name(self):
567        return self.ELF_MACHINES.get(self.e_machine, str(self.e_machine))
568
569
570    @property
571    def is_32bit(self):
572        return self.ei_class == ELF.ELFCLASS32
573
574
575    @property
576    def is_64bit(self):
577        return self.ei_class == ELF.ELFCLASS64
578
579
580    @property
581    def sorted_exported_symbols(self):
582        return sorted(list(self.exported_symbols))
583
584
585    @property
586    def sorted_imported_symbols(self):
587        return sorted(list(self.imported_symbols))
588
589
590    def dump(self, file=None):
591        """Print parsed ELF information to the file"""
592        file = file if file is not None else sys.stdout
593
594        print('EI_CLASS\t' + self.elf_class_name, file=file)
595        print('EI_DATA\t\t' + self.elf_data_name, file=file)
596        print('E_MACHINE\t' + self.elf_machine_name, file=file)
597        print('FILE_SIZE\t' + str(self.file_size), file=file)
598        print('RO_SEG_FILE_SIZE\t' + str(self.ro_seg_file_size), file=file)
599        print('RO_SEG_MEM_SIZE\t' + str(self.ro_seg_mem_size), file=file)
600        print('RW_SEG_FILE_SIZE\t' + str(self.rw_seg_file_size), file=file)
601        print('RW_SEG_MEM_SIZE\t' + str(self.rw_seg_mem_size), file=file)
602        for dt_rpath in self.dt_rpath:
603            print('DT_RPATH\t' + dt_rpath, file=file)
604        for dt_runpath in self.dt_runpath:
605            print('DT_RUNPATH\t' + dt_runpath, file=file)
606        for dt_needed in self.dt_needed:
607            print('DT_NEEDED\t' + dt_needed, file=file)
608        for symbol in self.sorted_exported_symbols:
609            print('EXP_SYMBOL\t' + symbol, file=file)
610        for symbol in self.sorted_imported_symbols:
611            print('IMP_SYMBOL\t' + symbol, file=file)
612
613
614    # Extract zero-terminated buffer slice.
615    def _extract_zero_terminated_buf_slice(self, buf, offset):
616        """Extract a zero-terminated buffer slice from the given offset"""
617        end = buf.find(b'\0', offset)
618        if end == -1:
619            return buf[offset:]
620        return buf[offset:end]
621
622
623    # Extract c-style interned string from the buffer.
624    if sys.version_info >= (3, 0):
625        def _extract_zero_terminated_str(self, buf, offset):
626            """Extract a c-style string from the given buffer and offset"""
627            buf_slice = self._extract_zero_terminated_buf_slice(buf, offset)
628            return intern(buf_slice.decode('utf-8'))
629    else:
630        def _extract_zero_terminated_str(self, buf, offset):
631            """Extract a c-style string from the given buffer and offset"""
632            return intern(self._extract_zero_terminated_buf_slice(buf, offset))
633
634
635    def _parse_from_buf_internal(self, buf):
636        """Parse ELF image resides in the buffer"""
637
638        # Check ELF ident.
639        if len(buf) < 8:
640            raise ELFError('bad ident')
641
642        if buf[0:4] != ELF.ELF_MAGIC:
643            raise ELFError('bad magic')
644
645        self.ei_class = buf[ELF.EI_CLASS]
646        if self.ei_class not in (ELF.ELFCLASS32, ELF.ELFCLASS64):
647            raise ELFError('unknown word size')
648
649        self.ei_data = buf[ELF.EI_DATA]
650        if self.ei_data not in (ELF.ELFDATA2LSB, ELF.ELFDATA2MSB):
651            raise ELFError('unknown endianness')
652
653        self.file_size = len(buf)
654
655        # ELF structure definitions.
656        endian_fmt = '<' if self.ei_data == ELF.ELFDATA2LSB else '>'
657
658        if self.is_32bit:
659            elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH'
660            elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL'
661            elf_phdr_fmt = endian_fmt + 'LLLLLLLL'
662            elf_dyn_fmt = endian_fmt + 'lL'
663            elf_sym_fmt = endian_fmt + 'LLLBBH'
664        else:
665            elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH'
666            elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ'
667            elf_phdr_fmt = endian_fmt + 'LLQQQQQQ'
668            elf_dyn_fmt = endian_fmt + 'QQ'
669            elf_sym_fmt = endian_fmt + 'LBBHQQ'
670
671        def parse_struct(cls, fmt, offset, error_msg):
672            try:
673                return cls._make(struct.unpack_from(fmt, buf, offset))
674            except struct.error:
675                raise ELFError(error_msg)
676
677        def parse_elf_hdr(offset):
678            return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header')
679
680        def parse_elf_shdr(offset):
681            return parse_struct(Elf_Shdr, elf_shdr_fmt, offset,
682                                'bad section header')
683
684        if self.is_32bit:
685            def parse_elf_phdr(offset):
686                return parse_struct(Elf_Phdr, elf_phdr_fmt, offset,
687                                    'bad program header')
688        else:
689            def parse_elf_phdr(offset):
690                try:
691                    p = struct.unpack_from(elf_phdr_fmt, buf, offset)
692                    return Elf_Phdr(p[0], p[2], p[3], p[4], p[5], p[6], p[1],
693                                    p[7])
694                except struct.error:
695                    raise ELFError('bad program header')
696
697        def parse_elf_dyn(offset):
698            return parse_struct(Elf_Dyn, elf_dyn_fmt, offset,
699                                'bad .dynamic entry')
700
701        if self.is_32bit:
702            def parse_elf_sym(offset):
703                return parse_struct(
704                    Elf_Sym, elf_sym_fmt, offset, 'bad elf sym')
705        else:
706            def parse_elf_sym(offset):
707                try:
708                    p = struct.unpack_from(elf_sym_fmt, buf, offset)
709                    return Elf_Sym(p[0], p[4], p[5], p[1], p[2], p[3])
710                except struct.error:
711                    raise ELFError('bad elf sym')
712
713        def extract_str(offset):
714            return self._extract_zero_terminated_str(buf, offset)
715
716        # Parse ELF header.
717        header = parse_elf_hdr(0)
718        self.e_machine = header.e_machine
719
720        # Parse ELF program header and calculate segment size.
721        if header.e_phentsize == 0:
722            raise ELFError('no program header')
723
724        ro_seg_file_size = 0
725        ro_seg_mem_size = 0
726        rw_seg_file_size = 0
727        rw_seg_mem_size = 0
728
729        assert struct.calcsize(elf_phdr_fmt) == header.e_phentsize
730        seg_end = header.e_phoff + header.e_phnum * header.e_phentsize
731        for phdr_off in range(header.e_phoff, seg_end, header.e_phentsize):
732            phdr = parse_elf_phdr(phdr_off)
733            if phdr.p_type != ELF.PT_LOAD:
734                continue
735            if phdr.p_flags & ELF.PF_W:
736                rw_seg_file_size += phdr.p_filesz
737                rw_seg_mem_size += phdr.p_memsz
738            else:
739                ro_seg_file_size += phdr.p_filesz
740                ro_seg_mem_size += phdr.p_memsz
741
742        self.ro_seg_file_size = ro_seg_file_size
743        self.ro_seg_mem_size = ro_seg_mem_size
744        self.rw_seg_file_size = rw_seg_file_size
745        self.rw_seg_mem_size = rw_seg_mem_size
746
747        # Check section header size.
748        if header.e_shentsize == 0:
749            raise ELFError('no section header')
750
751        # Find .shstrtab section.
752        shstrtab_shdr_off = \
753            header.e_shoff + header.e_shstridx * header.e_shentsize
754        shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off)
755        shstrtab_off = shstrtab_shdr.sh_offset
756
757        # Parse ELF section header.
758        sections = dict()
759        header_end = header.e_shoff + header.e_shnum * header.e_shentsize
760        for shdr_off in range(header.e_shoff, header_end, header.e_shentsize):
761            shdr = parse_elf_shdr(shdr_off)
762            name = extract_str(shstrtab_off + shdr.sh_name)
763            sections[name] = shdr
764
765        # Find .dynamic and .dynstr section header.
766        dynamic_shdr = sections.get('.dynamic')
767        if not dynamic_shdr:
768            raise ELFError('no .dynamic section')
769
770        dynstr_shdr = sections.get('.dynstr')
771        if not dynstr_shdr:
772            raise ELFError('no .dynstr section')
773
774        dynamic_off = dynamic_shdr.sh_offset
775        dynstr_off = dynstr_shdr.sh_offset
776
777        # Parse entries in .dynamic section.
778        assert struct.calcsize(elf_dyn_fmt) == dynamic_shdr.sh_entsize
779        dynamic_end = dynamic_off + dynamic_shdr.sh_size
780        for ent_off in range(
781                dynamic_off, dynamic_end, dynamic_shdr.sh_entsize):
782            ent = parse_elf_dyn(ent_off)
783            if ent.d_tag == ELF.DT_NEEDED:
784                self.dt_needed.append(extract_str(dynstr_off + ent.d_val))
785            elif ent.d_tag == ELF.DT_RPATH:
786                self.dt_rpath.extend(
787                    extract_str(dynstr_off + ent.d_val).split(':'))
788            elif ent.d_tag == ELF.DT_RUNPATH:
789                self.dt_runpath.extend(
790                    extract_str(dynstr_off + ent.d_val).split(':'))
791
792        # Parse exported symbols in .dynsym section.
793        dynsym_shdr = sections.get('.dynsym')
794        if dynsym_shdr:
795            exp_symbols = self.exported_symbols
796            imp_symbols = self.imported_symbols
797
798            dynsym_off = dynsym_shdr.sh_offset
799            dynsym_end = dynsym_off + dynsym_shdr.sh_size
800            dynsym_entsize = dynsym_shdr.sh_entsize
801
802            # Skip first symbol entry (null symbol).
803            dynsym_off += dynsym_entsize
804
805            for ent_off in range(dynsym_off, dynsym_end, dynsym_entsize):
806                ent = parse_elf_sym(ent_off)
807                symbol_name = extract_str(dynstr_off + ent.st_name)
808                if ent.is_undef:
809                    imp_symbols.add(symbol_name)
810                elif not ent.is_local:
811                    exp_symbols.add(symbol_name)
812
813
814    def _parse_from_buf(self, buf):
815        """Parse ELF image resides in the buffer"""
816        try:
817            self._parse_from_buf_internal(buf)
818        except IndexError:
819            raise ELFError('bad offset')
820
821
822    def _parse_from_file(self, path):
823        """Parse ELF image from the file path"""
824        with open(path, 'rb') as f:
825            st = os.fstat(f.fileno())
826            if not st.st_size:
827                raise ELFError('empty file')
828            with mmap(f.fileno(), st.st_size, access=ACCESS_READ) as image:
829                self._parse_from_buf(image)
830
831
832    def _parse_from_dump_lines(self, path, lines):
833        patt = re.compile('^([A-Za-z_]+)\t+(.*)$')
834        for line_no, line in enumerate(lines):
835            match = patt.match(line)
836            if not match:
837                print('error: {}: {}: Failed to parse'
838                      .format(path, line_no + 1), file=sys.stderr)
839                continue
840            key = match.group(1)
841            value = match.group(2)
842
843            if key == 'EI_CLASS':
844                self.ei_class = ELF.get_ei_class_from_name(value)
845            elif key == 'EI_DATA':
846                self.ei_data = ELF.get_ei_data_from_name(value)
847            elif key == 'E_MACHINE':
848                self.e_machine = ELF.get_e_machine_from_name(value)
849            elif key == 'FILE_SIZE':
850                self.file_size = int(value)
851            elif key == 'RO_SEG_FILE_SIZE':
852                self.ro_seg_file_size = int(value)
853            elif key == 'RO_SEG_MEM_SIZE':
854                self.ro_seg_mem_size = int(value)
855            elif key == 'RW_SEG_FILE_SIZE':
856                self.rw_seg_file_size = int(value)
857            elif key == 'RW_SEG_MEM_SIZE':
858                self.rw_seg_mem_size = int(value)
859            elif key == 'DT_RPATH':
860                self.dt_rpath.append(intern(value))
861            elif key == 'DT_RUNPATH':
862                self.dt_runpath.append(intern(value))
863            elif key == 'DT_NEEDED':
864                self.dt_needed.append(intern(value))
865            elif key == 'EXP_SYMBOL':
866                self.exported_symbols.add(intern(value))
867            elif key == 'IMP_SYMBOL':
868                self.imported_symbols.add(intern(value))
869            else:
870                print('error: {}: {}: unknown tag name: {}'
871                      .format(path, line_no + 1, key), file=sys.stderr)
872
873
874    def _parse_from_dump_file(self, path):
875        """Load information from ELF dump file."""
876        with open(path, 'r') as f:
877            self._parse_from_dump_lines(path, f)
878
879
880    def _parse_from_dump_buf(self, buf):
881        """Load information from ELF dump buffer."""
882        self._parse_from_dump_lines('<str:0x{:x}>'.format(id(buf)),
883                                    buf.splitlines())
884
885
886    @staticmethod
887    def load(path):
888        """Create an ELF instance from the file path"""
889        elf = ELF()
890        elf._parse_from_file(path)
891        return elf
892
893
894    @staticmethod
895    def loads(buf):
896        """Create an ELF instance from the buffer"""
897        elf = ELF()
898        elf._parse_from_buf(buf)
899        return elf
900
901
902    @staticmethod
903    def load_dump(path):
904        """Create an ELF instance from a dump file path"""
905        elf = ELF()
906        elf._parse_from_dump_file(path)
907        return elf
908
909
910    @staticmethod
911    def load_dumps(buf):
912        """Create an ELF instance from a dump file buffer"""
913        elf = ELF()
914        elf._parse_from_dump_buf(buf)
915        return elf
916
917
918    def is_jni_lib(self):
919        """Test whether the ELF file looks like a JNI library."""
920        for name in ['libnativehelper.so', 'libandroid_runtime.so']:
921            if name in self.dt_needed:
922                return True
923        for symbol in itertools.chain(self.imported_symbols,
924                                      self.exported_symbols):
925            if symbol.startswith('JNI_') or symbol.startswith('Java_') or \
926               symbol == 'jniRegisterNativeMethods':
927                return True
928        return False
929
930
931#------------------------------------------------------------------------------
932# APK / Dex File Reader
933#------------------------------------------------------------------------------
934
935class DexFileReader(object):
936    @classmethod
937    def extract_dex_string(cls, buf, offset=0):
938        end = buf.find(b'\0', offset)
939        return buf[offset:] if end == -1 else buf[offset:end]
940
941
942    @classmethod
943    def extract_uleb128(cls, buf, offset=0):
944        num_bytes = 0
945        result = 0
946        shift = 0
947        while True:
948            byte = buf[offset + num_bytes]
949            result |= (byte & 0x7f) << shift
950            num_bytes += 1
951            if (byte & 0x80) == 0:
952                break
953            shift += 7
954        return (result, num_bytes)
955
956
957    Header = create_struct('Header', (
958        ('magic', '4s'),
959        ('version', '4s'),
960        ('checksum', 'I'),
961        ('signature', '20s'),
962        ('file_size', 'I'),
963        ('header_size', 'I'),
964        ('endian_tag', 'I'),
965        ('link_size', 'I'),
966        ('link_off', 'I'),
967        ('map_off', 'I'),
968        ('string_ids_size', 'I'),
969        ('string_ids_off', 'I'),
970        ('type_ids_size', 'I'),
971        ('type_ids_off', 'I'),
972        ('proto_ids_size', 'I'),
973        ('proto_ids_off', 'I'),
974        ('field_ids_size', 'I'),
975        ('field_ids_off', 'I'),
976        ('method_ids_size', 'I'),
977        ('method_ids_off', 'I'),
978        ('class_defs_size', 'I'),
979        ('class_defs_off', 'I'),
980        ('data_size', 'I'),
981        ('data_off', 'I'),
982    ))
983
984
985    StringId = create_struct('StringId', (
986        ('string_data_off', 'I'),
987    ))
988
989
990    @staticmethod
991    def generate_classes_dex_names():
992        yield 'classes.dex'
993        for i in itertools.count(start=2):
994            yield 'classes{}.dex'.format(i)
995
996
997    @classmethod
998    def enumerate_dex_strings_buf(cls, buf, offset=0, data_offset=None):
999        buf = get_py3_bytes(buf)
1000        header = cls.Header.unpack_from(buf, offset=offset)
1001
1002        if data_offset is None:
1003            if header.magic == b'dex\n':
1004                # In the standard dex file, the data_offset is the offset of
1005                # the dex header.
1006                data_offset = offset
1007            else:
1008                # In the compact dex file, the data_offset is sum of the offset
1009                # of the dex header and header.data_off.
1010                data_offset = offset + header.data_off
1011
1012        StringId = cls.StringId
1013        struct_size = StringId.struct_size
1014
1015        offset_start = offset + header.string_ids_off
1016        offset_end = offset_start + header.string_ids_size * struct_size
1017
1018        for offset in range(offset_start, offset_end, struct_size):
1019            offset = StringId.unpack_from(buf, offset).string_data_off
1020            offset += data_offset
1021
1022            # Skip the ULEB128 integer for UTF-16 string length
1023            offset += cls.extract_uleb128(buf, offset)[1]
1024
1025            # Extract the string
1026            yield cls.extract_dex_string(buf, offset)
1027
1028
1029    @classmethod
1030    def enumerate_dex_strings_apk(cls, apk_file_path):
1031        with zipfile.ZipFile(apk_file_path, 'r') as zip_file:
1032            for name in cls.generate_classes_dex_names():
1033                try:
1034                    with zip_file.open(name) as dex_file:
1035                        dex_file_content = dex_file.read()
1036                    for s in cls.enumerate_dex_strings_buf(dex_file_content):
1037                        yield s
1038                except KeyError:
1039                    break
1040
1041
1042    @classmethod
1043    def is_vdex_file(cls, vdex_file_path):
1044        return vdex_file_path.endswith('.vdex')
1045
1046
1047    # VdexHeader 0
1048    VdexHeader0 = create_struct('VdexHeader0', (
1049        ('magic', '4s'),
1050        ('vdex_version', '4s'),
1051    ))
1052
1053
1054    # VdexHeader 1 - 15
1055    VdexHeader1 = create_struct('VdexHeader1', (
1056        ('magic', '4s'),
1057        ('vdex_version', '4s'),
1058        ('number_of_dex_files', 'I'),
1059        ('dex_size', 'I'),
1060        ('verifier_deps_size', 'I'),
1061        ('quickening_info_size', 'I'),
1062        # checksums
1063    ))
1064
1065
1066    # VdexHeader 16 - 18
1067    VdexHeader16 = create_struct('VdexHeader16', (
1068        ('magic', '4s'),
1069        ('vdex_version', '4s'),
1070        ('number_of_dex_files', 'I'),
1071        ('dex_size', 'I'),
1072        ('dex_shared_data_size', 'I'),
1073        ('verifier_deps_size', 'I'),
1074        ('quickening_info_size', 'I'),
1075        # checksums
1076    ))
1077
1078
1079    # VdexHeader 19
1080    VdexHeader19 = create_struct('VdexHeader19', (
1081        ('magic', '4s'),
1082        ('vdex_version', '4s'),
1083        ('dex_section_version', '4s'),
1084        ('number_of_dex_files', 'I'),
1085        ('verifier_deps_size', 'I'),
1086        # checksums
1087    ))
1088
1089
1090    # VdexHeader 21
1091    VdexHeader21 = create_struct('VdexHeader21', (
1092        ('magic', '4s'),
1093        ('vdex_version', '4s'),
1094        ('dex_section_version', '4s'),
1095        ('number_of_dex_files', 'I'),
1096        ('verifier_deps_size', 'I'),
1097        ('bootclasspath_checksums_size', 'I'),
1098        ('class_loader_context_size', 'I'),
1099        # checksums
1100    ))
1101
1102
1103    DexSectionHeader = create_struct('DexSectionHeader', (
1104        ('dex_size', 'I'),
1105        ('dex_shared_data_size', 'I'),
1106        ('quickening_info_size', 'I'),
1107    ))
1108
1109
1110    @classmethod
1111    def enumerate_dex_strings_vdex_buf(cls, buf):
1112        buf = get_py3_bytes(buf)
1113
1114        magic, version = struct.unpack_from('4s4s', buf)
1115
1116        # Check the vdex file magic word
1117        if magic != b'vdex':
1118            raise ValueError('bad vdex magic word')
1119
1120        # Parse vdex file header (w.r.t. version)
1121        if version == b'000\x00':
1122            VdexHeader = cls.VdexHeader0
1123        elif b'001\x00' <= version < b'016\x00':
1124            VdexHeader = cls.VdexHeader1
1125        elif b'016\x00' <= version < b'019\x00':
1126            VdexHeader = cls.VdexHeader16
1127        elif b'019\x00' <= version < b'021\x00':
1128            VdexHeader = cls.VdexHeader19
1129        elif version == b'021\x00':
1130            VdexHeader = cls.VdexHeader21
1131        else:
1132            raise ValueError('unknown vdex version ' + repr(version))
1133
1134        vdex_header = VdexHeader.unpack_from(buf, offset=0)
1135
1136        # Skip this vdex file if there is no dex file section
1137        if vdex_header.vdex_version < b'019\x00':
1138            if vdex_header.dex_size == 0:
1139                return
1140        else:
1141            if vdex_header.dex_section_version == b'000\x00':
1142                return
1143
1144        # Skip vdex file header struct
1145        offset = VdexHeader.struct_size
1146
1147        # Skip dex file checksums struct
1148        offset += 4 * vdex_header.number_of_dex_files
1149
1150        # Skip dex section header struct
1151        if vdex_header.vdex_version >= b'019\x00':
1152            offset += cls.DexSectionHeader.struct_size
1153
1154        # Calculate the quickening table offset
1155        if vdex_header.vdex_version >= b'012\x00':
1156            quickening_table_off_size = 4
1157        else:
1158            quickening_table_off_size = 0
1159
1160        for _ in range(vdex_header.number_of_dex_files):
1161            # Skip quickening_table_off size
1162            offset += quickening_table_off_size
1163
1164            # Check the dex file magic
1165            dex_magic = buf[offset:offset + 4]
1166            if dex_magic not in (b'dex\n', b'cdex'):
1167                raise ValueError('bad dex file offset {}'.format(offset))
1168
1169            dex_header = cls.Header.unpack_from(buf, offset)
1170            dex_file_end = offset + dex_header.file_size
1171            for s in cls.enumerate_dex_strings_buf(buf, offset):
1172                yield s
1173
1174            # Align to the end of the dex file
1175            offset = (dex_file_end + 3) // 4 * 4
1176
1177
1178    @classmethod
1179    def enumerate_dex_strings_vdex(cls, vdex_file_path):
1180        with open(vdex_file_path, 'rb') as vdex_file:
1181            return cls.enumerate_dex_strings_vdex_buf(vdex_file.read())
1182
1183
1184    @classmethod
1185    def enumerate_dex_strings(cls, path):
1186        if is_zipfile(path):
1187            return DexFileReader.enumerate_dex_strings_apk(path)
1188        if cls.is_vdex_file(path):
1189            return DexFileReader.enumerate_dex_strings_vdex(path)
1190        return None
1191
1192
1193#------------------------------------------------------------------------------
1194# Path Functions
1195#------------------------------------------------------------------------------
1196
1197def _is_under_dir(dir_path, path):
1198    dir_path = os.path.abspath(dir_path)
1199    path = os.path.abspath(path)
1200    return path == dir_path or path.startswith(dir_path + os.path.sep)
1201
1202
1203#------------------------------------------------------------------------------
1204# TaggedDict
1205#------------------------------------------------------------------------------
1206
1207class TaggedDict(object):
1208    def _define_tag_constants(local_ns):
1209        tag_list = [
1210            'll_ndk', 'll_ndk_private',
1211            'vndk_sp', 'vndk_sp_private',
1212            'vndk', 'vndk_private',
1213            'system_only', 'system_only_rs',
1214            'sp_hal', 'sp_hal_dep',
1215            'vendor_only',
1216            'system_ext_only',
1217            'product_only',
1218            'remove',
1219        ]
1220        assert len(tag_list) < 32
1221
1222        tags = {}
1223        for i, tag in enumerate(tag_list):
1224            local_ns[tag.upper()] = 1 << i
1225            tags[tag] = 1 << i
1226
1227        local_ns['TAGS'] = tags
1228
1229    _define_tag_constants(locals())
1230    del _define_tag_constants
1231
1232
1233    _TAG_ALIASES = {
1234        'fwk_only': 'system_only',
1235        'fwk_only_rs': 'system_only_rs',
1236        'vnd_only': 'vendor_only',
1237        'hl_ndk': 'system_only',  # Treat HL-NDK as SYSTEM-ONLY.
1238        'sp_ndk': 'll_ndk',
1239        'sp_ndk_indirect': 'll_ndk_private',
1240        'll_ndk_indirect': 'll_ndk_private',
1241        'vndk_sp_indirect': 'vndk_sp',
1242        'vndk_sp_indirect_private': 'vndk_sp_private',
1243        'vndk_indirect': 'vndk_private',
1244        'vndk_sp_hal': 'vndk_sp',  # Legacy
1245        'vndk_sp_both': 'vndk_sp',  # Legacy
1246    }
1247
1248
1249    @classmethod
1250    def _normalize_tag(cls, tag):
1251        tag = tag.lower().replace('-', '_')
1252        tag = cls._TAG_ALIASES.get(tag, tag)
1253        if tag not in cls.TAGS:
1254            raise ValueError('unknown lib tag ' + tag)
1255        return tag
1256
1257
1258    _LL_NDK_VIS = {
1259        'll_ndk', 'll_ndk_private',
1260    }
1261
1262    _VNDK_SP_VIS = {
1263        'll_ndk', 'vndk_sp', 'vndk_sp_private', 'system_only_rs',
1264    }
1265
1266    _VNDK_VIS = {
1267        'll_ndk', 'vndk_sp', 'vndk_sp_private', 'vndk', 'vndk_private',
1268    }
1269
1270    _SYSTEM_ONLY_VIS = {
1271        'll_ndk', 'll_ndk_private',
1272        'vndk_sp', 'vndk_sp_private',
1273        'vndk', 'vndk_private',
1274        'system_only', 'system_only_rs',
1275        'system_ext_only',
1276        'sp_hal',
1277    }
1278
1279    _PRODUCT_ONLY_VIS = {
1280        'll_ndk', 'vndk_sp', 'vndk', 'sp_hal',
1281
1282        # Remove the following after VNDK-ext can be checked separately.
1283        'sp_hal_dep', 'vendor_only',
1284    }
1285
1286    _VENDOR_ONLY_VIS = {
1287        'll_ndk', 'vndk_sp', 'vndk', 'sp_hal', 'sp_hal_dep',
1288        'vendor_only',
1289    }
1290
1291    _SP_HAL_VIS = {'ll_ndk', 'vndk_sp', 'sp_hal', 'sp_hal_dep'}
1292
1293    _TAG_VISIBILITY = {
1294        'll_ndk': _LL_NDK_VIS,
1295        'll_ndk_private': _LL_NDK_VIS,
1296
1297        'vndk_sp': _VNDK_SP_VIS,
1298        'vndk_sp_private': _VNDK_SP_VIS,
1299
1300        'vndk': _VNDK_VIS,
1301        'vndk_private': _VNDK_VIS,
1302
1303        'system_only': _SYSTEM_ONLY_VIS,
1304        'system_only_rs': _SYSTEM_ONLY_VIS,
1305        'system_ext_only': _SYSTEM_ONLY_VIS,
1306
1307        'sp_hal': _SP_HAL_VIS,
1308        'sp_hal_dep': _SP_HAL_VIS,
1309
1310        'vendor_only': _VENDOR_ONLY_VIS,
1311        'product_only': _PRODUCT_ONLY_VIS,
1312
1313        'remove': set(),
1314    }
1315
1316    del _LL_NDK_VIS, _VNDK_SP_VIS, _VNDK_VIS, _SYSTEM_ONLY_VIS, \
1317        _PRODUCT_ONLY_VIS, _VENDOR_ONLY_VIS, _SP_HAL_VIS
1318
1319
1320    @classmethod
1321    def is_tag_visible(cls, from_tag, to_tag):
1322        return to_tag in cls._TAG_VISIBILITY[from_tag]
1323
1324
1325    def __init__(self, vndk_lib_dirs=None):
1326        self._path_tag = dict()
1327        for tag in self.TAGS:
1328            setattr(self, tag, set())
1329        self._regex_patterns = []
1330
1331        if vndk_lib_dirs is None:
1332            self._vndk_suffixes = ['']
1333        else:
1334            self._vndk_suffixes = [VNDKLibDir.create_vndk_dir_suffix(version)
1335                                   for version in vndk_lib_dirs]
1336
1337
1338    def add(self, tag, lib):
1339        lib_set = getattr(self, tag)
1340        lib_set.add(lib)
1341        self._path_tag[lib] = tag
1342
1343
1344    def add_regex(self, tag, pattern):
1345        self._regex_patterns.append((re.compile(pattern), tag))
1346
1347
1348    def get_path_tag(self, lib):
1349        try:
1350            return self._path_tag[lib]
1351        except KeyError:
1352            pass
1353
1354        for pattern, tag in self._regex_patterns:
1355            if pattern.match(lib):
1356                return tag
1357
1358        return self.get_path_tag_default(lib)
1359
1360
1361    def get_path_tag_default(self, lib):
1362        raise NotImplementedError()
1363
1364
1365    def get_path_tag_bit(self, lib):
1366        return self.TAGS[self.get_path_tag(lib)]
1367
1368
1369    def is_path_visible(self, from_lib, to_lib):
1370        return self.is_tag_visible(self.get_path_tag(from_lib),
1371                                   self.get_path_tag(to_lib))
1372
1373
1374    @staticmethod
1375    def is_ll_ndk(tag_bit):
1376        return bool(tag_bit & TaggedDict.LL_NDK)
1377
1378
1379    @staticmethod
1380    def is_vndk_sp(tag_bit):
1381        return bool(tag_bit & TaggedDict.VNDK_SP)
1382
1383
1384    @staticmethod
1385    def is_vndk_sp_private(tag_bit):
1386        return bool(tag_bit & TaggedDict.VNDK_SP_PRIVATE)
1387
1388
1389    @staticmethod
1390    def is_system_only_rs(tag_bit):
1391        return bool(tag_bit & TaggedDict.SYSTEM_ONLY_RS)
1392
1393
1394    @staticmethod
1395    def is_sp_hal(tag_bit):
1396        return bool(tag_bit & TaggedDict.SP_HAL)
1397
1398
1399class TaggedPathDict(TaggedDict):
1400    def load_from_csv(self, fp):
1401        reader = csv.reader(fp)
1402
1403        # Read first row and check the existence of the header.
1404        try:
1405            row = next(reader)
1406        except StopIteration:
1407            return
1408
1409        try:
1410            path_col = row.index('Path')
1411            tag_col = row.index('Tag')
1412        except ValueError:
1413            path_col = 0
1414            tag_col = 1
1415            self.add(self._normalize_tag(row[tag_col]), row[path_col])
1416
1417        # Read the rest of rows.
1418        for row in reader:
1419            self.add(self._normalize_tag(row[tag_col]), row[path_col])
1420
1421
1422    @staticmethod
1423    def create_from_csv(fp, vndk_lib_dirs=None):
1424        d = TaggedPathDict(vndk_lib_dirs)
1425        d.load_from_csv(fp)
1426        return d
1427
1428
1429    @staticmethod
1430    def create_from_csv_path(path, vndk_lib_dirs=None):
1431        with open(path, 'r') as fp:
1432            return TaggedPathDict.create_from_csv(fp, vndk_lib_dirs)
1433
1434
1435    def _enumerate_paths_with_lib(self, pattern):
1436        if '${LIB}' in pattern:
1437            yield pattern.replace('${LIB}', 'lib')
1438            yield pattern.replace('${LIB}', 'lib64')
1439        else:
1440            yield pattern
1441
1442
1443    def _enumerate_paths(self, pattern):
1444        if '${VNDK_VER}' not in pattern:
1445            for path in self._enumerate_paths_with_lib(pattern):
1446                yield path
1447            return
1448        for suffix in self._vndk_suffixes:
1449            pattern_with_suffix = pattern.replace('${VNDK_VER}', suffix)
1450            for path in self._enumerate_paths_with_lib(pattern_with_suffix):
1451                yield path
1452
1453
1454    def add(self, tag, path):
1455        if path.startswith('[regex]'):
1456            super(TaggedPathDict, self).add_regex(tag, path[7:])
1457            return
1458        for path in self._enumerate_paths(path):
1459            super(TaggedPathDict, self).add(tag, path)
1460
1461
1462    @staticmethod
1463    def get_path_tag_default(path):
1464        if _is_under_dir('/vendor', path):
1465            return 'vendor_only'
1466        if _is_under_dir('/product', path):
1467            return 'product_only'
1468        if _is_under_dir('/system_ext', path):
1469            return 'system_ext_only'
1470        return 'system_only'
1471
1472
1473class TaggedLibDict(object):
1474    def __init__(self):
1475        self._path_tag = dict()
1476        for tag in TaggedDict.TAGS:
1477            setattr(self, tag, set())
1478
1479
1480    def add(self, tag, lib):
1481        lib_set = getattr(self, tag)
1482        lib_set.add(lib)
1483        self._path_tag[lib] = tag
1484
1485
1486    @staticmethod
1487    def create_from_graph(graph, tagged_paths, generic_refs=None):
1488        d = TaggedLibDict()
1489
1490        for lib in graph.lib_pt[PT_SYSTEM].values():
1491            d.add(tagged_paths.get_path_tag(lib.path), lib)
1492
1493        sp_lib = graph.compute_sp_lib(generic_refs)
1494        for lib in graph.lib_pt[PT_VENDOR].values():
1495            if lib in sp_lib.sp_hal:
1496                d.add('sp_hal', lib)
1497            elif lib in sp_lib.sp_hal_dep:
1498                d.add('sp_hal_dep', lib)
1499            else:
1500                d.add('vendor_only', lib)
1501
1502        for lib in graph.lib_pt[PT_PRODUCT].values():
1503            d.add('vendor_only', lib)
1504
1505        for lib in graph.lib_pt[PT_SYSTEM_EXT].values():
1506            d.add('vendor_only', lib)
1507
1508        return d
1509
1510
1511    def get_path_tag(self, lib):
1512        try:
1513            return self._path_tag[lib]
1514        except KeyError:
1515            return self.get_path_tag_default(lib)
1516
1517
1518    @staticmethod
1519    def get_path_tag_default(lib):
1520        return TaggedPathDict.get_path_tag_default(lib.path)
1521
1522
1523class LibProperties(object):
1524    Properties = collections.namedtuple(
1525        'Properties', 'vndk vndk_sp vendor_available rule')
1526
1527
1528    def __init__(self, csv_file=None):
1529        self.modules = {}
1530
1531        if csv_file:
1532            reader = csv.reader(csv_file)
1533
1534            header = next(reader)
1535            assert header == ['name', 'vndk', 'vndk_sp', 'vendor_available',
1536                              'rule'], repr(header)
1537
1538            for name, vndk, vndk_sp, vendor_available, rule in reader:
1539                self.modules[name] = self.Properties(
1540                    vndk == 'True', vndk_sp == 'True',
1541                    vendor_available == 'True', rule)
1542
1543
1544    @classmethod
1545    def load_from_path_or_default(cls, path):
1546        if not path:
1547            return LibProperties()
1548
1549        try:
1550            with open(path, 'r') as csv_file:
1551                return LibProperties(csv_file)
1552        except FileNotFoundError:
1553            return LibProperties()
1554
1555
1556    def get(self, name):
1557        try:
1558            return self.modules[name]
1559        except KeyError:
1560            return self.Properties(False, False, False, None)
1561
1562
1563    @staticmethod
1564    def get_lib_properties_file_path(tag_file_path):
1565        root, ext = os.path.splitext(tag_file_path)
1566        return root + '-properties' + ext
1567
1568
1569#------------------------------------------------------------------------------
1570# Public Libraries
1571#------------------------------------------------------------------------------
1572
1573class PublicLibSet(object):
1574    def __init__(self):
1575        self._lib_names = set()
1576
1577
1578    def load_from_public_libraries_txt(self, config_path):
1579        with open(config_path, 'r') as config_file:
1580            for line in config_file:
1581                line = line.strip()
1582                if line and not line.startswith('#'):
1583                    self._lib_names.add(line)
1584
1585
1586    def is_public_lib(self, path):
1587        lib_name = os.path.basename(path)
1588        return lib_name in self._lib_names
1589
1590
1591#------------------------------------------------------------------------------
1592# ELF Linker
1593#------------------------------------------------------------------------------
1594
1595def is_zipfile(path):
1596    # zipfile.is_zipfile() tries to find the zip header in the file.  But we
1597    # only want to scan the zip file that starts with the magic word.  Thus,
1598    # we read the magic word by ourselves.
1599    try:
1600        with open(path, 'rb') as fp:
1601            if fp.read(2) != b'PK':
1602                return False
1603    except IOError:
1604        return False
1605
1606    # Check whether this is a valid zip file.
1607    return zipfile.is_zipfile(path)
1608
1609
1610def scan_zip_file(zip_file_path):
1611    """Scan all ELF files in a zip archive."""
1612    with zipfile.ZipFile(zip_file_path, 'r') as zip_file:
1613        for name in zip_file.namelist():
1614            yield (os.path.join(zip_file_path, name),
1615                   zip_file.open(name, 'r').read())
1616
1617
1618def dump_ext4_img(img_file_path, out_dir):
1619    if ' ' in out_dir:
1620        raise ValueError('out_dir must not have space character')
1621
1622    cmd = ['debugfs', img_file_path, '-R', 'rdump / ' + out_dir]
1623
1624    # Run the debugfs command.
1625    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1626    stdout, stderr = proc.communicate()
1627    if proc.returncode != 0:
1628        raise subprocess.CalledProcessError(proc.returncode, cmd, stderr)
1629
1630    # Print error messages if they are not the ones that should be ignored.
1631    for line in stderr.splitlines():
1632        if line.startswith(b'debugfs '):
1633            continue
1634        if b'Operation not permitted while changing ownership of' in line:
1635            continue
1636        print_sb('error: debugfs:', line, file=sys.stderr)
1637
1638
1639def is_accessible(path):
1640    try:
1641        mode = os.stat(path).st_mode
1642        return (mode & (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)) != 0
1643    except FileNotFoundError:
1644        return False
1645
1646
1647def scan_ext4_image(img_file_path, mount_point, unzip_files):
1648    """Scan all ELF files in the ext4 image."""
1649    with TemporaryDirectory() as tmp_dir:
1650        dump_ext4_img(img_file_path, tmp_dir)
1651        for path, elf in scan_elf_files(tmp_dir, mount_point, unzip_files):
1652            yield path, elf
1653
1654
1655def scan_apex_dir(apex_collection_root, apex_dir, unzip_files):
1656    # Read the manifest file.
1657    manifest_file_path = os.path.join(apex_dir, 'apex_manifest.json')
1658    try:
1659        with open(manifest_file_path, 'r') as manifest_file:
1660            manifest = json.load(manifest_file)
1661    except FileNotFoundError:
1662        print('error: Failed to find apex manifest: {}'
1663              .format(manifest_file_path), file=sys.stderr)
1664        return
1665
1666    # Read the module name.
1667    try:
1668        apex_name = manifest['name']
1669    except KeyError:
1670        print('error: Failed to read apex name from manifest: {}'
1671              .format(manifest_file_path), file=sys.stderr)
1672        return
1673
1674    # Scan the payload (or the flatten payload).
1675    mount_point = os.path.join(apex_collection_root, apex_name)
1676    img_file_path = os.path.join(apex_dir, 'apex_payload.img')
1677    if os.path.exists(img_file_path):
1678        for path, elf in scan_ext4_image(img_file_path, mount_point,
1679                                         unzip_files):
1680            yield path, elf
1681    else:
1682        for path, elf in scan_elf_files(apex_dir, mount_point, unzip_files):
1683            yield path, elf
1684
1685
1686def scan_apex_file(apex_collection_root, apex_zip_file, unzip_files):
1687    with TemporaryDirectory() as tmp_dir:
1688        with zipfile.ZipFile(apex_zip_file) as zip_file:
1689            zip_file.extractall(tmp_dir)
1690        for path, elf in scan_apex_dir(apex_collection_root, tmp_dir,
1691                                       unzip_files):
1692            yield path, elf
1693
1694
1695def scan_apex_files(apex_collection_root, unzip_files):
1696    for ent in scandir(apex_collection_root):
1697        if ent.is_dir():
1698            for path, elf in scan_apex_dir(apex_collection_root, ent.path,
1699                                           unzip_files):
1700                yield path, elf
1701        elif ent.is_file() and ent.name.endswith('.apex'):
1702            for path, elf in scan_apex_file(apex_collection_root, ent.path,
1703                                            unzip_files):
1704                yield path, elf
1705
1706
1707def scan_elf_files(root, mount_point=None, unzip_files=True):
1708    """Scan all ELF files under a directory."""
1709
1710    if mount_point:
1711        root_prefix_len = len(root) + 1
1712        def norm_path(path):
1713            return os.path.join(mount_point, path[root_prefix_len:])
1714    else:
1715        def norm_path(path):
1716            return path
1717
1718    for base, dirnames, filenames in os.walk(root):
1719        if base == root and 'apex' in dirnames:
1720            dirnames.remove('apex')
1721            for path, elf in scan_apex_files(os.path.join(root, 'apex'),
1722                                             unzip_files):
1723                yield (path, elf)
1724
1725        for filename in filenames:
1726            path = os.path.join(base, filename)
1727            if not is_accessible(path):
1728                continue
1729
1730
1731            # If this is a zip file and unzip_file is true, scan the ELF files
1732            # in the zip file.
1733            if unzip_files and is_zipfile(path):
1734                for path, content in scan_zip_file(path):
1735                    try:
1736                        yield (norm_path(path), ELF.loads(content))
1737                    except ELFError:
1738                        pass
1739                continue
1740
1741            # Load ELF from the path.
1742            try:
1743                yield (norm_path(path), ELF.load(path))
1744            except ELFError:
1745                pass
1746
1747
1748PT_SYSTEM = 0
1749PT_VENDOR = 1
1750PT_PRODUCT = 2
1751PT_SYSTEM_EXT = 3
1752NUM_PARTITIONS = 4
1753
1754
1755SPLibResult = collections.namedtuple(
1756    'SPLibResult',
1757    'sp_hal sp_hal_dep vndk_sp_hal ll_ndk ll_ndk_private vndk_sp_both')
1758
1759
1760VNDKLibTuple = defaultnamedtuple('VNDKLibTuple', 'vndk_sp vndk', [])
1761
1762
1763class VNDKLibDir(list):
1764    """VNDKLibDir is a dict which maps version to VNDK-SP and VNDK directory
1765    paths."""
1766
1767
1768    @classmethod
1769    def create_vndk_dir_suffix(cls, version):
1770        """Create VNDK version suffix."""
1771        return '' if version == 'current' else '-' + version
1772
1773
1774    @classmethod
1775    def create_vndk_sp_dir_name(cls, version):
1776        """Create VNDK-SP directory name from a given version."""
1777        return 'vndk-sp' + cls.create_vndk_dir_suffix(version)
1778
1779
1780    @classmethod
1781    def create_vndk_dir_name(cls, version):
1782        """Create VNDK directory name from a given version."""
1783        return 'vndk' + cls.create_vndk_dir_suffix(version)
1784
1785
1786    @classmethod
1787    def extract_version_from_name(cls, name):
1788        """Extract VNDK version from a name."""
1789        if name in {'vndk', 'vndk-sp'}:
1790            return 'current'
1791        if name.startswith('vndk-sp-'):
1792            return name[len('vndk-sp-'):]
1793        if name.startswith('vndk-'):
1794            return name[len('vndk-'):]
1795        return None
1796
1797
1798    @classmethod
1799    def extract_path_component(cls, path, index):
1800        """Extract n-th path component from a posix path."""
1801        start = 0
1802        for _ in range(index):
1803            pos = path.find('/', start)
1804            if pos == -1:
1805                return None
1806            start = pos + 1
1807        end = path.find('/', start)
1808        if end == -1:
1809            return None
1810        return path[start:end]
1811
1812
1813    @classmethod
1814    def extract_version_from_path(cls, path):
1815        """Extract VNDK version from the third path component."""
1816        component = cls.extract_path_component(path, 3)
1817        if not component:
1818            return None
1819        return cls.extract_version_from_name(component)
1820
1821
1822    @classmethod
1823    def is_in_vndk_dir(cls, path):
1824        """Determine whether a path is under a VNDK directory."""
1825        component = cls.extract_path_component(path, 3)
1826        if not component:
1827            return False
1828        return (component == 'vndk' or
1829                (component.startswith('vndk-') and
1830                 not component == 'vndk-sp' and
1831                 not component.startswith('vndk-sp-')))
1832
1833
1834    @classmethod
1835    def is_in_vndk_sp_dir(cls, path):
1836        """Determine whether a path is under a VNDK-SP directory."""
1837        component = cls.extract_path_component(path, 3)
1838        if not component:
1839            return False
1840        return component == 'vndk-sp' or component.startswith('vndk-sp-')
1841
1842
1843    @classmethod
1844    def get_vndk_lib_dirs(cls, lib_dir, version):
1845        """Create VNDK/VNDK-SP search paths from lib_dir and version."""
1846        vndk_sp_name = cls.create_vndk_sp_dir_name(version)
1847        vndk_name = cls.create_vndk_dir_name(version)
1848        return VNDKLibTuple(
1849            [posixpath.join('/vendor', lib_dir, vndk_sp_name),
1850             posixpath.join('/system', lib_dir, vndk_sp_name)],
1851            [posixpath.join('/vendor', lib_dir, vndk_name),
1852             posixpath.join('/system', lib_dir, vndk_name)])
1853
1854
1855    @classmethod
1856    def create_default(cls):
1857        """Create default VNDK-SP and VNDK paths without versions."""
1858        vndk_lib_dirs = VNDKLibDir()
1859        vndk_lib_dirs.append('current')
1860        return vndk_lib_dirs
1861
1862
1863    @classmethod
1864    def create_from_version(cls, version):
1865        """Create default VNDK-SP and VNDK paths with the specified version."""
1866        vndk_lib_dirs = VNDKLibDir()
1867        vndk_lib_dirs.append(version)
1868        return vndk_lib_dirs
1869
1870
1871    @classmethod
1872    def create_from_dirs(cls, system_dirs, vendor_dirs):
1873        """Scan system_dirs and vendor_dirs and collect all VNDK-SP and VNDK
1874        directory paths."""
1875
1876        def collect_versions(base_dirs):
1877            versions = set()
1878            for base_dir in base_dirs:
1879                for lib_dir in ('lib', 'lib64'):
1880                    lib_dir_path = os.path.join(base_dir, lib_dir)
1881                    try:
1882                        for name in os.listdir(lib_dir_path):
1883                            version = cls.extract_version_from_name(name)
1884                            if version:
1885                                versions.add(version)
1886                    except FileNotFoundError:
1887                        pass
1888            return versions
1889
1890        versions = set()
1891        if system_dirs:
1892            versions.update(collect_versions(system_dirs))
1893        if vendor_dirs:
1894            versions.update(collect_versions(vendor_dirs))
1895
1896        # Validate: Versions must not be 'sp' or start with 'sp-'.
1897        bad_versions = [version for version in versions
1898                        if version == 'sp' or version.startswith('sp-')]
1899        if bad_versions:
1900            raise ValueError('bad vndk version: ' + repr(bad_versions))
1901
1902        return VNDKLibDir(cls.sorted_version(versions))
1903
1904
1905    def classify_vndk_libs(self, libs):
1906        """Classify VNDK/VNDK-SP shared libraries."""
1907        vndk_sp_libs = collections.defaultdict(set)
1908        vndk_libs = collections.defaultdict(set)
1909        other_libs = set()
1910
1911        for lib in libs:
1912            component = self.extract_path_component(lib.path, 3)
1913            if component is None:
1914                other_libs.add(lib)
1915                continue
1916
1917            version = self.extract_version_from_name(component)
1918            if version is None:
1919                other_libs.add(lib)
1920                continue
1921
1922            if component.startswith('vndk-sp'):
1923                vndk_sp_libs[version].add(lib)
1924            else:
1925                vndk_libs[version].add(lib)
1926
1927        return (vndk_sp_libs, vndk_libs, other_libs)
1928
1929
1930    @classmethod
1931    def _get_property(cls, property_file, name):
1932        """Read a property from a property file."""
1933        for line in property_file:
1934            if line.startswith(name + '='):
1935                return line[len(name) + 1:].strip()
1936        return None
1937
1938
1939    @classmethod
1940    def get_ro_vndk_version(cls, vendor_dirs):
1941        """Read ro.vendor.version property from vendor partitions."""
1942        for vendor_dir in vendor_dirs:
1943            path = os.path.join(vendor_dir, 'default.prop')
1944            try:
1945                with open(path, 'r') as property_file:
1946                    result = cls._get_property(
1947                        property_file, 'ro.vndk.version')
1948                    if result is not None:
1949                        return result
1950            except FileNotFoundError:
1951                pass
1952        return None
1953
1954
1955    @classmethod
1956    def sorted_version(cls, versions):
1957        """Sort versions in the following rule:
1958
1959        1. 'current' is the first.
1960
1961        2. The versions that cannot be converted to int are sorted
1962           lexicographically in descendant order.
1963
1964        3. The versions that can be converted to int are sorted as integers in
1965           descendant order.
1966        """
1967
1968        current = []
1969        alpha = []
1970        numeric = []
1971
1972        for version in versions:
1973            if version == 'current':
1974                current.append(version)
1975                continue
1976            try:
1977                numeric.append(int(version))
1978            except ValueError:
1979                alpha.append(version)
1980
1981        alpha.sort(reverse=True)
1982        numeric.sort(reverse=True)
1983
1984        return current + alpha + [str(x) for x in numeric]
1985
1986
1987    def find_vendor_vndk_version(self, vendor_dirs):
1988        """Find the best-fitting VNDK version."""
1989
1990        ro_vndk_version = self.get_ro_vndk_version(vendor_dirs)
1991        if ro_vndk_version is not None:
1992            return ro_vndk_version
1993
1994        if not self:
1995            return 'current'
1996
1997        return self.sorted_version(self)[0]
1998
1999
2000# File path patterns for Android apps
2001_APP_DIR_PATTERNS = re.compile('^(?:/[^/]+){1,2}/(?:priv-)?app/')
2002
2003
2004class ELFResolver(object):
2005    def __init__(self, lib_set, default_search_path):
2006        self.lib_set = lib_set
2007        self.default_search_path = default_search_path
2008
2009
2010    def get_candidates(self, requester, name, dt_rpath=None, dt_runpath=None):
2011        # Search app-specific search paths.
2012        if _APP_DIR_PATTERNS.match(requester):
2013            yield os.path.join(os.path.dirname(requester), name)
2014
2015        # Search default search paths.
2016        if dt_rpath:
2017            for d in dt_rpath:
2018                yield os.path.join(d, name)
2019        if dt_runpath:
2020            for d in dt_runpath:
2021                yield os.path.join(d, name)
2022        for d in self.default_search_path:
2023            yield os.path.join(d, name)
2024
2025
2026    def resolve(self, requester, name, dt_rpath=None, dt_runpath=None):
2027        for path in self.get_candidates(requester, name, dt_rpath, dt_runpath):
2028            try:
2029                return self.lib_set[path]
2030            except KeyError:
2031                continue
2032        return None
2033
2034
2035class ELFLinkData(object):
2036    def __init__(self, partition, path, elf, tag_bit):
2037        self.partition = partition
2038        self.path = path
2039        self.elf = elf
2040        self.deps_needed = set()
2041        self.deps_needed_hidden = set()
2042        self.deps_dlopen = set()
2043        self.deps_dlopen_hidden = set()
2044        self.users_needed = set()
2045        self.users_needed_hidden = set()
2046        self.users_dlopen = set()
2047        self.users_dlopen_hidden = set()
2048        self.imported_ext_symbols = collections.defaultdict(set)
2049        self._tag_bit = tag_bit
2050        self.unresolved_symbols = set()
2051        self.unresolved_dt_needed = []
2052        self.linked_symbols = dict()
2053
2054
2055    @property
2056    def is_ll_ndk(self):
2057        return TaggedDict.is_ll_ndk(self._tag_bit)
2058
2059
2060    @property
2061    def is_vndk_sp(self):
2062        return TaggedDict.is_vndk_sp(self._tag_bit)
2063
2064
2065    @property
2066    def is_vndk_sp_private(self):
2067        return TaggedDict.is_vndk_sp_private(self._tag_bit)
2068
2069
2070    @property
2071    def is_system_only_rs(self):
2072        return TaggedDict.is_system_only_rs(self._tag_bit)
2073
2074
2075    @property
2076    def is_sp_hal(self):
2077        return TaggedDict.is_sp_hal(self._tag_bit)
2078
2079
2080    def add_needed_dep(self, dst):
2081        assert dst not in self.deps_needed_hidden
2082        assert self not in dst.users_needed_hidden
2083        self.deps_needed.add(dst)
2084        dst.users_needed.add(self)
2085
2086
2087    def add_dlopen_dep(self, dst):
2088        assert dst not in self.deps_dlopen_hidden
2089        assert self not in dst.users_dlopen_hidden
2090        self.deps_dlopen.add(dst)
2091        dst.users_dlopen.add(self)
2092
2093
2094    def hide_needed_dep(self, dst):
2095        self.deps_needed.remove(dst)
2096        dst.users_needed.remove(self)
2097        self.deps_needed_hidden.add(dst)
2098        dst.users_needed_hidden.add(self)
2099
2100
2101    def hide_dlopen_dep(self, dst):
2102        self.deps_dlopen.remove(dst)
2103        dst.users_dlopen.remove(self)
2104        self.deps_dlopen_hidden.add(dst)
2105        dst.users_dlopen_hidden.add(self)
2106
2107
2108    @property
2109    def num_deps(self):
2110        """Get the number of dependencies.  If a library is linked by both
2111        NEEDED and DLOPEN relationship, then it will be counted twice."""
2112        return (len(self.deps_needed) + len(self.deps_needed_hidden) +
2113                len(self.deps_dlopen) + len(self.deps_dlopen_hidden))
2114
2115
2116    @property
2117    def deps_all(self):
2118        return itertools.chain(self.deps_needed, self.deps_needed_hidden,
2119                               self.deps_dlopen, self.deps_dlopen_hidden)
2120
2121
2122    @property
2123    def deps_good(self):
2124        return itertools.chain(self.deps_needed, self.deps_dlopen)
2125
2126
2127    @property
2128    def deps_needed_all(self):
2129        return itertools.chain(self.deps_needed, self.deps_needed_hidden)
2130
2131
2132    @property
2133    def deps_dlopen_all(self):
2134        return itertools.chain(self.deps_dlopen, self.deps_dlopen_hidden)
2135
2136
2137    @property
2138    def num_users(self):
2139        """Get the number of users.  If a library is linked by both NEEDED and
2140        DLOPEN relationship, then it will be counted twice."""
2141        return (len(self.users_needed) + len(self.users_needed_hidden) +
2142                len(self.users_dlopen) + len(self.users_dlopen_hidden))
2143
2144
2145    @property
2146    def users_all(self):
2147        return itertools.chain(self.users_needed, self.users_needed_hidden,
2148                               self.users_dlopen, self.users_dlopen_hidden)
2149
2150
2151    @property
2152    def users_good(self):
2153        return itertools.chain(self.users_needed, self.users_dlopen)
2154
2155
2156    @property
2157    def users_needed_all(self):
2158        return itertools.chain(self.users_needed, self.users_needed_hidden)
2159
2160
2161    @property
2162    def users_dlopen_all(self):
2163        return itertools.chain(self.users_dlopen, self.users_dlopen_hidden)
2164
2165
2166    def has_dep(self, dst):
2167        return (dst in self.deps_needed or dst in self.deps_needed_hidden or
2168                dst in self.deps_dlopen or dst in self.deps_dlopen_hidden)
2169
2170
2171    def has_user(self, dst):
2172        return (dst in self.users_needed or dst in self.users_needed_hidden or
2173                dst in self.users_dlopen or dst in self.users_dlopen_hidden)
2174
2175
2176    def is_system_lib(self):
2177        return self.partition == PT_SYSTEM
2178
2179
2180    def get_dep_linked_symbols(self, dep):
2181        symbols = set()
2182        for symbol, exp_lib in self.linked_symbols.items():
2183            if exp_lib == dep:
2184                symbols.add(symbol)
2185        return sorted(symbols)
2186
2187
2188    def __lt__(self, rhs):
2189        return self.path < rhs.path
2190
2191
2192def sorted_lib_path_list(libs):
2193    libs = [lib.path for lib in libs]
2194    libs.sort()
2195    return libs
2196
2197
2198_VNDK_RESULT_FIELD_NAMES = (
2199    'll_ndk', 'll_ndk_private',
2200    'vndk_sp', 'vndk_sp_unused',
2201    'vndk_sp_private', 'vndk_sp_private_unused',
2202    'vndk', 'vndk_private',
2203    'system_only', 'system_only_rs',
2204    'sp_hal', 'sp_hal_dep',
2205    'vendor_only',
2206    'vndk_ext',
2207    'vndk_sp_ext', 'vndk_sp_private_ext',
2208    'extra_vendor_libs')
2209
2210
2211VNDKResult = defaultnamedtuple('VNDKResult', _VNDK_RESULT_FIELD_NAMES, set())
2212
2213
2214_SIMPLE_VNDK_RESULT_FIELD_NAMES = (
2215    'vndk_sp', 'vndk_sp_ext', 'extra_vendor_libs')
2216
2217
2218SimpleVNDKResult = defaultnamedtuple(
2219    'SimpleVNDKResult', _SIMPLE_VNDK_RESULT_FIELD_NAMES, set())
2220
2221
2222class ELFLibDict(defaultnamedtuple('ELFLibDict', ('lib32', 'lib64'), {})):
2223    def get_lib_dict(self, elf_class):
2224        return self[elf_class - 1]
2225
2226
2227    def add(self, path, lib):
2228        self.get_lib_dict(lib.elf.ei_class)[path] = lib
2229
2230
2231    def remove(self, lib):
2232        del self.get_lib_dict(lib.elf.ei_class)[lib.path]
2233
2234
2235    def get(self, path, default=None):
2236        for lib_set in self:
2237            res = lib_set.get(path, None)
2238            if res:
2239                return res
2240        return default
2241
2242
2243    def keys(self):
2244        return itertools.chain(self.lib32.keys(), self.lib64.keys())
2245
2246
2247    def values(self):
2248        return itertools.chain(self.lib32.values(), self.lib64.values())
2249
2250
2251    def items(self):
2252        return itertools.chain(self.lib32.items(), self.lib64.items())
2253
2254
2255class ELFLinker(object):
2256    def __init__(self, tagged_paths=None, vndk_lib_dirs=None,
2257                 ro_vndk_version='current'):
2258        self.lib_pt = [ELFLibDict() for i in range(NUM_PARTITIONS)]
2259
2260        if vndk_lib_dirs is None:
2261            vndk_lib_dirs = VNDKLibDir.create_default()
2262
2263        self.vndk_lib_dirs = vndk_lib_dirs
2264
2265        if tagged_paths is None:
2266            script_dir = os.path.dirname(os.path.abspath(__file__))
2267            dataset_path = os.path.join(
2268                script_dir, 'datasets', 'minimum_tag_file.csv')
2269            self.tagged_paths = TaggedPathDict.create_from_csv_path(
2270                dataset_path, vndk_lib_dirs)
2271        else:
2272            self.tagged_paths = tagged_paths
2273
2274        self.ro_vndk_version = ro_vndk_version
2275
2276        self.apex_module_names = set()
2277
2278
2279    def _add_lib_to_lookup_dict(self, lib):
2280        self.lib_pt[lib.partition].add(lib.path, lib)
2281
2282
2283    def _remove_lib_from_lookup_dict(self, lib):
2284        self.lib_pt[lib.partition].remove(lib)
2285
2286
2287    def _rename_lib(self, lib, new_path):
2288        self._remove_lib_from_lookup_dict(lib)
2289        lib.path = new_path
2290        lib._tag_bit = self.tagged_paths.get_path_tag_bit(new_path)
2291        self._add_lib_to_lookup_dict(lib)
2292
2293
2294    def add_lib(self, partition, path, elf):
2295        lib = ELFLinkData(partition, path, elf,
2296                          self.tagged_paths.get_path_tag_bit(path))
2297        self._add_lib_to_lookup_dict(lib)
2298        return lib
2299
2300
2301    def add_dlopen_dep(self, src_path, dst_path):
2302        num_matches = 0
2303        for elf_class in (ELF.ELFCLASS32, ELF.ELFCLASS64):
2304            srcs = self._get_libs_in_elf_class(elf_class, src_path)
2305            dsts = self._get_libs_in_elf_class(elf_class, dst_path)
2306            for src, dst in itertools.product(srcs, dsts):
2307                src.add_dlopen_dep(dst)
2308                num_matches += 1
2309        if num_matches == 0:
2310            raise ValueError('Failed to add dlopen dependency from {} to {}'
2311                             .format(src_path, dst_path))
2312
2313
2314    def _get_libs_in_elf_class(self, elf_class, path):
2315        result = set()
2316        if '${LIB}' in path:
2317            lib_dir = 'lib' if elf_class == ELF.ELFCLASS32 else 'lib64'
2318            path = path.replace('${LIB}', lib_dir)
2319        if path.startswith('[regex]'):
2320            patt = re.compile(path[7:])
2321            for partition in range(NUM_PARTITIONS):
2322                lib_set = self.lib_pt[partition].get_lib_dict(elf_class)
2323                for path, lib in lib_set.items():
2324                    if patt.match(path):
2325                        result.add(lib)
2326        else:
2327            for partition in range(NUM_PARTITIONS):
2328                lib_set = self.lib_pt[partition].get_lib_dict(elf_class)
2329                lib = lib_set.get(path)
2330                if lib:
2331                    result.add(lib)
2332        return result
2333
2334
2335    def get_lib(self, path):
2336        for lib_set in self.lib_pt:
2337            lib = lib_set.get(path)
2338            if lib:
2339                return lib
2340        return None
2341
2342
2343    def get_libs(self, paths, report_error=None):
2344        result = set()
2345        for path in paths:
2346            lib = self.get_lib(path)
2347            if not lib:
2348                if report_error is None:
2349                    raise ValueError('path not found ' + path)
2350                report_error(path)
2351                continue
2352            result.add(lib)
2353        return result
2354
2355
2356    def all_libs(self):
2357        for lib_set in self.lib_pt:
2358            for lib in lib_set.values():
2359                yield lib
2360
2361
2362    def _compute_lib_dict(self, elf_class):
2363        res = dict()
2364        for lib_pt in self.lib_pt:
2365            res.update(lib_pt.get_lib_dict(elf_class))
2366        return res
2367
2368
2369    @staticmethod
2370    def _compile_path_matcher(root, subdirs):
2371        dirs = [os.path.normpath(os.path.join(root, i)) for i in subdirs]
2372        patts = ['(?:' + re.escape(i) + os.sep + ')' for i in dirs]
2373        return re.compile('|'.join(patts))
2374
2375
2376    def add_executables_in_dir(self, partition_name, partition, root,
2377                               alter_partition, alter_subdirs, ignored_subdirs,
2378                               unzip_files):
2379        root = os.path.abspath(root)
2380        prefix_len = len(root) + 1
2381
2382        if alter_subdirs:
2383            alter_patt = ELFLinker._compile_path_matcher(root, alter_subdirs)
2384        if ignored_subdirs:
2385            ignored_patt = ELFLinker._compile_path_matcher(
2386                root, ignored_subdirs)
2387
2388        for path, elf in scan_elf_files(root, unzip_files=unzip_files):
2389            # Ignore ELF files with unknown machine ID (eg. DSP).
2390            if elf.e_machine not in ELF.ELF_MACHINES:
2391                continue
2392
2393            # Ignore ELF files with matched path.
2394            if ignored_subdirs and ignored_patt.match(path):
2395                continue
2396
2397            short_path = os.path.join('/', partition_name, path[prefix_len:])
2398
2399            if alter_subdirs and alter_patt.match(path):
2400                self.add_lib(alter_partition, short_path, elf)
2401            else:
2402                self.add_lib(partition, short_path, elf)
2403
2404
2405    def add_dlopen_deps(self, path):
2406        patt = re.compile('([^:]*):\\s*(.*)')
2407        with open(path, 'r') as dlopen_dep_file:
2408            for line_no, line in enumerate(dlopen_dep_file, start=1):
2409                match = patt.match(line)
2410                if not match:
2411                    continue
2412                try:
2413                    self.add_dlopen_dep(match.group(1), match.group(2))
2414                except ValueError as e:
2415                    print('error:{}:{}: {}.'.format(path, line_no, e),
2416                          file=sys.stderr)
2417
2418
2419    def _find_exported_symbol(self, symbol, libs):
2420        """Find the shared library with the exported symbol."""
2421        for lib in libs:
2422            if symbol in lib.elf.exported_symbols:
2423                return lib
2424        return None
2425
2426
2427    def _resolve_lib_imported_symbols(self, lib, imported_libs, generic_refs):
2428        """Resolve the imported symbols in a library."""
2429        for symbol in lib.elf.imported_symbols:
2430            imported_lib = self._find_exported_symbol(symbol, imported_libs)
2431            if not imported_lib:
2432                lib.unresolved_symbols.add(symbol)
2433            else:
2434                lib.linked_symbols[symbol] = imported_lib
2435                if generic_refs:
2436                    ref_lib = generic_refs.refs.get(imported_lib.path)
2437                    if not ref_lib or not symbol in ref_lib.exported_symbols:
2438                        lib.imported_ext_symbols[imported_lib].add(symbol)
2439
2440
2441    def _resolve_lib_dt_needed(self, lib, resolver):
2442        imported_libs = []
2443        for dt_needed in lib.elf.dt_needed:
2444            dep = resolver.resolve(lib.path, dt_needed, lib.elf.dt_rpath,
2445                                   lib.elf.dt_runpath)
2446            if not dep:
2447                candidates = list(resolver.get_candidates(
2448                    lib.path, dt_needed, lib.elf.dt_rpath, lib.elf.dt_runpath))
2449                print('warning: {}: Missing needed library: {}  Tried: {}'
2450                      .format(lib.path, dt_needed, candidates),
2451                      file=sys.stderr)
2452                lib.unresolved_dt_needed.append(dt_needed)
2453                continue
2454            lib.add_needed_dep(dep)
2455            imported_libs.append(dep)
2456        return imported_libs
2457
2458
2459    def _resolve_lib_deps(self, lib, resolver, generic_refs):
2460        # Resolve DT_NEEDED entries.
2461        imported_libs = self._resolve_lib_dt_needed(lib, resolver)
2462
2463        if generic_refs:
2464            for imported_lib in imported_libs:
2465                if imported_lib.path not in generic_refs.refs:
2466                    # Add imported_lib to imported_ext_symbols to make sure
2467                    # non-AOSP libraries are in the imported_ext_symbols key
2468                    # set.
2469                    lib.imported_ext_symbols[imported_lib].update()
2470
2471        # Resolve imported symbols.
2472        self._resolve_lib_imported_symbols(lib, imported_libs, generic_refs)
2473
2474
2475    def _resolve_lib_set_deps(self, lib_set, resolver, generic_refs):
2476        for lib in lib_set:
2477            self._resolve_lib_deps(lib, resolver, generic_refs)
2478
2479
2480    def _get_apex_bionic_lib_dirs(self, lib_dir):
2481        return ['/apex/com.android.runtime/' + lib_dir + '/bionic']
2482
2483
2484    def _get_apex_lib_dirs(self, lib_dir):
2485        return ['/apex/' + name + '/' + lib_dir
2486                for name in sorted(self.apex_module_names)]
2487
2488
2489    def _get_system_lib_dirs(self, lib_dir):
2490        return ['/system/' + lib_dir]
2491
2492
2493    def _get_system_ext_lib_dirs(self, lib_dir):
2494        return [
2495            '/system_ext/' + lib_dir,
2496            '/system/system_ext/' + lib_dir,
2497        ]
2498
2499
2500    def _get_vendor_lib_dirs(self, lib_dir):
2501        return [
2502            '/vendor/' + lib_dir + '/hw',
2503            '/vendor/' + lib_dir + '/egl',
2504            '/vendor/' + lib_dir,
2505        ]
2506
2507
2508    def _get_product_lib_dirs(self, lib_dir):
2509        return [
2510            '/product/' + lib_dir,
2511            '/system/product/' + lib_dir,
2512        ]
2513
2514
2515    def _get_system_search_paths(self, lib_dir):
2516        return (
2517            self._get_apex_bionic_lib_dirs(lib_dir) +
2518            self._get_apex_lib_dirs(lib_dir) +
2519            self._get_system_ext_lib_dirs(lib_dir) +
2520            self._get_system_lib_dirs(lib_dir) +
2521
2522            # Search '/vendor/${LIB}' and '/product/${LIB}' to detect
2523            # violations.
2524            self._get_product_lib_dirs(lib_dir) +
2525            self._get_vendor_lib_dirs(lib_dir)
2526        )
2527
2528
2529    def _get_vendor_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
2530        return (
2531            self._get_vendor_lib_dirs(lib_dir) +
2532            vndk_sp_dirs +
2533            vndk_dirs +
2534
2535            # Search '/apex/*/${LIB}' and '/system/${LIB}' for degenerated VNDK
2536            # and LL-NDK or to detect violations.
2537            self._get_apex_bionic_lib_dirs(lib_dir) +
2538            self._get_apex_lib_dirs(lib_dir) +
2539            self._get_system_lib_dirs(lib_dir)
2540        )
2541
2542
2543    def _get_product_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
2544        return (
2545            self._get_product_lib_dirs(lib_dir) +
2546            vndk_sp_dirs +
2547            vndk_dirs +
2548
2549            # Search '/vendor/${LIB}', '/system/${LIB}', and '/apex/*/${LIB}'
2550            # for degenerated VNDK and LL-NDK or to detect violations.
2551            self._get_vendor_lib_dirs(lib_dir) +
2552            self._get_apex_bionic_lib_dirs(lib_dir) +
2553            self._get_apex_lib_dirs(lib_dir) +
2554            self._get_system_lib_dirs(lib_dir)
2555        )
2556
2557
2558    def _get_system_ext_search_paths(self, lib_dir):
2559        # Delegate to _get_system_search_paths() because there is no ABI
2560        # boundary between system and system_ext partition.
2561        return self._get_system_search_paths(lib_dir)
2562
2563
2564    def _get_vndk_sp_search_paths(self, lib_dir, vndk_sp_dirs):
2565        # To find missing dependencies or LL-NDK.
2566        fallback_lib_dirs = [
2567            '/vendor/' + lib_dir,
2568            '/system/' + lib_dir,
2569        ]
2570        fallback_lib_dirs += self._get_apex_bionic_lib_dirs(lib_dir)
2571        fallback_lib_dirs += self._get_apex_lib_dirs(lib_dir)
2572
2573        return vndk_sp_dirs + fallback_lib_dirs
2574
2575
2576    def _get_vndk_search_paths(self, lib_dir, vndk_sp_dirs, vndk_dirs):
2577        # To find missing dependencies or LL-NDK.
2578        fallback_lib_dirs = [
2579            '/vendor/' + lib_dir,
2580            '/system/' + lib_dir,
2581        ]
2582        fallback_lib_dirs += self._get_apex_bionic_lib_dirs(lib_dir)
2583        fallback_lib_dirs += self._get_apex_lib_dirs(lib_dir)
2584
2585        return vndk_sp_dirs + vndk_dirs + fallback_lib_dirs
2586
2587
2588    def _resolve_elf_class_deps(self, lib_dir, elf_class, generic_refs):
2589        # Classify libs.
2590        vndk_lib_dirs = self.vndk_lib_dirs
2591        lib_dict = self._compute_lib_dict(elf_class)
2592
2593        system_lib_dict = self.lib_pt[PT_SYSTEM].get_lib_dict(elf_class)
2594        system_vndk_sp_libs, system_vndk_libs, system_libs = \
2595            vndk_lib_dirs.classify_vndk_libs(system_lib_dict.values())
2596
2597        vendor_lib_dict = self.lib_pt[PT_VENDOR].get_lib_dict(elf_class)
2598        vendor_vndk_sp_libs, vendor_vndk_libs, vendor_libs = \
2599            vndk_lib_dirs.classify_vndk_libs(vendor_lib_dict.values())
2600
2601        # Resolve system libs.
2602        search_paths = self._get_system_search_paths(lib_dir)
2603        resolver = ELFResolver(lib_dict, search_paths)
2604        self._resolve_lib_set_deps(system_libs, resolver, generic_refs)
2605
2606        # Resolve vndk-sp libs
2607        for version in vndk_lib_dirs:
2608            vndk_sp_dirs, vndk_dirs = \
2609                vndk_lib_dirs.get_vndk_lib_dirs(lib_dir, version)
2610            vndk_sp_libs = \
2611                system_vndk_sp_libs[version] | vendor_vndk_sp_libs[version]
2612            search_paths = self._get_vndk_sp_search_paths(
2613                lib_dir, vndk_sp_dirs)
2614            resolver = ELFResolver(lib_dict, search_paths)
2615            self._resolve_lib_set_deps(vndk_sp_libs, resolver, generic_refs)
2616
2617        # Resolve vndk libs
2618        for version in vndk_lib_dirs:
2619            vndk_sp_dirs, vndk_dirs = \
2620                vndk_lib_dirs.get_vndk_lib_dirs(lib_dir, version)
2621            vndk_libs = system_vndk_libs[version] | vendor_vndk_libs[version]
2622            search_paths = self._get_vndk_search_paths(
2623                lib_dir, vndk_sp_dirs, vndk_dirs)
2624            resolver = ELFResolver(lib_dict, search_paths)
2625            self._resolve_lib_set_deps(vndk_libs, resolver, generic_refs)
2626
2627        # Resolve vendor libs.
2628        vndk_sp_dirs, vndk_dirs = vndk_lib_dirs.get_vndk_lib_dirs(
2629            lib_dir, self.ro_vndk_version)
2630        search_paths = self._get_vendor_search_paths(
2631            lib_dir, vndk_sp_dirs, vndk_dirs)
2632        resolver = ELFResolver(lib_dict, search_paths)
2633        self._resolve_lib_set_deps(vendor_libs, resolver, generic_refs)
2634
2635        # Resolve product libs
2636        product_lib_dict = self.lib_pt[PT_PRODUCT].get_lib_dict(elf_class)
2637        product_libs = set(product_lib_dict.values())
2638        search_paths = self._get_product_search_paths(
2639            lib_dir, vndk_sp_dirs, vndk_dirs)
2640        resolver = ELFResolver(lib_dict, search_paths)
2641        self._resolve_lib_set_deps(product_libs, resolver, generic_refs)
2642
2643        # Resolve system_ext libs
2644        system_ext_lib_dict = \
2645            self.lib_pt[PT_SYSTEM_EXT].get_lib_dict(elf_class)
2646        system_ext_libs = set(system_ext_lib_dict.values())
2647        search_paths = self._get_system_ext_search_paths(lib_dir)
2648        resolver = ELFResolver(lib_dict, search_paths)
2649        self._resolve_lib_set_deps(system_ext_libs, resolver, generic_refs)
2650
2651
2652    def resolve_deps(self, generic_refs=None):
2653        self._resolve_elf_class_deps('lib', ELF.ELFCLASS32, generic_refs)
2654        self._resolve_elf_class_deps('lib64', ELF.ELFCLASS64, generic_refs)
2655
2656
2657    def compute_predefined_sp_hal(self):
2658        """Find all same-process HALs."""
2659        return set(lib for lib in self.all_libs() if lib.is_sp_hal)
2660
2661
2662    def compute_sp_lib(self, generic_refs, ignore_hidden_deps=False):
2663        def is_ll_ndk_or_sp_hal(lib):
2664            return lib.is_ll_ndk or lib.is_sp_hal
2665
2666        ll_ndk = set(lib for lib in self.all_libs() if lib.is_ll_ndk)
2667        ll_ndk_closure = self.compute_deps_closure(
2668            ll_ndk, is_ll_ndk_or_sp_hal, ignore_hidden_deps)
2669        ll_ndk_private = ll_ndk_closure - ll_ndk
2670
2671        def is_ll_ndk(lib):
2672            return lib.is_ll_ndk
2673
2674        sp_hal = self.compute_predefined_sp_hal()
2675        sp_hal_closure = self.compute_deps_closure(
2676            sp_hal, is_ll_ndk, ignore_hidden_deps)
2677
2678        def is_aosp_lib(lib):
2679            return (not generic_refs or
2680                    generic_refs.classify_lib(lib) != GenericRefs.NEW_LIB)
2681
2682        vndk_sp_hal = set()
2683        sp_hal_dep = set()
2684        for lib in sp_hal_closure - sp_hal:
2685            if is_aosp_lib(lib):
2686                vndk_sp_hal.add(lib)
2687            else:
2688                sp_hal_dep.add(lib)
2689
2690        vndk_sp_both = ll_ndk_private & vndk_sp_hal
2691        ll_ndk_private -= vndk_sp_both
2692        vndk_sp_hal -= vndk_sp_both
2693
2694        return SPLibResult(sp_hal, sp_hal_dep, vndk_sp_hal, ll_ndk,
2695                           ll_ndk_private, vndk_sp_both)
2696
2697
2698    def normalize_partition_tags(self, sp_hals, generic_refs):
2699        def is_system_lib_or_sp_hal(lib):
2700            return lib.is_system_lib() or lib in sp_hals
2701
2702        for lib in self.lib_pt[PT_SYSTEM].values():
2703            if all(is_system_lib_or_sp_hal(dep) for dep in lib.deps_all):
2704                continue
2705            # If a system module is depending on a vendor shared library and
2706            # such shared library is not a SP-HAL library, then emit an error
2707            # and hide the dependency.
2708            for dep in list(lib.deps_needed_all):
2709                if not is_system_lib_or_sp_hal(dep):
2710                    print('error: {}: system exe/lib must not depend on '
2711                          'vendor lib {}.  Assume such dependency does '
2712                          'not exist.'.format(lib.path, dep.path),
2713                          file=sys.stderr)
2714                    lib.hide_needed_dep(dep)
2715            for dep in list(lib.deps_dlopen_all):
2716                if not is_system_lib_or_sp_hal(dep):
2717                    print('error: {}: system exe/lib must not dlopen() '
2718                          'vendor lib {}.  Assume such dependency does '
2719                          'not exist.'.format(lib.path, dep.path),
2720                          file=sys.stderr)
2721                    lib.hide_dlopen_dep(dep)
2722
2723
2724    def rewrite_apex_modules(self):
2725        """Rename ELF files under `/system/apex/${name}.apex` to
2726        `/apex/${name}/...` and collect apex module names."""
2727        APEX_PREFIX = '/system/apex/'
2728        APEX_PREFIX_LEN = len(APEX_PREFIX)
2729        for lib in list(self.all_libs()):
2730            if not lib.path.startswith(APEX_PREFIX):
2731                continue
2732
2733            apex_name_end = lib.path.find('/', APEX_PREFIX_LEN)
2734            apex_name = lib.path[APEX_PREFIX_LEN:apex_name_end]
2735            self.apex_module_names.add(apex_name)
2736
2737            self._rename_lib(lib, '/apex/' + lib.path[APEX_PREFIX_LEN:])
2738
2739
2740    @staticmethod
2741    def _parse_action_on_ineligible_lib(arg):
2742        follow = False
2743        warn = False
2744        for flag in arg.split(','):
2745            if flag == 'follow':
2746                follow = True
2747            elif flag == 'warn':
2748                warn = True
2749            elif flag == 'ignore':
2750                continue
2751            else:
2752                raise ValueError('unknown action \"{}\"'.format(flag))
2753        return (follow, warn)
2754
2755
2756    def compute_degenerated_vndk(self, generic_refs, tagged_paths=None,
2757                                 action_ineligible_vndk_sp='warn',
2758                                 action_ineligible_vndk='warn'):
2759        # Find LL-NDK libs.
2760        ll_ndk = set(lib for lib in self.all_libs() if lib.is_ll_ndk)
2761
2762        # Find pre-defined libs.
2763        system_only_rs = set(
2764            lib for lib in self.all_libs() if lib.is_system_only_rs)
2765        predefined_vndk_sp = set(
2766            lib for lib in self.all_libs() if lib.is_vndk_sp)
2767        predefined_vndk_sp_private = set(
2768            lib for lib in self.all_libs() if lib.is_vndk_sp_private)
2769
2770        # Find SP-HAL libs.
2771        sp_hal = self.compute_predefined_sp_hal()
2772
2773        # Normalize partition tags.  We expect many violations from the
2774        # pre-Treble world.  Guess a resolution for the incorrect partition
2775        # tag.
2776        self.normalize_partition_tags(sp_hal, generic_refs)
2777
2778        # Find SP-HAL-Dep libs.
2779        def is_aosp_lib(lib):
2780            if not generic_refs:
2781                # If generic reference is not available, then assume all system
2782                # libs are AOSP libs.
2783                return lib.partition == PT_SYSTEM
2784            return generic_refs.has_same_name_lib(lib)
2785
2786        def is_not_sp_hal_dep(lib):
2787            if lib.is_ll_ndk or lib in sp_hal:
2788                return True
2789            return is_aosp_lib(lib)
2790
2791        sp_hal_dep = self.compute_deps_closure(sp_hal, is_not_sp_hal_dep, True)
2792        sp_hal_dep -= sp_hal
2793
2794        # Find VNDK-SP libs.
2795        def is_not_vndk_sp(lib):
2796            return lib.is_ll_ndk or lib in sp_hal or lib in sp_hal_dep
2797
2798        follow_ineligible_vndk_sp, warn_ineligible_vndk_sp = \
2799            self._parse_action_on_ineligible_lib(action_ineligible_vndk_sp)
2800        vndk_sp = set()
2801        for lib in itertools.chain(sp_hal, sp_hal_dep):
2802            for dep in lib.deps_all:
2803                if is_not_vndk_sp(dep):
2804                    continue
2805                if dep in predefined_vndk_sp:
2806                    vndk_sp.add(dep)
2807                    continue
2808                if warn_ineligible_vndk_sp:
2809                    print('error: SP-HAL {} depends on non vndk-sp '
2810                          'library {}.'.format(lib.path, dep.path),
2811                          file=sys.stderr)
2812                if follow_ineligible_vndk_sp:
2813                    vndk_sp.add(dep)
2814
2815        # Find VNDK-SP-Indirect libs.
2816        def is_not_vndk_sp_private(lib):
2817            return lib.is_ll_ndk or lib in vndk_sp or lib in system_only_rs
2818
2819        vndk_sp_private = self.compute_deps_closure(
2820            vndk_sp, is_not_vndk_sp_private, True)
2821        vndk_sp_private -= vndk_sp
2822
2823        # Find unused predefined VNDK-SP libs.
2824        vndk_sp_unused = set(lib for lib in predefined_vndk_sp
2825                             if VNDKLibDir.is_in_vndk_sp_dir(lib.path))
2826        vndk_sp_unused -= vndk_sp
2827        vndk_sp_unused -= vndk_sp_private
2828
2829        # Find dependencies of unused predefined VNDK-SP libs.
2830        def is_not_vndk_sp_private_unused(lib):
2831            return is_not_vndk_sp_private(lib) or lib in vndk_sp_private
2832        vndk_sp_unused_deps = self.compute_deps_closure(
2833            vndk_sp_unused, is_not_vndk_sp_private_unused, True)
2834        vndk_sp_unused_deps -= vndk_sp_unused
2835
2836        vndk_sp_private_unused = set(
2837            lib for lib in predefined_vndk_sp_private
2838            if VNDKLibDir.is_in_vndk_sp_dir(lib.path))
2839        vndk_sp_private_unused -= vndk_sp_private
2840        vndk_sp_private_unused -= vndk_sp_unused
2841        vndk_sp_private_unused |= vndk_sp_unused_deps
2842
2843        assert not vndk_sp & vndk_sp_private
2844        assert not vndk_sp_unused & vndk_sp_private_unused
2845
2846        # Define helper functions for vndk_sp sets.
2847        def is_vndk_sp_public(lib):
2848            return lib in vndk_sp or lib in vndk_sp_unused or \
2849                   lib in vndk_sp_private or \
2850                   lib in vndk_sp_private_unused
2851
2852        def is_vndk_sp(lib):
2853            return is_vndk_sp_public(lib) or lib in vndk_sp_private
2854
2855        def is_vndk_sp_unused(lib):
2856            return lib in vndk_sp_unused or lib in vndk_sp_private_unused
2857
2858        def relabel_vndk_sp_as_used(lib):
2859            assert is_vndk_sp_unused(lib)
2860
2861            if lib in vndk_sp_unused:
2862                vndk_sp_unused.remove(lib)
2863                vndk_sp.add(lib)
2864            else:
2865                vndk_sp_private_unused.remove(lib)
2866                vndk_sp_private.add(lib)
2867
2868            # Add the dependencies to vndk_sp_private if they are not vndk_sp.
2869            closure = self.compute_deps_closure(
2870                {lib}, lambda lib: lib not in vndk_sp_private_unused, True)
2871            closure.remove(lib)
2872            vndk_sp_private_unused.difference_update(closure)
2873            vndk_sp_private.update(closure)
2874
2875        # Find VNDK-SP-Ext libs.
2876        vndk_sp_ext = set()
2877        def collect_vndk_ext(libs):
2878            result = set()
2879            for lib in libs:
2880                for dep in lib.imported_ext_symbols:
2881                    if dep in vndk_sp and dep not in vndk_sp_ext:
2882                        result.add(dep)
2883            return result
2884
2885        candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
2886        while candidates:
2887            vndk_sp_ext |= candidates
2888            candidates = collect_vndk_ext(candidates)
2889
2890        # Find VNDK-SP-Indirect-Ext libs.
2891        vndk_sp_private_ext = set()
2892        def collect_vndk_sp_private_ext(libs):
2893            result = set()
2894            for lib in libs:
2895                exts = set(lib.imported_ext_symbols.keys())
2896                for dep in lib.deps_all:
2897                    if not is_vndk_sp_public(dep):
2898                        continue
2899                    if dep in vndk_sp_ext or dep in vndk_sp_private_ext:
2900                        continue
2901                    # If lib is using extended definition from deps, then we
2902                    # have to make a copy of dep.
2903                    if dep in exts:
2904                        result.add(dep)
2905                        continue
2906                    # If lib is using non-predefined VNDK-SP-Indirect, then we
2907                    # have to make a copy of dep.
2908                    if dep not in predefined_vndk_sp and \
2909                            dep not in predefined_vndk_sp_private:
2910                        result.add(dep)
2911                        continue
2912            return result
2913
2914        def is_not_vndk_sp_private(lib):
2915            return lib.is_ll_ndk or lib in vndk_sp or lib in system_only_rs
2916
2917        candidates = collect_vndk_sp_private_ext(vndk_sp_ext)
2918        while candidates:
2919            vndk_sp_private_ext |= candidates
2920            candidates = collect_vndk_sp_private_ext(candidates)
2921
2922        # Find VNDK libs (a.k.a. system shared libs directly used by vendor
2923        # partition.)
2924        def is_not_vndk(lib):
2925            if lib.is_ll_ndk or is_vndk_sp_public(lib) or lib in system_only_rs:
2926                return True
2927            return lib.partition != PT_SYSTEM
2928
2929        def is_eligible_lib_access(lib, dep):
2930            return not tagged_paths or \
2931                    tagged_paths.is_path_visible(lib.path, dep.path)
2932
2933        follow_ineligible_vndk, warn_ineligible_vndk = \
2934                self._parse_action_on_ineligible_lib(action_ineligible_vndk)
2935        vndk = set()
2936        extra_vendor_libs = set()
2937        def collect_vndk(vendor_libs):
2938            next_vendor_libs = set()
2939            for lib in vendor_libs:
2940                for dep in lib.deps_all:
2941                    if is_vndk_sp_unused(dep):
2942                        relabel_vndk_sp_as_used(dep)
2943                        continue
2944                    if is_not_vndk(dep):
2945                        continue
2946                    if not is_aosp_lib(dep):
2947                        # The dependency should be copied into vendor partition
2948                        # as an extra vendor lib.
2949                        if dep not in extra_vendor_libs:
2950                            next_vendor_libs.add(dep)
2951                            extra_vendor_libs.add(dep)
2952                        continue
2953                    if is_eligible_lib_access(lib, dep):
2954                        vndk.add(dep)
2955                        continue
2956                    if warn_ineligible_vndk:
2957                        print('warning: vendor lib/exe {} depends on '
2958                              'ineligible framework shared lib {}.'
2959                              .format(lib.path, dep.path), file=sys.stderr)
2960                    if follow_ineligible_vndk:
2961                        vndk.add(dep)
2962            return next_vendor_libs
2963
2964        candidates = collect_vndk(self.lib_pt[PT_VENDOR].values())
2965        while candidates:
2966            candidates = collect_vndk(candidates)
2967
2968        vndk_private = self.compute_deps_closure(vndk, is_not_vndk, True)
2969        vndk_private -= vndk
2970
2971        def is_vndk(lib):
2972            return lib in vndk or lib in vndk_private
2973
2974        # Find VNDK-EXT libs (VNDK libs with extended definitions and the
2975        # extended definitions are used by the vendor modules (including
2976        # extra_vendor_libs).
2977
2978        # FIXME: DAUX libraries won't be found by the following algorithm.
2979        vndk_ext = set()
2980
2981        def collect_vndk_ext(libs):
2982            result = set()
2983            for lib in libs:
2984                for dep in lib.imported_ext_symbols:
2985                    if dep in vndk and dep not in vndk_ext:
2986                        result.add(dep)
2987            return result
2988
2989        candidates = collect_vndk_ext(self.lib_pt[PT_VENDOR].values())
2990        candidates |= collect_vndk_ext(extra_vendor_libs)
2991
2992        while candidates:
2993            vndk_ext |= candidates
2994            candidates = collect_vndk_ext(candidates)
2995
2996        # Compute LL-NDK-Indirect.
2997        def is_not_ll_ndk_private(lib):
2998            return lib.is_ll_ndk or lib.is_sp_hal or is_vndk_sp(lib) or \
2999                   is_vndk_sp(lib) or is_vndk(lib)
3000
3001        ll_ndk_private = self.compute_deps_closure(
3002            ll_ndk, is_not_ll_ndk_private, True)
3003        ll_ndk_private -= ll_ndk
3004
3005        # Return the VNDK classifications.
3006        return VNDKResult(
3007            ll_ndk=ll_ndk,
3008            ll_ndk_private=ll_ndk_private,
3009            vndk_sp=vndk_sp,
3010            vndk_sp_unused=vndk_sp_unused,
3011            vndk_sp_private=vndk_sp_private,
3012            vndk_sp_private_unused=vndk_sp_private_unused,
3013            vndk=vndk,
3014            vndk_private=vndk_private,
3015            # system_only=system_only,
3016            system_only_rs=system_only_rs,
3017            sp_hal=sp_hal,
3018            sp_hal_dep=sp_hal_dep,
3019            # vendor_only=vendor_only,
3020            vndk_ext=vndk_ext,
3021            vndk_sp_ext=vndk_sp_ext,
3022            vndk_sp_private_ext=vndk_sp_private_ext,
3023            extra_vendor_libs=extra_vendor_libs)
3024
3025
3026    @staticmethod
3027    def _compute_closure(root_set, is_excluded, get_successors):
3028        closure = set(root_set)
3029        stack = list(root_set)
3030        while stack:
3031            lib = stack.pop()
3032            for succ in get_successors(lib):
3033                if is_excluded(succ):
3034                    continue
3035                if succ not in closure:
3036                    closure.add(succ)
3037                    stack.append(succ)
3038        return closure
3039
3040
3041    @classmethod
3042    def compute_deps_closure(cls, root_set, is_excluded,
3043                             ignore_hidden_deps=False):
3044        get_successors = (lambda x: x.deps_good) if ignore_hidden_deps else \
3045                         (lambda x: x.deps_all)
3046        return cls._compute_closure(root_set, is_excluded, get_successors)
3047
3048
3049    @classmethod
3050    def compute_users_closure(cls, root_set, is_excluded,
3051                              ignore_hidden_users=False):
3052        get_successors = (lambda x: x.users_good) if ignore_hidden_users else \
3053                         (lambda x: x.users_all)
3054        return cls._compute_closure(root_set, is_excluded, get_successors)
3055
3056
3057    @staticmethod
3058    def create(system_dirs=None, system_dirs_as_vendor=None,
3059               system_dirs_ignored=None, vendor_dirs=None,
3060               vendor_dirs_as_system=None, vendor_dirs_ignored=None,
3061               product_dirs=None, system_ext_dirs=None, extra_deps=None,
3062               generic_refs=None, tagged_paths=None, vndk_lib_dirs=None,
3063               unzip_files=True):
3064        if vndk_lib_dirs is None:
3065            vndk_lib_dirs = VNDKLibDir.create_from_dirs(
3066                system_dirs, vendor_dirs)
3067        ro_vndk_version = vndk_lib_dirs.find_vendor_vndk_version(vendor_dirs)
3068        graph = ELFLinker(tagged_paths, vndk_lib_dirs, ro_vndk_version)
3069
3070        if system_dirs:
3071            for path in system_dirs:
3072                graph.add_executables_in_dir(
3073                    'system', PT_SYSTEM, path, PT_VENDOR,
3074                    system_dirs_as_vendor, system_dirs_ignored,
3075                    unzip_files)
3076
3077        if vendor_dirs:
3078            for path in vendor_dirs:
3079                graph.add_executables_in_dir(
3080                    'vendor', PT_VENDOR, path, PT_SYSTEM,
3081                    vendor_dirs_as_system, vendor_dirs_ignored,
3082                    unzip_files)
3083
3084        if product_dirs:
3085            for path in product_dirs:
3086                graph.add_executables_in_dir(
3087                    'product', PT_PRODUCT, path, None, None, None,
3088                    unzip_files)
3089
3090        if system_ext_dirs:
3091            for path in system_ext_dirs:
3092                graph.add_executables_in_dir(
3093                    'system_ext', PT_SYSTEM_EXT, path, None, None, None,
3094                    unzip_files)
3095
3096        if extra_deps:
3097            for path in extra_deps:
3098                graph.add_dlopen_deps(path)
3099
3100        graph.rewrite_apex_modules()
3101        graph.resolve_deps(generic_refs)
3102
3103        return graph
3104
3105
3106#------------------------------------------------------------------------------
3107# Generic Reference
3108#------------------------------------------------------------------------------
3109
3110class GenericRefs(object):
3111    NEW_LIB = 0
3112    EXPORT_EQUAL = 1
3113    EXPORT_SUPER_SET = 2
3114    MODIFIED = 3
3115
3116
3117    def __init__(self):
3118        self.refs = dict()
3119        self._lib_names = set()
3120
3121
3122    def add(self, path, elf):
3123        self.refs[path] = elf
3124        self._lib_names.add(os.path.basename(path))
3125
3126
3127    def _load_from_sym_dir(self, root):
3128        root = os.path.abspath(root)
3129        prefix_len = len(root) + 1
3130        for base, _, filenames in os.walk(root):
3131            for filename in filenames:
3132                if not filename.endswith('.sym'):
3133                    continue
3134                path = os.path.join(base, filename)
3135                lib_path = '/' + path[prefix_len:-4]
3136                self.add(lib_path, ELF.load_dump(path))
3137
3138
3139    @staticmethod
3140    def create_from_sym_dir(root):
3141        result = GenericRefs()
3142        result._load_from_sym_dir(root)
3143        return result
3144
3145
3146    def _load_from_image_dir(self, root, prefix):
3147        root = os.path.abspath(root)
3148        root_len = len(root) + 1
3149        for path, elf in scan_elf_files(root):
3150            self.add(os.path.join(prefix, path[root_len:]), elf)
3151
3152
3153    @staticmethod
3154    def create_from_image_dir(root, prefix):
3155        result = GenericRefs()
3156        result._load_from_image_dir(root, prefix)
3157        return result
3158
3159
3160    def classify_lib(self, lib):
3161        ref_lib = self.refs.get(lib.path)
3162        if not ref_lib:
3163            return GenericRefs.NEW_LIB
3164        exported_symbols = lib.elf.exported_symbols
3165        if exported_symbols == ref_lib.exported_symbols:
3166            return GenericRefs.EXPORT_EQUAL
3167        if exported_symbols > ref_lib.exported_symbols:
3168            return GenericRefs.EXPORT_SUPER_SET
3169        return GenericRefs.MODIFIED
3170
3171
3172    def is_equivalent_lib(self, lib):
3173        return self.classify_lib(lib) == GenericRefs.EXPORT_EQUAL
3174
3175
3176    def has_same_name_lib(self, lib):
3177        return os.path.basename(lib.path) in self._lib_names
3178
3179
3180#------------------------------------------------------------------------------
3181# APK Dep
3182#------------------------------------------------------------------------------
3183def _build_lib_names_dict(graph, min_name_len=6, lib_ext='.so'):
3184    names = collections.defaultdict(set)
3185    for lib in graph.all_libs():
3186        name = os.path.basename(lib.path)
3187        root, ext = os.path.splitext(name)
3188
3189        if ext != lib_ext:
3190            continue
3191
3192        if not lib.elf.is_jni_lib():
3193            continue
3194
3195        names[name].add(lib)
3196        names[root].add(lib)
3197
3198        if root.startswith('lib') and len(root) > min_name_len:
3199            # FIXME: libandroid.so is a JNI lib.  However, many apps have
3200            # "android" as a constant string literal, thus "android" is
3201            # skipped here to reduce the false positives.
3202            #
3203            # Note: It is fine to exclude libandroid.so because it is only
3204            # a user of JNI and it does not define any JNI methods.
3205            if root != 'libandroid':
3206                names[root[3:]].add(lib)
3207    return names
3208
3209
3210def _enumerate_partition_paths(partition, root):
3211    prefix_len = len(root) + 1
3212    for base, _, files in os.walk(root):
3213        for filename in files:
3214            path = os.path.join(base, filename)
3215            if not is_accessible(path):
3216                continue
3217            android_path = posixpath.join('/', partition, path[prefix_len:])
3218            yield (android_path, path)
3219
3220
3221def _enumerate_paths(system_dirs, vendor_dirs, product_dirs, system_ext_dirs):
3222    for root in system_dirs:
3223        for ap, path in _enumerate_partition_paths('system', root):
3224            yield (ap, path)
3225    for root in vendor_dirs:
3226        for ap, path in _enumerate_partition_paths('vendor', root):
3227            yield (ap, path)
3228    for root in product_dirs:
3229        for ap, path in _enumerate_partition_paths('product', root):
3230            yield (ap, path)
3231    for root in system_ext_dirs:
3232        for ap, path in _enumerate_partition_paths('system_ext', root):
3233            yield (ap, path)
3234
3235
3236def scan_apk_dep(graph, system_dirs, vendor_dirs, product_dirs,
3237                 system_ext_dirs):
3238    libnames = _build_lib_names_dict(graph)
3239    results = []
3240
3241    if str is bytes:
3242        def decode(string):  # PY2
3243            return string.decode('mutf-8').encode('utf-8')
3244    else:
3245        def decode(string):  # PY3
3246            return string.decode('mutf-8')
3247
3248    for ap, path in _enumerate_paths(system_dirs, vendor_dirs, product_dirs,
3249                                     system_ext_dirs):
3250        # Read the dex file from various file formats
3251        try:
3252            dex_string_iter = DexFileReader.enumerate_dex_strings(path)
3253            if dex_string_iter is None:
3254                continue
3255
3256            strings = set()
3257            for string in dex_string_iter:
3258                try:
3259                    strings.add(decode(string))
3260                except UnicodeSurrogateDecodeError:
3261                    pass
3262        except FileNotFoundError:
3263            continue
3264        except:
3265            print('error: Failed to parse', path, file=sys.stderr)
3266            raise
3267
3268        # Skip the file that does not call System.loadLibrary()
3269        if 'loadLibrary' not in strings:
3270            continue
3271
3272        # Collect libraries from string tables
3273        libs = set()
3274        for string in strings:
3275            try:
3276                for dep_file in libnames[string]:
3277                    match = _APP_DIR_PATTERNS.match(dep_file.path)
3278
3279                    # List the lib if it is not embedded in the app.
3280                    if not match:
3281                        libs.add(dep_file)
3282                        continue
3283
3284                    # Only list the embedded lib if it is in the same app.
3285                    common = os.path.commonprefix([ap, dep_file.path])
3286                    if len(common) > len(match.group(0)):
3287                        libs.add(dep_file)
3288                        continue
3289            except KeyError:
3290                pass
3291
3292        if libs:
3293            results.append((ap, sorted_lib_path_list(libs)))
3294
3295    results.sort()
3296    return results
3297
3298
3299#------------------------------------------------------------------------------
3300# Module Info
3301#------------------------------------------------------------------------------
3302
3303class ModuleInfo(object):
3304    def __init__(self, json=None):
3305        if not json:
3306            self._mods = dict()
3307            return
3308
3309        mods = collections.defaultdict(set)
3310        installed_path_patt = re.compile(
3311            '.*[\\\\/]target[\\\\/]product[\\\\/][^\\\\/]+([\\\\/].*)$')
3312        for module in json.values():
3313            for path in module['installed']:
3314                match = installed_path_patt.match(path)
3315                if match:
3316                    for path in module['path']:
3317                        mods[match.group(1)].add(path)
3318        self._mods = {installed_path: sorted(src_dirs)
3319                      for installed_path, src_dirs in mods.items()}
3320
3321
3322    def get_module_path(self, installed_path):
3323        return self._mods.get(installed_path, [])
3324
3325
3326    @staticmethod
3327    def load(f):
3328        return ModuleInfo(json.load(f))
3329
3330
3331    @staticmethod
3332    def load_from_path_or_default(path):
3333        if not path:
3334            return ModuleInfo()
3335        with open(path, 'r') as f:
3336            return ModuleInfo.load(f)
3337
3338
3339#------------------------------------------------------------------------------
3340# Commands
3341#------------------------------------------------------------------------------
3342
3343class Command(object):
3344    def __init__(self, name, help):
3345        self.name = name
3346        self.help = help
3347
3348    def add_argparser_options(self, parser):
3349        pass
3350
3351    def main(self, args):
3352        return 0
3353
3354
3355class ELFDumpCommand(Command):
3356    def __init__(self):
3357        super(ELFDumpCommand, self).__init__(
3358            'elfdump', help='Dump ELF .dynamic section')
3359
3360
3361    def add_argparser_options(self, parser):
3362        parser.add_argument('path', help='path to an ELF file')
3363
3364
3365    def main(self, args):
3366        try:
3367            ELF.load(args.path).dump()
3368        except ELFError as e:
3369            print('error: {}: Bad ELF file ({})'.format(args.path, e),
3370                  file=sys.stderr)
3371            sys.exit(1)
3372        return 0
3373
3374
3375class CreateGenericRefCommand(Command):
3376    def __init__(self):
3377        super(CreateGenericRefCommand, self).__init__(
3378            'create-generic-ref', help='Create generic references')
3379
3380
3381    def add_argparser_options(self, parser):
3382        parser.add_argument('dir')
3383
3384        parser.add_argument('-o', '--output', required=True,
3385                            help='output directory')
3386
3387
3388    def main(self, args):
3389        root = os.path.abspath(args.dir)
3390        print(root)
3391        prefix_len = len(root) + 1
3392        for path, elf in scan_elf_files(root):
3393            name = path[prefix_len:]
3394            print('Processing:', name, file=sys.stderr)
3395            out = os.path.join(args.output, name) + '.sym'
3396            makedirs(os.path.dirname(out), exist_ok=True)
3397            with open(out, 'w') as f:
3398                elf.dump(f)
3399        return 0
3400
3401
3402class ELFGraphCommand(Command):
3403    def add_argparser_options(self, parser, is_tag_file_required=None):
3404        parser.add_argument(
3405            '--load-extra-deps', action='append',
3406            help='load extra module dependencies')
3407
3408        parser.add_argument(
3409            '--system', action='append', default=[],
3410            help='path to system partition contents')
3411
3412        parser.add_argument(
3413            '--vendor', action='append', default=[],
3414            help='path to vendor partition contents')
3415
3416        parser.add_argument(
3417            '--product', action='append', default=[],
3418            help='path to product partition contents')
3419
3420        parser.add_argument(
3421            '--system_ext', action='append', default=[],
3422            help='path to system_ext partition contents')
3423
3424        # XXX: BEGIN: Remove these options
3425        parser.add_argument(
3426            '--system-dir-as-vendor', action='append',
3427            help='sub directory of system partition that has vendor files')
3428
3429        parser.add_argument(
3430            '--system-dir-ignored', action='append',
3431            help='sub directory of system partition that must be ignored')
3432
3433        parser.add_argument(
3434            '--vendor-dir-as-system', action='append',
3435            help='sub directory of vendor partition that has system files')
3436
3437        parser.add_argument(
3438            '--vendor-dir-ignored', action='append',
3439            help='sub directory of vendor partition that must be ignored')
3440        # XXX: END: Remove these options
3441
3442        parser.add_argument(
3443            '--load-generic-refs',
3444            help='compare with generic reference symbols')
3445
3446        parser.add_argument(
3447            '--aosp-system',
3448            help='compare with AOSP generic system image directory')
3449
3450        parser.add_argument(
3451            '--unzip-files', action='store_true', default=True,
3452            help='scan ELF files in zip files')
3453
3454        parser.add_argument(
3455            '--no-unzip-files', action='store_false', dest='unzip_files',
3456            help='do not scan ELF files in zip files')
3457
3458        parser.add_argument(
3459            '--tag-file', required=is_tag_file_required,
3460            help='lib tag file')
3461
3462
3463    def get_generic_refs_from_args(self, args):
3464        if args.load_generic_refs:
3465            return GenericRefs.create_from_sym_dir(args.load_generic_refs)
3466        if args.aosp_system:
3467            return GenericRefs.create_from_image_dir(
3468                args.aosp_system, '/system')
3469        return None
3470
3471
3472    def _check_arg_dir_exists(self, arg_name, dirs):
3473        for path in dirs:
3474            if not os.path.exists(path):
3475                print('error: Failed to find the directory "{}" specified in '
3476                      '"{}"'.format(path, arg_name), file=sys.stderr)
3477                sys.exit(1)
3478            if not os.path.isdir(path):
3479                print('error: Path "{}" specified in {} is not a directory'
3480                      .format(path, arg_name), file=sys.stderr)
3481                sys.exit(1)
3482
3483
3484    def check_dirs_from_args(self, args):
3485        self._check_arg_dir_exists('--system', args.system)
3486        self._check_arg_dir_exists('--vendor', args.vendor)
3487        self._check_arg_dir_exists('--product', args.product)
3488        self._check_arg_dir_exists('--system_ext', args.system_ext)
3489
3490
3491    def create_from_args(self, args):
3492        self.check_dirs_from_args(args)
3493
3494        generic_refs = self.get_generic_refs_from_args(args)
3495
3496        vndk_lib_dirs = VNDKLibDir.create_from_dirs(args.system, args.vendor)
3497
3498        if args.tag_file:
3499            tagged_paths = TaggedPathDict.create_from_csv_path(
3500                args.tag_file, vndk_lib_dirs)
3501        else:
3502            tagged_paths = None
3503
3504        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
3505                                 args.system_dir_ignored,
3506                                 args.vendor, args.vendor_dir_as_system,
3507                                 args.vendor_dir_ignored,
3508                                 args.product,
3509                                 args.system_ext,
3510                                 args.load_extra_deps,
3511                                 generic_refs=generic_refs,
3512                                 tagged_paths=tagged_paths,
3513                                 unzip_files=args.unzip_files)
3514
3515        return (generic_refs, graph, tagged_paths, vndk_lib_dirs)
3516
3517
3518class VNDKCommandBase(ELFGraphCommand):
3519    def add_argparser_options(self, parser):
3520        super(VNDKCommandBase, self).add_argparser_options(parser)
3521
3522        parser.add_argument(
3523            '--no-default-dlopen-deps', action='store_true',
3524            help='do not add default dlopen dependencies')
3525
3526        parser.add_argument(
3527            '--action-ineligible-vndk-sp', default='warn',
3528            help='action when a sp-hal uses non-vndk-sp libs '
3529                 '(option: follow,warn,ignore)')
3530
3531        parser.add_argument(
3532            '--action-ineligible-vndk', default='warn',
3533            help='action when a vendor lib/exe uses fwk-only libs '
3534                 '(option: follow,warn,ignore)')
3535
3536
3537    def create_from_args(self, args):
3538        """Create all essential data structures for VNDK computation."""
3539
3540        generic_refs, graph, tagged_paths, vndk_lib_dirs = \
3541            super(VNDKCommandBase, self).create_from_args(args)
3542
3543        if not args.no_default_dlopen_deps:
3544            script_dir = os.path.dirname(os.path.abspath(__file__))
3545            minimum_dlopen_deps = os.path.join(
3546                script_dir, 'datasets', 'minimum_dlopen_deps.txt')
3547            graph.add_dlopen_deps(minimum_dlopen_deps)
3548
3549        return (generic_refs, graph, tagged_paths, vndk_lib_dirs)
3550
3551
3552class VNDKCommand(VNDKCommandBase):
3553    def __init__(self):
3554        super(VNDKCommand, self).__init__(
3555            'vndk', help='Compute VNDK libraries set')
3556
3557
3558    def add_argparser_options(self, parser):
3559        super(VNDKCommand, self).add_argparser_options(parser)
3560
3561        parser.add_argument(
3562            '--warn-incorrect-partition', action='store_true',
3563            help='warn about libraries only have cross partition linkages')
3564
3565        parser.add_argument(
3566            '--full', action='store_true',
3567            help='print all classification')
3568
3569        parser.add_argument(
3570            '--output-format', default='tag',
3571            help='output format for vndk classification')
3572
3573        parser.add_argument(
3574            '--file-size-output',
3575            help='output file for calculated file sizes')
3576
3577
3578    def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
3579        for lib in lib_set.values():
3580            if not lib.num_users:
3581                continue
3582            if all((user.partition != partition for user in lib.users_all)):
3583                print(error_msg.format(lib.path), file=sys.stderr)
3584
3585
3586    def _warn_incorrect_partition(self, graph):
3587        self._warn_incorrect_partition_lib_set(
3588            graph.lib_pt[PT_VENDOR], PT_VENDOR,
3589            'warning: {}: This is a vendor library with framework-only '
3590            'usages.')
3591
3592        self._warn_incorrect_partition_lib_set(
3593            graph.lib_pt[PT_SYSTEM], PT_SYSTEM,
3594            'warning: {}: This is a framework library with vendor-only '
3595            'usages.')
3596
3597
3598    @staticmethod
3599    def _extract_simple_vndk_result(vndk_result):
3600        field_name_tags = [
3601            ('vndk_sp', 'vndk_sp'),
3602            ('vndk_sp_unused', 'vndk_sp'),
3603            ('vndk_sp_private', 'vndk_sp'),
3604            ('vndk_sp_private_unused', 'vndk_sp'),
3605
3606            ('vndk_sp_ext', 'vndk_sp_ext'),
3607            ('vndk_sp_private_ext', 'vndk_sp_ext'),
3608
3609            ('vndk_ext', 'extra_vendor_libs'),
3610            ('extra_vendor_libs', 'extra_vendor_libs'),
3611        ]
3612        results = SimpleVNDKResult()
3613        for field_name, tag in field_name_tags:
3614            getattr(results, tag).update(getattr(vndk_result, field_name))
3615        return results
3616
3617
3618    def _print_tags(self, vndk_lib, full, file=sys.stdout):
3619        if full:
3620            result_tags = _VNDK_RESULT_FIELD_NAMES
3621            results = vndk_lib
3622        else:
3623            # Simplified VNDK output with only three sets.
3624            result_tags = _SIMPLE_VNDK_RESULT_FIELD_NAMES
3625            results = self._extract_simple_vndk_result(vndk_lib)
3626
3627        for tag in result_tags:
3628            libs = getattr(results, tag)
3629            tag += ':'
3630            for lib in sorted_lib_path_list(libs):
3631                print(tag, lib, file=file)
3632
3633
3634    def _print_make(self, vndk_lib, file=sys.stdout):
3635        def get_module_name(path):
3636            return os.path.splitext(os.path.basename(path))[0]
3637
3638        def get_module_names(lib_set):
3639            return sorted({get_module_name(lib.path) for lib in lib_set})
3640
3641        results = self._extract_simple_vndk_result(vndk_lib)
3642        vndk_sp = get_module_names(results.vndk_sp)
3643        vndk_sp_ext = get_module_names(results.vndk_sp_ext)
3644        extra_vendor_libs = get_module_names(results.extra_vendor_libs)
3645
3646        def format_module_names(module_names):
3647            return '\\\n    ' +  ' \\\n    '.join(module_names)
3648
3649        script_dir = os.path.dirname(os.path.abspath(__file__))
3650        template_path = os.path.join(script_dir, 'templates', 'vndk.txt')
3651        with open(template_path, 'r') as f:
3652            template = f.read()
3653
3654        template = template.replace('##_VNDK_SP_##',
3655                                    format_module_names(vndk_sp))
3656        template = template.replace('##_VNDK_SP_EXT_##',
3657                                    format_module_names(vndk_sp_ext))
3658        template = template.replace('##_EXTRA_VENDOR_LIBS_##',
3659                                    format_module_names(extra_vendor_libs))
3660
3661        file.write(template)
3662
3663
3664    def _print_file_size_output(self, graph, vndk_lib, file=sys.stderr):
3665        def collect_tags(lib):
3666            tags = []
3667            for field_name in _VNDK_RESULT_FIELD_NAMES:
3668                if lib in getattr(vndk_lib, field_name):
3669                    tags.append(field_name)
3670            return ' '.join(tags)
3671
3672        writer = csv.writer(file, lineterminator='\n')
3673        writer.writerow(('Path', 'Tag', 'File size', 'RO segment file size',
3674                         'RO segment mem size', 'RW segment file size',
3675                         'RW segment mem size'))
3676
3677        # Print the file size of all ELF files.
3678        for lib in sorted(graph.all_libs()):
3679            writer.writerow((
3680                lib.path, collect_tags(lib), lib.elf.file_size,
3681                lib.elf.ro_seg_file_size, lib.elf.ro_seg_mem_size,
3682                lib.elf.rw_seg_file_size, lib.elf.rw_seg_mem_size))
3683
3684        # Calculate the summation of each sets.
3685        def calc_total_size(lib_set):
3686            total_file_size = 0
3687            total_ro_seg_file_size = 0
3688            total_ro_seg_mem_size = 0
3689            total_rw_seg_file_size = 0
3690            total_rw_seg_mem_size = 0
3691
3692            for lib in lib_set:
3693                total_file_size += lib.elf.file_size
3694                total_ro_seg_file_size += lib.elf.ro_seg_file_size
3695                total_ro_seg_mem_size += lib.elf.ro_seg_mem_size
3696                total_rw_seg_file_size += lib.elf.rw_seg_file_size
3697                total_rw_seg_mem_size += lib.elf.rw_seg_mem_size
3698
3699            return [total_file_size, total_ro_seg_file_size,
3700                    total_ro_seg_mem_size, total_rw_seg_file_size,
3701                    total_rw_seg_mem_size]
3702
3703        SEPARATOR = ('----------', None, None, None, None, None, None)
3704
3705        writer.writerow(SEPARATOR)
3706        for tag in _VNDK_RESULT_FIELD_NAMES:
3707            lib_set = getattr(vndk_lib, tag)
3708            lib_set = [lib for lib in lib_set if lib.elf.is_32bit]
3709            total = calc_total_size(lib_set)
3710            writer.writerow(['Subtotal ' + tag + ' (32-bit)', None] + total)
3711
3712        writer.writerow(SEPARATOR)
3713        for tag in _VNDK_RESULT_FIELD_NAMES:
3714            lib_set = getattr(vndk_lib, tag)
3715            lib_set = [lib for lib in lib_set if not lib.elf.is_32bit]
3716            total = calc_total_size(lib_set)
3717            writer.writerow(['Subtotal ' + tag + ' (64-bit)', None] + total)
3718
3719        writer.writerow(SEPARATOR)
3720        for tag in _VNDK_RESULT_FIELD_NAMES:
3721            total = calc_total_size(getattr(vndk_lib, tag))
3722            writer.writerow(['Subtotal ' + tag + ' (both)', None] + total)
3723
3724        # Calculate the summation of all ELF files.
3725        writer.writerow(SEPARATOR)
3726        writer.writerow(['Total', None] + calc_total_size(graph.all_libs()))
3727
3728
3729    def main(self, args):
3730        generic_refs, graph, tagged_paths, _ = self.create_from_args(args)
3731
3732        if args.warn_incorrect_partition:
3733            self._warn_incorrect_partition(graph)
3734
3735        # Compute vndk heuristics.
3736        vndk_lib = graph.compute_degenerated_vndk(
3737            generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
3738            args.action_ineligible_vndk)
3739
3740        # Print results.
3741        if args.output_format == 'make':
3742            self._print_make(vndk_lib)
3743        else:
3744            self._print_tags(vndk_lib, args.full)
3745
3746        # Calculate and print file sizes.
3747        if args.file_size_output:
3748            with open(args.file_size_output, 'w') as fp:
3749                self._print_file_size_output(graph, vndk_lib, file=fp)
3750        return 0
3751
3752
3753class DepsInsightCommand(VNDKCommandBase):
3754    def __init__(self):
3755        super(DepsInsightCommand, self).__init__(
3756            'deps-insight', help='Generate HTML to show dependencies')
3757
3758
3759    def add_argparser_options(self, parser):
3760        super(DepsInsightCommand, self).add_argparser_options(parser)
3761
3762        parser.add_argument('--module-info')
3763
3764        parser.add_argument('-o', '--output', required=True,
3765                            help='output directory')
3766
3767
3768    @staticmethod
3769    def serialize_data(libs, vndk_lib, module_info):
3770        strs = []
3771        strs_dict = dict()
3772
3773        libs.sort(key=lambda lib: lib.path)
3774        libs_dict = {lib: i for i, lib in enumerate(libs)}
3775
3776        def get_str_idx(s):
3777            try:
3778                return strs_dict[s]
3779            except KeyError:
3780                idx = len(strs)
3781                strs_dict[s] = idx
3782                strs.append(s)
3783                return idx
3784
3785        def collect_path_sorted_lib_idxs(libs):
3786            return [libs_dict[lib] for lib in sorted(libs)]
3787
3788        def collect_deps(lib):
3789            queue = list(lib.deps_all)
3790            visited = set(queue)
3791            visited.add(lib)
3792            deps = []
3793
3794            # Traverse dependencies with breadth-first search.
3795            while queue:
3796                # Collect dependencies for next queue.
3797                next_queue = []
3798                for lib in queue:
3799                    for dep in lib.deps_all:
3800                        if dep not in visited:
3801                            next_queue.append(dep)
3802                            visited.add(dep)
3803
3804                # Append current queue to result.
3805                deps.append(collect_path_sorted_lib_idxs(queue))
3806
3807                queue = next_queue
3808
3809            return deps
3810
3811        def collect_source_dir_paths(lib):
3812            return [get_str_idx(path)
3813                    for path in module_info.get_module_path(lib.path)]
3814
3815        def collect_tags(lib):
3816            tags = []
3817            for field_name in _VNDK_RESULT_FIELD_NAMES:
3818                if lib in getattr(vndk_lib, field_name):
3819                    tags.append(get_str_idx(field_name))
3820            return tags
3821
3822        mods = []
3823        for lib in libs:
3824            mods.append([get_str_idx(lib.path),
3825                         32 if lib.elf.is_32bit else 64,
3826                         collect_tags(lib),
3827                         collect_deps(lib),
3828                         collect_path_sorted_lib_idxs(lib.users_all),
3829                         collect_source_dir_paths(lib)])
3830
3831        return (strs, mods)
3832
3833
3834    def main(self, args):
3835        generic_refs, graph, tagged_paths, _ = self.create_from_args(args)
3836
3837        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
3838
3839        # Compute vndk heuristics.
3840        vndk_lib = graph.compute_degenerated_vndk(
3841            generic_refs, tagged_paths, args.action_ineligible_vndk_sp,
3842            args.action_ineligible_vndk)
3843
3844        # Serialize data.
3845        strs, mods = self.serialize_data(
3846            list(graph.all_libs()), vndk_lib, module_info)
3847
3848        # Generate output files.
3849        makedirs(args.output, exist_ok=True)
3850        script_dir = os.path.dirname(os.path.abspath(__file__))
3851        for name in ('index.html', 'insight.css', 'insight.js'):
3852            shutil.copyfile(
3853                os.path.join(script_dir, 'assets', 'insight', name),
3854                os.path.join(args.output, name))
3855
3856        with open(os.path.join(args.output, 'insight-data.js'), 'w') as f:
3857            f.write('''(function () {
3858    var strs = ''' + json.dumps(strs) + ''';
3859    var mods = ''' + json.dumps(mods) + ''';
3860    insight.init(document, strs, mods);
3861})();''')
3862
3863        return 0
3864
3865
3866class DepsCommand(ELFGraphCommand):
3867    def __init__(self):
3868        super(DepsCommand, self).__init__(
3869            'deps', help='Print binary dependencies for debugging')
3870
3871
3872    def add_argparser_options(self, parser):
3873        super(DepsCommand, self).add_argparser_options(parser)
3874
3875        parser.add_argument(
3876            '--revert', action='store_true',
3877            help='print usage dependency')
3878
3879        parser.add_argument(
3880            '--leaf', action='store_true',
3881            help='print binaries without dependencies or usages')
3882
3883        parser.add_argument(
3884            '--symbols', action='store_true',
3885            help='print symbols')
3886
3887        parser.add_argument(
3888            '--path-filter',
3889            help='filter paths by a regular expression')
3890
3891        parser.add_argument('--module-info')
3892
3893
3894    def main(self, args):
3895        _, graph, _, _ = self.create_from_args(args)
3896
3897        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
3898
3899        if args.path_filter:
3900            path_filter = re.compile(args.path_filter)
3901        else:
3902            path_filter = None
3903
3904        if args.symbols:
3905            def collect_symbols(user, definer):
3906                return user.get_dep_linked_symbols(definer)
3907        else:
3908            def collect_symbols(user, definer):
3909                return ()
3910
3911        results = []
3912        for partition in range(NUM_PARTITIONS):
3913            for name, lib in graph.lib_pt[partition].items():
3914                if path_filter and not path_filter.match(name):
3915                    continue
3916
3917                data = []
3918                if args.revert:
3919                    for assoc_lib in sorted(lib.users_all):
3920                        data.append((assoc_lib.path,
3921                                     collect_symbols(assoc_lib, lib)))
3922                else:
3923                    for assoc_lib in sorted(lib.deps_all):
3924                        data.append((assoc_lib.path,
3925                                     collect_symbols(lib, assoc_lib)))
3926                results.append((name, data))
3927        results.sort()
3928
3929        if args.leaf:
3930            for name, deps in results:
3931                if not deps:
3932                    print(name)
3933        else:
3934            delimiter = ''
3935            for name, assoc_libs in results:
3936                print(delimiter, end='')
3937                delimiter = '\n'
3938
3939                print(name)
3940                for module_path in module_info.get_module_path(name):
3941                    print('\tMODULE_PATH:', module_path)
3942                for assoc_lib, symbols in assoc_libs:
3943                    print('\t' + assoc_lib)
3944                    for module_path in module_info.get_module_path(assoc_lib):
3945                        print('\t\tMODULE_PATH:', module_path)
3946                    for symbol in symbols:
3947                        print('\t\t' + symbol)
3948        return 0
3949
3950
3951class DepsClosureCommand(ELFGraphCommand):
3952    def __init__(self):
3953        super(DepsClosureCommand, self).__init__(
3954            'deps-closure', help='Find transitive closure of dependencies')
3955
3956
3957    def add_argparser_options(self, parser):
3958        super(DepsClosureCommand, self).add_argparser_options(parser)
3959
3960        parser.add_argument('lib', nargs='*',
3961                            help='root set of the shared libraries')
3962
3963        parser.add_argument('--exclude-lib', action='append', default=[],
3964                            help='libraries to be excluded')
3965
3966        parser.add_argument('--exclude-ndk', action='store_true',
3967                            help='exclude ndk libraries')
3968
3969        parser.add_argument('--revert', action='store_true',
3970                            help='print usage dependency')
3971
3972        parser.add_argument('--enumerate', action='store_true',
3973                            help='print closure for each lib instead of union')
3974
3975
3976    def print_deps_closure(self, root_libs, graph, is_excluded_libs,
3977                           is_reverted, indent):
3978        if is_reverted:
3979            closure = graph.compute_users_closure(root_libs, is_excluded_libs)
3980        else:
3981            closure = graph.compute_deps_closure(root_libs, is_excluded_libs)
3982
3983        for lib in sorted_lib_path_list(closure):
3984            print(indent + lib)
3985
3986
3987    def main(self, args):
3988        _, graph, _, _ = self.create_from_args(args)
3989
3990        # Find root/excluded libraries by their paths.
3991        def report_error(path):
3992            print('error: no such lib: {}'.format(path), file=sys.stderr)
3993        root_libs = graph.get_libs(args.lib, report_error)
3994        excluded_libs = graph.get_libs(args.exclude_lib, report_error)
3995
3996        # Define the exclusion filter.
3997        if args.exclude_ndk:
3998            def is_excluded_libs(lib):
3999                return lib.is_ll_ndk or lib in excluded_libs
4000        else:
4001            def is_excluded_libs(lib):
4002                return lib in excluded_libs
4003
4004        if not args.enumerate:
4005            self.print_deps_closure(root_libs, graph, is_excluded_libs,
4006                                    args.revert, '')
4007        else:
4008            if not root_libs:
4009                root_libs = list(graph.all_libs())
4010            for lib in sorted(root_libs):
4011                print(lib.path)
4012                self.print_deps_closure({lib}, graph, is_excluded_libs,
4013                                        args.revert, '\t')
4014        return 0
4015
4016
4017class DepsUnresolvedCommand(ELFGraphCommand):
4018    def __init__(self):
4019        super(DepsUnresolvedCommand, self).__init__(
4020            'deps-unresolved',
4021            help='Show unresolved dt_needed entries or symbols')
4022
4023
4024    def add_argparser_options(self, parser):
4025        super(DepsUnresolvedCommand, self).add_argparser_options(parser)
4026        parser.add_argument('--module-info')
4027        parser.add_argument('--path-filter')
4028
4029
4030    def _dump_unresolved(self, lib, module_info, delimiter):
4031        if not lib.unresolved_symbols and not lib.unresolved_dt_needed:
4032            return
4033
4034        print(delimiter, end='')
4035        print(lib.path)
4036        for module_path in module_info.get_module_path(lib.path):
4037            print('\tMODULE_PATH:', module_path)
4038        for dt_needed in sorted(lib.unresolved_dt_needed):
4039            print('\tUNRESOLVED_DT_NEEDED:', dt_needed)
4040        for symbol in sorted(lib.unresolved_symbols):
4041            print('\tUNRESOLVED_SYMBOL:', symbol)
4042
4043
4044    def main(self, args):
4045        _, graph, _, _ = self.create_from_args(args)
4046        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
4047
4048        libs = graph.all_libs()
4049        if args.path_filter:
4050            path_filter = re.compile(args.path_filter)
4051            libs = [lib for lib in libs if path_filter.match(lib.path)]
4052
4053        delimiter = ''
4054        for lib in sorted(libs):
4055            self._dump_unresolved(lib, module_info, delimiter)
4056            delimiter = '\n'
4057
4058
4059class ApkDepsCommand(ELFGraphCommand):
4060    def __init__(self):
4061        super(ApkDepsCommand, self).__init__(
4062            'apk-deps', help='Print APK dependencies for debugging')
4063
4064
4065    def main(self, args):
4066        _, graph, _, _ = self.create_from_args(args)
4067
4068        apk_deps = scan_apk_dep(graph, args.system, args.vendor, args.product,
4069                                args.system_ext)
4070
4071        for apk_path, dep_paths in apk_deps:
4072            print(apk_path)
4073            for dep_path in dep_paths:
4074                print('\t' + dep_path)
4075
4076        return 0
4077
4078
4079class CheckDepCommandBase(ELFGraphCommand):
4080    def __init__(self, *args, **kwargs):
4081        super(CheckDepCommandBase, self).__init__(*args, **kwargs)
4082        self.delimiter = ''
4083
4084
4085    def add_argparser_options(self, parser):
4086        super(CheckDepCommandBase, self).add_argparser_options(parser)
4087        parser.add_argument('--module-info')
4088
4089
4090    def _print_delimiter(self):
4091        print(self.delimiter, end='')
4092        self.delimiter = '\n'
4093
4094
4095    def _dump_dep(self, lib, bad_deps, module_info):
4096        self._print_delimiter()
4097        print(lib.path)
4098        for module_path in module_info.get_module_path(lib.path):
4099            print('\tMODULE_PATH:', module_path)
4100        for dep in sorted(bad_deps):
4101            print('\t' + dep.path)
4102            for module_path in module_info.get_module_path(dep.path):
4103                print('\t\tMODULE_PATH:', module_path)
4104            for symbol in lib.get_dep_linked_symbols(dep):
4105                print('\t\t' + symbol)
4106
4107
4108    def _dump_apk_dep(self, apk_path, bad_deps, module_info):
4109        self._print_delimiter()
4110        print(apk_path)
4111        for module_path in module_info.get_module_path(apk_path):
4112            print('\tMODULE_PATH:', module_path)
4113        for dep_path in sorted(bad_deps):
4114            print('\t' + dep_path)
4115            for module_path in module_info.get_module_path(dep_path):
4116                print('\t\tMODULE_PATH:', module_path)
4117
4118
4119class CheckDepCommand(CheckDepCommandBase):
4120    def __init__(self):
4121        super(CheckDepCommand, self).__init__(
4122            'check-dep', help='Check the eligible dependencies')
4123
4124
4125    def add_argparser_options(self, parser):
4126        super(CheckDepCommand, self).add_argparser_options(parser)
4127
4128        group = parser.add_mutually_exclusive_group()
4129
4130        group.add_argument('--check-apk', action='store_true', default=False,
4131                           help='Check JNI dependencies in APK files')
4132
4133        group.add_argument('--no-check-apk', action='store_false',
4134                           dest='check_apk',
4135                           help='Do not check JNI dependencies in APK files')
4136
4137        group = parser.add_mutually_exclusive_group()
4138
4139        group.add_argument('--check-dt-needed-ordering',
4140                           action='store_true', default=False,
4141                           help='Check ordering of DT_NEEDED entries')
4142
4143        group.add_argument('--no-check-dt-needed-ordering',
4144                           action='store_false',
4145                           dest='check_dt_needed_ordering',
4146                           help='Do not check ordering of DT_NEEDED entries')
4147
4148
4149    def _load_public_lib_names(self, system_dirs, vendor_dirs):
4150        names = PublicLibSet()
4151        for base in itertools.chain(system_dirs, vendor_dirs):
4152            config_path = os.path.join(base, 'etc', 'public.libraries.txt')
4153            try:
4154                names.load_from_public_libraries_txt(config_path)
4155            except FileNotFoundError:
4156                pass
4157        return names
4158
4159
4160    def _check_vendor_dep(self, graph, tagged_libs, lib_properties,
4161                          module_info, public_libs):
4162        """Check whether vendor libs are depending on non-eligible libs."""
4163        num_errors = 0
4164
4165        vendor_libs = set(graph.lib_pt[PT_VENDOR].values())
4166        vendor_libs.update(graph.lib_pt[PT_PRODUCT].values())
4167
4168        eligible_libs = (tagged_libs.ll_ndk | tagged_libs.vndk_sp |
4169                         tagged_libs.vndk_sp_private | tagged_libs.vndk)
4170
4171        def _is_app_lib(lib):
4172            app_dirs = [
4173                '/product/app',
4174                '/product/priv-app',
4175                '/system_ext/app',
4176                '/system_ext/priv-app',
4177                '/vendor/app',
4178                '/vendor/priv-app',
4179            ]
4180            return any(_is_under_dir(d, lib.path) for d in app_dirs)
4181
4182        for lib in sorted(vendor_libs):
4183            bad_deps = set()
4184
4185            # Check whether vendor modules depend on extended NDK symbols.
4186            for dep, symbols in lib.imported_ext_symbols.items():
4187                if dep.is_ll_ndk:
4188                    num_errors += 1
4189                    bad_deps.add(dep)
4190                    for symbol in symbols:
4191                        print('error: vendor lib "{}" depends on extended '
4192                              'NDK symbol "{}" from "{}".'
4193                              .format(lib.path, symbol, dep.path),
4194                              file=sys.stderr)
4195
4196            # Check whether vendor modules depend on ineligible libs.
4197            for dep in lib.deps_all:
4198                if dep not in vendor_libs and dep not in eligible_libs:
4199                    if _is_app_lib(lib) and public_libs.is_public_lib(dep.path):
4200                        # It is fine for APK files to depend on public
4201                        # libraries (including NDK or other explicitly exposed
4202                        # libs).
4203                        continue
4204
4205                    num_errors += 1
4206                    bad_deps.add(dep)
4207
4208                    dep_name = os.path.splitext(os.path.basename(dep.path))[0]
4209                    dep_properties = lib_properties.get(dep_name)
4210                    if not dep_properties.vendor_available:
4211                        print('error: vendor lib "{}" depends on non-eligible '
4212                              'lib "{}".'.format(lib.path, dep.path),
4213                              file=sys.stderr)
4214                    elif dep_properties.vndk_sp:
4215                        print('error: vendor lib "{}" depends on vndk-sp "{}" '
4216                              'but it must be copied to '
4217                              '/system/lib[64]/vndk-sp.'
4218                              .format(lib.path, dep.path),
4219                              file=sys.stderr)
4220                    elif dep_properties.vndk:
4221                        print('error: vendor lib "{}" depends on vndk "{}" '
4222                              'but it must be copied to /system/lib[64]/vndk.'
4223                              .format(lib.path, dep.path),
4224                              file=sys.stderr)
4225                    else:
4226                        print('error: vendor lib "{}" depends on '
4227                              'vendor_available "{}" but it must be copied to '
4228                              '/vendor/lib[64].'.format(lib.path, dep.path),
4229                              file=sys.stderr)
4230
4231            if bad_deps:
4232                self._dump_dep(lib, bad_deps, module_info)
4233
4234        return num_errors
4235
4236
4237    def _check_dt_needed_ordering(self, graph):
4238        """Check DT_NEEDED entries order of all libraries"""
4239
4240        num_errors = 0
4241
4242        def _is_libc_prior_to_libdl(lib):
4243            dt_needed = lib.elf.dt_needed
4244            try:
4245                return dt_needed.index('libc.so') < dt_needed.index('libdl.so')
4246            except ValueError:
4247                return True
4248
4249        for lib in sorted(graph.all_libs()):
4250            if _is_libc_prior_to_libdl(lib):
4251                continue
4252
4253            print('error: The ordering of DT_NEEDED entries in "{}" may be '
4254                  'problematic.  libc.so must be prior to libdl.so.  '
4255                  'But found: {}.'
4256                  .format(lib.path, lib.elf.dt_needed), file=sys.stderr)
4257
4258            num_errors += 1
4259
4260        return num_errors
4261
4262
4263    def _check_apk_dep(self, graph, system_dirs, vendor_dirs, product_dirs,
4264                       system_ext_dirs, module_info):
4265        num_errors = 0
4266
4267        def is_in_system_partition(path):
4268            return path.startswith('/system/') or \
4269                   path.startswith('/product/') or \
4270                   path.startswith('/oem/')
4271
4272        apk_deps = scan_apk_dep(graph, system_dirs, vendor_dirs, product_dirs,
4273                                system_ext_dirs)
4274
4275        for apk_path, dep_paths in apk_deps:
4276            apk_in_system = is_in_system_partition(apk_path)
4277            bad_deps = []
4278            for dep_path in dep_paths:
4279                dep_in_system = is_in_system_partition(dep_path)
4280                if apk_in_system != dep_in_system:
4281                    bad_deps.append(dep_path)
4282                    print('error: apk "{}" has cross-partition dependency '
4283                          'lib "{}".'.format(apk_path, dep_path),
4284                          file=sys.stderr)
4285                    num_errors += 1
4286            if bad_deps:
4287                self._dump_apk_dep(apk_path, sorted(bad_deps), module_info)
4288        return num_errors
4289
4290
4291    def main(self, args):
4292        generic_refs, graph, tagged_paths, vndk_lib_dirs = \
4293            self.create_from_args(args)
4294
4295        tagged_paths = TaggedPathDict.create_from_csv_path(
4296            args.tag_file, vndk_lib_dirs)
4297        tagged_libs = TaggedLibDict.create_from_graph(
4298            graph, tagged_paths, generic_refs)
4299
4300        module_info = ModuleInfo.load_from_path_or_default(args.module_info)
4301
4302        lib_properties_path = \
4303            LibProperties.get_lib_properties_file_path(args.tag_file)
4304        lib_properties = \
4305            LibProperties.load_from_path_or_default(lib_properties_path)
4306
4307        public_libs = self._load_public_lib_names(args.system, args.vendor)
4308
4309        num_errors = self._check_vendor_dep(graph, tagged_libs, lib_properties,
4310                                            module_info, public_libs)
4311
4312        if args.check_dt_needed_ordering:
4313            num_errors += self._check_dt_needed_ordering(graph)
4314
4315        if args.check_apk:
4316            num_errors += self._check_apk_dep(
4317                graph, args.system, args.vendor, args.product, args.system_ext,
4318                module_info)
4319
4320        return 0 if num_errors == 0 else 1
4321
4322
4323class DumpDexStringCommand(Command):
4324    def __init__(self):
4325        super(DumpDexStringCommand, self).__init__(
4326            'dump-dex-string',
4327            help='Dump string literals defined in a dex file')
4328
4329
4330    def add_argparser_options(self, parser):
4331        super(DumpDexStringCommand, self).add_argparser_options(parser)
4332
4333        parser.add_argument('dex_file', help='path to an input dex file')
4334
4335
4336    def main(self, args):
4337        for string in DexFileReader.enumerate_dex_strings(args.dex_file):
4338            try:
4339                print(string)
4340            except (UnicodeEncodeError, UnicodeDecodeError):
4341                print(repr(string))
4342
4343
4344class DepGraphCommand(ELFGraphCommand):
4345    def __init__(self):
4346        super(DepGraphCommand, self).__init__(
4347            'dep-graph', help='Visualize violating dependencies with HTML')
4348
4349
4350    def add_argparser_options(self, parser):
4351        super(DepGraphCommand, self).add_argparser_options(
4352            parser, is_tag_file_required=True)
4353
4354        parser.add_argument('-o', '--output', required=True,
4355                            help='output directory')
4356
4357
4358    @staticmethod
4359    def _create_tag_hierarchy():
4360        hierarchy = dict()
4361        for tag in TaggedPathDict.TAGS:
4362            if tag in {'sp_hal', 'sp_hal_dep', 'vendor_only'}:
4363                hierarchy[tag] = 'vendor.private.{}'.format(tag)
4364            else:
4365                vendor_visible = TaggedPathDict.is_tag_visible(
4366                    'vendor_only', tag)
4367                pub = 'public' if vendor_visible else 'private'
4368                hierarchy[tag] = 'system.{}.{}'.format(pub, tag)
4369        return hierarchy
4370
4371
4372    @staticmethod
4373    def _get_lib_tag(hierarchy, tagged_paths, lib):
4374        return hierarchy[tagged_paths.get_path_tag(lib.path)]
4375
4376
4377    @staticmethod
4378    def _is_dep_allowed(user_tag, dep_tag):
4379        user_partition, _, _ = user_tag.split('.')
4380        dep_partition, dep_visibility, _ = dep_tag.split('.')
4381        if user_partition == 'system' and dep_partition == 'vendor':
4382            return False
4383        if user_partition == 'vendor' and dep_partition == 'system':
4384            if dep_visibility == 'private':
4385                return False
4386        return True
4387
4388
4389    def _get_dep_graph(self, graph, tagged_paths):
4390        hierarchy = self._create_tag_hierarchy()
4391
4392        # Build data and violate_libs.
4393        data = []
4394        violate_libs = collections.defaultdict(list)
4395
4396        for lib in graph.all_libs():
4397            lib_tag = self._get_lib_tag(hierarchy, tagged_paths, lib)
4398            lib_item = {
4399                'name': lib.path,
4400                'tag': lib_tag,
4401                'depends': [],
4402                'violates': [],
4403            }
4404            violate_count = 0
4405            for dep in lib.deps_all:
4406                dep_tag = self._get_lib_tag(hierarchy, tagged_paths, dep)
4407                if self._is_dep_allowed(lib_tag, dep_tag):
4408                    lib_item['depends'].append(dep.path)
4409                else:
4410                    lib_item['violates'].append([
4411                        dep.path, lib.get_dep_linked_symbols(dep)])
4412                    violate_count += 1
4413            lib_item['violate_count'] = violate_count
4414            if violate_count > 0:
4415                violate_libs[lib_tag].append((lib.path, violate_count))
4416            data.append(lib_item)
4417
4418        # Sort data and violate_libs.
4419        data.sort(
4420            key=lambda lib_item: (lib_item['tag'], lib_item['violate_count']))
4421        for libs in violate_libs.values():
4422            libs.sort(key=lambda violate_item: violate_item[1], reverse=True)
4423
4424        return data, violate_libs
4425
4426
4427    def main(self, args):
4428        _, graph, tagged_paths, _ = self.create_from_args(args)
4429
4430        data, violate_libs = self._get_dep_graph(graph, tagged_paths)
4431
4432        makedirs(args.output, exist_ok=True)
4433        script_dir = os.path.dirname(os.path.abspath(__file__))
4434        for name in ('index.html', 'dep-graph.js', 'dep-graph.css'):
4435            shutil.copyfile(os.path.join(script_dir, 'assets', 'visual', name),
4436                            os.path.join(args.output, name))
4437        with open(os.path.join(args.output, 'dep-data.js'), 'w') as f:
4438            f.write('var violatedLibs = ' + json.dumps(violate_libs) + ';\n')
4439            f.write('var depData = ' + json.dumps(data) + ';\n')
4440
4441        return 0
4442
4443
4444def main():
4445    parser = argparse.ArgumentParser()
4446    subparsers = parser.add_subparsers(dest='subcmd')
4447    subcmds = dict()
4448
4449    def register_subcmd(cmd):
4450        subcmds[cmd.name] = cmd
4451        cmd.add_argparser_options(
4452            subparsers.add_parser(cmd.name, help=cmd.help))
4453
4454    register_subcmd(ELFDumpCommand())
4455    register_subcmd(CreateGenericRefCommand())
4456    register_subcmd(VNDKCommand())
4457    register_subcmd(DepsCommand())
4458    register_subcmd(DepsClosureCommand())
4459    register_subcmd(DepsInsightCommand())
4460    register_subcmd(DepsUnresolvedCommand())
4461    register_subcmd(ApkDepsCommand())
4462    register_subcmd(CheckDepCommand())
4463    register_subcmd(DepGraphCommand())
4464    register_subcmd(DumpDexStringCommand())
4465
4466    args = parser.parse_args()
4467    if not args.subcmd:
4468        parser.print_help()
4469        sys.exit(1)
4470    return subcmds[args.subcmd].main(args)
4471
4472if __name__ == '__main__':
4473    sys.exit(main())
4474