blob: 10d11231eaa862a71daca4e386a19312a1bbca73 [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
Chris Kayf59b6fb2025-05-01 17:00:06 +010012from elftools.elf.segments import Segment
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000013
14
Harrison Mutaicc60aba2023-02-23 11:30:55 +000015@dataclass(frozen=True)
16class TfaMemObject:
17 name: str
18 start: int
19 end: int
20 size: int
21 children: list
22
23
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000024class TfaElfParser:
25 """A class representing an ELF file built for TF-A.
26
27 Provides a basic interface for reading the symbol table and other
28 attributes of an ELF file. The constructor accepts a file-like object with
29 the contents an ELF file.
30 """
31
32 def __init__(self, elf_file: BinaryIO):
33 self._segments = {}
34 self._memory_layout = {}
35
36 elf = ELFFile(elf_file)
37
38 self._symbols = {
39 sym.name: sym.entry["st_value"]
40 for sym in elf.get_section_by_name(".symtab").iter_symbols()
41 }
42
Harrison Mutaicc60aba2023-02-23 11:30:55 +000043 self.set_segment_section_map(elf.iter_segments(), elf.iter_sections())
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000044 self._memory_layout = self.get_memory_layout_from_symbols()
Harrison Mutaicc60aba2023-02-23 11:30:55 +000045 self._start = elf["e_entry"]
46 self._size, self._free = self._get_mem_usage()
47 self._end = self._start + self._size
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000048
Harrison Mutaiaf5b49e2023-02-23 10:33:58 +000049 @property
50 def symbols(self):
51 return self._symbols.items()
Harrison Mutaid9d5eb12023-02-23 11:30:17 +000052
Harrison Mutaicc60aba2023-02-23 11:30:55 +000053 @staticmethod
Chris Kayf59b6fb2025-05-01 17:00:06 +010054 def tfa_mem_obj_factory(elf_obj, name=None, children=None):
Harrison Mutaicc60aba2023-02-23 11:30:55 +000055 """Converts a pyelfparser Segment or Section to a TfaMemObject."""
56 # Ensure each segment is provided a name since they aren't in the
57 # program header.
Chris Kayf59b6fb2025-05-01 17:00:06 +010058 assert not (isinstance(elf_obj, Segment) and name is None), (
Chris Kay1bed7702025-04-01 17:20:56 +010059 "Attempting to make segment without a name"
60 )
Harrison Mutaicc60aba2023-02-23 11:30:55 +000061
62 if children is None:
63 children = list()
64
65 # Segment and sections header keys have different prefixes.
Chris Kayf59b6fb2025-05-01 17:00:06 +010066 vaddr = "p_vaddr" if isinstance(elf_obj, Segment) else "sh_addr"
67 size = "p_memsz" if isinstance(elf_obj, Segment) else "sh_size"
Harrison Mutaicc60aba2023-02-23 11:30:55 +000068
69 # TODO figure out how to handle free space for sections and segments
70 return TfaMemObject(
Chris Kayf59b6fb2025-05-01 17:00:06 +010071 name if isinstance(elf_obj, Segment) else elf_obj.name,
Harrison Mutaicc60aba2023-02-23 11:30:55 +000072 elf_obj[vaddr],
73 elf_obj[vaddr] + elf_obj[size],
74 elf_obj[size],
75 [] if not children else children,
76 )
77
78 def _get_mem_usage(self) -> (int, int):
79 """Get total size and free space for this component."""
80 size = free = 0
81
82 # Use information encoded in the segment header if we can't get a
83 # memory configuration.
84 if not self._memory_layout:
85 return sum(s.size for s in self._segments.values()), 0
86
87 for v in self._memory_layout.values():
88 size += v["length"]
89 free += v["start"] + v["length"] - v["end"]
90
91 return size, free
92
93 def set_segment_section_map(self, segments, sections):
94 """Set segment to section mappings."""
Chris Kay1bed7702025-04-01 17:20:56 +010095 segments = list(filter(lambda seg: seg["p_type"] == "PT_LOAD", segments))
Harrison Mutaicc60aba2023-02-23 11:30:55 +000096
97 for sec in sections:
98 for n, seg in enumerate(segments):
99 if seg.section_in_segment(sec):
100 if n not in self._segments.keys():
101 self._segments[n] = self.tfa_mem_obj_factory(
Chris Kayf59b6fb2025-05-01 17:00:06 +0100102 seg, name=f"{n:#02}"
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000103 )
104
Chris Kay1bed7702025-04-01 17:20:56 +0100105 self._segments[n].children.append(self.tfa_mem_obj_factory(sec))
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000106
Chris Kay015a8fd2025-04-17 12:14:38 +0100107 def get_memory_layout_from_symbols(self) -> dict:
Harrison Mutaid9d5eb12023-02-23 11:30:17 +0000108 """Retrieve information about the memory configuration from the symbol
109 table.
110 """
111 assert len(self._symbols), "Symbol table is empty!"
112
Chris Kay015a8fd2025-04-17 12:14:38 +0100113 expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)"
Harrison Mutaid9d5eb12023-02-23 11:30:17 +0000114 region_symbols = filter(lambda s: re.match(expr, s), self._symbols)
115 memory_layout = {}
116
117 for symbol in region_symbols:
118 region, _, attr = tuple(symbol.lower().strip("__").split("_"))
119 if region not in memory_layout:
120 memory_layout[region] = {}
121
122 # Retrieve the value of the symbol using the symbol as the key.
123 memory_layout[region][attr] = self._symbols[symbol]
124
125 return memory_layout
126
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000127 def get_seg_map_as_dict(self):
128 """Get a dictionary of segments and their section mappings."""
129 return [asdict(v) for k, v in self._segments.items()]
130
Harrison Mutaid0e30532023-06-07 11:28:16 +0100131 def get_memory_layout(self):
Harrison Mutaid9d5eb12023-02-23 11:30:17 +0000132 """Get the total memory consumed by this module from the memory
133 configuration.
134 {"rom": {"start": 0x0, "end": 0xFF, "length": ... }
135 """
136 mem_dict = {}
137
138 for mem, attrs in self._memory_layout.items():
139 limit = attrs["start"] + attrs["length"]
140 mem_dict[mem] = {
141 "start": attrs["start"],
142 "limit": limit,
143 "size": attrs["end"] - attrs["start"],
144 "free": limit - attrs["end"],
145 "total": attrs["length"],
146 }
147 return mem_dict
Harrison Mutaicc60aba2023-02-23 11:30:55 +0000148
149 def get_mod_mem_usage_dict(self):
150 """Get the total memory consumed by the module, this combines the
151 information in the memory configuration.
152 """
153 return {
154 "start": self._start,
155 "end": self._end,
156 "size": self._size,
157 "free": self._free,
158 }