1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-3-Clause 3# SPDX-FileCopyrightText: Copyright TF-RMM Contributors. 4# 5 6""" 7Script for creating a bin file from the compiled app's elf file. 8 9The name of the elf sections that are expected and added in the output binary 10are hardcoded in the script. The resulting binary consists of a header which is 11page in size and content of selected sections which are appended after the 12header. 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 34from argparse import ArgumentParser 35from collections import namedtuple 36import logging 37import struct 38 39 40from elftools.elf.elffile import ELFFile 41 42INDENTATION = "\t" 43APP_HEADER_MAGIC = 0x000E10ABB4EAD000 # El0 APP HEAD 44HEADER_VERSION_MAJOR = 0 45HEADER_VERSION_MINOR = 1 46# TODO: get page size from command line 47PAGE_SIZE = 4096 48APP_HEADER_RESERVED_BYTES = 10 * 8 49APP_NAME_BUF_SIZE = 32 50 51HEADER_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' 56BIN_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 84SectionParams = namedtuple("SectionParams", ["name", "emit"]) 85SectionData = namedtuple("SectionData", ["params", "vma", "size", "data"]) 86 87 88def 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 101def 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 108def 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 183def 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 199def 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 210def 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 233def 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 293def 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 315def 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 359if __name__ == "__main__": 360 main() 361