#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # SPDX-FileCopyrightText: Copyright TF-RMM Contributors. # """ Script for creating a bin file from the compiled app's elf file. The name of the elf sections that are expected and added in the output binary are hardcoded in the script. The resulting binary consists of a header which is page in size and content of selected sections which are appended after the header. ``` +----------------------+ | | | Header | | | +----------------------+ | | | Content of section 1 | | | +----------------------+ | | ... | | +----------------------+ | | |Content of section n | | | +----------------------+ ``` """ from argparse import ArgumentParser from collections import namedtuple import logging import struct from elftools.elf.elffile import ELFFile INDENTATION = "\t" APP_HEADER_MAGIC = 0x000E10ABB4EAD000 # El0 APP HEAD HEADER_VERSION_MAJOR = 0 HEADER_VERSION_MINOR = 1 # TODO: get page size from command line PAGE_SIZE = 4096 APP_HEADER_RESERVED_BYTES = 10 * 8 APP_NAME_BUF_SIZE = 32 HEADER_VERSION = (HEADER_VERSION_MAJOR << 16) | HEADER_VERSION_MINOR # The header format needs to match the header in # "app/common/framework/include/fake_host/app_header_structures.h" file as defined in # 'struct el0_app_header' BIN_HEADER_FORMAT = "".join( [ "<", # little endian "Q", # uint64_t padding; "Q", # uint64_t app_header_magic; "L", # uint32_t app_header_version; f"{APP_NAME_BUF_SIZE}s" # const char app_name[APP_NAME_BUF_SIZE]; "L", # uint32_t app_id; "Q", # uint64_t app_len; /* including header */ "Q", # uintptr_t section_text_offset; "Q", # uintptr_t section_text_va; "Q", # size_t section_text_size; "Q", # uintptr_t section_rodata_offset; "Q", # uintptr_t section_rodata_va; "Q", # size_t section_rodata_size; "Q", # uintptr_t section_data_offset; "Q", # uintptr_t section_data_va; "Q", # size_t section_data_size; "Q", # uintptr_t section_bss_va ; "Q", # size_t section_bss_size; "Q", # section_shared_va; "Q", # size_t stack_page_count; "Q", # size_t heap_page_count; f"{APP_HEADER_RESERVED_BYTES}s" # reserved "Q", # uint64_t app_header_magic2; ] ) SectionParams = namedtuple("SectionParams", ["name", "emit"]) SectionData = namedtuple("SectionData", ["params", "vma", "size", "data"]) def get_section(sections, idx, name): """Returns the section in the sections list at the given index. The function makes sure that the section at the specified index has the specified name. """ if sections[idx].params.name != name: logging.error( f"At idx {idx} section name '{sections[idx].params.name}' doesn't match '{name}'" ) assert False return sections[idx] def get_app_name_bytes(app_name): if len(app_name) >= (APP_NAME_BUF_SIZE): app_name = app_name[:APP_NAME_BUF_SIZE -1] b_appname = app_name.encode() assert(len(b_appname) < APP_NAME_BUF_SIZE) return bytes().join([b_appname, bytes([0] * (APP_NAME_BUF_SIZE - len(b_appname)))]) def emit_bin_file_header(out_bin_file, app_name, app_id, app_len, stack_page_count, heap_page_count, sections): """Emit the bin file header that will be parsed by the RMM code""" text_section = get_section(sections, 0, ".text") rodata_section = get_section(sections, 1, ".rodata") data_section = get_section(sections, 2, ".data") bss_section = get_section(sections, 3, ".bss") shared_section = get_section(sections, 4, ".shared") text_offset = 0 rodata_offset = text_offset + text_section.size data_offset = rodata_offset + rodata_section.size header = struct.pack( BIN_HEADER_FORMAT, 0, APP_HEADER_MAGIC, HEADER_VERSION, get_app_name_bytes(app_name), app_id, app_len, 0, # text text_section.vma, text_section.size, rodata_offset, # rodata rodata_section.vma, rodata_section.size, data_offset, # data data_section.vma, data_section.size, bss_section.vma, # bss bss_section.size, shared_section.vma, # shared stack_page_count, # stack heap_page_count, # heap bytes(APP_HEADER_RESERVED_BYTES), APP_HEADER_MAGIC, ) logging.info(f"Emitting binary header, {len(header)} bytes.") logging.info( f" app_header_magic: #1={APP_HEADER_MAGIC:016x}, #2={APP_HEADER_MAGIC:016x}" ) logging.info(f" app_name = {app_name:16}") logging.info(f" app_header_version = 0x{HEADER_VERSION:16x}") logging.info(f" app_id = {app_id:16}") logging.info(f" app_len = 0x{app_len:16x}") logging.info(" section | offset | va | size") logging.info(" ---------|----------|------------------|---------") logging.info( f" text | {0:8x} | {text_section.vma:16x} | {text_section.size:8x}" ) logging.info( f" rodata | {rodata_offset:8x} | {rodata_section.vma:16x} | {rodata_section.size:8x}" ) logging.info( f" data | {data_offset:8x} | {data_section.vma:16x} | {data_section.size:8x}" ) logging.info( f" bss | N/A | {bss_section.vma:16x} | {bss_section.size:8x}" ) logging.info(f" shared | N/A | {shared_section.vma:16x} | {PAGE_SIZE:8x}") logging.info( f" stack | N/A | N/A | {stack_page_count*PAGE_SIZE:8x}" ) logging.info( f" heap | N/A | N/A | {heap_page_count*PAGE_SIZE:8x}" ) out_bin_file.write(header) # emit padding to keep the app binary page aligned assert len(header) < PAGE_SIZE out_bin_file.write(bytearray([0] * (PAGE_SIZE - len(header)))) return PAGE_SIZE def emit_section_data(out_bin_file, sections): """Emitting content of a section Return the number of bytes emitted for this section """ bytes_emitted = 0 for section in sections: if section.data is not None and section.params.emit: logging.info( f"Emitting section '{section.params.name}', {len(section.data)} bytes." ) out_bin_file.write(section.data) bytes_emitted += len(section.data) return bytes_emitted def calc_sections_size(sections): """Calculates the length of the sections part of the bin""" length = PAGE_SIZE # accounting for header for section in sections: if section.data is not None and section.params.emit: length += len(section.data) if length % PAGE_SIZE != 0: length += PAGE_SIZE - (length % PAGE_SIZE) return length def emit_bin_file(out_bin_file_name, app_name, app_id, stack_page_count, heap_page_count, sections): """Write the bin file""" bytes_emitted = 0 with open(out_bin_file_name, "wb") as out_bin_file: # Calculate the length of the bin file payload to be written in to the header app_len = calc_sections_size(sections) # Write the bin header bytes_emitted += emit_bin_file_header( out_bin_file, app_name, app_id, app_len, stack_page_count, heap_page_count, sections ) # Write the sections that needs to be emitted bytes_emitted += emit_section_data(out_bin_file, sections) # Add padding so that the bin file is aligned to page boundary padding_length = PAGE_SIZE - (((bytes_emitted + PAGE_SIZE - 1) % PAGE_SIZE) + 1) assert (bytes_emitted + padding_length) % PAGE_SIZE == 0 assert padding_length < PAGE_SIZE if padding_length: out_bin_file.write(bytearray([0] * padding_length)) bytes_emitted += padding_length assert bytes_emitted == app_len def get_sections_data(elffile, sections_params): elf_section_idx = 0 section_idx = 0 sections_found = [] expected_section_names = [section.name for section in sections_params] # Assume that the interesting sections are in the expected order while elf_section_idx < elffile.num_sections() and section_idx < len(sections_params): elf_section = elffile.get_section(elf_section_idx) if elf_section.name not in expected_section_names[section_idx:]: logging.info( f"Skipping section {elf_section.name}, ({elf_section.data_size} bytes)" ) elf_section_idx += 1 if elf_section_idx == elffile.num_sections(): break continue section_params = sections_params[section_idx] if elf_section.name != section_params.name: logging.info( f"Section {expected_section_names[section_idx]} not found in the elf file" ) section_idx += 1 continue assert elf_section.name == section_params.name logging.info(f"Found section {elf_section.name} size={elf_section.data_size}") section_data = SectionData( params=section_params, vma=elf_section.header["sh_addr"], size=elf_section.data_size, data=elf_section.data(), ) sections_found.append(section_data) assert section_data.size == len(section_data.data) elf_section_idx += 1 section_idx += 1 while elf_section_idx < elffile.num_sections(): elf_section = elffile.get_section(elf_section_idx) logging.info( f"Skipping section {elf_section.name}, ({elf_section.data_size} bytes)" ) elf_section_idx += 1 while section_idx < len(sections_params): section = sections_params[section_idx] logging.info(f"Section {section.name} not found in the elf file") section_idx += 1 return sections_found def parse_elf_file(elf_file_name): """parse the elf file returns the relevant sections' data found in the file """ with open(elf_file_name, "rb") as in_file: sections = [ SectionParams(".text", emit=True), SectionParams(".rodata", emit=True), SectionParams(".data", emit=True), SectionParams(".bss", emit=False), SectionParams(".shared", emit=False), ] elffile = ELFFile(in_file) logging.info(f"{elf_file_name} has {elffile.num_sections()} sections.") # Assume that the interesting sections are in the expected order return get_sections_data(elffile, sections) def main(): """Main function of the script""" logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) parser = ArgumentParser(description="Generate app data files for packaging.") parser.add_argument( "--elf-file", type=str, required=True, help="elf file to generate raw data for" ) parser.add_argument( "--app-id", type=lambda v: int(v, 0), required=True, help="The ID of the application used in the RMM code", ) parser.add_argument( "--app-name", required=True, help="The name of the app", ) parser.add_argument( "--stack-page-count", type=int, required=True, help="The stack size required by the application", ) parser.add_argument( "--heap-page-count", type=int, required=True, help="The heap size required by the application (0 is valid)", ) parser.add_argument( "--out-bin", type=str, required=True, help="application data for the bin generation", ) args = parser.parse_args() logging.info(f"Processing {args.elf_file}, app_name='{args.app_name}', app_id={args.app_id:x}") sections = parse_elf_file(args.elf_file) emit_bin_file(args.out_bin, args.app_name, args.app_id, args.stack_page_count, args.heap_page_count, sections) if __name__ == "__main__": main()