167 lines
5.2 KiB
Python
Executable File
167 lines
5.2 KiB
Python
Executable File
#!/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)
|