1#!/usr/bin/env python3 2# 3# Copyright (c) 2016, 2020 Intel Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7# Based on a script by: 8# Chereau, Fabien <fabien.chereau@intel.com> 9 10""" 11Process an ELF file to generate size report on RAM and ROM. 12""" 13 14import argparse 15import locale 16import os 17import sys 18import re 19from pathlib import Path 20import json 21 22from packaging import version 23 24from colorama import init, Fore 25 26from anytree import RenderTree, NodeMixin, findall_by_attr 27from anytree.exporter import DictExporter 28 29import elftools 30from elftools.elf.elffile import ELFFile 31from elftools.elf.sections import SymbolTableSection 32from elftools.dwarf.descriptions import describe_form_class 33from elftools.dwarf.descriptions import ( 34 describe_DWARF_expr, set_global_machine_arch) 35from elftools.dwarf.locationlists import ( 36 LocationExpr, LocationParser) 37 38if version.parse(elftools.__version__) < version.parse('0.24'): 39 sys.exit("pyelftools is out of date, need version 0.24 or later") 40 41 42# ELF section flags 43SHF_WRITE = 0x1 44SHF_ALLOC = 0x2 45SHF_EXEC = 0x4 46SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC 47SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC 48 49DT_LOCATION = re.compile(r"\(DW_OP_addr: ([0-9a-f]+)\)") 50 51SRC_FILE_EXT = ('.h', '.c', '.hpp', '.cpp', '.hxx', '.cxx', '.c++') 52 53 54def get_symbol_addr(sym): 55 """Get the address of a symbol""" 56 return sym['st_value'] 57 58 59def get_symbol_size(sym): 60 """Get the size of a symbol""" 61 return sym['st_size'] 62 63 64def is_symbol_in_ranges(sym, ranges): 65 """ 66 Given a list of start/end addresses, test if the symbol 67 lies within any of these address ranges. 68 """ 69 for bound in ranges: 70 if bound['start'] <= sym['st_value'] <= bound['end']: 71 return True 72 73 return False 74 75 76def get_die_mapped_address(die, parser, dwarfinfo): 77 """Get the bounding addresses from a DIE variable or subprogram""" 78 low = None 79 high = None 80 81 if die.tag == 'DW_TAG_variable': 82 if 'DW_AT_location' in die.attributes: 83 loc_attr = die.attributes['DW_AT_location'] 84 if parser.attribute_has_location(loc_attr, die.cu['version']): 85 loc = parser.parse_from_attribute(loc_attr, die.cu['version']) 86 if isinstance(loc, LocationExpr): 87 addr = describe_DWARF_expr(loc.loc_expr, 88 dwarfinfo.structs) 89 90 matcher = DT_LOCATION.match(addr) 91 if matcher: 92 low = int(matcher.group(1), 16) 93 high = low + 1 94 95 if die.tag == 'DW_TAG_subprogram': 96 if 'DW_AT_low_pc' in die.attributes: 97 low = die.attributes['DW_AT_low_pc'].value 98 99 high_pc = die.attributes['DW_AT_high_pc'] 100 high_pc_class = describe_form_class(high_pc.form) 101 if high_pc_class == 'address': 102 high = high_pc.value 103 elif high_pc_class == 'constant': 104 high = low + high_pc.value 105 106 return low, high 107 108 109def match_symbol_address(symlist, die, parser, dwarfinfo): 110 """ 111 Find the symbol from a symbol list 112 where it matches the address in DIE variable, 113 or within the range of a DIE subprogram. 114 """ 115 low, high = get_die_mapped_address(die, parser, dwarfinfo) 116 117 if low is None: 118 return None 119 120 for sym in symlist: 121 if low <= sym['symbol']['st_value'] < high: 122 return sym 123 124 return None 125 126 127def get_symbols(elf, addr_ranges): 128 """ 129 Fetch the symbols from the symbol table and put them 130 into ROM, RAM buckets. 131 """ 132 rom_syms = dict() 133 ram_syms = dict() 134 unassigned_syms = dict() 135 136 rom_addr_ranges = addr_ranges['rom'] 137 ram_addr_ranges = addr_ranges['ram'] 138 139 for section in elf.iter_sections(): 140 if isinstance(section, SymbolTableSection): 141 for sym in section.iter_symbols(): 142 # Ignore symbols with size == 0 143 if get_symbol_size(sym) == 0: 144 continue 145 146 found_sec = False 147 entry = {'name': sym.name, 148 'symbol': sym, 149 'mapped_files': set()} 150 151 # If symbol is in ROM area? 152 if is_symbol_in_ranges(sym, rom_addr_ranges): 153 if sym.name not in rom_syms: 154 rom_syms[sym.name] = list() 155 rom_syms[sym.name].append(entry) 156 found_sec = True 157 158 # If symbol is in RAM area? 159 if is_symbol_in_ranges(sym, ram_addr_ranges): 160 if sym.name not in ram_syms: 161 ram_syms[sym.name] = list() 162 ram_syms[sym.name].append(entry) 163 found_sec = True 164 165 if not found_sec: 166 unassigned_syms['sym_name'] = entry 167 168 ret = {'rom': rom_syms, 169 'ram': ram_syms, 170 'unassigned': unassigned_syms} 171 return ret 172 173 174def get_section_ranges(elf): 175 """ 176 Parse ELF header to find out the address ranges of ROM or RAM sections 177 and their total sizes. 178 """ 179 rom_addr_ranges = list() 180 ram_addr_ranges = list() 181 rom_size = 0 182 ram_size = 0 183 184 for section in elf.iter_sections(): 185 size = section['sh_size'] 186 sec_start = section['sh_addr'] 187 sec_end = sec_start + size - 1 188 bound = {'start': sec_start, 'end': sec_end} 189 190 if section['sh_type'] == 'SHT_NOBITS': 191 # BSS and noinit sections 192 ram_addr_ranges.append(bound) 193 ram_size += size 194 elif section['sh_type'] == 'SHT_PROGBITS': 195 # Sections to be in flash or memory 196 flags = section['sh_flags'] 197 if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC: 198 # Text section 199 rom_addr_ranges.append(bound) 200 rom_size += size 201 elif (flags & SHF_WRITE_ALLOC) == SHF_WRITE_ALLOC: 202 # Data occupies both ROM and RAM 203 # since at boot, content is copied from ROM to RAM 204 rom_addr_ranges.append(bound) 205 rom_size += size 206 207 ram_addr_ranges.append(bound) 208 ram_size += size 209 elif (flags & SHF_ALLOC) == SHF_ALLOC: 210 # Read only data 211 rom_addr_ranges.append(bound) 212 rom_size += size 213 214 ret = {'rom': rom_addr_ranges, 215 'rom_total_size': rom_size, 216 'ram': ram_addr_ranges, 217 'ram_total_size': ram_size} 218 return ret 219 220 221def get_die_filename(die, lineprog): 222 """Get the source code filename associated with a DIE""" 223 file_index = die.attributes['DW_AT_decl_file'].value 224 file_entry = lineprog['file_entry'][file_index - 1] 225 226 dir_index = file_entry['dir_index'] 227 if dir_index == 0: 228 filename = file_entry.name 229 else: 230 directory = lineprog.header['include_directory'][dir_index - 1] 231 filename = os.path.join(directory, file_entry.name) 232 233 path = Path(filename.decode(locale.getpreferredencoding())) 234 235 # Prepend output path to relative path 236 if not path.is_absolute(): 237 output = Path(args.output) 238 path = output.joinpath(path) 239 240 # Change path to relative to Zephyr base 241 try: 242 path = path.resolve() 243 except OSError as e: 244 # built-ins can't be resolved, so it's not an issue 245 if '<built-in>' not in str(path): 246 raise e 247 248 return path 249 250 251def do_simple_name_matching(elf, symbol_dict, processed): 252 """ 253 Sequentially process DIEs in compiler units with direct file mappings 254 within the DIEs themselves, and do simply matching between DIE names 255 and symbol names. 256 """ 257 mapped_symbols = processed['mapped_symbols'] 258 mapped_addresses = processed['mapped_addr'] 259 unmapped_symbols = processed['unmapped_symbols'] 260 newly_mapped_syms = set() 261 262 dwarfinfo = elf.get_dwarf_info() 263 location_lists = dwarfinfo.location_lists() 264 location_parser = LocationParser(location_lists) 265 266 unmapped_dies = set() 267 268 # Loop through all compile units 269 for compile_unit in dwarfinfo.iter_CUs(): 270 lineprog = dwarfinfo.line_program_for_CU(compile_unit) 271 if lineprog is None: 272 continue 273 274 # Loop through each DIE and find variables and 275 # subprograms (i.e. functions) 276 for die in compile_unit.iter_DIEs(): 277 sym_name = None 278 279 # Process variables 280 if die.tag == 'DW_TAG_variable': 281 # DW_AT_declaration 282 283 # having 'DW_AT_location' means this maps 284 # to an actual address (e.g. not an extern) 285 if 'DW_AT_location' in die.attributes: 286 sym_name = die.get_full_path() 287 288 # Process subprograms (i.e. functions) if they are valid 289 if die.tag == 'DW_TAG_subprogram': 290 # Refer to another DIE for name 291 if ('DW_AT_abstract_origin' in die.attributes) or ( 292 'DW_AT_specification' in die.attributes): 293 unmapped_dies.add(die) 294 295 # having 'DW_AT_low_pc' means it maps to 296 # an actual address 297 elif 'DW_AT_low_pc' in die.attributes: 298 # DW_AT_low_pc == 0 is a weak function 299 # which has been overriden 300 if die.attributes['DW_AT_low_pc'].value != 0: 301 sym_name = die.get_full_path() 302 303 # For mangled function names, the linkage name 304 # is what appears in the symbol list 305 if 'DW_AT_linkage_name' in die.attributes: 306 linkage = die.attributes['DW_AT_linkage_name'] 307 sym_name = linkage.value.decode() 308 309 if sym_name is not None: 310 # Skip DIE with no reference back to a file 311 if not 'DW_AT_decl_file' in die.attributes: 312 continue 313 314 is_die_mapped = False 315 if sym_name in symbol_dict: 316 mapped_symbols.add(sym_name) 317 symlist = symbol_dict[sym_name] 318 symbol = match_symbol_address(symlist, die, 319 location_parser, 320 dwarfinfo) 321 322 if symbol is not None: 323 symaddr = symbol['symbol']['st_value'] 324 if symaddr not in mapped_addresses: 325 is_die_mapped = True 326 path = get_die_filename(die, lineprog) 327 symbol['mapped_files'].add(path) 328 mapped_addresses.add(symaddr) 329 newly_mapped_syms.add(sym_name) 330 331 if not is_die_mapped: 332 unmapped_dies.add(die) 333 334 mapped_symbols = mapped_symbols.union(newly_mapped_syms) 335 unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms) 336 337 processed['mapped_symbols'] = mapped_symbols 338 processed['mapped_addr'] = mapped_addresses 339 processed['unmapped_symbols'] = unmapped_symbols 340 processed['unmapped_dies'] = unmapped_dies 341 342 343def mark_address_aliases(symbol_dict, processed): 344 """ 345 Mark symbol aliases as already mapped to prevent 346 double counting. 347 348 There are functions and variables which are aliases to 349 other functions/variables. So this marks them as mapped 350 so they will not get counted again when a tree is being 351 built for display. 352 """ 353 mapped_symbols = processed['mapped_symbols'] 354 mapped_addresses = processed['mapped_addr'] 355 unmapped_symbols = processed['unmapped_symbols'] 356 already_mapped_syms = set() 357 358 for ums in unmapped_symbols: 359 for one_sym in symbol_dict[ums]: 360 symbol = one_sym['symbol'] 361 if symbol['st_value'] in mapped_addresses: 362 already_mapped_syms.add(ums) 363 364 mapped_symbols = mapped_symbols.union(already_mapped_syms) 365 unmapped_symbols = unmapped_symbols.difference(already_mapped_syms) 366 367 processed['mapped_symbols'] = mapped_symbols 368 processed['mapped_addr'] = mapped_addresses 369 processed['unmapped_symbols'] = unmapped_symbols 370 371 372def do_address_range_matching(elf, symbol_dict, processed): 373 """ 374 Match symbols indirectly using address ranges. 375 376 This uses the address ranges of DIEs and map them to symbols 377 residing within those ranges, and works on DIEs that have not 378 been mapped in previous steps. This works on symbol names 379 that do not match the names in DIEs, e.g. "<func>" in DIE, 380 but "<func>.constprop.*" in symbol name list. This also 381 helps with mapping the mangled function names in C++, 382 since the names in DIE are actual function names in source 383 code and not mangled version of them. 384 """ 385 if 'unmapped_dies' not in processed: 386 return 387 388 mapped_symbols = processed['mapped_symbols'] 389 mapped_addresses = processed['mapped_addr'] 390 unmapped_symbols = processed['unmapped_symbols'] 391 newly_mapped_syms = set() 392 393 dwarfinfo = elf.get_dwarf_info() 394 location_lists = dwarfinfo.location_lists() 395 location_parser = LocationParser(location_lists) 396 397 unmapped_dies = processed['unmapped_dies'] 398 399 # Group DIEs by compile units 400 cu_list = dict() 401 402 for die in unmapped_dies: 403 cu = die.cu 404 if cu not in cu_list: 405 cu_list[cu] = {'dies': set()} 406 cu_list[cu]['dies'].add(die) 407 408 # Loop through all compile units 409 for cu in cu_list: 410 lineprog = dwarfinfo.line_program_for_CU(cu) 411 412 # Map offsets from DIEs 413 offset_map = dict() 414 for die in cu.iter_DIEs(): 415 offset_map[die.offset] = die 416 417 for die in cu_list[cu]['dies']: 418 if not die.tag == 'DW_TAG_subprogram': 419 continue 420 421 path = None 422 423 # Has direct reference to file, so use it 424 if 'DW_AT_decl_file' in die.attributes: 425 path = get_die_filename(die, lineprog) 426 427 # Loop through indirect reference until a direct 428 # reference to file is found 429 if ('DW_AT_abstract_origin' in die.attributes) or ( 430 'DW_AT_specification' in die.attributes): 431 die_ptr = die 432 while path is None: 433 if not (die_ptr.tag == 'DW_TAG_subprogram') or not ( 434 ('DW_AT_abstract_origin' in die_ptr.attributes) or 435 ('DW_AT_specification' in die_ptr.attributes)): 436 break 437 438 if 'DW_AT_abstract_origin' in die_ptr.attributes: 439 ofname = 'DW_AT_abstract_origin' 440 elif 'DW_AT_specification' in die_ptr.attributes: 441 ofname = 'DW_AT_specification' 442 443 offset = die_ptr.attributes[ofname].value 444 offset += die_ptr.cu.cu_offset 445 446 # There is nothing to reference so no need to continue 447 if offset not in offset_map: 448 break 449 450 die_ptr = offset_map[offset] 451 if 'DW_AT_decl_file' in die_ptr.attributes: 452 path = get_die_filename(die_ptr, lineprog) 453 454 # Nothing to map 455 if path is not None: 456 low, high = get_die_mapped_address(die, location_parser, 457 dwarfinfo) 458 if low is None: 459 continue 460 461 for ums in unmapped_symbols: 462 for one_sym in symbol_dict[ums]: 463 symbol = one_sym['symbol'] 464 symaddr = symbol['st_value'] 465 466 if symaddr not in mapped_addresses: 467 if low <= symaddr < high: 468 one_sym['mapped_files'].add(path) 469 mapped_addresses.add(symaddr) 470 newly_mapped_syms.add(ums) 471 472 mapped_symbols = mapped_symbols.union(newly_mapped_syms) 473 unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms) 474 475 processed['mapped_symbols'] = mapped_symbols 476 processed['mapped_addr'] = mapped_addresses 477 processed['unmapped_symbols'] = unmapped_symbols 478 479 480def set_root_path_for_unmapped_symbols(symbol_dict, addr_range, processed): 481 """ 482 Set root path for unmapped symbols. 483 484 Any unmapped symbols are added under the root node if those 485 symbols reside within the desired memory address ranges 486 (e.g. ROM or RAM). 487 """ 488 mapped_symbols = processed['mapped_symbols'] 489 mapped_addresses = processed['mapped_addr'] 490 unmapped_symbols = processed['unmapped_symbols'] 491 newly_mapped_syms = set() 492 493 for ums in unmapped_symbols: 494 for one_sym in symbol_dict[ums]: 495 symbol = one_sym['symbol'] 496 symaddr = symbol['st_value'] 497 498 if is_symbol_in_ranges(symbol, addr_range): 499 if symaddr not in mapped_addresses: 500 path = Path(':') 501 one_sym['mapped_files'].add(path) 502 mapped_addresses.add(symaddr) 503 newly_mapped_syms.add(ums) 504 505 mapped_symbols = mapped_symbols.union(newly_mapped_syms) 506 unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms) 507 508 processed['mapped_symbols'] = mapped_symbols 509 processed['mapped_addr'] = mapped_addresses 510 processed['unmapped_symbols'] = unmapped_symbols 511 512def find_common_path_prefix(symbol_dict): 513 """ 514 Find the common path prefix of all mapped files. 515 Must be called before set_root_path_for_unmapped_symbols(). 516 """ 517 paths = list() 518 519 for _, sym in symbol_dict.items(): 520 for symbol in sym: 521 for file in symbol['mapped_files']: 522 paths.append(file) 523 524 try: 525 return os.path.commonpath(paths) 526 except ValueError: 527 return None 528 529class TreeNode(NodeMixin): 530 """ 531 A symbol node. 532 """ 533 534 def __init__(self, name, identifier, size=0, parent=None, children=None): 535 super().__init__() 536 self._name = name 537 self._size = size 538 self.parent = parent 539 self._identifier = identifier 540 if children: 541 self.children = children 542 543 def __repr__(self): 544 return self._name 545 546 547def sum_node_children_size(node): 548 """ 549 Calculate the sum of symbol size of all direct children. 550 """ 551 size = 0 552 553 for child in node.children: 554 size += child._size 555 556 return size 557 558 559def generate_any_tree(symbol_dict, total_size, path_prefix): 560 """ 561 Generate a symbol tree for output. 562 """ 563 root = TreeNode('Root', "root") 564 node_no_paths = TreeNode('(no paths)', ":", parent=root) 565 566 if path_prefix and Path(path_prefix) == Path(args.zephyrbase): 567 # All source files are under ZEPHYR_BASE so there is 568 # no need for another level. 569 node_zephyr_base = root 570 node_output_dir = root 571 node_workspace = root 572 node_others = root 573 else: 574 node_zephyr_base = TreeNode('ZEPHYR_BASE', args.zephyrbase) 575 node_output_dir = TreeNode('OUTPUT_DIR', args.output) 576 node_others = TreeNode("/", "/") 577 578 if args.workspace: 579 node_workspace = TreeNode('WORKSPACE', args.workspace) 580 else: 581 node_workspace = node_others 582 583 # A set of helper function for building a simple tree with a path-like 584 # hierarchy. 585 def _insert_one_elem(root, path, size): 586 cur = None 587 node = None 588 parent = root 589 for part in path.parts: 590 if cur is None: 591 cur = part 592 else: 593 cur = str(Path(cur, part)) 594 595 results = findall_by_attr(root, cur, name="_identifier") 596 if results: 597 item = results[0] 598 item._size += size 599 parent = item 600 else: 601 if node: 602 parent = node 603 node = TreeNode(name=str(part), identifier=cur, size=size, parent=parent) 604 605 # Mapping paths to tree nodes 606 path_node_map = [ 607 [Path(args.zephyrbase), node_zephyr_base], 608 [Path(args.output), node_output_dir], 609 ] 610 611 if args.workspace: 612 path_node_map.append( 613 [Path(args.workspace), node_workspace] 614 ) 615 616 for name, sym in symbol_dict.items(): 617 for symbol in sym: 618 size = get_symbol_size(symbol['symbol']) 619 for file in symbol['mapped_files']: 620 path = Path(file, name) 621 if path.is_absolute(): 622 has_node = False 623 624 for one_path in path_node_map: 625 if one_path[0] in path.parents: 626 path = path.relative_to(one_path[0]) 627 dest_node = one_path[1] 628 has_node = True 629 break 630 631 if not has_node: 632 dest_node = node_others 633 else: 634 dest_node = node_no_paths 635 636 _insert_one_elem(dest_node, path, size) 637 638 639 if node_zephyr_base is not root: 640 # ZEPHYR_BASE and OUTPUT_DIR nodes don't have sum of symbol size 641 # so calculate them here. 642 node_zephyr_base._size = sum_node_children_size(node_zephyr_base) 643 node_output_dir._size = sum_node_children_size(node_output_dir) 644 645 # Find out which nodes need to be in the tree. 646 # "(no path)", ZEPHYR_BASE nodes are essential. 647 children = [node_no_paths, node_zephyr_base] 648 if node_output_dir.height != 0: 649 # OUTPUT_DIR may be under ZEPHYR_BASE. 650 children.append(node_output_dir) 651 if node_others.height != 0: 652 # Only include "others" node if there is something. 653 children.append(node_others) 654 655 if args.workspace: 656 node_workspace._size = sum_node_children_size(node_workspace) 657 if node_workspace.height != 0: 658 children.append(node_workspace) 659 660 root.children = children 661 662 root._size = total_size 663 664 # Need to account for code and data where there are not emitted 665 # symbols associated with them. 666 node_hidden_syms = TreeNode('(hidden)', "(hidden)", parent=root) 667 node_hidden_syms._size = root._size - sum_node_children_size(root) 668 669 return root 670 671 672def node_sort(items): 673 """ 674 Node sorting used with RenderTree. 675 """ 676 return sorted(items, key=lambda item: item._name) 677 678 679def print_any_tree(root, total_size, depth): 680 """ 681 Print the symbol tree. 682 """ 683 print('{:101s} {:7s} {:8s}'.format( 684 Fore.YELLOW + "Path", "Size", "%" + Fore.RESET)) 685 print('=' * 110) 686 for row in RenderTree(root, childiter=node_sort, maxlevel=depth): 687 f = len(row.pre) + len(row.node._name) 688 s = str(row.node._size).rjust(100-f) 689 percent = 100 * float(row.node._size) / float(total_size) 690 691 cc = cr = "" 692 if not row.node.children: 693 if row.node._name != "(hidden)": 694 cc = Fore.CYAN 695 cr = Fore.RESET 696 elif row.node._name.endswith(SRC_FILE_EXT): 697 cc = Fore.GREEN 698 cr = Fore.RESET 699 700 print(f"{row.pre}{cc}{row.node._name} {s} {cr}{Fore.BLUE}{percent:6.2f}%{Fore.RESET}") 701 print('=' * 110) 702 print(f'{total_size:>101}') 703 704 705def parse_args(): 706 """ 707 Parse command line arguments. 708 """ 709 global args 710 711 parser = argparse.ArgumentParser(allow_abbrev=False) 712 713 parser.add_argument("-k", "--kernel", required=True, 714 help="Zephyr ELF binary") 715 parser.add_argument("-z", "--zephyrbase", required=True, 716 help="Zephyr base path") 717 parser.add_argument("-q", "--quiet", action="store_true", 718 help="Do not output anything on the screen.") 719 parser.add_argument("-o", "--output", required=True, 720 help="Output path") 721 parser.add_argument("-w", "--workspace", default=None, 722 help="Workspace path (Usually the same as WEST_TOPDIR)") 723 parser.add_argument("target", choices=['rom', 'ram', 'all']) 724 parser.add_argument("-d", "--depth", dest="depth", 725 type=int, default=None, 726 help="How deep should we go into the tree", 727 metavar="DEPTH") 728 parser.add_argument("-v", "--verbose", action="store_true", 729 help="Print extra debugging information") 730 parser.add_argument("--json", help="store results in a JSON file.") 731 args = parser.parse_args() 732 733 734def main(): 735 """ 736 Main program. 737 """ 738 parse_args() 739 740 sys.stdout.reconfigure(encoding='utf-8') 741 742 # Init colorama 743 init() 744 745 assert os.path.exists(args.kernel), "{0} does not exist.".format(args.kernel) 746 if args.target == 'ram': 747 targets = ['ram'] 748 elif args.target == 'rom': 749 targets = ['rom'] 750 elif args.target == 'all': 751 targets = ['rom', 'ram'] 752 753 for t in targets: 754 755 elf = ELFFile(open(args.kernel, "rb")) 756 757 assert elf.has_dwarf_info(), "ELF file has no DWARF information" 758 759 set_global_machine_arch(elf.get_machine_arch()) 760 761 addr_ranges = get_section_ranges(elf) 762 763 symbols = get_symbols(elf, addr_ranges) 764 765 for sym in symbols['unassigned'].values(): 766 print("WARN: Symbol '{0}' is not in RAM or ROM".format(sym['name'])) 767 768 symbol_dict = None 769 770 if args.json: 771 jsonout = args.json 772 else: 773 jsonout = os.path.join(args.output, f'{t}.json') 774 775 symbol_dict = symbols[t] 776 symsize = addr_ranges[f'{t}_total_size'] 777 ranges = addr_ranges[t] 778 779 if symbol_dict is not None: 780 processed = {"mapped_symbols": set(), 781 "mapped_addr": set(), 782 "unmapped_symbols": set(symbol_dict.keys())} 783 784 do_simple_name_matching(elf, symbol_dict, processed) 785 mark_address_aliases(symbol_dict, processed) 786 do_address_range_matching(elf, symbol_dict, processed) 787 mark_address_aliases(symbol_dict, processed) 788 common_path_prefix = find_common_path_prefix(symbol_dict) 789 set_root_path_for_unmapped_symbols(symbol_dict, ranges, processed) 790 791 if args.verbose: 792 for sym in processed['unmapped_symbols']: 793 print("INFO: Unmapped symbol: {0}".format(sym)) 794 795 root = generate_any_tree(symbol_dict, symsize, common_path_prefix) 796 if not args.quiet: 797 print_any_tree(root, symsize, args.depth) 798 799 exporter = DictExporter(attriter=lambda attrs: [(k.lstrip('_'), v) for k, v in attrs]) 800 data = dict() 801 data["symbols"] = exporter.export(root) 802 data["total_size"] = symsize 803 with open(jsonout, "w") as fp: 804 json.dump(data, fp, indent=4) 805 806 807if __name__ == "__main__": 808 main() 809