feat(memmap): add topological memory view

Present memory usage in hierarchical view. This view maps modules to
their respective segments and sections.

Change-Id: I5c374b46738edbc83133441ff3f4268f08cb011d
Signed-off-by: Harrison Mutai <harrison.mutai@arm.com>
This commit is contained in:
Harrison Mutai 2023-02-23 11:30:55 +00:00
parent d9d5eb138d
commit cc60aba227
5 changed files with 234 additions and 2 deletions

View file

@ -147,6 +147,88 @@ and usage. This is the default output generated by the tool.
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

@ -59,6 +59,18 @@ class TfaBuildParser:
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

@ -5,11 +5,21 @@
#
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.
@ -29,12 +39,74 @@ 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.
@ -55,6 +127,10 @@ class TfaElfParser:
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.
@ -72,3 +148,14 @@ class TfaElfParser:
"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

@ -42,6 +42,17 @@ from memory.printer import TfaPrettyPrinter
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",
@ -59,8 +70,10 @@ def main(
root: Path,
platform: str,
build_type: str,
footprint: bool,
footprint: str,
tree: bool,
symbols: bool,
depth: int,
width: int,
d: bool,
):
@ -70,9 +83,14 @@ def main(
parser = TfaBuildParser(build_path)
printer = TfaPrettyPrinter(columns=width, as_decimal=d)
if footprint or not symbols:
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,8 @@
# SPDX-License-Identifier: BSD-3-Clause
#
from anytree import RenderTree
from anytree.importer import DictImporter
from prettytable import PrettyTable
@ -17,6 +19,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._tree = None
self._footprint = None
self._symbol_map = None
self.as_decimal = as_decimal
@ -26,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,
@ -125,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")