| #!/usr/bin/env python3 |
| # Copyright (c) 2020 Intel Corporation |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import os.path |
| import subprocess |
| import elftools.elf.elffile |
| import argparse |
| |
| ENTRY_SYM = "__start64" |
| BOOTARGS_SYM = "efi_bootargs" |
| |
| args = None |
| |
| def verbose(msg): |
| if args.verbose: |
| print(msg) |
| |
| def build_elf(elf_file, include_dirs): |
| base_dir = os.path.dirname(os.path.abspath(__file__)) |
| |
| cfile = os.path.join(base_dir, "zefi.c") |
| ldscript = os.path.join(base_dir, "efi.ld") |
| |
| assert os.path.isfile(cfile) |
| assert os.path.isfile(ldscript) |
| |
| # |
| # Open the ELF file up and find our entry point |
| # |
| fp = open(elf_file, "rb") |
| ef = elftools.elf.elffile.ELFFile(fp) |
| |
| symtab = ef.get_section_by_name(".symtab") |
| entry_addr = symtab.get_symbol_by_name(ENTRY_SYM)[0].entry.st_value |
| |
| verbose("Entry point address (symbol: %s) 0x%x" % (ENTRY_SYM, entry_addr)) |
| |
| # |
| # Parse the ELF file and extract segment data |
| # |
| |
| data_blob = b'' |
| data_segs = [] |
| zero_segs = [] |
| |
| for seg in ef.iter_segments(): |
| h = seg.header |
| if h.p_type != "PT_LOAD": |
| continue |
| |
| assert h.p_memsz >= h.p_filesz |
| assert len(seg.data()) == h.p_filesz |
| |
| if h.p_filesz > 0: |
| sd = seg.data() |
| verbose("%d bytes of data at 0x%x, data offset %d" |
| % (len(sd), h.p_vaddr, len(data_blob))) |
| data_segs.append((h.p_vaddr, len(sd), len(data_blob))) |
| data_blob = data_blob + sd |
| |
| if h.p_memsz > h.p_filesz: |
| bytesz = h.p_memsz - h.p_filesz |
| addr = h.p_vaddr + h.p_filesz |
| verbose("%d bytes of zero-fill at 0x%x" % (bytesz, addr)) |
| zero_segs.append((addr, bytesz)) |
| |
| verbose(f"{len(data_blob)} bytes of data to include in image") |
| |
| # |
| # Emit a C header containing the metadata |
| # |
| cf = open("zefi-segments.h", "w") |
| |
| cf.write("/* GENERATED CODE. DO NOT EDIT. */\n\n") |
| |
| cf.write("/* Sizes and offsets specified in 4-byte units.\n") |
| cf.write(" * All addresses 4-byte aligned.\n") |
| cf.write(" */\n") |
| |
| cf.write("struct data_seg { uint64_t addr; uint32_t sz; uint32_t off; };\n\n") |
| |
| cf.write("static struct data_seg zefi_dsegs[] = {\n") |
| for s in data_segs: |
| cf.write(" { 0x%x, %d, %d },\n" |
| % (s[0], s[1], s[2])) |
| cf.write("};\n\n") |
| |
| cf.write("struct zero_seg { uint64_t addr; uint32_t sz; };\n\n") |
| |
| cf.write("static struct zero_seg zefi_zsegs[] = {\n") |
| for s in zero_segs: |
| cf.write(" { 0x%x, %d },\n" |
| % (s[0], s[1])) |
| cf.write("};\n\n") |
| |
| cf.write("static uintptr_t zefi_entry = 0x%xUL;\n" % (entry_addr)) |
| |
| if symtab.get_symbol_by_name(BOOTARGS_SYM): |
| cf.write("static uintptr_t zefi_bootargs = 0x%xUL;\n" % (symtab.get_symbol_by_name(BOOTARGS_SYM)[0].entry.st_value)) |
| |
| cf.close() |
| |
| verbose("Metadata header generated.") |
| |
| # |
| # Build |
| # |
| |
| # First stage ELF binary. Flag notes: |
| # + Stack protector is default on some distros and needs library support |
| # + We need pic to enforce that the linker adds no relocations |
| # + UEFI can take interrupts on our stack, so no red zone |
| # + UEFI API assumes 16-bit wchar_t |
| includes = [] |
| for include_dir in include_dirs: |
| includes.extend(["-I", include_dir]) |
| cmd = ([args.compiler, "-shared", "-Wall", "-Werror", "-I."] + includes + |
| ["-fno-stack-protector", "-fpic", "-mno-red-zone", "-fshort-wchar", |
| "-Wl,-nostdlib", "-T", ldscript, "-o", "zefi.elf", cfile]) |
| verbose(" ".join(cmd)) |
| subprocess.run(cmd, check = True) |
| |
| # Extract the .data segment and append our extra blob |
| cmd = [args.objcopy, "-O", "binary", "-j", ".data", "zefi.elf", "data.dat"] |
| verbose(" ".join(cmd)) |
| subprocess.run(cmd, check = True) |
| |
| assert (os.stat("data.dat").st_size % 8) == 0 |
| df = open("data.dat", "ab") |
| df.write(data_blob) |
| df.close() |
| |
| # FIXME: this generates warnings about our unused trash section having to be moved to make room. Set its address far away... |
| subprocess.run([args.objcopy, "--update-section", ".data=data.dat", |
| "zefi.elf"], check = True) |
| |
| # Convert it to a PE-COFF DLL. |
| cmd = [args.objcopy, "--target=efi-app-x86_64", |
| "-j", ".text", "-j", ".reloc", "-j", ".data", |
| "zefi.elf", "zephyr.efi"] |
| verbose(" ".join(cmd)) |
| subprocess.run(cmd, check = True) |
| |
| verbose("Build complete; zephyr.efi wrapper binary is ready") |
| |
| |
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) |
| |
| parser.add_argument("-c", "--compiler", required=True, help="Compiler to be used") |
| parser.add_argument("-o", "--objcopy", required=True, help="objcopy to be used") |
| parser.add_argument("-f", "--elf-file", required=True, help="Input file") |
| parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") |
| parser.add_argument("-i", "--includes", required=True, nargs="+", |
| help="Zephyr base include directories") |
| |
| return parser.parse_args() |
| |
| if __name__ == "__main__": |
| |
| args = parse_args() |
| verbose(f"Working on {args.elf_file} with {args.includes}...") |
| build_elf(args.elf_file, args.includes) |