Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # SPDX-License-Identifier: BSD-3-Clause |
| 3 | # |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 4 | # Copyright (c) 2021-2024, Arm Limited. All rights reserved. |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 5 | |
| 6 | """ |
| 7 | This module's goal is to take an ELF file as an input and extract the memory |
| 8 | regions that need to be configured on load. The memory region description is put |
| 9 | into the manifest file so the SPMC set up the memory layout when loading a |
| 10 | binary format SP. |
| 11 | """ |
| 12 | |
| 13 | from enum import IntFlag |
| 14 | from math import ceil |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 15 | from elftools import __version__ as module_version |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 16 | from elftools.elf.elffile import ELFFile |
| 17 | from elftools.elf.constants import P_FLAGS |
| 18 | |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 19 | assert module_version == "0.31" |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 20 | |
| 21 | class ElfSegmentsToManifest: |
| 22 | """ |
| 23 | The class loads an ELF file and builds up an internal represention of its |
| 24 | memory layout then it can write this information into a manifest file. |
| 25 | """ |
| 26 | PAGE_SIZE = 4096 |
| 27 | |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 28 | class GnuNotePropertySection: |
| 29 | """ Provides an API to process GNU note property section. """ |
| 30 | |
| 31 | GNU_PROPERTY_AARCH64_FEATURE_1_BTI = 1 |
| 32 | |
| 33 | def __init__(self, section): |
| 34 | self.section = section |
| 35 | |
| 36 | def is_bti_enabled(self): |
| 37 | """ Returns whether any of the notes has a BTI enable property """ |
| 38 | |
| 39 | def is_bti_property(prop): |
| 40 | return prop['pr_type'] == "GNU_PROPERTY_AARCH64_FEATURE_1_AND" and \ |
| 41 | prop['pr_data'] & self.GNU_PROPERTY_AARCH64_FEATURE_1_BTI |
| 42 | |
| 43 | def has_bti_property(note): |
| 44 | return note["n_name"] == 'GNU' and note["n_type"] == "NT_GNU_PROPERTY_TYPE_0" and \ |
| 45 | any(is_bti_property(p) for p in note["n_desc"]) |
| 46 | |
| 47 | return any(has_bti_property(n) for n in self.section.iter_notes()) |
| 48 | |
| 49 | @staticmethod |
| 50 | def is_matching_section(section): |
| 51 | """ Checks if the section is a GNU note property section """ |
| 52 | return section.name == '.note.gnu.property' |
| 53 | |
| 54 | |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 55 | class Region: |
| 56 | """ Describes a memory region and its attributes. """ |
| 57 | |
| 58 | class ManifestMemAttr(IntFlag): |
| 59 | """ Type for describing the memory flags of a region. """ |
| 60 | R = 0x01 |
| 61 | W = 0x02 |
| 62 | X = 0x04 |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 63 | S = 0x08 |
| 64 | GP = 0x10 |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 65 | |
| 66 | def get_attr(self): |
| 67 | """ Queries the value of the attributes in manifest format. """ |
| 68 | return self.value |
| 69 | |
| 70 | def __str__(self): |
| 71 | return ",".join([str(f.name) for f in __class__ if f in self]) |
| 72 | |
| 73 | @staticmethod |
| 74 | def from_p_flags(p_flags): |
| 75 | """ Creates an instanced initialized by p_flags. """ |
| 76 | instance = 0 |
| 77 | instance |= __class__.R if p_flags & P_FLAGS.PF_R else 0 |
| 78 | instance |= __class__.W if p_flags & P_FLAGS.PF_W else 0 |
| 79 | instance |= __class__.X if p_flags & P_FLAGS.PF_X else 0 |
| 80 | return instance |
| 81 | |
| 82 | class LoadFlags(IntFlag): |
| 83 | """ Type for describing the memory load flags of a region. """ |
| 84 | NO_BITS = 0x01 |
| 85 | |
| 86 | def get_flags(self): |
| 87 | """ Queries the value of the flags in manifest format. """ |
| 88 | return self.value |
| 89 | |
| 90 | def is_compatible(self, other): |
| 91 | """ Return true of the other flags can be merged with self. """ |
| 92 | return (self & self.NO_BITS) == (other & self.NO_BITS) |
| 93 | |
| 94 | def __str__(self): |
| 95 | return ",".join([str(f.name) for f in __class__ if f in self]) |
| 96 | |
| 97 | def __init__(self, segment, section): |
| 98 | segment = segment.segment |
| 99 | sec_h = section.header |
| 100 | self.start_address = sec_h.sh_addr |
| 101 | self.end_address = sec_h.sh_addr + sec_h.sh_size |
| 102 | self.attributes = self.ManifestMemAttr.from_p_flags(segment.header.p_flags) |
| 103 | self.load_flags = self.LoadFlags(0) |
| 104 | self.load_flags |= self.LoadFlags.NO_BITS if sec_h.sh_type == "SHT_NOBITS" else 0 |
| 105 | self.sections = [section.name] |
| 106 | |
| 107 | def is_compatible_region(self, region): |
| 108 | """ Checks if the other region has compatible attributes/flags. """ |
| 109 | return self.load_flags.is_compatible(region.load_flags) |
| 110 | |
| 111 | def append_region(self, region): |
| 112 | """ Extends the region by the other region. """ |
| 113 | self.end_address = region.end_address |
| 114 | self.sections += region.sections |
| 115 | |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 116 | def set_bti_if_executable(self): |
| 117 | """ Sets GP flag if the region is executable. """ |
| 118 | if self.attributes & self.ManifestMemAttr.X: |
| 119 | self.attributes |= self.ManifestMemAttr.GP |
| 120 | |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 121 | def write_manifest(self, load_base_addr, manifest_file): |
| 122 | """ |
| 123 | Writes the region into the manifest file. The address is adjusted by load_base_address. |
| 124 | """ |
| 125 | manifest_file.write(f"{self.generate_region_name(load_base_addr)} {{\n") |
| 126 | manifest_file.write(f"\t/* {self.generate_section_list()} */\n") |
| 127 | manifest_file.write(f"\t{self.serialize_offset(load_base_addr)}\n") |
| 128 | manifest_file.write(f"\t{self.serialize_pages_count()}\n") |
| 129 | manifest_file.write(f"\t{self.serialize_attributes()}\n") |
| 130 | manifest_file.write(f"\t{self.serialize_load_flags()}\n") |
| 131 | manifest_file.write("};\n") |
| 132 | |
| 133 | def generate_region_name(self, load_base_addr): |
| 134 | """ Generates a name for the region using the region_[start address] pattern. """ |
| 135 | return f"region_{self.start_address - load_base_addr:x}" |
| 136 | |
| 137 | def generate_section_list(self): |
| 138 | """ Lists the name of member sections of the region. """ |
| 139 | return ", ".join(self.sections) |
| 140 | |
| 141 | def serialize_offset(self, load_base_addr): |
| 142 | """ Calculates and outputs the offset of the region in manifest format. """ |
| 143 | base = self.start_address - load_base_addr |
| 144 | end = self.end_address - load_base_addr |
| 145 | high, low = (base >> 32) & 0xffffffff, base & 0xffffffff |
| 146 | return f"load-address-relative-offset = <0x{high:x} 0x{low:x}>;\t" + \ |
| 147 | f"/* 0x{base:x} - 0x{end:x} */" |
| 148 | |
| 149 | def serialize_pages_count(self): |
| 150 | """ Calculates and outputs the page count of the region in manifest format. """ |
| 151 | region_length = self.end_address - self.start_address |
| 152 | pages_count = ceil(region_length / ElfSegmentsToManifest.PAGE_SIZE) |
| 153 | return f"pages-count = <{pages_count}>;\t/* {region_length} bytes */" |
| 154 | |
| 155 | def serialize_attributes(self): |
| 156 | """ Generates the memory region attribute value in manifest format. """ |
| 157 | return f"attributes = <{self.attributes.get_attr()}>;\t/* {self.attributes} */" |
| 158 | |
| 159 | def serialize_load_flags(self): |
| 160 | """ Generates the memory region load flags value in manifest format. """ |
| 161 | return f"load-flags = <{self.load_flags.get_flags()}>;\t/* {self.load_flags} */" |
| 162 | |
| 163 | class Segment: |
| 164 | """ Stores a segment and its sections. Able to produce a region list. """ |
| 165 | def __init__(self, segment, sections): |
| 166 | def is_aligned(segment): |
| 167 | return segment.header.p_align == ElfSegmentsToManifest.PAGE_SIZE |
| 168 | assert is_aligned(segment), "Segments must be 4k aligned, check LD script" |
| 169 | self.segment = segment |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 170 | self.sections = [] |
| 171 | self.gnu_note = None |
| 172 | |
| 173 | for section in sections: |
| 174 | if self.segment.section_in_segment(section): |
| 175 | if ElfSegmentsToManifest.GnuNotePropertySection.is_matching_section(section): |
| 176 | self.gnu_note = ElfSegmentsToManifest.GnuNotePropertySection(section) |
| 177 | else: |
| 178 | self.sections.append(section) |
| 179 | |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 180 | self.regions = [] |
| 181 | self.merge_sections_to_regions() |
| 182 | |
| 183 | def get_load_address(self): |
| 184 | """ Queries the load address of the region. """ |
| 185 | return self.segment.header.p_vaddr |
| 186 | |
| 187 | def merge_sections_to_regions(self): |
| 188 | """ Merges consecutive sections with comptabile attributes/flags into regions. """ |
| 189 | current_region = None |
| 190 | for section in self.sections: |
| 191 | region = ElfSegmentsToManifest.Region(self, section) |
| 192 | if current_region and current_region.is_compatible_region(region): |
| 193 | current_region.append_region(region) |
| 194 | else: |
| 195 | self.regions.append(region) |
| 196 | current_region = region |
| 197 | |
Gabor Toth | 65e1dac | 2024-05-17 10:37:24 +0200 | [diff] [blame] | 198 | # Set GP only for the executable regions if BTI is enabled in the segment |
| 199 | if self.gnu_note and self.gnu_note.is_bti_enabled(): |
| 200 | for region in self.regions: |
| 201 | region.set_bti_if_executable() |
| 202 | |
Imre Kis | 686bd27 | 2021-12-15 19:19:02 +0100 | [diff] [blame] | 203 | def write_manifest(self, load_base_addr, manifest_file): |
| 204 | """ Writes the regions into the manifest file. """ |
| 205 | for region in self.regions: |
| 206 | region.write_manifest(load_base_addr, manifest_file) |
| 207 | |
| 208 | def __init__(self): |
| 209 | self.segments = [] |
| 210 | self.load_base_addr = None |
| 211 | |
| 212 | def read_elf(self, elf_file_fp): |
| 213 | """ Reads and parses the sections and segments of the ELF file. """ |
| 214 | elf_file = ELFFile(elf_file_fp) |
| 215 | segments = elf_file.iter_segments() |
| 216 | |
| 217 | def is_load(segment): |
| 218 | return segment.header.p_type == "PT_LOAD" |
| 219 | self.segments = [self.Segment(s, elf_file.iter_sections()) for s in segments if is_load(s)] |
| 220 | self.load_base_addr = min([s.get_load_address() for s in self.segments]) |
| 221 | |
| 222 | def write_manifest(self, manifest_file): |
| 223 | """ Writes the memory regions of each segment into the manifest. """ |
| 224 | for segment in self.segments: |
| 225 | segment.write_manifest(self.load_base_addr, manifest_file) |
| 226 | |
| 227 | |
| 228 | if __name__ == "__main__": |
| 229 | import sys |
| 230 | |
| 231 | ELF_SEGMENTS_TO_MANIFEST = ElfSegmentsToManifest() |
| 232 | |
| 233 | with open(sys.argv[1], "rb") as fp: |
| 234 | ELF_SEGMENTS_TO_MANIFEST.read_elf(fp) |
| 235 | |
| 236 | with open(sys.argv[2], "wt", encoding="ascii") as fp: |
| 237 | ELF_SEGMENTS_TO_MANIFEST.write_manifest(fp) |