blob: e6581c98a47a4297ac4f7a2163679ac9e82cce07 [file] [log] [blame]
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +00001#
Chris Kayed0c8012025-01-28 18:04:11 +00002# Copyright (c) 2023-2025, Arm Limited. All rights reserved.
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +00003#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6
Harrison Mutaid9d5eb12023-02-23 11:30:17 +00007import re
Harrison Mutaicc60aba2023-02-23 11:30:55 +00008from dataclasses import asdict, dataclass
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +00009from typing import BinaryIO
10
11from elftools.elf.elffile import ELFFile
12
13
Harrison Mutaicc60aba2023-02-23 11:30:55 +000014@dataclass(frozen=True)
15class TfaMemObject:
16 name: str
17 start: int
18 end: int
19 size: int
20 children: list
21
22
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000023class TfaElfParser:
24 """A class representing an ELF file built for TF-A.
25
26 Provides a basic interface for reading the symbol table and other
27 attributes of an ELF file. The constructor accepts a file-like object with
28 the contents an ELF file.
29 """
30
31 def __init__(self, elf_file: BinaryIO):
32 self._segments = {}
33 self._memory_layout = {}
34
35 elf = ELFFile(elf_file)
36
37 self._symbols = {
38 sym.name: sym.entry["st_value"]
39 for sym in elf.get_section_by_name(".symtab").iter_symbols()
40 }
41
Harrison Mutaicc60aba2023-02-23 11:30:55 +000042 self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000043 self._memory_layout = self.get_memory_layout_from_symbols()
Harrison Mutaicc60aba2023-02-23 11:30:55 +000044 self._start = elf["e_entry"]
45 self._size, self._free = self._get_mem_usage()
46 self._end = self._start + self._size
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000047
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000048 @property
49 def symbols(self):
50 return self._symbols.items()
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000051
Harrison Mutaicc60aba2023-02-23 11:30:55 +000052 @staticmethod
53 def tfa_mem_obj_factory(elf_obj, name=None, children=None, segment=False):
54 """Converts a pyelfparser Segment or Section to a TfaMemObject."""
55 # Ensure each segment is provided a name since they aren't in the
56 # program header.
57 assert not (
58 segment and name is None
59 ), "Attempting to make segment without a name"
60
61 if children is None:
62 children = list()
63
64 # Segment and sections header keys have different prefixes.
65 vaddr = "p_vaddr" if segment else "sh_addr"
66 size = "p_memsz" if segment else "sh_size"
67
68 # TODO figure out how to handle free space for sections and segments
69 return TfaMemObject(
70 name if segment else elf_obj.name,
71 elf_obj[vaddr],
72 elf_obj[vaddr] + elf_obj[size],
73 elf_obj[size],
74 [] if not children else children,
75 )
76
77 def _get_mem_usage(self) -> (int, int):
78 """Get total size and free space for this component."""
79 size = free = 0
80
81 # Use information encoded in the segment header if we can't get a
82 # memory configuration.
83 if not self._memory_layout:
84 return sum(s.size for s in self._segments.values()), 0
85
86 for v in self._memory_layout.values():
87 size += v["length"]
88 free += v["start"] + v["length"] - v["end"]
89
90 return size, free
91
92 def set_segment_section_map(self, segments, sections):
93 """Set segment to section mappings."""
94 segments = list(
95 filter(lambda seg: seg["p_type"] == "PT_LOAD", segments)
96 )
97
98 for sec in sections:
99 for n, seg in enumerate(segments):
100 if seg.section_in_segment(sec):
101 if n not in self._segments.keys():
102 self._segments[n] = self.tfa_mem_obj_factory(
103 seg, name=f"{n:#02}", segment=True
104 )
105
106 self._segments[n].children.append(
107 self.tfa_mem_obj_factory(sec)
108 )
109
Harrison Mutaid9d5eb12023-02-23 11:30:17 +0000110 def get_memory_layout_from_symbols(self, expr=None) -> dict:
111 """Retrieve information about the memory configuration from the symbol
112 table.
113 """
114 assert len(self._symbols), "Symbol table is empty!"
115
116 expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)" if not expr else expr
117 region_symbols = filter(lambda s: re.match(expr, s), self._symbols)
118 memory_layout = {}
119
120 for symbol in region_symbols:
121 region, _, attr = tuple(symbol.lower().strip("__").split("_"))
122 if region not in memory_layout:
123 memory_layout[region] = {}
124
125 # Retrieve the value of the symbol using the symbol as the key.
126 memory_layout[region][attr] = self._symbols[symbol]
127
128 return memory_layout
129
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000130 def get_seg_map_as_dict(self):
131 """Get a dictionary of segments and their section mappings."""
132 return [asdict(v) for k, v in self._segments.items()]
133
Harrison Mutaid0e30532023-06-07 11:28:16 +0100134 def get_memory_layout(self):
Harrison Mutaid9d5eb12023-02-23 11:30:17 +0000135 """Get the total memory consumed by this module from the memory
136 configuration.
137 {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
138 """
139 mem_dict = {}
140
141 for mem, attrs in self._memory_layout.items():
142 limit = attrs["start"] + attrs["length"]
143 mem_dict[mem] = {
144 "start": attrs["start"],
145 "limit": limit,
146 "size": attrs["end"] - attrs["start"],
147 "free": limit - attrs["end"],
148 "total": attrs["length"],
149 }
150 return mem_dict
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000151
152 def get_mod_mem_usage_dict(self):
153 """Get the total memory consumed by the module, this combines the
154 information in the memory configuration.
155 """
156 return {
157 "start": self._start,
158 "end": self._end,
159 "size": self._size,
160 "free": self._free,
161 }