You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2021/12/03 14:17:04 UTC
[incubator-nuttx] 01/02: tools: Add size report script
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
commit 87772796e3463472cd62f261e688f17cb8665fbd
Author: Lingao Meng <me...@xiaomi.com>
AuthorDate: Wed Dec 1 14:09:20 2021 +0800
tools: Add size report script
Add a resource statistics script, which can be used to
analyze the resource occupation of ELF files, including
BSS, data, ROM, etc.
Signed-off-by: Lingao Meng <me...@xiaomi.com>
LICENSE: Add size_report to license file
Declare license for intel Corporation.
Signed-off-by: Lingao Meng <me...@xiaomi.com>
---
LICENSE | 1 +
tools/size_report | 803 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 804 insertions(+)
diff --git a/LICENSE b/LICENSE
index 6762630..3f095af 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2397,6 +2397,7 @@ libs/libc/math/lib_copysignf.c
drivers/wireless/bluetooth/bt_uart.c
drivers/wireless/bluetooth/bt_uart.h
wireless/bluetooth
+tools/size_report
===========================
Copyright (c) 2016, Intel Corporation
All rights reserved.
diff --git a/tools/size_report b/tools/size_report
new file mode 100755
index 0000000..7b4ae7d
--- /dev/null
+++ b/tools/size_report
@@ -0,0 +1,803 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2016, 2020 Intel Corporation
+#
+# SPDX-License-Identifier: Apache-2.0
+
+# Based on a script by:
+# Chereau, Fabien <fa...@intel.com>
+
+"""
+Process an ELF file to generate size report on RAM and ROM.
+"""
+
+import argparse
+import os
+import sys
+import re
+from pathlib import Path
+import json
+
+from packaging import version
+
+from colorama import init, Fore
+
+from anytree import RenderTree, NodeMixin, findall_by_attr
+from anytree.exporter import DictExporter
+
+import elftools
+from elftools.elf.elffile import ELFFile
+from elftools.elf.sections import SymbolTableSection
+from elftools.dwarf.descriptions import describe_form_class
+from elftools.dwarf.descriptions import (
+ describe_DWARF_expr, set_global_machine_arch)
+from elftools.dwarf.locationlists import (
+ LocationExpr, LocationParser)
+
+if version.parse(elftools.__version__) < version.parse('0.24'):
+ sys.exit("pyelftools is out of date, need version 0.24 or later")
+
+
+# ELF section flags
+SHF_WRITE = 0x1
+SHF_ALLOC = 0x2
+SHF_EXEC = 0x4
+SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC
+SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC
+
+DT_LOCATION = re.compile(r"\(DW_OP_addr: ([0-9a-f]+)\)")
+
+SRC_FILE_EXT = ('.h', '.c', '.hpp', '.cpp', '.hxx', '.cxx', '.c++')
+
+
+def get_symbol_addr(sym):
+ """Get the address of a symbol"""
+ return sym['st_value']
+
+
+def get_symbol_size(sym):
+ """Get the size of a symbol"""
+ return sym['st_size']
+
+
+def is_symbol_in_ranges(sym, ranges):
+ """
+ Given a list of start/end addresses, test if the symbol
+ lies within any of these address ranges.
+ """
+ for bound in ranges:
+ if bound['start'] <= sym['st_value'] <= bound['end']:
+ return True
+
+ return False
+
+
+def get_die_mapped_address(die, parser, dwarfinfo):
+ """Get the bounding addresses from a DIE variable or subprogram"""
+ low = None
+ high = None
+
+ if die.tag == 'DW_TAG_variable':
+ if 'DW_AT_location' in die.attributes:
+ loc_attr = die.attributes['DW_AT_location']
+ if parser.attribute_has_location(loc_attr, die.cu['version']):
+ loc = parser.parse_from_attribute(loc_attr, die.cu['version'])
+ if isinstance(loc, LocationExpr):
+ addr = describe_DWARF_expr(loc.loc_expr,
+ dwarfinfo.structs)
+
+ matcher = DT_LOCATION.match(addr)
+ if matcher:
+ low = int(matcher.group(1), 16)
+ high = low + 1
+
+ if die.tag == 'DW_TAG_subprogram':
+ if 'DW_AT_low_pc' in die.attributes:
+ low = die.attributes['DW_AT_low_pc'].value
+
+ high_pc = die.attributes['DW_AT_high_pc']
+ high_pc_class = describe_form_class(high_pc.form)
+ if high_pc_class == 'address':
+ high = high_pc.value
+ elif high_pc_class == 'constant':
+ high = low + high_pc.value
+
+ return low, high
+
+
+def match_symbol_address(symlist, die, parser, dwarfinfo):
+ """
+ Find the symbol from a symbol list
+ where it matches the address in DIE variable,
+ or within the range of a DIE subprogram.
+ """
+ low, high = get_die_mapped_address(die, parser, dwarfinfo)
+
+ if low is None:
+ return None
+
+ for sym in symlist:
+ if low <= sym['symbol']['st_value'] < high:
+ return sym
+
+ return None
+
+
+def get_symbols(elf, addr_ranges):
+ """
+ Fetch the symbols from the symbol table and put them
+ into ROM, RAM buckets.
+ """
+ rom_syms = dict()
+ ram_syms = dict()
+ unassigned_syms = dict()
+
+ rom_addr_ranges = addr_ranges['rom']
+ ram_addr_ranges = addr_ranges['ram']
+
+ for section in elf.iter_sections():
+ if isinstance(section, SymbolTableSection):
+ for sym in section.iter_symbols():
+ # Ignore symbols with size == 0
+ if get_symbol_size(sym) == 0:
+ continue
+
+ found_sec = False
+ entry = {'name': sym.name,
+ 'symbol': sym,
+ 'mapped_files': set()}
+
+ # If symbol is in ROM area?
+ if is_symbol_in_ranges(sym, rom_addr_ranges):
+ if sym.name not in rom_syms:
+ rom_syms[sym.name] = list()
+ rom_syms[sym.name].append(entry)
+ found_sec = True
+
+ # If symbol is in RAM area?
+ if is_symbol_in_ranges(sym, ram_addr_ranges):
+ if sym.name not in ram_syms:
+ ram_syms[sym.name] = list()
+ ram_syms[sym.name].append(entry)
+ found_sec = True
+
+ if not found_sec:
+ unassigned_syms['sym_name'] = entry
+
+ ret = {'rom': rom_syms,
+ 'ram': ram_syms,
+ 'unassigned': unassigned_syms}
+ return ret
+
+
+def get_section_ranges(elf):
+ """
+ Parse ELF header to find out the address ranges of ROM or RAM sections
+ and their total sizes.
+ """
+ rom_addr_ranges = list()
+ ram_addr_ranges = list()
+ rom_size = 0
+ ram_size = 0
+
+ for section in elf.iter_sections():
+ size = section['sh_size']
+ sec_start = section['sh_addr']
+ sec_end = sec_start + size - 1
+ bound = {'start': sec_start, 'end': sec_end}
+
+ if section['sh_type'] == 'SHT_NOBITS':
+ # BSS and noinit sections
+ ram_addr_ranges.append(bound)
+ ram_size += size
+ elif section['sh_type'] == 'SHT_PROGBITS':
+ # Sections to be in flash or memory
+ flags = section['sh_flags']
+ if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC:
+ # Text section
+ rom_addr_ranges.append(bound)
+ rom_size += size
+ elif (flags & SHF_WRITE_ALLOC) == SHF_WRITE_ALLOC:
+ # Data occupies both ROM and RAM
+ # since at boot, content is copied from ROM to RAM
+ rom_addr_ranges.append(bound)
+ rom_size += size
+
+ ram_addr_ranges.append(bound)
+ ram_size += size
+ elif (flags & SHF_ALLOC) == SHF_ALLOC:
+ # Read only data
+ rom_addr_ranges.append(bound)
+ rom_size += size
+
+ ret = {'rom': rom_addr_ranges,
+ 'rom_total_size': rom_size,
+ 'ram': ram_addr_ranges,
+ 'ram_total_size': ram_size}
+ return ret
+
+
+def get_die_filename(die, lineprog):
+ """Get the source code filename associated with a DIE"""
+ file_index = die.attributes['DW_AT_decl_file'].value
+ file_entry = lineprog['file_entry'][file_index - 1]
+
+ dir_index = file_entry['dir_index']
+ if dir_index == 0:
+ filename = file_entry.name
+ else:
+ directory = lineprog.header['include_directory'][dir_index - 1]
+ filename = os.path.join(directory, file_entry.name)
+
+ path = Path(filename.decode())
+
+ # Prepend output path to relative path
+ if not path.is_absolute():
+ output = Path(args.output)
+ path = output.joinpath(path)
+
+ # Change path to relative to Zephyr base
+ try:
+ path = path.resolve()
+ except OSError as e:
+ # built-ins can't be resolved, so it's not an issue
+ if '<built-in>' not in str(path):
+ raise e
+
+ return path
+
+
+def do_simple_name_matching(elf, symbol_dict, processed):
+ """
+ Sequentially process DIEs in compiler units with direct file mappings
+ within the DIEs themselves, and do simply matching between DIE names
+ and symbol names.
+ """
+ mapped_symbols = processed['mapped_symbols']
+ mapped_addresses = processed['mapped_addr']
+ unmapped_symbols = processed['unmapped_symbols']
+ newly_mapped_syms = set()
+
+ dwarfinfo = elf.get_dwarf_info()
+ location_lists = dwarfinfo.location_lists()
+ location_parser = LocationParser(location_lists)
+
+ unmapped_dies = set()
+
+ # Loop through all compile units
+ for compile_unit in dwarfinfo.iter_CUs():
+ lineprog = dwarfinfo.line_program_for_CU(compile_unit)
+ if lineprog is None:
+ continue
+
+ # Loop through each DIE and find variables and
+ # subprograms (i.e. functions)
+ for die in compile_unit.iter_DIEs():
+ sym_name = None
+
+ # Process variables
+ if die.tag == 'DW_TAG_variable':
+ # DW_AT_declaration
+
+ # having 'DW_AT_location' means this maps
+ # to an actual address (e.g. not an extern)
+ if 'DW_AT_location' in die.attributes:
+ sym_name = die.get_full_path()
+
+ # Process subprograms (i.e. functions) if they are valid
+ if die.tag == 'DW_TAG_subprogram':
+ # Refer to another DIE for name
+ if ('DW_AT_abstract_origin' in die.attributes) or (
+ 'DW_AT_specification' in die.attributes):
+ unmapped_dies.add(die)
+
+ # having 'DW_AT_low_pc' means it maps to
+ # an actual address
+ elif 'DW_AT_low_pc' in die.attributes:
+ # DW_AT_low_pc == 0 is a weak function
+ # which has been overriden
+ if die.attributes['DW_AT_low_pc'].value != 0:
+ sym_name = die.get_full_path()
+
+ # For mangled function names, the linkage name
+ # is what appears in the symbol list
+ if 'DW_AT_linkage_name' in die.attributes:
+ linkage = die.attributes['DW_AT_linkage_name']
+ sym_name = linkage.value.decode()
+
+ if sym_name is not None:
+ # Skip DIE with no reference back to a file
+ if not 'DW_AT_decl_file' in die.attributes:
+ continue
+
+ is_die_mapped = False
+ if sym_name in symbol_dict:
+ mapped_symbols.add(sym_name)
+ symlist = symbol_dict[sym_name]
+ symbol = match_symbol_address(symlist, die,
+ location_parser,
+ dwarfinfo)
+
+ if symbol is not None:
+ symaddr = symbol['symbol']['st_value']
+ if symaddr not in mapped_addresses:
+ is_die_mapped = True
+ path = get_die_filename(die, lineprog)
+ symbol['mapped_files'].add(path)
+ mapped_addresses.add(symaddr)
+ newly_mapped_syms.add(sym_name)
+
+ if not is_die_mapped:
+ unmapped_dies.add(die)
+
+ mapped_symbols = mapped_symbols.union(newly_mapped_syms)
+ unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms)
+
+ processed['mapped_symbols'] = mapped_symbols
+ processed['mapped_addr'] = mapped_addresses
+ processed['unmapped_symbols'] = unmapped_symbols
+ processed['unmapped_dies'] = unmapped_dies
+
+
+def mark_address_aliases(symbol_dict, processed):
+ """
+ Mark symbol aliases as already mapped to prevent
+ double counting.
+
+ There are functions and variables which are aliases to
+ other functions/variables. So this marks them as mapped
+ so they will not get counted again when a tree is being
+ built for display.
+ """
+ mapped_symbols = processed['mapped_symbols']
+ mapped_addresses = processed['mapped_addr']
+ unmapped_symbols = processed['unmapped_symbols']
+ already_mapped_syms = set()
+
+ for ums in unmapped_symbols:
+ for one_sym in symbol_dict[ums]:
+ symbol = one_sym['symbol']
+ if symbol['st_value'] in mapped_addresses:
+ already_mapped_syms.add(ums)
+
+ mapped_symbols = mapped_symbols.union(already_mapped_syms)
+ unmapped_symbols = unmapped_symbols.difference(already_mapped_syms)
+
+ processed['mapped_symbols'] = mapped_symbols
+ processed['mapped_addr'] = mapped_addresses
+ processed['unmapped_symbols'] = unmapped_symbols
+
+
+def do_address_range_matching(elf, symbol_dict, processed):
+ """
+ Match symbols indirectly using address ranges.
+
+ This uses the address ranges of DIEs and map them to symbols
+ residing within those ranges, and works on DIEs that have not
+ been mapped in previous steps. This works on symbol names
+ that do not match the names in DIEs, e.g. "<func>" in DIE,
+ but "<func>.constprop.*" in symbol name list. This also
+ helps with mapping the mangled function names in C++,
+ since the names in DIE are actual function names in source
+ code and not mangled version of them.
+ """
+ if 'unmapped_dies' not in processed:
+ return
+
+ mapped_symbols = processed['mapped_symbols']
+ mapped_addresses = processed['mapped_addr']
+ unmapped_symbols = processed['unmapped_symbols']
+ newly_mapped_syms = set()
+
+ dwarfinfo = elf.get_dwarf_info()
+ location_lists = dwarfinfo.location_lists()
+ location_parser = LocationParser(location_lists)
+
+ unmapped_dies = processed['unmapped_dies']
+
+ # Group DIEs by compile units
+ cu_list = dict()
+
+ for die in unmapped_dies:
+ cu = die.cu
+ if cu not in cu_list:
+ cu_list[cu] = {'dies': set()}
+ cu_list[cu]['dies'].add(die)
+
+ # Loop through all compile units
+ for cu in cu_list:
+ lineprog = dwarfinfo.line_program_for_CU(cu)
+
+ # Map offsets from DIEs
+ offset_map = dict()
+ for die in cu.iter_DIEs():
+ offset_map[die.offset] = die
+
+ for die in cu_list[cu]['dies']:
+ if not die.tag == 'DW_TAG_subprogram':
+ continue
+
+ path = None
+
+ # Has direct reference to file, so use it
+ if 'DW_AT_decl_file' in die.attributes:
+ path = get_die_filename(die, lineprog)
+
+ # Loop through indirect reference until a direct
+ # reference to file is found
+ if ('DW_AT_abstract_origin' in die.attributes) or (
+ 'DW_AT_specification' in die.attributes):
+ die_ptr = die
+ while path is None:
+ if not (die_ptr.tag == 'DW_TAG_subprogram') or not (
+ ('DW_AT_abstract_origin' in die_ptr.attributes) or
+ ('DW_AT_specification' in die_ptr.attributes)):
+ break
+
+ if 'DW_AT_abstract_origin' in die_ptr.attributes:
+ ofname = 'DW_AT_abstract_origin'
+ elif 'DW_AT_specification' in die_ptr.attributes:
+ ofname = 'DW_AT_specification'
+
+ offset = die_ptr.attributes[ofname].value
+ offset += die_ptr.cu.cu_offset
+
+ # There is nothing to reference so no need to continue
+ if offset not in offset_map:
+ break
+
+ die_ptr = offset_map[offset]
+ if 'DW_AT_decl_file' in die_ptr.attributes:
+ path = get_die_filename(die_ptr, lineprog)
+
+ # Nothing to map
+ if path is not None:
+ low, high = get_die_mapped_address(die, location_parser,
+ dwarfinfo)
+ if low is None:
+ continue
+
+ for ums in unmapped_symbols:
+ for one_sym in symbol_dict[ums]:
+ symbol = one_sym['symbol']
+ symaddr = symbol['st_value']
+
+ if symaddr not in mapped_addresses:
+ if low <= symaddr < high:
+ one_sym['mapped_files'].add(path)
+ mapped_addresses.add(symaddr)
+ newly_mapped_syms.add(ums)
+
+ mapped_symbols = mapped_symbols.union(newly_mapped_syms)
+ unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms)
+
+ processed['mapped_symbols'] = mapped_symbols
+ processed['mapped_addr'] = mapped_addresses
+ processed['unmapped_symbols'] = unmapped_symbols
+
+
+def set_root_path_for_unmapped_symbols(symbol_dict, addr_range, processed):
+ """
+ Set root path for unmapped symbols.
+
+ Any unmapped symbols are added under the root node if those
+ symbols reside within the desired memory address ranges
+ (e.g. ROM or RAM).
+ """
+ mapped_symbols = processed['mapped_symbols']
+ mapped_addresses = processed['mapped_addr']
+ unmapped_symbols = processed['unmapped_symbols']
+ newly_mapped_syms = set()
+
+ for ums in unmapped_symbols:
+ for one_sym in symbol_dict[ums]:
+ symbol = one_sym['symbol']
+ symaddr = symbol['st_value']
+
+ if is_symbol_in_ranges(symbol, addr_range):
+ if symaddr not in mapped_addresses:
+ path = Path(':')
+ one_sym['mapped_files'].add(path)
+ mapped_addresses.add(symaddr)
+ newly_mapped_syms.add(ums)
+
+ mapped_symbols = mapped_symbols.union(newly_mapped_syms)
+ unmapped_symbols = unmapped_symbols.difference(newly_mapped_syms)
+
+ processed['mapped_symbols'] = mapped_symbols
+ processed['mapped_addr'] = mapped_addresses
+ processed['unmapped_symbols'] = unmapped_symbols
+
+def find_common_path_prefix(symbol_dict):
+ """
+ Find the common path prefix of all mapped files.
+ Must be called before set_root_path_for_unmapped_symbols().
+ """
+ paths = list()
+
+ for _, sym in symbol_dict.items():
+ for symbol in sym:
+ for file in symbol['mapped_files']:
+ paths.append(file)
+
+ return os.path.commonpath(paths)
+
+
+class TreeNode(NodeMixin):
+ """
+ A symbol node.
+ """
+
+ def __init__(self, name, identifier, size=0, parent=None, children=None):
+ super().__init__()
+ self.name = name
+ self.size = size
+ self.parent = parent
+ self.identifier = identifier
+ if children:
+ self.children = children
+
+ def __repr__(self):
+ return self.name
+
+
+def sum_node_children_size(node):
+ """
+ Calculate the sum of symbol size of all direct children.
+ """
+ size = 0
+
+ for child in node.children:
+ size += child.size
+
+ return size
+
+
+def generate_any_tree(symbol_dict, total_size, path_prefix):
+ """
+ Generate a symbol tree for output.
+ """
+ root = TreeNode('Root', "root")
+ node_no_paths = TreeNode('(no paths)', ":", parent=root)
+
+ if Path(path_prefix) == Path(args.zephyrbase):
+ # All source files are under ZEPHYR_BASE so there is
+ # no need for another level.
+ node_zephyr_base = root
+ node_output_dir = root
+ node_workspace = root
+ node_others = root
+ else:
+ node_zephyr_base = TreeNode('ZEPHYR_BASE', args.zephyrbase)
+ node_output_dir = TreeNode('OUTPUT_DIR', args.output)
+ node_others = TreeNode("/", "/")
+
+ if args.workspace:
+ node_workspace = TreeNode('WORKSPACE', args.workspace)
+ else:
+ node_workspace = node_others
+
+ # A set of helper function for building a simple tree with a path-like
+ # hierarchy.
+ def _insert_one_elem(root, path, size):
+ cur = None
+ node = None
+ parent = root
+ for part in path.parts:
+ if cur is None:
+ cur = part
+ else:
+ cur = str(Path(cur, part))
+
+ results = findall_by_attr(root, cur, name="identifier")
+ if results:
+ item = results[0]
+ item.size += size
+ parent = item
+ else:
+ if node:
+ parent = node
+ node = TreeNode(name=str(part), identifier=cur, size=size, parent=parent)
+
+ # Mapping paths to tree nodes
+ path_node_map = [
+ [Path(args.zephyrbase), node_zephyr_base],
+ [Path(args.output), node_output_dir],
+ ]
+
+ if args.workspace:
+ path_node_map.append(
+ [Path(args.workspace), node_workspace]
+ )
+
+ for name, sym in symbol_dict.items():
+ for symbol in sym:
+ size = get_symbol_size(symbol['symbol'])
+ for file in symbol['mapped_files']:
+ path = Path(file, name)
+ if path.is_absolute():
+ has_node = False
+
+ for one_path in path_node_map:
+ if one_path[0] in path.parents:
+ path = path.relative_to(one_path[0])
+ dest_node = one_path[1]
+ has_node = True
+ break
+
+ if not has_node:
+ dest_node = node_others
+ else:
+ dest_node = node_no_paths
+
+ _insert_one_elem(dest_node, path, size)
+
+
+ if node_zephyr_base is not root:
+ # ZEPHYR_BASE and OUTPUT_DIR nodes don't have sum of symbol size
+ # so calculate them here.
+ node_zephyr_base.size = sum_node_children_size(node_zephyr_base)
+ node_output_dir.size = sum_node_children_size(node_output_dir)
+
+ # Find out which nodes need to be in the tree.
+ # "(no path)", ZEPHYR_BASE nodes are essential.
+ children = [node_no_paths, node_zephyr_base]
+ if node_output_dir.height != 0:
+ # OUTPUT_DIR may be under ZEPHYR_BASE.
+ children.append(node_output_dir)
+ if node_others.height != 0:
+ # Only include "others" node if there is something.
+ children.append(node_others)
+
+ if args.workspace:
+ node_workspace.size = sum_node_children_size(node_workspace)
+ if node_workspace.height != 0:
+ children.append(node_workspace)
+
+ root.children = children
+
+ root.size = total_size
+
+ # Need to account for code and data where there are not emitted
+ # symbols associated with them.
+ node_hidden_syms = TreeNode('(hidden)', "(hidden)", parent=root)
+ node_hidden_syms.size = root.size - sum_node_children_size(root)
+
+ return root
+
+
+def node_sort(items):
+ """
+ Node sorting used with RenderTree.
+ """
+ return sorted(items, key=lambda item: item.name)
+
+
+def print_any_tree(root, total_size, depth):
+ """
+ Print the symbol tree.
+ """
+ print('{:101s} {:7s} {:8s}'.format(
+ Fore.YELLOW + "Path", "Size", "%" + Fore.RESET))
+ print('=' * 110)
+ for row in RenderTree(root, childiter=node_sort, maxlevel=depth):
+ f = len(row.pre) + len(row.node.name)
+ s = str(row.node.size).rjust(100-f)
+ percent = 100 * float(row.node.size) / float(total_size)
+
+ cc = cr = ""
+ if not row.node.children:
+ if row.node.name != "(hidden)":
+ cc = Fore.CYAN
+ cr = Fore.RESET
+ elif row.node.name.endswith(SRC_FILE_EXT):
+ cc = Fore.GREEN
+ cr = Fore.RESET
+
+ print(f"{row.pre}{cc}{row.node.name} {s} {cr}{Fore.BLUE}{percent:6.2f}%{Fore.RESET}")
+ print('=' * 110)
+ print(f'{total_size:>101}')
+
+
+def parse_args():
+ """
+ Parse command line arguments.
+ """
+ global args
+
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("-k", "--kernel", required=True,
+ help="Zephyr ELF binary")
+ parser.add_argument("-z", "--zephyrbase", required=True,
+ help="Zephyr base path")
+ parser.add_argument("-q", "--quiet", action="store_true",
+ help="Do not output anything on the screen.")
+ parser.add_argument("-o", "--output", required=True,
+ help="Output path")
+ parser.add_argument("-w", "--workspace", default=None,
+ help="Workspace path (Usually the same as WEST_TOPDIR)")
+ parser.add_argument("target", choices=['rom', 'ram', 'all'])
+ parser.add_argument("-d", "--depth", dest="depth",
+ type=int, default=None,
+ help="How deep should we go into the tree",
+ metavar="DEPTH")
+ parser.add_argument("-v", "--verbose", action="store_true",
+ help="Print extra debugging information")
+ parser.add_argument("--json", help="store results in a JSON file.")
+ args = parser.parse_args()
+
+
+def main():
+ """
+ Main program.
+ """
+ parse_args()
+
+ # Init colorama
+ init()
+
+ assert os.path.exists(args.kernel), "{0} does not exist.".format(args.kernel)
+ if args.target == 'ram':
+ targets = ['ram']
+ elif args.target == 'rom':
+ targets = ['rom']
+ elif args.target == 'all':
+ targets = ['rom', 'ram']
+
+ for t in targets:
+
+ elf = ELFFile(open(args.kernel, "rb"))
+
+ assert elf.has_dwarf_info(), "ELF file has no DWARF information"
+
+ set_global_machine_arch(elf.get_machine_arch())
+
+ addr_ranges = get_section_ranges(elf)
+
+ symbols = get_symbols(elf, addr_ranges)
+
+ for sym in symbols['unassigned'].values():
+ print("WARN: Symbol '{0}' is not in RAM or ROM".format(sym['name']))
+
+ symbol_dict = None
+
+ if args.json:
+ jsonout = args.json
+ else:
+ jsonout = os.path.join(args.output, f'{t}.json')
+
+ symbol_dict = symbols[t]
+ symsize = addr_ranges[f'{t}_total_size']
+ ranges = addr_ranges[t]
+
+ if symbol_dict is not None:
+ processed = {"mapped_symbols": set(),
+ "mapped_addr": set(),
+ "unmapped_symbols": set(symbol_dict.keys())}
+
+ do_simple_name_matching(elf, symbol_dict, processed)
+ mark_address_aliases(symbol_dict, processed)
+ do_address_range_matching(elf, symbol_dict, processed)
+ mark_address_aliases(symbol_dict, processed)
+ common_path_prefix = find_common_path_prefix(symbol_dict)
+ set_root_path_for_unmapped_symbols(symbol_dict, ranges, processed)
+
+ if args.verbose:
+ for sym in processed['unmapped_symbols']:
+ print("INFO: Unmapped symbol: {0}".format(sym))
+
+ root = generate_any_tree(symbol_dict, symsize, common_path_prefix)
+ if not args.quiet:
+ print_any_tree(root, symsize, args.depth)
+
+ exporter = DictExporter()
+ data = dict()
+ data["symbols"] = exporter.export(root)
+ data["total_size"] = symsize
+ with open(jsonout, "w") as fp:
+ json.dump(data, fp, indent=4)
+
+
+if __name__ == "__main__":
+ main()