xref: /rk3399_ARM-atf/tools/memory/src/memory/printer.py (revision d8fdff38b544b79c4f0b757e3b3c82ce9c8a2f9e)
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