| #!/usr/bin/env python3 | |
| # | |
| # Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) | |
| # | |
| # SPDX-License-Identifier: GPL-2.0-only | |
| # | |
| """ | |
| Generate a C header file to standard output with a preprocessor symbol | |
| definition of the physical load address in hexadecimal of the ELF-loader in | |
| `payload_filename`. The address is calculated using the description of memory | |
| in `platform_filename` and the CPIO archive members embedded in the payload | |
| file, including the loadable segments of the ELF objects and a possible DTB | |
| (device tree binary) file. The ELF-loader is placed in the first (lowest) | |
| sufficiently-large memory region. | |
| THIS IS NOT A STABLE API. Use as a script, not a module. | |
| """ | |
| import argparse | |
| import io | |
| import os.path | |
| import re | |
| import sys | |
| import libarchive | |
| import elf_sift | |
| import platform_sift | |
| do_debug = False | |
| program_name = 'shoehorn' | |
| def write(message: str): | |
| """ | |
| Write diagnostic `message` to standard error. | |
| """ | |
| sys.stderr.write('{}: {}\n'.format(program_name, message)) | |
| def debug(message: str): | |
| """ | |
| Emit debugging diagnostic `message`. | |
| """ | |
| if do_debug: | |
| write('debug: {}'.format(message)) | |
| def debug_marker_set(mark: int, obj: str): | |
| debug('setting marker to 0x{:x} ({})'.format(mark, obj)) | |
| def die(message: str, status: int = 3): | |
| """ | |
| Emit fatal diagnostic `message` and exit with `status` (3 if not specified). | |
| """ | |
| write('fatal error: {}'.format(message)) | |
| sys.exit(status) | |
| def notice(message: str): | |
| """ | |
| Emit notification diagnostic `message`. | |
| """ | |
| write('notice: {}'.format(message)) | |
| def warn(message: str): | |
| """ | |
| Emit warning diagnostic `message`. | |
| """ | |
| write('warning: {}'.format(message)) | |
| def get_bytes(entry: libarchive.entry.ArchiveEntry) -> io.BytesIO: | |
| """ | |
| Return an io.BytesIO object with the contents of the given archive entry. | |
| """ | |
| bytes = bytearray() | |
| bytes.extend([byte for block in entry.get_blocks() for byte in block]) | |
| return io.BytesIO(bytes) | |
| def get_cpio(payload_filename: str) -> io.BytesIO: | |
| """ | |
| Return an io.BytesIO object containing the CPIO part of `payload_filename`. | |
| The payload file is a CPIO archive with an object file header (e.g., an ELF | |
| prologue) prepended. The embedded CPIO archive file is expected to be of | |
| the format the `file` command calls an "ASCII cpio archive (SVR4 with no | |
| CRC)". | |
| We assume that the CPIO "magic number" is not a valid sequence inside the | |
| object file header of `payload_filename`. | |
| """ | |
| cpio_magic = b'070701' | |
| with open(payload_filename, 'rb') as payload: | |
| match = re.search(cpio_magic, payload.read()) | |
| if match: | |
| debug('found CPIO identifying sequence {} at offset 0x{:x} in {}' | |
| .format(cpio_magic, match.start(), payload_filename)) | |
| payload.seek(match.start()) | |
| cpio_bytes = payload.read() | |
| else: | |
| warn('did not find the CPIO identifying sequence {} expected in {}' | |
| .format(cpio_magic, header_size, payload_filename)) | |
| cpio_bytes = None | |
| return cpio_bytes | |
| def main() -> int: | |
| parser = argparse.ArgumentParser( | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| description=""" | |
| Generate a C header file to standard output with a preprocessor symbol | |
| definition of the physical load address in hexadecimal of the ELF-loader in | |
| `payload_filename`. The address is calculated using the description of memory | |
| in `platform_filename` and the CPIO archive members embedded in the payload | |
| file, including the loadable segments of the ELF objects and a possible DTB | |
| (device tree binary) file. The ELF-loader is placed in the first (lowest) | |
| sufficiently-large memory region. | |
| """) | |
| parser.add_argument('--load-rootservers-high', dest='load_rootservers_high', | |
| default=False, action='store_true', | |
| help='assume ELF-loader will put rootservers at top of' | |
| ' memory') | |
| parser.add_argument('platform_filename', nargs=1, type=str, | |
| help='YAML description of platform parameters (e.g.,' | |
| ' platform_gen.yaml)') | |
| parser.add_argument('payload_filename', nargs=1, type=str, | |
| help='ELF-loader image file (e.g., archive.o)') | |
| # Set up some simpler names for argument data and derived information. | |
| args = parser.parse_args() | |
| image = args.payload_filename[0] | |
| image_size = os.path.getsize(image) | |
| do_load_rootservers_high = args.load_rootservers_high | |
| platform = platform_sift.load_data(args.platform_filename[0]) | |
| rootservers = [] | |
| is_dtb_present = False | |
| is_good_fit = False | |
| with libarchive.memory_reader(get_cpio(image)) as archive: | |
| for entry in archive: | |
| name = entry.name | |
| debug('encountered CPIO entry name: {}'.format(name)) | |
| if name == 'kernel.elf': | |
| kernel_elf = get_bytes(entry) | |
| elif name == 'kernel.dtb': | |
| # The ELF-loader loads the entire DTB into memory. | |
| is_dtb_present = True | |
| dtb_size = entry.size | |
| elif name.endswith('.bin'): | |
| # Skip checksum entries. | |
| notice('skipping checkum entry "{}"'.format(name)) | |
| else: | |
| rootservers.append(get_bytes(entry)) | |
| # Enumerate the regions as we encounter them for diagnostic purposes. | |
| region_counter = -1 | |
| last_region = len(platform['memory']) | |
| for region in platform['memory']: | |
| region_counter += 1 | |
| marker = region['start'] | |
| debug_marker_set(marker, 'region {} start'.format(region_counter)) | |
| # Note: We assume that the kernel is loaded at the start of memory | |
| # because that is what elfloader-tool/src/arch-arm/linker.lds ensures | |
| # will happen. This assumption may change in the future! | |
| kernel_start = region['start'] | |
| kernel_size = elf_sift.get_memory_usage(kernel_elf, align=True) | |
| kernel_end = elf_sift.get_aligned_size(kernel_start + kernel_size) | |
| marker = kernel_end | |
| debug_marker_set(marker, 'kernel_end') | |
| if is_dtb_present: | |
| dtb_start = marker | |
| dtb_end = elf_sift.get_aligned_size(dtb_start + dtb_size) | |
| marker = dtb_end | |
| debug_marker_set(marker, 'dtb_end') | |
| if do_load_rootservers_high and (region_counter == last_region): | |
| warn('"--load-rootservers-high" specified but placing' | |
| ' ELF-loader in last (or only) region ({} of {}); overlap' | |
| ' may not be detected by this tool' | |
| .format(region_counter, last_region)) | |
| # Deal with the 1..(# of CPUs - 1) possible user payloads, if we're not | |
| # loading them in high memory, discontiguously with the kernel. | |
| # | |
| # TODO: Handle this differently (skipping, or checking to see if we had | |
| # to push the kernel so high that it whacks the user payloads--but the | |
| # ELF-loader itself should detect that case). At present the case of | |
| # multiple rootservers is difficult to debug because it is not | |
| # implemented on the archive-construction side; see JIRA SELFOUR-2368. | |
| if not do_load_rootservers_high: | |
| for elf in rootservers: | |
| marker += elf_sift.get_memory_usage(elf, align=True) | |
| debug_marker_set(marker, 'end of rootserver') | |
| image_start_address = marker | |
| if (image_start_address + image_size) <= region['end']: | |
| is_good_fit = True | |
| break | |
| if not is_good_fit: | |
| die('ELF-loader image "{image}" of size 0x{size:x} does not fit within' | |
| ' any memory region described in "{yaml}"' | |
| .format(image=image, size=image_size, yaml=platform), status=1) | |
| sys.stdout.write('#define IMAGE_START_ADDR 0x{load:x}\n' | |
| .format(load=image_start_address)) | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |