diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 17:43:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 17:43:51 +0000 |
commit | be58c81aff4cd4c0ccf43dbd7998da4a6a08c03b (patch) | |
tree | 779c248fb61c83f65d1f0dc867f2053d76b4e03a /tools/memory | |
parent | Initial commit. (diff) | |
download | arm-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__.py | 7 | ||||
-rw-r--r-- | tools/memory/memory/__init__.py | 7 | ||||
-rwxr-xr-x | tools/memory/memory/buildparser.py | 88 | ||||
-rw-r--r-- | tools/memory/memory/elfparser.py | 161 | ||||
-rw-r--r-- | tools/memory/memory/mapparser.py | 75 | ||||
-rwxr-xr-x | tools/memory/memory/memmap.py | 111 | ||||
-rwxr-xr-x | tools/memory/memory/printer.py | 163 |
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") |