Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 1 | # |
Chris Kay | ed0c801 | 2025-01-28 18:04:11 +0000 | [diff] [blame] | 2 | # Copyright (c) 2023-2025, Arm Limited. All rights reserved. |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 3 | # |
| 4 | # SPDX-License-Identifier: BSD-3-Clause |
| 5 | # |
| 6 | |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 7 | import re |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 8 | from dataclasses import asdict, dataclass |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 9 | from typing import BinaryIO |
| 10 | |
| 11 | from elftools.elf.elffile import ELFFile |
| 12 | |
| 13 | |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 14 | @dataclass(frozen=True) |
| 15 | class TfaMemObject: |
| 16 | name: str |
| 17 | start: int |
| 18 | end: int |
| 19 | size: int |
| 20 | children: list |
| 21 | |
| 22 | |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 23 | class 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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 42 | self.set_segment_section_map(elf.iter_segments(), elf.iter_sections()) |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 43 | self._memory_layout = self.get_memory_layout_from_symbols() |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 44 | self._start = elf["e_entry"] |
| 45 | self._size, self._free = self._get_mem_usage() |
| 46 | self._end = self._start + self._size |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 47 | |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 48 | @property |
| 49 | def symbols(self): |
| 50 | return self._symbols.items() |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 51 | |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 52 | @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 Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 110 | 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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 130 | 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 Mutai | d0e3053 | 2023-06-07 11:28:16 +0100 | [diff] [blame] | 134 | def get_memory_layout(self): |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 135 | """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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 151 | |
| 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 | } |