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 | # |
| 4 | # Copyright (c) 2021-2022, Arm Limited. All rights reserved. |
| 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 |
| 15 | from elftools.elf.elffile import ELFFile |
| 16 | from elftools.elf.constants import P_FLAGS |
| 17 | |
| 18 | |
| 19 | class 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 | |
| 178 | if __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) |