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>
This commit is contained in:
Harrison Mutai 2023-02-23 11:30:17 +00:00
parent fcb72e16ce
commit d9d5eb138d
6 changed files with 132 additions and 2 deletions

View file

@ -1269,6 +1269,9 @@ subsections:
- title: Certificate Creation Tool
scope: cert-create
- title: Memory Mapping Tool
scope: memmap
deprecated:
- cert_create

View file

@ -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.*

View file

@ -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."""

View file

@ -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

View file

@ -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"

View file

@ -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,