From d9d5eb138ded8d4abeaf0cd1341ddf451aa299b8 Mon Sep 17 00:00:00 2001 From: Harrison Mutai <harrison.mutai@arm.com> Date: Thu, 23 Feb 2023 11:30:17 +0000 Subject: [PATCH] feat(memmap): add tabular memory use data Add support for tabulating static memory consumption data from ELF binaries. This relies on static symbols, defined in the linker files, that provide information about the memory ranges. Change-Id: Ie19cd2b80a7b591607640feeb84c63266963ea4d Signed-off-by: Harrison Mutai <harrison.mutai@arm.com> --- changelog.yaml | 3 +++ docs/tools/memory-layout-tool.rst | 34 +++++++++++++++++++++++++ tools/memory/memory/buildparser.py | 9 +++++++ tools/memory/memory/elfparser.py | 41 ++++++++++++++++++++++++++++++ tools/memory/memory/memmap.py | 13 ++++++++-- tools/memory/memory/printer.py | 34 +++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 2 deletions(-) diff --git a/changelog.yaml b/changelog.yaml index 9114dadb5..39dbf3b6b 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -1269,6 +1269,9 @@ subsections: - title: Certificate Creation Tool scope: cert-create + - title: Memory Mapping Tool + scope: memmap + deprecated: - cert_create diff --git a/docs/tools/memory-layout-tool.rst b/docs/tools/memory-layout-tool.rst index ce14dab64..ff5188e4b 100644 --- a/docs/tools/memory-layout-tool.rst +++ b/docs/tools/memory-layout-tool.rst @@ -113,6 +113,40 @@ For more detailed help instructions, run: poetry run memory --help +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. + +.. code:: shell + + $ poetry run memory -f + build-path: build/fvp/release + +----------------------------------------------------------------------------+ + | Memory Usage (bytes) [RAM] | + +-----------+------------+------------+------------+------------+------------+ + | Component | Start | Limit | Size | Free | Total | + +-----------+------------+------------+------------+------------+------------+ + | BL1 | 4034000 | 4040000 | 7000 | 5000 | c000 | + | BL2 | 4021000 | 4034000 | d000 | 6000 | 13000 | + | BL2U | 4021000 | 4034000 | a000 | 9000 | 13000 | + | BL31 | 4003000 | 4040000 | 1e000 | 1f000 | 3d000 | + +-----------+------------+------------+------------+------------+------------+ + + +----------------------------------------------------------------------------+ + | Memory Usage (bytes) [ROM] | + +-----------+------------+------------+------------+------------+------------+ + | Component | Start | Limit | Size | Free | Total | + +-----------+------------+------------+------------+------------+------------+ + | BL1 | 0 | 4000000 | 5df0 | 3ffa210 | 4000000 | + +-----------+------------+------------+------------+------------+------------+ + +The script relies on symbols in the symbol table to determine the start, end, +and limit addresses of each bootloader stage. + -------------- *Copyright (c) 2023, Arm Limited. All rights reserved.* diff --git a/tools/memory/memory/buildparser.py b/tools/memory/memory/buildparser.py index 6f467cd6f..0e3beaaa5 100755 --- a/tools/memory/memory/buildparser.py +++ b/tools/memory/memory/buildparser.py @@ -50,6 +50,15 @@ class TfaBuildParser: reverse=True, ) + def get_mem_usage_dict(self) -> dict: + """Returns map of memory usage per memory type for each module.""" + mem_map = {} + for k, v in self._modules.items(): + mod_mem_map = v.get_elf_memory_layout() + if len(mod_mem_map): + mem_map[k] = mod_mem_map + return mem_map + @property def module_names(self): """Returns sorted list of module names.""" diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py index 3964e6cb2..b61f32859 100644 --- a/tools/memory/memory/elfparser.py +++ b/tools/memory/memory/elfparser.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause # +import re from typing import BinaryIO from elftools.elf.elffile import ELFFile @@ -28,6 +29,46 @@ class TfaElfParser: for sym in elf.get_section_by_name(".symtab").iter_symbols() } + self._memory_layout = self.get_memory_layout_from_symbols() + @property def symbols(self): return self._symbols.items() + + def get_memory_layout_from_symbols(self, expr=None) -> dict: + """Retrieve information about the memory configuration from the symbol + table. + """ + assert len(self._symbols), "Symbol table is empty!" + + expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)" if not expr else expr + region_symbols = filter(lambda s: re.match(expr, s), self._symbols) + memory_layout = {} + + for symbol in region_symbols: + region, _, attr = tuple(symbol.lower().strip("__").split("_")) + if region not in memory_layout: + memory_layout[region] = {} + + # Retrieve the value of the symbol using the symbol as the key. + memory_layout[region][attr] = self._symbols[symbol] + + return memory_layout + + def get_elf_memory_layout(self): + """Get the total memory consumed by this module from the memory + configuration. + {"rom": {"start": 0x0, "end": 0xFF, "length": ... } + """ + mem_dict = {} + + for mem, attrs in self._memory_layout.items(): + limit = attrs["start"] + attrs["length"] + mem_dict[mem] = { + "start": attrs["start"], + "limit": limit, + "size": attrs["end"] - attrs["start"], + "free": limit - attrs["end"], + "total": attrs["length"], + } + return mem_dict diff --git a/tools/memory/memory/memmap.py b/tools/memory/memory/memmap.py index 705722870..e6b66f9c2 100755 --- a/tools/memory/memory/memmap.py +++ b/tools/memory/memory/memmap.py @@ -35,12 +35,17 @@ from memory.printer import TfaPrettyPrinter help="The target build type.", 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( "-s", "--symbols", is_flag=True, - show_default=True, - default=True, help="Generate a map of important TF symbols.", ) @click.option("-w", "--width", type=int, envvar="COLUMNS") @@ -54,6 +59,7 @@ def main( root: Path, platform: str, build_type: str, + footprint: bool, symbols: bool, width: int, d: bool, @@ -64,6 +70,9 @@ def main( parser = TfaBuildParser(build_path) printer = TfaPrettyPrinter(columns=width, as_decimal=d) + if footprint or not symbols: + printer.print_footprint(parser.get_mem_usage_dict()) + if symbols: expr = ( r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF" diff --git a/tools/memory/memory/printer.py b/tools/memory/memory/printer.py index 11fd7f021..c6019c211 100755 --- a/tools/memory/memory/printer.py +++ b/tools/memory/memory/printer.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: BSD-3-Clause # +from prettytable import PrettyTable + class TfaPrettyPrinter: """A class for printing the memory layout of ELF files. @@ -15,6 +17,7 @@ class TfaPrettyPrinter: def __init__(self, columns: int = None, as_decimal: bool = False): self.term_size = columns if columns and columns > 120 else 120 + self._footprint = None self._symbol_map = None self.as_decimal = as_decimal @@ -50,6 +53,37 @@ class TfaPrettyPrinter: return leading + sec_row_l + sec_row + sec_row_r + def print_footprint( + self, app_mem_usage: dict, sort_key: str = None, fields: list = None + ): + assert len(app_mem_usage), "Empty memory layout dictionary!" + if not fields: + fields = ["Component", "Start", "Limit", "Size", "Free", "Total"] + + sort_key = fields[0] if not sort_key else sort_key + + # Iterate through all the memory types, create a table for each + # type, rows represent a single module. + for mem in sorted(set(k for _, v in app_mem_usage.items() for k in v)): + table = PrettyTable( + sortby=sort_key, + title=f"Memory Usage (bytes) [{mem.upper()}]", + field_names=fields, + ) + + for mod, vals in app_mem_usage.items(): + if mem in vals.keys(): + val = vals[mem] + table.add_row( + [ + mod.upper(), + *self.format_args( + *[val[k.lower()] for k in fields[1:]] + ), + ] + ) + print(table, "\n") + def print_symbol_table( self, symbols: list,