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