blob: e02010bc52fb9ebbb4ecf34e1a4dae7a9a43095d [file] [log] [blame]
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +00001#
Chris Kayed0c8012025-01-28 18:04:11 +00002# Copyright (c) 2023-2025, Arm Limited. All rights reserved.
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +00003#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6
Chris Kay8daebef2025-04-15 14:00:50 +01007import re
Chris Kay8ab677b2025-04-17 12:06:11 +01008import shutil
Chris Kay8daebef2025-04-15 14:00:50 +01009from dataclasses import dataclass
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000010from pathlib import Path
Chris Kay8daebef2025-04-15 14:00:50 +010011from typing import Any, Dict, List, Optional
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000012
13import click
Chris Kay1bed7702025-04-01 17:20:56 +010014
Chris Kay8daebef2025-04-15 14:00:50 +010015from memory.elfparser import TfaElfParser
16from memory.image import Image
17from memory.mapparser import TfaMapParser
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000018from memory.printer import TfaPrettyPrinter
Jimmy Brissona98d4662024-09-16 10:10:46 -050019from memory.summary import MapParser
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000020
21
Chris Kay8daebef2025-04-15 14:00:50 +010022@dataclass
23class Context:
24 build_path: Optional[Path] = None
25 printer: Optional[TfaPrettyPrinter] = None
26
27
28@click.group()
29@click.pass_obj
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000030@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(
Chris Kay8ab677b2025-04-17 12:06:11 +010052 "-w",
53 "--width",
54 type=int,
55 default=shutil.get_terminal_size().columns,
56 help="Column width for printing.",
57)
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000058@click.option(
59 "-d",
60 is_flag=True,
61 default=False,
62 help="Display numbers in decimal base.",
63)
Chris Kay8daebef2025-04-15 14:00:50 +010064def 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
Harrison Mutaid0e30532023-06-07 11:28:16 +010080@click.option(
81 "--no-elf-images",
82 is_flag=True,
83 help="Analyse the build's map files instead of ELF images.",
84)
Chris Kay8daebef2025-04-15 14:00:50 +010085def footprint(obj: Context, no_elf_images: bool):
86 """Generate a high level view of memory usage by memory types."""
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000087
Chris Kay8daebef2025-04-15 14:00:50 +010088 assert obj.build_path is not None
89 assert obj.printer is not None
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000090
Chris Kay8daebef2025-04-15 14:00:50 +010091 elf_image_paths: List[Path] = (
92 [] if no_elf_images else list(obj.build_path.glob("**/*.elf"))
93 )
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000094
Chris Kay8daebef2025-04-15 14:00:50 +010095 map_file_paths: List[Path] = (
96 [] if not no_elf_images else list(obj.build_path.glob("**/*.map"))
97 )
Harrison Mutaicc60aba2023-02-23 11:30:55 +000098
Chris Kay8daebef2025-04-15 14:00:50 +010099 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
Jimmy Brissona98d4662024-09-16 10:10:46 -0500194@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
Chris Kay8daebef2025-04-15 14:00:50 +0100215def main():
216 cli(obj=Context())
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +0000217
218
219if __name__ == "__main__":
220 main()