| #!/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()) |