Mate Toth-Pal | e60deeb | 2024-08-01 10:19:20 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # SPDX-License-Identifier: BSD-3-Clause |
| 3 | # SPDX-FileCopyrightText: Copyright TF-RMM Contributors. |
| 4 | # |
| 5 | |
| 6 | """ |
| 7 | Script for creating a bin file from the compiled app's elf file. |
| 8 | |
| 9 | The name of the elf sections that are expected and added in the output binary |
| 10 | are hardcoded in the script. The resulting binary consists of a header which is |
| 11 | page in size and content of selected sections which are appended after the |
| 12 | header. |
| 13 | ``` |
| 14 | +----------------------+ |
| 15 | | | |
| 16 | | Header | |
| 17 | | | |
| 18 | +----------------------+ |
| 19 | | | |
| 20 | | Content of section 1 | |
| 21 | | | |
| 22 | +----------------------+ |
| 23 | | | |
| 24 | ... |
| 25 | | | |
| 26 | +----------------------+ |
| 27 | | | |
| 28 | |Content of section n | |
| 29 | | | |
| 30 | +----------------------+ |
| 31 | ``` |
| 32 | """ |
| 33 | |
| 34 | from argparse import ArgumentParser |
| 35 | from collections import namedtuple |
| 36 | import logging |
| 37 | import struct |
| 38 | |
| 39 | |
| 40 | from elftools.elf.elffile import ELFFile |
| 41 | |
| 42 | INDENTATION = "\t" |
| 43 | APP_HEADER_MAGIC = 0x000E10ABB4EAD000 # El0 APP HEAD |
| 44 | HEADER_VERSION_MAJOR = 0 |
| 45 | HEADER_VERSION_MINOR = 1 |
| 46 | # TODO: get page size from command line |
| 47 | PAGE_SIZE = 4096 |
| 48 | APP_HEADER_RESERVED_BYTES = 10 * 8 |
| 49 | APP_NAME_BUF_SIZE = 32 |
| 50 | |
| 51 | HEADER_VERSION = (HEADER_VERSION_MAJOR << 16) | HEADER_VERSION_MINOR |
| 52 | |
| 53 | # The header format needs to match the header in |
| 54 | # "app/common/framework/include/fake_host/app_header_structures.h" file as defined in |
| 55 | # 'struct el0_app_header' |
| 56 | BIN_HEADER_FORMAT = "".join( |
| 57 | [ |
| 58 | "<", # little endian |
| 59 | "Q", # uint64_t padding; |
| 60 | "Q", # uint64_t app_header_magic; |
| 61 | "L", # uint32_t app_header_version; |
| 62 | f"{APP_NAME_BUF_SIZE}s" # const char app_name[APP_NAME_BUF_SIZE]; |
| 63 | "L", # uint32_t app_id; |
| 64 | "Q", # uint64_t app_len; /* including header */ |
| 65 | "Q", # uintptr_t section_text_offset; |
| 66 | "Q", # uintptr_t section_text_va; |
| 67 | "Q", # size_t section_text_size; |
| 68 | "Q", # uintptr_t section_rodata_offset; |
| 69 | "Q", # uintptr_t section_rodata_va; |
| 70 | "Q", # size_t section_rodata_size; |
| 71 | "Q", # uintptr_t section_data_offset; |
| 72 | "Q", # uintptr_t section_data_va; |
| 73 | "Q", # size_t section_data_size; |
| 74 | "Q", # uintptr_t section_bss_va ; |
| 75 | "Q", # size_t section_bss_size; |
| 76 | "Q", # section_shared_va; |
| 77 | "Q", # size_t stack_page_count; |
| 78 | "Q", # size_t heap_page_count; |
| 79 | f"{APP_HEADER_RESERVED_BYTES}s" # reserved |
| 80 | "Q", # uint64_t app_header_magic2; |
| 81 | ] |
| 82 | ) |
| 83 | |
| 84 | SectionParams = namedtuple("SectionParams", ["name", "emit"]) |
| 85 | SectionData = namedtuple("SectionData", ["params", "vma", "size", "data"]) |
| 86 | |
| 87 | |
| 88 | def get_section(sections, idx, name): |
| 89 | """Returns the section in the sections list at the given index. |
| 90 | |
| 91 | The function makes sure that the section at the specified index has the |
| 92 | specified name. |
| 93 | """ |
| 94 | if sections[idx].params.name != name: |
| 95 | logging.error( |
| 96 | f"At idx {idx} section name '{sections[idx].params.name}' doesn't match '{name}'" |
| 97 | ) |
| 98 | assert False |
| 99 | return sections[idx] |
| 100 | |
| 101 | def get_app_name_bytes(app_name): |
| 102 | if len(app_name) >= (APP_NAME_BUF_SIZE): |
| 103 | app_name = app_name[:APP_NAME_BUF_SIZE -1] |
| 104 | b_appname = app_name.encode() |
| 105 | assert(len(b_appname) < APP_NAME_BUF_SIZE) |
| 106 | return bytes().join([b_appname, bytes([0] * (APP_NAME_BUF_SIZE - len(b_appname)))]) |
| 107 | |
| 108 | def emit_bin_file_header(out_bin_file, app_name, app_id, app_len, stack_page_count, heap_page_count, sections): |
| 109 | """Emit the bin file header that will be parsed by the RMM code""" |
| 110 | text_section = get_section(sections, 0, ".text") |
| 111 | rodata_section = get_section(sections, 1, ".rodata") |
| 112 | data_section = get_section(sections, 2, ".data") |
| 113 | bss_section = get_section(sections, 3, ".bss") |
| 114 | shared_section = get_section(sections, 4, ".shared") |
| 115 | text_offset = 0 |
| 116 | rodata_offset = text_offset + text_section.size |
| 117 | data_offset = rodata_offset + rodata_section.size |
| 118 | header = struct.pack( |
| 119 | BIN_HEADER_FORMAT, |
| 120 | 0, |
| 121 | APP_HEADER_MAGIC, |
| 122 | HEADER_VERSION, |
| 123 | get_app_name_bytes(app_name), |
| 124 | app_id, |
| 125 | app_len, |
| 126 | 0, # text |
| 127 | text_section.vma, |
| 128 | text_section.size, |
| 129 | rodata_offset, # rodata |
| 130 | rodata_section.vma, |
| 131 | rodata_section.size, |
| 132 | data_offset, # data |
| 133 | data_section.vma, |
| 134 | data_section.size, |
| 135 | bss_section.vma, # bss |
| 136 | bss_section.size, |
| 137 | shared_section.vma, # shared |
| 138 | stack_page_count, # stack |
| 139 | heap_page_count, # heap |
| 140 | bytes(APP_HEADER_RESERVED_BYTES), |
| 141 | APP_HEADER_MAGIC, |
| 142 | ) |
| 143 | logging.info(f"Emitting binary header, {len(header)} bytes.") |
| 144 | logging.info( |
| 145 | f" app_header_magic: #1={APP_HEADER_MAGIC:016x}, #2={APP_HEADER_MAGIC:016x}" |
| 146 | ) |
| 147 | logging.info(f" app_name = {app_name:16}") |
| 148 | logging.info(f" app_header_version = 0x{HEADER_VERSION:16x}") |
| 149 | logging.info(f" app_id = {app_id:16}") |
| 150 | logging.info(f" app_len = 0x{app_len:16x}") |
| 151 | |
| 152 | logging.info(" section | offset | va | size") |
| 153 | logging.info(" ---------|----------|------------------|---------") |
| 154 | logging.info( |
| 155 | f" text | {0:8x} | {text_section.vma:16x} | {text_section.size:8x}" |
| 156 | ) |
| 157 | logging.info( |
| 158 | f" rodata | {rodata_offset:8x} | {rodata_section.vma:16x} | {rodata_section.size:8x}" |
| 159 | ) |
| 160 | logging.info( |
| 161 | f" data | {data_offset:8x} | {data_section.vma:16x} | {data_section.size:8x}" |
| 162 | ) |
| 163 | logging.info( |
| 164 | f" bss | N/A | {bss_section.vma:16x} | {bss_section.size:8x}" |
| 165 | ) |
| 166 | logging.info(f" shared | N/A | {shared_section.vma:16x} | {PAGE_SIZE:8x}") |
| 167 | logging.info( |
| 168 | f" stack | N/A | N/A | {stack_page_count*PAGE_SIZE:8x}" |
| 169 | ) |
| 170 | logging.info( |
| 171 | f" heap | N/A | N/A | {heap_page_count*PAGE_SIZE:8x}" |
| 172 | ) |
| 173 | |
| 174 | out_bin_file.write(header) |
| 175 | |
| 176 | # emit padding to keep the app binary page aligned |
| 177 | assert len(header) < PAGE_SIZE |
| 178 | out_bin_file.write(bytearray([0] * (PAGE_SIZE - len(header)))) |
| 179 | |
| 180 | return PAGE_SIZE |
| 181 | |
| 182 | |
| 183 | def emit_section_data(out_bin_file, sections): |
| 184 | """Emitting content of a section |
| 185 | |
| 186 | Return the number of bytes emitted for this section |
| 187 | """ |
| 188 | bytes_emitted = 0 |
| 189 | for section in sections: |
| 190 | if section.data is not None and section.params.emit: |
| 191 | logging.info( |
| 192 | f"Emitting section '{section.params.name}', {len(section.data)} bytes." |
| 193 | ) |
| 194 | out_bin_file.write(section.data) |
| 195 | bytes_emitted += len(section.data) |
| 196 | return bytes_emitted |
| 197 | |
| 198 | |
| 199 | def calc_sections_size(sections): |
| 200 | """Calculates the length of the sections part of the bin""" |
| 201 | length = PAGE_SIZE # accounting for header |
| 202 | for section in sections: |
| 203 | if section.data is not None and section.params.emit: |
| 204 | length += len(section.data) |
| 205 | if length % PAGE_SIZE != 0: |
| 206 | length += PAGE_SIZE - (length % PAGE_SIZE) |
| 207 | return length |
| 208 | |
| 209 | |
| 210 | def emit_bin_file(out_bin_file_name, app_name, app_id, stack_page_count, heap_page_count, sections): |
| 211 | """Write the bin file""" |
| 212 | bytes_emitted = 0 |
| 213 | with open(out_bin_file_name, "wb") as out_bin_file: |
| 214 | # Calculate the length of the bin file payload to be written in to the header |
| 215 | app_len = calc_sections_size(sections) |
| 216 | # Write the bin header |
| 217 | bytes_emitted += emit_bin_file_header( |
| 218 | out_bin_file, app_name, app_id, app_len, stack_page_count, heap_page_count, sections |
| 219 | ) |
| 220 | # Write the sections that needs to be emitted |
| 221 | bytes_emitted += emit_section_data(out_bin_file, sections) |
| 222 | |
| 223 | # Add padding so that the bin file is aligned to page boundary |
| 224 | padding_length = PAGE_SIZE - (((bytes_emitted + PAGE_SIZE - 1) % PAGE_SIZE) + 1) |
| 225 | assert (bytes_emitted + padding_length) % PAGE_SIZE == 0 |
| 226 | assert padding_length < PAGE_SIZE |
| 227 | if padding_length: |
| 228 | out_bin_file.write(bytearray([0] * padding_length)) |
| 229 | bytes_emitted += padding_length |
| 230 | assert bytes_emitted == app_len |
| 231 | |
| 232 | |
| 233 | def get_sections_data(elffile, sections_params): |
| 234 | elf_section_idx = 0 |
| 235 | section_idx = 0 |
| 236 | sections_found = [] |
| 237 | |
| 238 | expected_section_names = [section.name for section in sections_params] |
| 239 | |
| 240 | # Assume that the interesting sections are in the expected order |
| 241 | while elf_section_idx < elffile.num_sections() and section_idx < len(sections_params): |
| 242 | elf_section = elffile.get_section(elf_section_idx) |
| 243 | |
| 244 | if elf_section.name not in expected_section_names[section_idx:]: |
| 245 | logging.info( |
| 246 | f"Skipping section {elf_section.name}, ({elf_section.data_size} bytes)" |
| 247 | ) |
| 248 | elf_section_idx += 1 |
| 249 | if elf_section_idx == elffile.num_sections(): |
| 250 | break |
| 251 | continue |
| 252 | |
| 253 | section_params = sections_params[section_idx] |
| 254 | |
| 255 | if elf_section.name != section_params.name: |
| 256 | logging.info( |
| 257 | f"Section {expected_section_names[section_idx]} not found in the elf file" |
| 258 | ) |
| 259 | section_idx += 1 |
| 260 | continue |
| 261 | |
| 262 | assert elf_section.name == section_params.name |
| 263 | |
| 264 | logging.info(f"Found section {elf_section.name} size={elf_section.data_size}") |
| 265 | |
| 266 | section_data = SectionData( |
| 267 | params=section_params, |
| 268 | vma=elf_section.header["sh_addr"], |
| 269 | size=elf_section.data_size, |
| 270 | data=elf_section.data(), |
| 271 | ) |
| 272 | sections_found.append(section_data) |
| 273 | assert section_data.size == len(section_data.data) |
| 274 | |
| 275 | elf_section_idx += 1 |
| 276 | section_idx += 1 |
| 277 | |
| 278 | while elf_section_idx < elffile.num_sections(): |
| 279 | elf_section = elffile.get_section(elf_section_idx) |
| 280 | logging.info( |
| 281 | f"Skipping section {elf_section.name}, ({elf_section.data_size} bytes)" |
| 282 | ) |
| 283 | elf_section_idx += 1 |
| 284 | |
| 285 | while section_idx < len(sections_params): |
| 286 | section = sections_params[section_idx] |
| 287 | logging.info(f"Section {section.name} not found in the elf file") |
| 288 | section_idx += 1 |
| 289 | |
| 290 | return sections_found |
| 291 | |
| 292 | |
| 293 | def parse_elf_file(elf_file_name): |
| 294 | """parse the elf file |
| 295 | |
| 296 | returns the relevant sections' data found in the file |
| 297 | """ |
| 298 | with open(elf_file_name, "rb") as in_file: |
| 299 | sections = [ |
| 300 | SectionParams(".text", emit=True), |
| 301 | SectionParams(".rodata", emit=True), |
| 302 | SectionParams(".data", emit=True), |
| 303 | SectionParams(".bss", emit=False), |
| 304 | SectionParams(".shared", emit=False), |
| 305 | ] |
| 306 | |
| 307 | elffile = ELFFile(in_file) |
| 308 | |
| 309 | logging.info(f"{elf_file_name} has {elffile.num_sections()} sections.") |
| 310 | |
| 311 | # Assume that the interesting sections are in the expected order |
| 312 | return get_sections_data(elffile, sections) |
| 313 | |
| 314 | |
| 315 | def main(): |
| 316 | """Main function of the script""" |
| 317 | logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) |
| 318 | |
| 319 | parser = ArgumentParser(description="Generate app data files for packaging.") |
| 320 | parser.add_argument( |
| 321 | "--elf-file", type=str, required=True, help="elf file to generate raw data for" |
| 322 | ) |
| 323 | parser.add_argument( |
| 324 | "--app-id", |
| 325 | type=lambda v: int(v, 0), |
| 326 | required=True, |
| 327 | help="The ID of the application used in the RMM code", |
| 328 | ) |
| 329 | parser.add_argument( |
| 330 | "--app-name", |
| 331 | required=True, |
| 332 | help="The name of the app", |
| 333 | ) |
| 334 | parser.add_argument( |
| 335 | "--stack-page-count", |
| 336 | type=int, |
| 337 | required=True, |
| 338 | help="The stack size required by the application", |
| 339 | ) |
| 340 | parser.add_argument( |
| 341 | "--heap-page-count", |
| 342 | type=int, |
| 343 | required=True, |
| 344 | help="The heap size required by the application (0 is valid)", |
| 345 | ) |
| 346 | parser.add_argument( |
| 347 | "--out-bin", |
| 348 | type=str, |
| 349 | required=True, |
| 350 | help="application data for the bin generation", |
| 351 | ) |
| 352 | args = parser.parse_args() |
| 353 | |
| 354 | logging.info(f"Processing {args.elf_file}, app_name='{args.app_name}', app_id={args.app_id:x}") |
| 355 | sections = parse_elf_file(args.elf_file) |
| 356 | emit_bin_file(args.out_bin, args.app_name, args.app_id, args.stack_page_count, args.heap_page_count, sections) |
| 357 | |
| 358 | |
| 359 | if __name__ == "__main__": |
| 360 | main() |