xref: /rk3399_ARM-atf/tools/memory/src/memory/memmap.py (revision d8fdff38b544b79c4f0b757e3b3c82ce9c8a2f9e)
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