1# 2# Copyright (c) 2023-2025, Arm Limited. All rights reserved. 3# 4# SPDX-License-Identifier: BSD-3-Clause 5# 6 7import re 8import shutil 9from dataclasses import dataclass 10from pathlib import Path 11from typing import Any, Dict, List, Optional 12 13import click 14 15from memory.elfparser import TfaElfParser 16from memory.image import Image 17from memory.mapparser import TfaMapParser 18from memory.printer import TfaPrettyPrinter 19from memory.summary import MapParser 20 21 22@dataclass 23class Context: 24 build_path: Optional[Path] = None 25 printer: Optional[TfaPrettyPrinter] = None 26 27 28@click.group() 29@click.pass_obj 30@click.option( 31 "-r", 32 "--root", 33 type=Path, 34 default=None, 35 help="Root containing build output.", 36) 37@click.option( 38 "-p", 39 "--platform", 40 show_default=True, 41 default="fvp", 42 help="The platform targeted for analysis.", 43) 44@click.option( 45 "-b", 46 "--build-type", 47 default="release", 48 help="The target build type.", 49 type=click.Choice(["debug", "release"], case_sensitive=False), 50) 51@click.option( 52 "-w", 53 "--width", 54 type=int, 55 default=shutil.get_terminal_size().columns, 56 help="Column width for printing.", 57) 58@click.option( 59 "-d", 60 is_flag=True, 61 default=False, 62 help="Display numbers in decimal base.", 63) 64def cli( 65 obj: Context, 66 root: Optional[Path], 67 platform: str, 68 build_type: str, 69 width: int, 70 d: bool, 71): 72 obj.build_path = root if root is not None else Path("build", platform, build_type) 73 click.echo(f"build-path: {obj.build_path.resolve()}") 74 75 obj.printer = TfaPrettyPrinter(columns=width, as_decimal=d) 76 77 78@cli.command() 79@click.pass_obj 80@click.option( 81 "--no-elf-images", 82 is_flag=True, 83 help="Analyse the build's map files instead of ELF images.", 84) 85def footprint(obj: Context, no_elf_images: bool): 86 """Generate a high level view of memory usage by memory types.""" 87 88 assert obj.build_path is not None 89 assert obj.printer is not None 90 91 elf_image_paths: List[Path] = ( 92 [] if no_elf_images else list(obj.build_path.glob("**/*.elf")) 93 ) 94 95 map_file_paths: List[Path] = ( 96 [] if not no_elf_images else list(obj.build_path.glob("**/*.map")) 97 ) 98 99 images: Dict[str, Image] = dict() 100 101 for elf_image_path in elf_image_paths: 102 with open(elf_image_path, "rb") as elf_image_io: 103 images[elf_image_path.stem.upper()] = TfaElfParser(elf_image_io) 104 105 for map_file_path in map_file_paths: 106 with open(map_file_path, "r") as map_file_io: 107 images[map_file_path.stem.upper()] = TfaMapParser(map_file_io) 108 109 obj.printer.print_footprint({k: v.footprint for k, v in images.items()}) 110 111 112@cli.command() 113@click.pass_obj 114@click.option( 115 "--depth", 116 default=3, 117 show_default=True, 118 help="Generate a virtual address map of important TF symbols.", 119) 120def tree(obj: Context, depth: int): 121 """Generate a hierarchical view of the modules, segments and sections.""" 122 123 assert obj.build_path is not None 124 assert obj.printer is not None 125 126 paths: List[Path] = list(obj.build_path.glob("**/*.elf")) 127 images: Dict[str, TfaElfParser] = dict() 128 129 for path in paths: 130 with open(path, "rb") as io: 131 images[path.stem] = TfaElfParser(io) 132 133 mtree: Dict[str, Dict[str, Any]] = { 134 k: { 135 "name": k, 136 **v.get_mod_mem_usage_dict(), 137 **{"children": v.get_seg_map_as_dict()}, 138 } 139 for k, v in images.items() 140 } 141 142 obj.printer.print_mem_tree(mtree, list(mtree.keys()), depth=depth) 143 144 145@cli.command() 146@click.pass_obj 147@click.option( 148 "--no-elf-images", 149 is_flag=True, 150 help="Analyse the build's map files instead of ELF images.", 151) 152def symbols(obj: Context, no_elf_images: bool): 153 """Generate a map of important TF symbols.""" 154 155 assert obj.build_path is not None 156 assert obj.printer is not None 157 158 expr: str = ( 159 r"(.*)(TEXT|BSS|RO|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF|RELA" 160 r"|R.M)(.*)(START|UNALIGNED|END)__$" 161 ) 162 163 elf_image_paths: List[Path] = ( 164 [] if no_elf_images else list(obj.build_path.glob("**/*.elf")) 165 ) 166 167 map_file_paths: List[Path] = ( 168 [] if not no_elf_images else list(obj.build_path.glob("**/*.map")) 169 ) 170 171 images: Dict[str, Image] = dict() 172 173 for elf_image_path in elf_image_paths: 174 with open(elf_image_path, "rb") as elf_image_io: 175 images[elf_image_path.stem] = TfaElfParser(elf_image_io) 176 177 for map_file_path in map_file_paths: 178 with open(map_file_path, "r") as map_file_io: 179 images[map_file_path.stem] = TfaMapParser(map_file_io) 180 181 symbols = {k: v.symbols for k, v in images.items()} 182 symbols = { 183 image: { 184 symbol: symbol_value 185 for symbol, symbol_value in symbols.items() 186 if re.match(expr, symbol) 187 } 188 for image, symbols in symbols.items() 189 } 190 191 obj.printer.print_symbol_table(symbols, list(images.keys())) 192 193 194@cli.command() 195@click.option("-o", "--old", type=click.Path(exists=True)) 196@click.option("-d", "--depth", type=int, default=2) 197@click.option("-e", "--exclude-fill") 198@click.option( 199 "-t", 200 "--type", 201 type=click.Choice(MapParser.export_formats, case_sensitive=False), 202 default="table", 203) 204@click.argument("file", type=click.Path(exists=True)) 205def summary(file: Path, old: Optional[Path], depth: int, exclude_fill: bool, type: str): 206 """Summarize the sizes of translation units within the resulting binary""" 207 memap = MapParser() 208 209 if not memap.parse(file, old, exclude_fill): 210 exit(1) 211 212 memap.generate_output(type, depth) 213 214 215def main(): 216 cli(obj=Context()) 217 218 219if __name__ == "__main__": 220 main() 221