summaryrefslogtreecommitdiffstats
path: root/tools/memory
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 17:43:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 17:43:51 +0000
commitbe58c81aff4cd4c0ccf43dbd7998da4a6a08c03b (patch)
tree779c248fb61c83f65d1f0dc867f2053d76b4e03a /tools/memory
parentInitial commit. (diff)
downloadarm-trusted-firmware-be58c81aff4cd4c0ccf43dbd7998da4a6a08c03b.tar.xz
arm-trusted-firmware-be58c81aff4cd4c0ccf43dbd7998da4a6a08c03b.zip
Adding upstream version 2.10.0+dfsg.upstream/2.10.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/memory')
-rw-r--r--tools/memory/__init__.py7
-rw-r--r--tools/memory/memory/__init__.py7
-rwxr-xr-xtools/memory/memory/buildparser.py88
-rw-r--r--tools/memory/memory/elfparser.py161
-rw-r--r--tools/memory/memory/mapparser.py75
-rwxr-xr-xtools/memory/memory/memmap.py111
-rwxr-xr-xtools/memory/memory/printer.py163
7 files changed, 612 insertions, 0 deletions
diff --git a/tools/memory/__init__.py b/tools/memory/__init__.py
new file mode 100644
index 0000000..0b4c8d3
--- /dev/null
+++ b/tools/memory/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
diff --git a/tools/memory/memory/__init__.py b/tools/memory/memory/__init__.py
new file mode 100644
index 0000000..0b4c8d3
--- /dev/null
+++ b/tools/memory/memory/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
diff --git a/tools/memory/memory/buildparser.py b/tools/memory/memory/buildparser.py
new file mode 100755
index 0000000..dedff79
--- /dev/null
+++ b/tools/memory/memory/buildparser.py
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import re
+from pathlib import Path
+
+from memory.elfparser import TfaElfParser
+from memory.mapparser import TfaMapParser
+
+
+class TfaBuildParser:
+ """A class for performing analysis on the memory layout of a TF-A build."""
+
+ def __init__(self, path: Path, map_backend=False):
+ self._modules = dict()
+ self._path = path
+ self.map_backend = map_backend
+ self._parse_modules()
+
+ def __getitem__(self, module: str):
+ """Returns an TfaElfParser instance indexed by module."""
+ return self._modules[module]
+
+ def _parse_modules(self):
+ """Parse the build files using the selected backend."""
+ backend = TfaElfParser
+ files = list(self._path.glob("**/*.elf"))
+ io_perms = "rb"
+
+ if self.map_backend or len(files) == 0:
+ backend = TfaMapParser
+ files = self._path.glob("**/*.map")
+ io_perms = "r"
+
+ for file in files:
+ module_name = file.name.split("/")[-1].split(".")[0]
+ with open(file, io_perms) as f:
+ self._modules[module_name] = backend(f)
+
+ if not len(self._modules):
+ raise FileNotFoundError(
+ f"failed to find files to analyse in path {self._path}!"
+ )
+
+ @property
+ def symbols(self) -> list:
+ return [
+ (*sym, k) for k, v in self._modules.items() for sym in v.symbols
+ ]
+
+ @staticmethod
+ def filter_symbols(symbols: list, regex: str = None) -> list:
+ """Returns a map of symbols to modules."""
+ regex = r".*" if not regex else regex
+ return sorted(
+ filter(lambda s: re.match(regex, s[0]), symbols),
+ key=lambda s: (-s[1], s[0]),
+ 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_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."""
+ return sorted(self._modules.keys())
diff --git a/tools/memory/memory/elfparser.py b/tools/memory/memory/elfparser.py
new file mode 100644
index 0000000..2dd2513
--- /dev/null
+++ b/tools/memory/memory/elfparser.py
@@ -0,0 +1,161 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# 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.
+
+ Provides a basic interface for reading the symbol table and other
+ attributes of an ELF file. The constructor accepts a file-like object with
+ the contents an ELF file.
+ """
+
+ def __init__(self, elf_file: BinaryIO):
+ self._segments = {}
+ self._memory_layout = {}
+
+ elf = ELFFile(elf_file)
+
+ self._symbols = {
+ sym.name: sym.entry["st_value"]
+ 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_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,
+ }
diff --git a/tools/memory/memory/mapparser.py b/tools/memory/memory/mapparser.py
new file mode 100644
index 0000000..b1a4b4c
--- /dev/null
+++ b/tools/memory/memory/mapparser.py
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from re import match, search
+from typing import TextIO
+
+
+class TfaMapParser:
+ """A class representing a map file built for TF-A.
+
+ Provides a basic interface for reading the symbol table. The constructor
+ accepts a file-like object with the contents a Map file. Only GNU map files
+ are supported at this stage.
+ """
+
+ def __init__(self, map_file: TextIO):
+ self._symbols = self.read_symbols(map_file)
+
+ @property
+ def symbols(self):
+ return self._symbols.items()
+
+ @staticmethod
+ def read_symbols(file: TextIO, pattern: str = None) -> dict:
+ pattern = r"\b(0x\w*)\s*(\w*)\s=" if not pattern else pattern
+ symbols = {}
+
+ for line in file.readlines():
+ match = search(pattern, line)
+
+ if match is not None:
+ value, name = match.groups()
+ symbols[name] = int(value, 16)
+
+ return symbols
+
+ def get_memory_layout(self) -> dict:
+ """Get the total memory consumed by this module from the memory
+ configuration.
+ {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
+ """
+ assert len(self._symbols), "Symbol table is empty!"
+ expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
+ memory_layout = {}
+
+ region_symbols = filter(lambda s: match(expr, s), self._symbols)
+
+ for symbol in region_symbols:
+ region, _, attr = tuple(symbol.lower().strip("__").split("_"))
+ if region not in memory_layout:
+ memory_layout[region] = {}
+
+ memory_layout[region][attr] = self._symbols[symbol]
+
+ if "start" and "length" and "end" in memory_layout[region]:
+ memory_layout[region]["limit"] = (
+ memory_layout[region]["end"]
+ + memory_layout[region]["length"]
+ )
+ memory_layout[region]["free"] = (
+ memory_layout[region]["limit"]
+ - memory_layout[region]["end"]
+ )
+ memory_layout[region]["total"] = memory_layout[region][
+ "length"
+ ]
+ memory_layout[region]["size"] = (
+ memory_layout[region]["end"]
+ - memory_layout[region]["start"]
+ )
+
+ return memory_layout
diff --git a/tools/memory/memory/memmap.py b/tools/memory/memory/memmap.py
new file mode 100755
index 0000000..99149b5
--- /dev/null
+++ b/tools/memory/memory/memmap.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from pathlib import Path
+
+import click
+from memory.buildparser import TfaBuildParser
+from memory.printer import TfaPrettyPrinter
+
+
+@click.command()
+@click.option(
+ "-r",
+ "--root",
+ type=Path,
+ default=None,
+ help="Root containing build output.",
+)
+@click.option(
+ "-p",
+ "--platform",
+ show_default=True,
+ default="fvp",
+ help="The platform targeted for analysis.",
+)
+@click.option(
+ "-b",
+ "--build-type",
+ default="release",
+ 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,
+ help="Generate a map of important TF symbols.",
+)
+@click.option("-w", "--width", type=int, envvar="COLUMNS")
+@click.option(
+ "-d",
+ is_flag=True,
+ default=False,
+ help="Display numbers in decimal base.",
+)
+@click.option(
+ "--no-elf-images",
+ is_flag=True,
+ help="Analyse the build's map files instead of ELF images.",
+)
+def main(
+ root: Path,
+ platform: str,
+ build_type: str,
+ footprint: str,
+ tree: bool,
+ symbols: bool,
+ depth: int,
+ width: int,
+ d: bool,
+ no_elf_images: bool,
+):
+ build_path = root if root else Path("build/", platform, build_type)
+ click.echo(f"build-path: {build_path.resolve()}")
+
+ parser = TfaBuildParser(build_path, map_backend=no_elf_images)
+ 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"
+ r"|R.M)(.*)(START|UNALIGNED|END)__$"
+ )
+ printer.print_symbol_table(
+ parser.filter_symbols(parser.symbols, expr), parser.module_names
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/memory/memory/printer.py b/tools/memory/memory/printer.py
new file mode 100755
index 0000000..4b18560
--- /dev/null
+++ b/tools/memory/memory/printer.py
@@ -0,0 +1,163 @@
+#
+# Copyright (c) 2023, Arm Limited. All rights reserved.
+#
+# 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.
+
+ This class provides interfaces for printing various memory layout views of
+ ELF files in a TF-A build. It can be used to understand how the memory is
+ structured and consumed.
+ """
+
+ 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
+
+ def format_args(self, *args, width=10, fmt=None):
+ if not fmt and type(args[0]) is int:
+ 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,
+ section_name: str,
+ rel_pos: int,
+ columns: int,
+ width: int = None,
+ is_edge: bool = False,
+ ):
+ empty_col = "{:{}{}}"
+
+ # Some symbols are longer than the column width, truncate them until
+ # we find a more elegant way to display them!
+ len_over = len(section_name) - width
+ if len_over > 0:
+ section_name = section_name[len_over:-len_over]
+
+ sec_row = f"+{section_name:-^{width-1}}+"
+ sep, fill = ("+", "-") if is_edge else ("|", "")
+
+ sec_row_l = empty_col.format(sep, fill + "<", width) * rel_pos
+ sec_row_r = empty_col.format(sep, fill + ">", width) * (
+ columns - rel_pos - 1
+ )
+
+ 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,
+ modules: list,
+ start: int = 12,
+ ):
+ assert len(symbols), "Empty symbol list!"
+ modules = sorted(modules)
+ col_width = int((self.term_size - start) / len(modules))
+ address_fixed_width = 11
+
+ num_fmt = (
+ f"0=#0{address_fixed_width}x" if not self.as_decimal else ">10"
+ )
+
+ _symbol_map = [
+ " " * start
+ + "".join(self.format_args(*modules, fmt=f"^{col_width}"))
+ ]
+ last_addr = None
+
+ for i, (name, addr, mod) in enumerate(symbols):
+ # Do not print out an address twice if two symbols overlap,
+ # for example, at the end of one region and start of another.
+ leading = (
+ f"{addr:{num_fmt}}" + " " if addr != last_addr else " " * start
+ )
+
+ _symbol_map.append(
+ self.map_elf_symbol(
+ leading,
+ name,
+ modules.index(mod),
+ len(modules),
+ width=col_width,
+ is_edge=(not i or i == len(symbols) - 1),
+ )
+ )
+
+ last_addr = addr
+
+ 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")