1#!/usr/bin/env python3 2# Copyright (c) 2020 Intel Corporation 3# SPDX-License-Identifier: Apache-2.0 4 5import os.path 6import subprocess 7import elftools.elf.elffile 8import argparse 9 10ENTRY_SYM = "__start64" 11 12def verbose(msg): 13 if args.verbose: 14 print(msg) 15 16def build_elf(elf_file, include_dirs): 17 base_dir = os.path.dirname(os.path.abspath(__file__)) 18 19 cfile = os.path.join(base_dir, "zefi.c") 20 ldscript = os.path.join(base_dir, "efi.ld") 21 22 assert os.path.isfile(cfile) 23 assert os.path.isfile(ldscript) 24 25 # 26 # Open the ELF file up and find our entry point 27 # 28 fp = open(elf_file, "rb") 29 ef = elftools.elf.elffile.ELFFile(fp) 30 31 symtab = ef.get_section_by_name(".symtab") 32 entry_addr = symtab.get_symbol_by_name(ENTRY_SYM)[0].entry.st_value 33 34 verbose("Entry point address (symbol: %s) 0x%x" % (ENTRY_SYM, entry_addr)) 35 36 # 37 # Parse the ELF file and extract segment data 38 # 39 40 data_blob = b'' 41 data_segs = [] 42 zero_segs = [] 43 44 for seg in ef.iter_segments(): 45 h = seg.header 46 if h.p_type != "PT_LOAD": 47 continue 48 49 assert h.p_memsz >= h.p_filesz 50 assert len(seg.data()) == h.p_filesz 51 52 if h.p_filesz > 0: 53 sd = seg.data() 54 verbose("%d bytes of data at 0x%x, data offset %d" 55 % (len(sd), h.p_vaddr, len(data_blob))) 56 data_segs.append((h.p_vaddr, len(sd), len(data_blob))) 57 data_blob = data_blob + sd 58 59 if h.p_memsz > h.p_filesz: 60 bytesz = h.p_memsz - h.p_filesz 61 addr = h.p_vaddr + h.p_filesz 62 verbose("%d bytes of zero-fill at 0x%x" % (bytesz, addr)) 63 zero_segs.append((addr, bytesz)) 64 65 verbose(f"{len(data_blob)} bytes of data to include in image") 66 67 # 68 # Emit a C header containing the metadata 69 # 70 cf = open("zefi-segments.h", "w") 71 72 cf.write("/* GENERATED CODE. DO NOT EDIT. */\n\n") 73 74 cf.write("/* Sizes and offsets specified in 4-byte units.\n") 75 cf.write(" * All addresses 4-byte aligned.\n") 76 cf.write(" */\n") 77 78 cf.write("struct data_seg { uint64_t addr; uint32_t sz; uint32_t off; };\n\n") 79 80 cf.write("static struct data_seg zefi_dsegs[] = {\n") 81 for s in data_segs: 82 cf.write(" { 0x%x, %d, %d },\n" 83 % (s[0], s[1], s[2])) 84 cf.write("};\n\n") 85 86 cf.write("struct zero_seg { uint64_t addr; uint32_t sz; };\n\n") 87 88 cf.write("static struct zero_seg zefi_zsegs[] = {\n") 89 for s in zero_segs: 90 cf.write(" { 0x%x, %d },\n" 91 % (s[0], s[1])) 92 cf.write("};\n\n") 93 94 cf.write("static uintptr_t zefi_entry = 0x%xUL;\n" % (entry_addr)) 95 96 cf.close() 97 98 verbose("Metadata header generated.") 99 100 # 101 # Build 102 # 103 104 # First stage ELF binary. Flag notes: 105 # + Stack protector is default on some distros and needs library support 106 # + We need pic to enforce that the linker adds no relocations 107 # + UEFI can take interrupts on our stack, so no red zone 108 # + UEFI API assumes 16-bit wchar_t 109 includes = [] 110 for include_dir in include_dirs: 111 includes.extend(["-I", include_dir]) 112 cmd = ([args.compiler, "-shared", "-Wall", "-Werror", "-I."] + includes + 113 ["-fno-stack-protector", "-fpic", "-mno-red-zone", "-fshort-wchar", 114 "-Wl,-nostdlib", "-T", ldscript, "-o", "zefi.elf", cfile]) 115 verbose(" ".join(cmd)) 116 subprocess.run(cmd, check = True) 117 118 # Extract the .data segment and append our extra blob 119 cmd = [args.objcopy, "-O", "binary", "-j", ".data", "zefi.elf", "data.dat"] 120 verbose(" ".join(cmd)) 121 subprocess.run(cmd, check = True) 122 123 assert (os.stat("data.dat").st_size % 8) == 0 124 df = open("data.dat", "ab") 125 df.write(data_blob) 126 df.close() 127 128 # FIXME: this generates warnings about our unused trash section having to be moved to make room. Set its address far away... 129 subprocess.run([args.objcopy, "--update-section", ".data=data.dat", 130 "zefi.elf"], check = True) 131 132 # Convert it to a PE-COFF DLL. 133 cmd = [args.objcopy, "--target=efi-app-x86_64", 134 "-j", ".text", "-j", ".reloc", "-j", ".data", 135 "zefi.elf", "zephyr.efi"] 136 verbose(" ".join(cmd)) 137 subprocess.run(cmd, check = True) 138 139 verbose("Build complete; zephyr.efi wrapper binary is ready") 140 141 142def parse_args(): 143 parser = argparse.ArgumentParser( 144 description=__doc__, 145 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) 146 147 parser.add_argument("-c", "--compiler", required=True, help="Compiler to be used") 148 parser.add_argument("-o", "--objcopy", required=True, help="objcopy to be used") 149 parser.add_argument("-f", "--elf-file", required=True, help="Input file") 150 parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") 151 parser.add_argument("-i", "--includes", required=True, nargs="+", 152 help="Zephyr base include directories") 153 154 return parser.parse_args() 155 156if __name__ == "__main__": 157 158 args = parse_args() 159 verbose(f"Working on {args.elf_file} with {args.includes}...") 160 build_elf(args.elf_file, args.includes) 161