blob: 7c1ac2aed422c929a619ddf5096c7db541915877 [file] [log] [blame]
Mate Toth-Pale60deeb2024-08-01 10:19:20 +02001#!/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()