refactor(memmap)!: change behavioural flags to commands
This change factors out the following memory map tool flags into
independent commands:
- `--footprint` (becomes `memory footprint`)
- `--tree` (becomes `memory tree`)
- `--symbol` (becomes `memory symbol`)
So, for example, where previously you would generate the memory
footprint of a build with:
memory --tree
You would now instead use:
memory footprint
Any flags specific to a command (e.g. `--depth` for `tree`) must be
specified after the command, e.g.
memory tree --depth 1
... instead of:
memory --depth 1 tree
BREAKING-CHANGE: The image memory map visualization tool now uses
commands, rather than arguments, to determine the behaviour of the
script. See the commit message for further details.
Change-Id: I11d54d1f6276b8447bdfb8496544ab80399459ac
Signed-off-by: Chris Kay <chris.kay@arm.com>
diff --git a/Makefile b/Makefile
index 150aa30..de608fe 100644
--- a/Makefile
+++ b/Makefile
@@ -1852,7 +1852,7 @@
memmap: all
$(if $(host-poetry),$(q)poetry -q install --no-root)
- $(q)$(if $(host-poetry),poetry run )memory -sr ${BUILD_PLAT}
+ $(q)$(if $(host-poetry),poetry run )memory symbols --root ${BUILD_PLAT}
tl: ${BUILD_PLAT}/tl.bin
${BUILD_PLAT}/tl.bin: ${HW_CONFIG}
diff --git a/docs/tools/memory-layout-tool.rst b/docs/tools/memory-layout-tool.rst
index d9c358d..2506d2f 100644
--- a/docs/tools/memory-layout-tool.rst
+++ b/docs/tools/memory-layout-tool.rst
@@ -41,7 +41,7 @@
.. code:: shell
- $ poetry run memory -s
+ $ poetry run memory symbols
build-path: build/fvp/release
Virtual Address Map:
+------------__BL1_RAM_END__------------+---------------------------------------+
@@ -116,14 +116,14 @@
Memory Footprint
~~~~~~~~~~~~~~~~
-The tool enables users to view static memory consumption. When the options
-``-f``, or ``--footprint`` are provided, the script analyses the ELF binaries in
-the build path to generate a table (per memory type), showing memory allocation
-and usage. This is the default output generated by the tool.
+The tool enables users to view static memory consumption. When the ``footprint``
+command is provided, the script analyses the ELF binaries in the build path to
+generate a table (per memory type), showing memory allocation and usage. This is
+the default output generated by the tool.
.. code:: shell
- $ poetry run memory -f
+ $ poetry run memory footprint
build-path: build/fvp/release
+----------------------------------------------------------------------------+
| Memory Usage (bytes) [RAM] |
@@ -150,13 +150,13 @@
Memory Tree
~~~~~~~~~~~
-A hierarchical view of the memory layout can be produced by passing the option
-``-t`` or ``--tree`` to the tool. This gives the start, end, and size of each
-module, their ELF segments as well as sections.
+A hierarchical view of the memory layout can be produced by passing the ``tree``
+command to the tool. This gives the start, end, and size of each module, their
+ELF segments as well as sections.
.. code:: shell
- $ poetry run memory -t
+ $ poetry run memory tree
build-path: build/fvp/release
name start end size
bl1 0 400c000 400c000
@@ -209,7 +209,7 @@
.. code::
- $ poetry run memory -t --depth 2
+ $ poetry run memory tree --depth 2
build-path: build/fvp/release
name start end size
bl1 0 400c000 400c000
diff --git a/tools/memory/src/memory/buildparser.py b/tools/memory/src/memory/buildparser.py
deleted file mode 100755
index 3c73ea4..0000000
--- a/tools/memory/src/memory/buildparser.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# Copyright (c) 2023-2025, Arm Limited. All rights reserved.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-#
-
-import re
-from pathlib import Path
-from typing import (
- Any,
- Dict,
- List,
- Union,
-)
-
-from memory.elfparser import TfaElfParser
-from memory.image import Region
-from memory.mapparser import TfaMapParser
-
-
-class TfaBuildParser:
- """A class for performing analysis on the memory layout of a TF-A build."""
-
- def __init__(self, path: Path, map_backend: bool = False) -> None:
- self._modules: Dict[str, Union[TfaElfParser, TfaMapParser]] = {}
- self._path: Path = path
- self.map_backend: bool = map_backend
- self._parse_modules()
-
- def __getitem__(self, module: str) -> Union[TfaElfParser, TfaMapParser]:
- """Returns an TfaElfParser instance indexed by module."""
- return self._modules[module]
-
- def _parse_modules(self) -> None:
- """Parse the build files using the selected backend."""
- backend = TfaElfParser
- files = list(self._path.glob("**/*.elf"))
- io_perms = "rb"
-
- if self.map_backend or not files:
- backend = TfaMapParser
- files = self._path.glob("**/*.map")
- io_perms = "r"
-
- for file in files:
- module_name = file.name.split("/")[-1].split(".")[0]
- with open(file, io_perms) as f:
- self._modules[module_name] = backend(f)
-
- if not self._modules:
- raise FileNotFoundError(
- f"failed to find files to analyse in path {self._path}!"
- )
-
- @property
- def symbols(self) -> Dict[str, Dict[str, int]]:
- return {k: v.symbols for k, v in self._modules.items()}
-
- @staticmethod
- def filter_symbols(
- images: Dict[str, Dict[str, int]], regex: str
- ) -> Dict[str, Dict[str, int]]:
- """Returns a map of symbols to modules."""
-
- return {
- image: {
- symbol: symbol_value
- for symbol, symbol_value in symbols.items()
- if re.match(regex, symbol)
- }
- for image, symbols in images.items()
- }
-
- def get_mem_usage_dict(self) -> Dict[str, Dict[str, Region]]:
- """Returns map of memory usage per memory type for each module."""
- return {k: v.footprint for k, v in self._modules.items()}
-
- def get_mem_tree_as_dict(self) -> Dict[str, Dict[str, Any]]:
- """Returns _tree of modules, segments and segments and their total
- memory usage."""
- return {
- k: {
- "name": k,
- **v.get_mod_mem_usage_dict(),
- "children": v.get_seg_map_as_dict(),
- }
- for k, v in self._modules.items()
- }
-
- @property
- def module_names(self) -> List[str]:
- """Returns sorted list of module names."""
- return sorted(self._modules.keys())
diff --git a/tools/memory/src/memory/memmap.py b/tools/memory/src/memory/memmap.py
index 10087f2..fd24680 100755
--- a/tools/memory/src/memory/memmap.py
+++ b/tools/memory/src/memory/memmap.py
@@ -4,17 +4,28 @@
# SPDX-License-Identifier: BSD-3-Clause
#
+import re
import shutil
+from dataclasses import dataclass
from pathlib import Path
-from typing import Optional
+from typing import Any, Dict, List, Optional
import click
-from memory.buildparser import TfaBuildParser
+from memory.elfparser import TfaElfParser
+from memory.image import Image
+from memory.mapparser import TfaMapParser
from memory.printer import TfaPrettyPrinter
-@click.command()
+@dataclass
+class Context:
+ build_path: Optional[Path] = None
+ printer: Optional[TfaPrettyPrinter] = None
+
+
+@click.group()
+@click.pass_obj
@click.option(
"-r",
"--root",
@@ -37,31 +48,6 @@
type=click.Choice(["debug", "release"], case_sensitive=False),
)
@click.option(
- "-f",
- "--footprint",
- is_flag=True,
- show_default=True,
- help="Generate a high level view of memory usage by memory types.",
-)
-@click.option(
- "-t",
- "--tree",
- is_flag=True,
- help="Generate a hierarchical view of the modules, segments and sections.",
-)
-@click.option(
- "--depth",
- default=3,
- show_default=True,
- help="Generate a virtual address map of important TF symbols.",
-)
-@click.option(
- "-s",
- "--symbols",
- is_flag=True,
- help="Generate a map of important TF symbols.",
-)
-@click.option(
"-w",
"--width",
type=int,
@@ -74,48 +60,138 @@
default=False,
help="Display numbers in decimal base.",
)
-@click.option(
- "--no-elf-images",
- is_flag=True,
- help="Analyse the build's map files instead of ELF images.",
-)
-def main(
+def cli(
+ obj: Context,
root: Optional[Path],
platform: str,
build_type: str,
- footprint: bool,
- tree: bool,
- symbols: bool,
- depth: int,
width: int,
d: bool,
- no_elf_images: bool,
):
- build_path: Path = root if root is not None else Path("build", platform, build_type)
- click.echo(f"build-path: {build_path.resolve()}")
+ obj.build_path = root if root is not None else Path("build", platform, build_type)
+ click.echo(f"build-path: {obj.build_path.resolve()}")
+
+ obj.printer = TfaPrettyPrinter(columns=width, as_decimal=d)
+
+
+@cli.command()
+@click.pass_obj
+@click.option(
+ "--no-elf-images",
+ is_flag=True,
+ help="Analyse the build's map files instead of ELF images.",
+)
+def footprint(obj: Context, no_elf_images: bool):
+ """Generate a high level view of memory usage by memory types."""
+
+ assert obj.build_path is not None
+ assert obj.printer is not None
+
+ elf_image_paths: List[Path] = (
+ [] if no_elf_images else list(obj.build_path.glob("**/*.elf"))
+ )
- parser: TfaBuildParser = TfaBuildParser(build_path, map_backend=no_elf_images)
- printer: TfaPrettyPrinter = TfaPrettyPrinter(columns=width, as_decimal=d)
+ map_file_paths: List[Path] = (
+ [] if not no_elf_images else list(obj.build_path.glob("**/*.map"))
+ )
- if footprint or not (tree or symbols):
- printer.print_footprint(parser.get_mem_usage_dict())
+ images: Dict[str, Image] = dict()
- if tree:
- printer.print_mem_tree(
- parser.get_mem_tree_as_dict(),
- parser.module_names,
- depth=depth,
- )
+ for elf_image_path in elf_image_paths:
+ with open(elf_image_path, "rb") as elf_image_io:
+ images[elf_image_path.stem.upper()] = TfaElfParser(elf_image_io)
+
+ for map_file_path in map_file_paths:
+ with open(map_file_path, "r") as map_file_io:
+ images[map_file_path.stem.upper()] = TfaMapParser(map_file_io)
+
+ obj.printer.print_footprint({k: v.footprint for k, v in images.items()})
+
+
+@cli.command()
+@click.pass_obj
+@click.option(
+ "--depth",
+ default=3,
+ show_default=True,
+ help="Generate a virtual address map of important TF symbols.",
+)
+def tree(obj: Context, depth: int):
+ """Generate a hierarchical view of the modules, segments and sections."""
+
+ assert obj.build_path is not None
+ assert obj.printer is not None
+
+ paths: List[Path] = list(obj.build_path.glob("**/*.elf"))
+ images: Dict[str, TfaElfParser] = dict()
+
+ for path in paths:
+ with open(path, "rb") as io:
+ images[path.stem] = TfaElfParser(io)
+
+ mtree: Dict[str, Dict[str, Any]] = {
+ k: {
+ "name": k,
+ **v.get_mod_mem_usage_dict(),
+ **{"children": v.get_seg_map_as_dict()},
+ }
+ for k, v in images.items()
+ }
+
+ obj.printer.print_mem_tree(mtree, list(mtree.keys()), depth=depth)
+
+
+@cli.command()
+@click.pass_obj
+@click.option(
+ "--no-elf-images",
+ is_flag=True,
+ help="Analyse the build's map files instead of ELF images.",
+)
+def symbols(obj: Context, no_elf_images: bool):
+ """Generate a map of important TF symbols."""
+
+ assert obj.build_path is not None
+ assert obj.printer is not None
+
+ expr: str = (
+ r"(.*)(TEXT|BSS|RO|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF|RELA"
+ r"|R.M)(.*)(START|UNALIGNED|END)__$"
+ )
+
+ elf_image_paths: List[Path] = (
+ [] if no_elf_images else list(obj.build_path.glob("**/*.elf"))
+ )
+
+ map_file_paths: List[Path] = (
+ [] if not no_elf_images else list(obj.build_path.glob("**/*.map"))
+ )
+
+ images: Dict[str, Image] = dict()
+
+ for elf_image_path in elf_image_paths:
+ with open(elf_image_path, "rb") as elf_image_io:
+ images[elf_image_path.stem] = TfaElfParser(elf_image_io)
+
+ for map_file_path in map_file_paths:
+ with open(map_file_path, "r") as map_file_io:
+ images[map_file_path.stem] = TfaMapParser(map_file_io)
+
+ symbols = {k: v.symbols for k, v in images.items()}
+ symbols = {
+ image: {
+ symbol: symbol_value
+ for symbol, symbol_value in symbols.items()
+ if re.match(expr, symbol)
+ }
+ for image, symbols in symbols.items()
+ }
+
+ obj.printer.print_symbol_table(symbols, list(images.keys()))
+
- if symbols:
- expr: str = (
- r"(.*)(TEXT|BSS|RO|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF|RELA"
- r"|R.M)(.*)(START|UNALIGNED|END)__$"
- )
- printer.print_symbol_table(
- parser.filter_symbols(parser.symbols, expr),
- parser.module_names,
- )
+def main():
+ cli(obj=Context())
if __name__ == "__main__":
diff --git a/tools/memory/src/memory/printer.py b/tools/memory/src/memory/printer.py
index 1a8c1c3..6debf53 100755
--- a/tools/memory/src/memory/printer.py
+++ b/tools/memory/src/memory/printer.py
@@ -95,7 +95,7 @@
val = vals[mem]
table.add_row(
[
- mod.upper(),
+ mod,
*self.format_args(
*[
val.start if val.start is not None else "?",