blob: 7c1ac2aed422c929a619ddf5096c7db541915877 [file] [log] [blame]
#!/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()