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 # Sanity check: 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