Merge changes from topic "hm/memmap-feat" into integration

* changes:
  feat(memmap): add topological memory view
  feat(memmap): add tabular memory use data
This commit is contained in:
Manish Pandey 2023-06-07 17:48:14 +02:00 committed by TrustedFirmware Code Review
commit f6bf4d6bc8
6 changed files with 364 additions and 2 deletions

View file

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

View file

@ -113,6 +113,122 @@ 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.
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.
.. code:: shell
$ poetry run memory -t
build-path: build/fvp/release
name start end size
bl1 0 400c000 400c000
├── 00 0 5de0 5de0
│ ├── .text 0 5000 5000
│ └── .rodata 5000 5de0 de0
├── 01 4034000 40344c5 4c5
│ └── .data 4034000 40344c5 4c5
├── 02 4034500 4034a00 500
│ └── .stacks 4034500 4034a00 500
├── 04 4034a00 4035600 c00
│ └── .bss 4034a00 4035600 c00
└── 03 4036000 403b000 5000
└── .xlat_table 4036000 403b000 5000
bl2 4021000 4034000 13000
├── 00 4021000 4027000 6000
│ ├── .text 4021000 4026000 5000
│ └── .rodata 4026000 4027000 1000
└── 01 4027000 402e000 7000
├── .data 4027000 4027809 809
├── .stacks 4027840 4027e40 600
├── .bss 4028000 4028800 800
└── .xlat_table 4029000 402e000 5000
bl2u 4021000 4034000 13000
├── 00 4021000 4025000 4000
│ ├── .text 4021000 4024000 3000
│ └── .rodata 4024000 4025000 1000
└── 01 4025000 402b000 6000
├── .data 4025000 4025065 65
├── .stacks 4025080 4025480 400
├── .bss 4025600 4025c00 600
└── .xlat_table 4026000 402b000 5000
bl31 4003000 4040000 3d000
├── 02 ffe00000 ffe03000 3000
│ └── .el3_tzc_dram ffe00000 ffe03000 3000
├── 00 4003000 4010000 d000
│ └── .text 4003000 4010000 d000
└── 01 4010000 4021000 11000
├── .rodata 4010000 4012000 2000
├── .data 4012000 401219d 19d
├── .stacks 40121c0 40161c0 4000
├── .bss 4016200 4018c00 2a00
├── .xlat_table 4019000 4020000 7000
└── .coherent_ram 4020000 4021000 1000
The granularity of this view can be modified with the ``--depth`` option. For
instance, if you only require the tree up to the level showing segment data,
you can specify the depth with:
.. code::
$ poetry run memory -t --depth 2
build-path: build/fvp/release
name start end size
bl1 0 400c000 400c000
├── 00 0 5df0 5df0
├── 01 4034000 40344c5 4c5
├── 02 4034500 4034a00 500
├── 04 4034a00 4035600 c00
└── 03 4036000 403b000 5000
bl2 4021000 4034000 13000
├── 00 4021000 4027000 6000
└── 01 4027000 402e000 7000
bl2u 4021000 4034000 13000
├── 00 4021000 4025000 4000
└── 01 4025000 402b000 6000
bl31 4003000 4040000 3d000
├── 02 ffe00000 ffe03000 3000
├── 00 4003000 4010000 d000
└── 01 4010000 4021000 11000
--------------
*Copyright (c) 2023, Arm Limited. All rights reserved.*

View file

@ -50,6 +50,27 @@ 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
def get_mem_tree_as_dict(self) -> dict:
"""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):
"""Returns sorted list of module names."""

View file

@ -4,11 +4,22 @@
# SPDX-License-Identifier: BSD-3-Clause
#
import re
from dataclasses import asdict, dataclass
from typing import BinaryIO
from elftools.elf.elffile import ELFFile
@dataclass(frozen=True)
class TfaMemObject:
name: str
start: int
end: int
size: int
children: list
class TfaElfParser:
"""A class representing an ELF file built for TF-A.
@ -28,6 +39,123 @@ class TfaElfParser:
for sym in elf.get_section_by_name(".symtab").iter_symbols()
}
self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
self._memory_layout = self.get_memory_layout_from_symbols()
self._start = elf["e_entry"]
self._size, self._free = self._get_mem_usage()
self._end = self._start + self._size
@property
def symbols(self):
return self._symbols.items()
@staticmethod
def tfa_mem_obj_factory(elf_obj, name=None, children=None, segment=False):
"""Converts a pyelfparser Segment or Section to a TfaMemObject."""
# Ensure each segment is provided a name since they aren't in the
# program header.
assert not (
segment and name is None
), "Attempting to make segment without a name"
if children is None:
children = list()
# Segment and sections header keys have different prefixes.
vaddr = "p_vaddr" if segment else "sh_addr"
size = "p_memsz" if segment else "sh_size"
# TODO figure out how to handle free space for sections and segments
return TfaMemObject(
name if segment else elf_obj.name,
elf_obj[vaddr],
elf_obj[vaddr] + elf_obj[size],
elf_obj[size],
[] if not children else children,
)
def _get_mem_usage(self) -> (int, int):
"""Get total size and free space for this component."""
size = free = 0
# Use information encoded in the segment header if we can't get a
# memory configuration.
if not self._memory_layout:
return sum(s.size for s in self._segments.values()), 0
for v in self._memory_layout.values():
size += v["length"]
free += v["start"] + v["length"] - v["end"]
return size, free
def set_segment_section_map(self, segments, sections):
"""Set segment to section mappings."""
segments = list(
filter(lambda seg: seg["p_type"] == "PT_LOAD", segments)
)
for sec in sections:
for n, seg in enumerate(segments):
if seg.section_in_segment(sec):
if n not in self._segments.keys():
self._segments[n] = self.tfa_mem_obj_factory(
seg, name=f"{n:#02}", segment=True
)
self._segments[n].children.append(
self.tfa_mem_obj_factory(sec)
)
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_seg_map_as_dict(self):
"""Get a dictionary of segments and their section mappings."""
return [asdict(v) for k, v in self._segments.items()]
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
def get_mod_mem_usage_dict(self):
"""Get the total memory consumed by the module, this combines the
information in the memory configuration.
"""
return {
"start": self._start,
"end": self._end,
"size": self._size,
"free": self._free,
}

View file

@ -35,12 +35,28 @@ 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(
"-t",
"--tree",
is_flag=True,
help="Generate a hierarchical view of the modules, segments and sections.",
)
@click.option(
"--depth",
default=3,
help="Generate a virtual address map of important TF symbols.",
)
@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,7 +70,10 @@ def main(
root: Path,
platform: str,
build_type: str,
footprint: str,
tree: bool,
symbols: bool,
depth: int,
width: int,
d: bool,
):
@ -64,6 +83,14 @@ def main(
parser = TfaBuildParser(build_path)
printer = TfaPrettyPrinter(columns=width, as_decimal=d)
if footprint or not (tree or symbols):
printer.print_footprint(parser.get_mem_usage_dict())
if tree:
printer.print_mem_tree(
parser.get_mem_tree_as_dict(), parser.module_names, depth=depth
)
if symbols:
expr = (
r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF"

View file

@ -4,6 +4,10 @@
# SPDX-License-Identifier: BSD-3-Clause
#
from anytree import RenderTree
from anytree.importer import DictImporter
from prettytable import PrettyTable
class TfaPrettyPrinter:
"""A class for printing the memory layout of ELF files.
@ -15,6 +19,8 @@ class TfaPrettyPrinter:
def __init__(self, columns: int = None, as_decimal: bool = False):
self.term_size = columns if columns and columns > 120 else 120
self._tree = None
self._footprint = None
self._symbol_map = None
self.as_decimal = as_decimal
@ -23,6 +29,10 @@ class TfaPrettyPrinter:
fmt = f">{width}x" if not self.as_decimal else f">{width}"
return [f"{arg:{fmt}}" if fmt else arg for arg in args]
def format_row(self, leading, *args, width=10, fmt=None):
formatted_args = self.format_args(*args, width=width, fmt=fmt)
return leading + " ".join(formatted_args)
@staticmethod
def map_elf_symbol(
leading: str,
@ -50,6 +60,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,
@ -91,3 +132,29 @@ class TfaPrettyPrinter:
self._symbol_map = ["Memory Layout:"]
self._symbol_map += list(reversed(_symbol_map))
print("\n".join(self._symbol_map))
def print_mem_tree(
self, mem_map_dict, modules, depth=1, min_pad=12, node_right_pad=12
):
# Start column should have some padding between itself and its data
# values.
anchor = min_pad + node_right_pad * (depth - 1)
headers = ["start", "end", "size"]
self._tree = [
(f"{'name':<{anchor}}" + " ".join(f"{arg:>10}" for arg in headers))
]
for mod in sorted(modules):
root = DictImporter().import_(mem_map_dict[mod])
for pre, fill, node in RenderTree(root, maxlevel=depth):
leading = f"{pre}{node.name}".ljust(anchor)
self._tree.append(
self.format_row(
leading,
node.start,
node.end,
node.size,
)
)
print("\n".join(self._tree), "\n")