build(app): add python scripts for El0 apps
Add python scripts for creating final RMM image file with apps boundled
to it.
* app/gen_app_bin.py is for generating bin file from an app elf file.
This bin can be bundled with an RMM bin file
* app/bundle_app_rmm.py is for creating a bundle from an RMM bin image,
and 1 or more app bin files.
Change-Id: I209d6869370659a764b2849d45bb6b147c484e7d
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/app/gen_app_bin.py b/app/gen_app_bin.py
new file mode 100755
index 0000000..7c1ac2a
--- /dev/null
+++ b/app/gen_app_bin.py
@@ -0,0 +1,360 @@
+#!/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()