blob: 552816456cb9ee1ca3970965c853ae274dde967b [file] [log] [blame]
Imre Kis686bd272021-12-15 19:19:02 +01001#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3#
4# Copyright (c) 2021-2022, 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.elf.elffile import ELFFile
16from elftools.elf.constants import P_FLAGS
17
18
19class ElfSegmentsToManifest:
20 """
21 The class loads an ELF file and builds up an internal represention of its
22 memory layout then it can write this information into a manifest file.
23 """
24 PAGE_SIZE = 4096
25
26 class Region:
27 """ Describes a memory region and its attributes. """
28
29 class ManifestMemAttr(IntFlag):
30 """ Type for describing the memory flags of a region. """
31 R = 0x01
32 W = 0x02
33 X = 0x04
34
35 def get_attr(self):
36 """ Queries the value of the attributes in manifest format. """
37 return self.value
38
39 def __str__(self):
40 return ",".join([str(f.name) for f in __class__ if f in self])
41
42 @staticmethod
43 def from_p_flags(p_flags):
44 """ Creates an instanced initialized by p_flags. """
45 instance = 0
46 instance |= __class__.R if p_flags & P_FLAGS.PF_R else 0
47 instance |= __class__.W if p_flags & P_FLAGS.PF_W else 0
48 instance |= __class__.X if p_flags & P_FLAGS.PF_X else 0
49 return instance
50
51 class LoadFlags(IntFlag):
52 """ Type for describing the memory load flags of a region. """
53 NO_BITS = 0x01
54
55 def get_flags(self):
56 """ Queries the value of the flags in manifest format. """
57 return self.value
58
59 def is_compatible(self, other):
60 """ Return true of the other flags can be merged with self. """
61 return (self & self.NO_BITS) == (other & self.NO_BITS)
62
63 def __str__(self):
64 return ",".join([str(f.name) for f in __class__ if f in self])
65
66 def __init__(self, segment, section):
67 segment = segment.segment
68 sec_h = section.header
69 self.start_address = sec_h.sh_addr
70 self.end_address = sec_h.sh_addr + sec_h.sh_size
71 self.attributes = self.ManifestMemAttr.from_p_flags(segment.header.p_flags)
72 self.load_flags = self.LoadFlags(0)
73 self.load_flags |= self.LoadFlags.NO_BITS if sec_h.sh_type == "SHT_NOBITS" else 0
74 self.sections = [section.name]
75
76 def is_compatible_region(self, region):
77 """ Checks if the other region has compatible attributes/flags. """
78 return self.load_flags.is_compatible(region.load_flags)
79
80 def append_region(self, region):
81 """ Extends the region by the other region. """
82 self.end_address = region.end_address
83 self.sections += region.sections
84
85 def write_manifest(self, load_base_addr, manifest_file):
86 """
87 Writes the region into the manifest file. The address is adjusted by load_base_address.
88 """
89 manifest_file.write(f"{self.generate_region_name(load_base_addr)} {{\n")
90 manifest_file.write(f"\t/* {self.generate_section_list()} */\n")
91 manifest_file.write(f"\t{self.serialize_offset(load_base_addr)}\n")
92 manifest_file.write(f"\t{self.serialize_pages_count()}\n")
93 manifest_file.write(f"\t{self.serialize_attributes()}\n")
94 manifest_file.write(f"\t{self.serialize_load_flags()}\n")
95 manifest_file.write("};\n")
96
97 def generate_region_name(self, load_base_addr):
98 """ Generates a name for the region using the region_[start address] pattern. """
99 return f"region_{self.start_address - load_base_addr:x}"
100
101 def generate_section_list(self):
102 """ Lists the name of member sections of the region. """
103 return ", ".join(self.sections)
104
105 def serialize_offset(self, load_base_addr):
106 """ Calculates and outputs the offset of the region in manifest format. """
107 base = self.start_address - load_base_addr
108 end = self.end_address - load_base_addr
109 high, low = (base >> 32) & 0xffffffff, base & 0xffffffff
110 return f"load-address-relative-offset = <0x{high:x} 0x{low:x}>;\t" + \
111 f"/* 0x{base:x} - 0x{end:x} */"
112
113 def serialize_pages_count(self):
114 """ Calculates and outputs the page count of the region in manifest format. """
115 region_length = self.end_address - self.start_address
116 pages_count = ceil(region_length / ElfSegmentsToManifest.PAGE_SIZE)
117 return f"pages-count = <{pages_count}>;\t/* {region_length} bytes */"
118
119 def serialize_attributes(self):
120 """ Generates the memory region attribute value in manifest format. """
121 return f"attributes = <{self.attributes.get_attr()}>;\t/* {self.attributes} */"
122
123 def serialize_load_flags(self):
124 """ Generates the memory region load flags value in manifest format. """
125 return f"load-flags = <{self.load_flags.get_flags()}>;\t/* {self.load_flags} */"
126
127 class Segment:
128 """ Stores a segment and its sections. Able to produce a region list. """
129 def __init__(self, segment, sections):
130 def is_aligned(segment):
131 return segment.header.p_align == ElfSegmentsToManifest.PAGE_SIZE
132 assert is_aligned(segment), "Segments must be 4k aligned, check LD script"
133 self.segment = segment
134 self.sections = [s for s in sections if self.segment.section_in_segment(s)]
135 self.regions = []
136 self.merge_sections_to_regions()
137
138 def get_load_address(self):
139 """ Queries the load address of the region. """
140 return self.segment.header.p_vaddr
141
142 def merge_sections_to_regions(self):
143 """ Merges consecutive sections with comptabile attributes/flags into regions. """
144 current_region = None
145 for section in self.sections:
146 region = ElfSegmentsToManifest.Region(self, section)
147 if current_region and current_region.is_compatible_region(region):
148 current_region.append_region(region)
149 else:
150 self.regions.append(region)
151 current_region = region
152
153 def write_manifest(self, load_base_addr, manifest_file):
154 """ Writes the regions into the manifest file. """
155 for region in self.regions:
156 region.write_manifest(load_base_addr, manifest_file)
157
158 def __init__(self):
159 self.segments = []
160 self.load_base_addr = None
161
162 def read_elf(self, elf_file_fp):
163 """ Reads and parses the sections and segments of the ELF file. """
164 elf_file = ELFFile(elf_file_fp)
165 segments = elf_file.iter_segments()
166
167 def is_load(segment):
168 return segment.header.p_type == "PT_LOAD"
169 self.segments = [self.Segment(s, elf_file.iter_sections()) for s in segments if is_load(s)]
170 self.load_base_addr = min([s.get_load_address() for s in self.segments])
171
172 def write_manifest(self, manifest_file):
173 """ Writes the memory regions of each segment into the manifest. """
174 for segment in self.segments:
175 segment.write_manifest(self.load_base_addr, manifest_file)
176
177
178if __name__ == "__main__":
179 import sys
180
181 ELF_SEGMENTS_TO_MANIFEST = ElfSegmentsToManifest()
182
183 with open(sys.argv[1], "rb") as fp:
184 ELF_SEGMENTS_TO_MANIFEST.read_elf(fp)
185
186 with open(sys.argv[2], "wt", encoding="ascii") as fp:
187 ELF_SEGMENTS_TO_MANIFEST.write_manifest(fp)