1#!/usr/bin/env python3 2# 3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7""" 8Generate a C header file to standard output with a preprocessor symbol 9definition of the physical load address in hexadecimal of the ELF-loader in 10`payload_filename`. The address is calculated using the description of memory 11in `platform_filename` and the CPIO archive members embedded in the payload 12file, including the loadable segments of the ELF objects and a possible DTB 13(device tree binary) file. The ELF-loader is placed in the first (lowest) 14sufficiently-large memory region. 15 16THIS IS NOT A STABLE API. Use as a script, not a module. 17""" 18 19import argparse 20import io 21import os.path 22import re 23import sys 24 25import libarchive 26 27import elf_sift 28import platform_sift 29 30do_debug = False 31program_name = 'shoehorn' 32 33 34def write(message: str): 35 """ 36 Write diagnostic `message` to standard error. 37 """ 38 sys.stderr.write('{}: {}\n'.format(program_name, message)) 39 40 41def debug(message: str): 42 """ 43 Emit debugging diagnostic `message`. 44 """ 45 if do_debug: 46 write('debug: {}'.format(message)) 47 48 49def debug_marker_set(mark: int, obj: str): 50 debug('setting marker to 0x{:x} ({})'.format(mark, obj)) 51 52 53def die(message: str, status: int = 3): 54 """ 55 Emit fatal diagnostic `message` and exit with `status` (3 if not specified). 56 """ 57 write('fatal error: {}'.format(message)) 58 sys.exit(status) 59 60 61def notice(message: str): 62 """ 63 Emit notification diagnostic `message`. 64 """ 65 write('notice: {}'.format(message)) 66 67 68def warn(message: str): 69 """ 70 Emit warning diagnostic `message`. 71 """ 72 write('warning: {}'.format(message)) 73 74 75def get_bytes(entry: libarchive.entry.ArchiveEntry) -> io.BytesIO: 76 """ 77 Return an io.BytesIO object with the contents of the given archive entry. 78 """ 79 bytes = bytearray() 80 bytes.extend([byte for block in entry.get_blocks() for byte in block]) 81 return io.BytesIO(bytes) 82 83 84def get_cpio(payload_filename: str) -> io.BytesIO: 85 """ 86 Return an io.BytesIO object containing the CPIO part of `payload_filename`. 87 The payload file is a CPIO archive with an object file header (e.g., an ELF 88 prologue) prepended. The embedded CPIO archive file is expected to be of 89 the format the `file` command calls an "ASCII cpio archive (SVR4 with no 90 CRC)". 91 92 We assume that the CPIO "magic number" is not a valid sequence inside the 93 object file header of `payload_filename`. 94 """ 95 cpio_magic = b'070701' 96 97 with open(payload_filename, 'rb') as payload: 98 match = re.search(cpio_magic, payload.read()) 99 100 if match: 101 debug('found CPIO identifying sequence {} at offset 0x{:x} in {}' 102 .format(cpio_magic, match.start(), payload_filename)) 103 payload.seek(match.start()) 104 cpio_bytes = payload.read() 105 else: 106 warn('did not find the CPIO identifying sequence {} expected in {}' 107 .format(cpio_magic, header_size, payload_filename)) 108 cpio_bytes = None 109 110 return cpio_bytes 111 112 113def main() -> int: 114 parser = argparse.ArgumentParser( 115 formatter_class=argparse.RawDescriptionHelpFormatter, 116 description=""" 117Generate a C header file to standard output with a preprocessor symbol 118definition of the physical load address in hexadecimal of the ELF-loader in 119`payload_filename`. The address is calculated using the description of memory 120in `platform_filename` and the CPIO archive members embedded in the payload 121file, including the loadable segments of the ELF objects and a possible DTB 122(device tree binary) file. The ELF-loader is placed in the first (lowest) 123sufficiently-large memory region. 124""") 125 parser.add_argument('--load-rootservers-high', dest='load_rootservers_high', 126 default=False, action='store_true', 127 help='assume ELF-loader will put rootservers at top of' 128 ' memory') 129 parser.add_argument('platform_filename', nargs=1, type=str, 130 help='YAML description of platform parameters (e.g.,' 131 ' platform_gen.yaml)') 132 parser.add_argument('payload_filename', nargs=1, type=str, 133 help='ELF-loader image file (e.g., archive.o)') 134 135 # Set up some simpler names for argument data and derived information. 136 args = parser.parse_args() 137 image = args.payload_filename[0] 138 image_size = os.path.getsize(image) 139 do_load_rootservers_high = args.load_rootservers_high 140 platform = platform_sift.load_data(args.platform_filename[0]) 141 142 rootservers = [] 143 is_dtb_present = False 144 is_good_fit = False 145 146 with libarchive.memory_reader(get_cpio(image)) as archive: 147 for entry in archive: 148 name = entry.name 149 debug('encountered CPIO entry name: {}'.format(name)) 150 151 if name == 'kernel.elf': 152 kernel_elf = get_bytes(entry) 153 elif name == 'kernel.dtb': 154 # The ELF-loader loads the entire DTB into memory. 155 is_dtb_present = True 156 dtb_size = entry.size 157 elif name.endswith('.bin'): 158 # Skip checksum entries. 159 notice('skipping checkum entry "{}"'.format(name)) 160 else: 161 rootservers.append(get_bytes(entry)) 162 163 # Enumerate the regions as we encounter them for diagnostic purposes. 164 region_counter = -1 165 last_region = len(platform['memory']) 166 167 for region in platform['memory']: 168 region_counter += 1 169 marker = region['start'] 170 debug_marker_set(marker, 'region {} start'.format(region_counter)) 171 # Note: We assume that the kernel is loaded at the start of memory 172 # because that is what elfloader-tool/src/arch-arm/linker.lds ensures 173 # will happen. This assumption may change in the future! 174 kernel_start = region['start'] 175 kernel_size = elf_sift.get_memory_usage(kernel_elf, align=True) 176 kernel_end = elf_sift.get_aligned_size(kernel_start + kernel_size) 177 marker = kernel_end 178 debug_marker_set(marker, 'kernel_end') 179 180 if is_dtb_present: 181 dtb_start = marker 182 dtb_end = elf_sift.get_aligned_size(dtb_start + dtb_size) 183 marker = dtb_end 184 debug_marker_set(marker, 'dtb_end') 185 186 if do_load_rootservers_high and (region_counter == last_region): 187 warn('"--load-rootservers-high" specified but placing' 188 ' ELF-loader in last (or only) region ({} of {}); overlap' 189 ' may not be detected by this tool' 190 .format(region_counter, last_region)) 191 192 # Deal with the 1..(# of CPUs - 1) possible user payloads, if we're not 193 # loading them in high memory, discontiguously with the kernel. 194 # 195 # TODO: Handle this differently (skipping, or checking to see if we had 196 # to push the kernel so high that it whacks the user payloads--but the 197 # ELF-loader itself should detect that case). At present the case of 198 # multiple rootservers is difficult to debug because it is not 199 # implemented on the archive-construction side; see JIRA SELFOUR-2368. 200 if not do_load_rootservers_high: 201 for elf in rootservers: 202 marker += elf_sift.get_memory_usage(elf, align=True) 203 debug_marker_set(marker, 'end of rootserver') 204 205 # Note: sel4test_driver eats (almost) 4 more MiB than it claims to. 206 # Fixing this is JIRA SELFOUR-2335. 207 fudge_factor = 4 * 1024 * 1024 208 marker += elf_sift.get_aligned_size(fudge_factor) 209 debug_marker_set(marker, 'end of (aligned) fudge factor') 210 211 image_start_address = marker 212 213 if (image_start_address + image_size) <= region['end']: 214 is_good_fit = True 215 break 216 217 if not is_good_fit: 218 die('ELF-loader image "{image}" of size 0x{size:x} does not fit within' 219 ' any memory region described in "{yaml}"' 220 .format(image=image, size=image_size, yaml=platform), status=1) 221 222 sys.stdout.write('#define IMAGE_START_ADDR 0x{load:x}\n' 223 .format(load=image_start_address)) 224 return 0 225 226 227if __name__ == '__main__': 228 sys.exit(main()) 229