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