xref: /rk3399_ARM-atf/tools/memory/src/memory/elfparser.py (revision d8fdff38b544b79c4f0b757e3b3c82ce9c8a2f9e)
1#
2# Copyright (c) 2023-2025, Arm Limited. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6
7import re
8from dataclasses import asdict, dataclass
9from typing import (
10    Any,
11    BinaryIO,
12    Dict,
13    Iterable,
14    List,
15    Optional,
16    Tuple,
17    Union,
18)
19
20from elftools.elf.elffile import ELFFile
21from elftools.elf.sections import Section, SymbolTableSection
22from elftools.elf.segments import Segment
23
24from memory.image import Image, Region
25
26
27@dataclass(frozen=True)
28class TfaMemObject:
29    name: str
30    start: int
31    end: int
32    size: int
33    children: List["TfaMemObject"]
34
35
36class TfaElfParser(Image):
37    """A class representing an ELF file built for TF-A.
38
39    Provides a basic interface for reading the symbol table and other
40    attributes of an ELF file. The constructor accepts a file-like object with
41    the contents an ELF file.
42    """
43
44    def __init__(self, elf_file: BinaryIO) -> None:
45        self._segments: Dict[int, TfaMemObject] = {}
46        self._memory_layout: Dict[str, Dict[str, int]] = {}
47
48        elf = ELFFile(elf_file)
49
50        symtab = elf.get_section_by_name(".symtab")
51        assert isinstance(symtab, SymbolTableSection)
52
53        self._symbols: Dict[str, int] = {
54            sym.name: sym.entry["st_value"] for sym in symtab.iter_symbols()
55        }
56
57        self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
58        self._memory_layout = self.get_memory_layout_from_symbols()
59        self._start: int = elf["e_entry"]
60        self._size: int
61        self._free: int
62        self._size, self._free = self._get_mem_usage()
63        self._end: int = self._start + self._size
64
65        self._footprint: Dict[str, Region] = {}
66
67        for mem, attrs in self._memory_layout.items():
68            self._footprint[mem] = Region(
69                attrs["start"],
70                attrs["end"],
71                attrs["length"],
72            )
73
74    @property
75    def symbols(self) -> Dict[str, int]:
76        return self._symbols
77
78    @staticmethod
79    def tfa_mem_obj_factory(
80        elf_obj: Union[Segment, Section],
81        name: Optional[str] = None,
82        children: Optional[List[TfaMemObject]] = None,
83    ) -> TfaMemObject:
84        """Converts a pyelfparser Segment or Section to a TfaMemObject."""
85        # Ensure each segment is provided a name since they aren't in the
86        # program header.
87        assert not (isinstance(elf_obj, Segment) and name is None), (
88            "Attempting to make segment without a name"
89        )
90
91        # Segment and sections header keys have different prefixes.
92        vaddr = "p_vaddr" if isinstance(elf_obj, Segment) else "sh_addr"
93        size = "p_memsz" if isinstance(elf_obj, Segment) else "sh_size"
94
95        name = name if isinstance(elf_obj, Segment) else elf_obj.name
96        assert name is not None
97
98        # TODO figure out how to handle free space for sections and segments
99        return TfaMemObject(
100            name,
101            elf_obj[vaddr],
102            elf_obj[vaddr] + elf_obj[size],
103            elf_obj[size],
104            children or [],
105        )
106
107    def _get_mem_usage(self) -> Tuple[int, int]:
108        """Get total size and free space for this component."""
109        size = free = 0
110
111        # Use information encoded in the segment header if we can't get a
112        # memory configuration.
113        if not self._memory_layout:
114            return sum(s.size for s in self._segments.values()), 0
115
116        for v in self._memory_layout.values():
117            size += v["length"]
118            free += v["start"] + v["length"] - v["end"]
119
120        return size, free
121
122    def set_segment_section_map(
123        self,
124        segments: Iterable[Segment],
125        sections: Iterable[Section],
126    ) -> None:
127        """Set segment to section mappings."""
128        segments = filter(lambda seg: seg["p_type"] == "PT_LOAD", segments)
129        segments_list = list(segments)
130
131        for sec in sections:
132            for n, seg in enumerate(segments_list):
133                if seg.section_in_segment(sec):
134                    if n not in self._segments:
135                        self._segments[n] = self.tfa_mem_obj_factory(
136                            seg, name=f"{n:#02}"
137                        )
138
139                    self._segments[n].children.append(self.tfa_mem_obj_factory(sec))
140
141    def get_memory_layout_from_symbols(self) -> Dict[str, Dict[str, int]]:
142        """Retrieve information about the memory configuration from the symbol
143        table.
144        """
145        assert self._symbols, "Symbol table is empty!"
146
147        expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
148        region_symbols = filter(lambda s: re.match(expr, s), self._symbols)
149        memory_layout: Dict[str, Dict[str, int]] = {}
150
151        for symbol in region_symbols:
152            region, _, attr = symbol.lower().strip("__").split("_")
153            if region not in memory_layout:
154                memory_layout[region] = {}
155
156            # Retrieve the value of the symbol using the symbol as the key.
157            memory_layout[region][attr] = self._symbols[symbol]
158
159        return memory_layout
160
161    def get_seg_map_as_dict(self) -> List[Dict[str, Any]]:
162        """Get a dictionary of segments and their section mappings."""
163        return [asdict(segment) for segment in self._segments.values()]
164
165    def get_memory_layout(self) -> Dict[str, Region]:
166        """Get the total memory consumed by this module from the memory
167        configuration.
168        """
169        mem_dict: Dict[str, Region] = {}
170
171        for mem, attrs in self._memory_layout.items():
172            mem_dict[mem] = Region(
173                attrs["start"],
174                attrs["end"],
175                attrs["length"],
176            )
177
178        return mem_dict
179
180    @property
181    def footprint(self) -> Dict[str, Region]:
182        return self._footprint
183
184    def get_mod_mem_usage_dict(self) -> Dict[str, int]:
185        """Get the total memory consumed by the module, this combines the
186        information in the memory configuration.
187        """
188        return {
189            "start": self._start,
190            "end": self._end,
191            "size": self._size,
192            "free": self._free,
193        }
194