mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-16 17:44:19 +00:00
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:
parent
d9d5eb138d
commit
cc60aba227
5 changed files with 234 additions and 2 deletions
|
@ -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,
|
The script relies on symbols in the symbol table to determine the start, end,
|
||||||
and limit addresses of each bootloader stage.
|
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.*
|
*Copyright (c) 2023, Arm Limited. All rights reserved.*
|
||||||
|
|
|
@ -59,6 +59,18 @@ class TfaBuildParser:
|
||||||
mem_map[k] = mod_mem_map
|
mem_map[k] = mod_mem_map
|
||||||
return 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
|
@property
|
||||||
def module_names(self):
|
def module_names(self):
|
||||||
"""Returns sorted list of module names."""
|
"""Returns sorted list of module names."""
|
||||||
|
|
|
@ -5,11 +5,21 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
from typing import BinaryIO
|
from typing import BinaryIO
|
||||||
|
|
||||||
from elftools.elf.elffile import ELFFile
|
from elftools.elf.elffile import ELFFile
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TfaMemObject:
|
||||||
|
name: str
|
||||||
|
start: int
|
||||||
|
end: int
|
||||||
|
size: int
|
||||||
|
children: list
|
||||||
|
|
||||||
|
|
||||||
class TfaElfParser:
|
class TfaElfParser:
|
||||||
"""A class representing an ELF file built for TF-A.
|
"""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()
|
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._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
|
@property
|
||||||
def symbols(self):
|
def symbols(self):
|
||||||
return self._symbols.items()
|
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:
|
def get_memory_layout_from_symbols(self, expr=None) -> dict:
|
||||||
"""Retrieve information about the memory configuration from the symbol
|
"""Retrieve information about the memory configuration from the symbol
|
||||||
table.
|
table.
|
||||||
|
@ -55,6 +127,10 @@ class TfaElfParser:
|
||||||
|
|
||||||
return memory_layout
|
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):
|
def get_elf_memory_layout(self):
|
||||||
"""Get the total memory consumed by this module from the memory
|
"""Get the total memory consumed by this module from the memory
|
||||||
configuration.
|
configuration.
|
||||||
|
@ -72,3 +148,14 @@ class TfaElfParser:
|
||||||
"total": attrs["length"],
|
"total": attrs["length"],
|
||||||
}
|
}
|
||||||
return mem_dict
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,17 @@ from memory.printer import TfaPrettyPrinter
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Generate a high level view of memory usage by memory types.",
|
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(
|
@click.option(
|
||||||
"-s",
|
"-s",
|
||||||
"--symbols",
|
"--symbols",
|
||||||
|
@ -59,8 +70,10 @@ def main(
|
||||||
root: Path,
|
root: Path,
|
||||||
platform: str,
|
platform: str,
|
||||||
build_type: str,
|
build_type: str,
|
||||||
footprint: bool,
|
footprint: str,
|
||||||
|
tree: bool,
|
||||||
symbols: bool,
|
symbols: bool,
|
||||||
|
depth: int,
|
||||||
width: int,
|
width: int,
|
||||||
d: bool,
|
d: bool,
|
||||||
):
|
):
|
||||||
|
@ -70,9 +83,14 @@ def main(
|
||||||
parser = TfaBuildParser(build_path)
|
parser = TfaBuildParser(build_path)
|
||||||
printer = TfaPrettyPrinter(columns=width, as_decimal=d)
|
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())
|
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:
|
if symbols:
|
||||||
expr = (
|
expr = (
|
||||||
r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF"
|
r"(.*)(TEXT|BSS|RODATA|STACKS|_OPS|PMF|XLAT|GOT|FCONF"
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from anytree import RenderTree
|
||||||
|
from anytree.importer import DictImporter
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +19,7 @@ class TfaPrettyPrinter:
|
||||||
|
|
||||||
def __init__(self, columns: int = None, as_decimal: bool = False):
|
def __init__(self, columns: int = None, as_decimal: bool = False):
|
||||||
self.term_size = columns if columns and columns > 120 else 120
|
self.term_size = columns if columns and columns > 120 else 120
|
||||||
|
self._tree = None
|
||||||
self._footprint = None
|
self._footprint = None
|
||||||
self._symbol_map = None
|
self._symbol_map = None
|
||||||
self.as_decimal = as_decimal
|
self.as_decimal = as_decimal
|
||||||
|
@ -26,6 +29,10 @@ class TfaPrettyPrinter:
|
||||||
fmt = f">{width}x" if not self.as_decimal else f">{width}"
|
fmt = f">{width}x" if not self.as_decimal else f">{width}"
|
||||||
return [f"{arg:{fmt}}" if fmt else arg for arg in args]
|
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
|
@staticmethod
|
||||||
def map_elf_symbol(
|
def map_elf_symbol(
|
||||||
leading: str,
|
leading: str,
|
||||||
|
@ -125,3 +132,29 @@ class TfaPrettyPrinter:
|
||||||
self._symbol_map = ["Memory Layout:"]
|
self._symbol_map = ["Memory Layout:"]
|
||||||
self._symbol_map += list(reversed(_symbol_map))
|
self._symbol_map += list(reversed(_symbol_map))
|
||||||
print("\n".join(self._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")
|
||||||
|
|
Loading…
Add table
Reference in a new issue