1# 2# Copyright (c) 2023-2025, Arm Limited. All rights reserved. 3# 4# SPDX-License-Identifier: BSD-3-Clause 5# 6 7from typing import Any, Dict, List, Optional, Tuple 8 9from anytree import RenderTree 10from anytree.importer import DictImporter 11from prettytable import PrettyTable 12 13from memory.image import Region 14 15 16class TfaPrettyPrinter: 17 """A class for printing the memory layout of ELF files. 18 19 This class provides interfaces for printing various memory layout views of 20 ELF files in a TF-A build. It can be used to understand how the memory is 21 structured and consumed. 22 """ 23 24 def __init__(self, columns: int, as_decimal: bool = False) -> None: 25 self.term_size: int = columns 26 self._tree: Optional[List[str]] = None 27 self._symbol_map: Optional[List[str]] = None 28 self.as_decimal: bool = as_decimal 29 30 def format_args( 31 self, 32 *args: Any, 33 width: int = 10, 34 fmt: Optional[str] = None, 35 ) -> List[str]: 36 if not fmt and isinstance(args[0], int): 37 fmt = f">{width}x" if not self.as_decimal else f">{width}" 38 return [f"{arg:{fmt}}" if fmt else str(arg) for arg in args] 39 40 def format_row( 41 self, 42 leading: str, 43 *args: Any, 44 width: int = 10, 45 fmt: Optional[str] = None, 46 ) -> str: 47 formatted_args = self.format_args(*args, width=width, fmt=fmt) 48 return leading + " ".join(formatted_args) 49 50 @staticmethod 51 def map_elf_symbol( 52 leading: str, 53 section_name: str, 54 rel_pos: int, 55 columns: int, 56 width: int, 57 is_edge: bool = False, 58 ) -> str: 59 empty_col = "{:{}{}}" 60 61 # Some symbols are longer than the column width, truncate them until 62 # we find a more elegant way to display them! 63 len_over = len(section_name) - width 64 if len_over > 0: 65 section_name = section_name[len_over:-len_over] 66 67 sec_row = f"+{section_name:-^{width - 1}}+" 68 sep, fill = ("+", "-") if is_edge else ("|", "") 69 70 sec_row_l = empty_col.format(sep, fill + "<", width) * rel_pos 71 sec_row_r = empty_col.format(sep, fill + ">", width) * (columns - rel_pos - 1) 72 73 return leading + sec_row_l + sec_row + sec_row_r 74 75 def print_footprint( 76 self, 77 app_mem_usage: Dict[str, Dict[str, Region]], 78 ): 79 assert app_mem_usage, "Empty memory layout dictionary!" 80 81 fields = ["Component", "Start", "Limit", "Size", "Free", "Total"] 82 sort_key = fields[0] 83 84 # Iterate through all the memory types, create a table for each 85 # type, rows represent a single module. 86 for mem in sorted({k for v in app_mem_usage.values() for k in v}): 87 table = PrettyTable( 88 sortby=sort_key, 89 title=f"Memory Usage (bytes) [{mem.upper()}]", 90 field_names=fields, 91 ) 92 93 for mod, vals in app_mem_usage.items(): 94 if mem in vals: 95 val = vals[mem] 96 table.add_row( 97 [ 98 mod, 99 *self.format_args( 100 *[ 101 val.start if val.start is not None else "?", 102 val.limit if val.limit is not None else "?", 103 val.size if val.size is not None else "?", 104 val.free if val.free is not None else "?", 105 val.length if val.length is not None else "?", 106 ] 107 ), 108 ] 109 ) 110 print(table, "\n") 111 112 def print_symbol_table( 113 self, 114 symbol_table: Dict[str, Dict[str, int]], 115 modules: List[str], 116 start: int = 12, 117 ) -> None: 118 assert len(symbol_table), "Empty symbol list!" 119 modules = sorted(modules) 120 col_width = (self.term_size - start) // len(modules) 121 address_fixed_width = 11 122 123 num_fmt = f"0=#0{address_fixed_width}x" if not self.as_decimal else ">10" 124 125 _symbol_map = [ 126 " " * start + "".join(self.format_args(*modules, fmt=f"^{col_width}")) 127 ] 128 last_addr = None 129 130 symbols_list: List[Tuple[str, int, str]] = [ 131 (name, addr, mod) 132 for mod, syms in symbol_table.items() 133 for name, addr in syms.items() 134 ] 135 136 symbols_list.sort(key=lambda x: (-x[1], x[0]), reverse=True) 137 138 for i, (name, addr, mod) in enumerate(symbols_list): 139 # Do not print out an address twice if two symbols overlap, 140 # for example, at the end of one region and start of another. 141 leading = f"{addr:{num_fmt}}" + " " if addr != last_addr else " " * start 142 143 _symbol_map.append( 144 self.map_elf_symbol( 145 leading, 146 name, 147 modules.index(mod), 148 len(modules), 149 col_width, 150 is_edge=(i == 0 or i == len(symbols_list) - 1), 151 ) 152 ) 153 154 last_addr = addr 155 156 self._symbol_map = ["Memory Layout:"] + list(reversed(_symbol_map)) 157 print("\n".join(self._symbol_map)) 158 159 def print_mem_tree( 160 self, 161 mem_map_dict: Dict[str, Any], 162 modules: List[str], 163 depth: int = 1, 164 min_pad: int = 12, 165 node_right_pad: int = 12, 166 ) -> None: 167 # Start column should have some padding between itself and its data 168 # values. 169 anchor = min_pad + node_right_pad * (depth - 1) 170 headers = ["start", "end", "size"] 171 172 self._tree = [f"{'name':<{anchor}}" + " ".join(f"{arg:>10}" for arg in headers)] 173 174 for mod in sorted(modules): 175 root = DictImporter().import_(mem_map_dict[mod]) 176 for pre, fill, node in RenderTree(root, maxlevel=depth): 177 leading = f"{pre}{node.name}".ljust(anchor) 178 self._tree.append( 179 self.format_row( 180 leading, 181 node.start, 182 node.end, 183 node.size, 184 ) 185 ) 186 print("\n".join(self._tree), "\n") 187