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 |
Chris Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 12 | from elftools.elf.segments import Segment |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 13 | |
| 14 | |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 15 | @dataclass(frozen=True) |
| 16 | class TfaMemObject: |
| 17 | name: str |
| 18 | start: int |
| 19 | end: int |
| 20 | size: int |
| 21 | children: list |
| 22 | |
| 23 | |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 24 | class 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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 43 | self.set_segment_section_map(elf.iter_segments(), elf.iter_sections()) |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 44 | self._memory_layout = self.get_memory_layout_from_symbols() |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 45 | self._start = elf["e_entry"] |
| 46 | self._size, self._free = self._get_mem_usage() |
| 47 | self._end = self._start + self._size |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 48 | |
Harrison Mutai | af5b49e | 2023-02-23 10:33:58 +0000 | [diff] [blame] | 49 | @property |
| 50 | def symbols(self): |
| 51 | return self._symbols.items() |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 52 | |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 53 | @staticmethod |
Chris Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 54 | def tfa_mem_obj_factory(elf_obj, name=None, children=None): |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 55 | """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 Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 58 | assert not (isinstance(elf_obj, Segment) and name is None), ( |
Chris Kay | 1bed770 | 2025-04-01 17:20:56 +0100 | [diff] [blame] | 59 | "Attempting to make segment without a name" |
| 60 | ) |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 61 | |
| 62 | if children is None: |
| 63 | children = list() |
| 64 | |
| 65 | # Segment and sections header keys have different prefixes. |
Chris Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 66 | vaddr = "p_vaddr" if isinstance(elf_obj, Segment) else "sh_addr" |
| 67 | size = "p_memsz" if isinstance(elf_obj, Segment) else "sh_size" |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 68 | |
| 69 | # TODO figure out how to handle free space for sections and segments |
| 70 | return TfaMemObject( |
Chris Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 71 | name if isinstance(elf_obj, Segment) else elf_obj.name, |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 72 | 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 Kay | 1bed770 | 2025-04-01 17:20:56 +0100 | [diff] [blame] | 95 | segments = list(filter(lambda seg: seg["p_type"] == "PT_LOAD", segments)) |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 96 | |
| 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 Kay | f59b6fb | 2025-05-01 17:00:06 +0100 | [diff] [blame^] | 102 | seg, name=f"{n:#02}" |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 103 | ) |
| 104 | |
Chris Kay | 1bed770 | 2025-04-01 17:20:56 +0100 | [diff] [blame] | 105 | self._segments[n].children.append(self.tfa_mem_obj_factory(sec)) |
Harrison Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 106 | |
Chris Kay | 015a8fd | 2025-04-17 12:14:38 +0100 | [diff] [blame] | 107 | def get_memory_layout_from_symbols(self) -> dict: |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 108 | """Retrieve information about the memory configuration from the symbol |
| 109 | table. |
| 110 | """ |
| 111 | assert len(self._symbols), "Symbol table is empty!" |
| 112 | |
Chris Kay | 015a8fd | 2025-04-17 12:14:38 +0100 | [diff] [blame] | 113 | expr = r".*(.?R.M)_REGION.*(START|END|LENGTH)" |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 114 | 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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 127 | 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 Mutai | d0e3053 | 2023-06-07 11:28:16 +0100 | [diff] [blame] | 131 | def get_memory_layout(self): |
Harrison Mutai | d9d5eb1 | 2023-02-23 11:30:17 +0000 | [diff] [blame] | 132 | """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 Mutai | cc60aba | 2023-02-23 11:30:55 +0000 | [diff] [blame] | 148 | |
| 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 | } |