1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-3-Clause 3# 4# Copyright (c) 2021-2024, Arm Limited. All rights reserved. 5 6""" 7This module's goal is to take an ELF file as an input and extract the memory 8regions that need to be configured on load. The memory region description is put 9into the manifest file so the SPMC set up the memory layout when loading a 10binary format SP. 11""" 12 13from enum import IntFlag 14from math import ceil 15from elftools import __version__ as module_version 16from elftools.elf.elffile import ELFFile 17from elftools.elf.constants import P_FLAGS 18 19assert module_version == "0.31" 20 21class 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 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 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 63 S = 0x08 64 GP = 0x10 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 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 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 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 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 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 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 228if __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) 238