blob: 378c318d4271bbc4dcce43d6e0aecdf04be409df [file] [log] [blame]
Imre Kis686bd272021-12-15 19:19:02 +01001#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3#
Gabor Toth65e1dac2024-05-17 10:37:24 +02004# Copyright (c) 2021-2024, Arm Limited. All rights reserved.
Imre Kis686bd272021-12-15 19:19:02 +01005
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
Gabor Toth65e1dac2024-05-17 10:37:24 +020015from elftools import __version__ as module_version
Imre Kis686bd272021-12-15 19:19:02 +010016from elftools.elf.elffile import ELFFile
17from elftools.elf.constants import P_FLAGS
18
Gabor Toth65e1dac2024-05-17 10:37:24 +020019assert module_version == "0.31"
Imre Kis686bd272021-12-15 19:19:02 +010020
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
Gabor Toth65e1dac2024-05-17 10:37:24 +020028 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 Kis686bd272021-12-15 19:19:02 +010055 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 Toth65e1dac2024-05-17 10:37:24 +020063 S = 0x08
64 GP = 0x10
Imre Kis686bd272021-12-15 19:19:02 +010065
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 Toth65e1dac2024-05-17 10:37:24 +0200116 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 Kis686bd272021-12-15 19:19:02 +0100121 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 Toth65e1dac2024-05-17 10:37:24 +0200170 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 Kis686bd272021-12-15 19:19:02 +0100180 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 Toth65e1dac2024-05-17 10:37:24 +0200198 # 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 Kis686bd272021-12-15 19:19:02 +0100203 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)