summaryrefslogtreecommitdiffstats
path: root/tools/elf2efi.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:49:52 +0000
commit55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch)
tree33f869f55a1b149e9b7c2b7e201867ca5dd52992 /tools/elf2efi.py
parentInitial commit. (diff)
downloadsystemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz
systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-xtools/elf2efi.py699
1 files changed, 699 insertions, 0 deletions
diff --git a/tools/elf2efi.py b/tools/elf2efi.py
new file mode 100755
index 0000000..54f64fa
--- /dev/null
+++ b/tools/elf2efi.py
@@ -0,0 +1,699 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# Convert ELF static PIE to PE/EFI image.
+
+# To do so we simply copy desired ELF sections while preserving their memory layout to ensure that
+# code still runs as expected. We then translate ELF relocations to PE relocations so that the EFI
+# loader/firmware can properly load the binary to any address at runtime.
+#
+# To make this as painless as possible we only operate on static PIEs as they should only contain
+# base relocations that are easy to handle as they have a one-to-one mapping to PE relocations.
+#
+# EDK2 does a similar process using their GenFw tool. The main difference is that they use the
+# --emit-relocs linker flag, which emits a lot of different (static) ELF relocation types that have
+# to be handled differently for each architecture and is overall more work than its worth.
+#
+# Note that on arches where binutils has PE support (x86/x86_64 mostly, aarch64 only recently)
+# objcopy can be used to convert ELF to PE. But this will still not convert ELF relocations, making
+# the resulting binary useless. gnu-efi relies on this method and contains a stub that performs the
+# ELF dynamic relocations at runtime.
+
+# pylint: disable=attribute-defined-outside-init
+
+import argparse
+import hashlib
+import io
+import os
+import pathlib
+import time
+import typing
+from ctypes import (
+ c_char,
+ c_uint8,
+ c_uint16,
+ c_uint32,
+ c_uint64,
+ LittleEndianStructure,
+ sizeof,
+)
+
+from elftools.elf.constants import SH_FLAGS
+from elftools.elf.elffile import ELFFile
+from elftools.elf.enums import (
+ ENUM_DT_FLAGS_1,
+ ENUM_RELOC_TYPE_AARCH64,
+ ENUM_RELOC_TYPE_ARM,
+ ENUM_RELOC_TYPE_i386,
+ ENUM_RELOC_TYPE_x64,
+)
+from elftools.elf.relocation import (
+ Relocation as ElfRelocation,
+ RelocationTable as ElfRelocationTable,
+)
+
+
+class PeCoffHeader(LittleEndianStructure):
+ _fields_ = (
+ ("Machine", c_uint16),
+ ("NumberOfSections", c_uint16),
+ ("TimeDateStamp", c_uint32),
+ ("PointerToSymbolTable", c_uint32),
+ ("NumberOfSymbols", c_uint32),
+ ("SizeOfOptionalHeader", c_uint16),
+ ("Characteristics", c_uint16),
+ )
+
+
+class PeDataDirectory(LittleEndianStructure):
+ _fields_ = (
+ ("VirtualAddress", c_uint32),
+ ("Size", c_uint32),
+ )
+
+
+class PeRelocationBlock(LittleEndianStructure):
+ _fields_ = (
+ ("PageRVA", c_uint32),
+ ("BlockSize", c_uint32),
+ )
+
+ def __init__(self, PageRVA: int):
+ super().__init__(PageRVA)
+ self.entries: typing.List[PeRelocationEntry] = []
+
+
+class PeRelocationEntry(LittleEndianStructure):
+ _fields_ = (
+ ("Offset", c_uint16, 12),
+ ("Type", c_uint16, 4),
+ )
+
+
+class PeOptionalHeaderStart(LittleEndianStructure):
+ _fields_ = (
+ ("Magic", c_uint16),
+ ("MajorLinkerVersion", c_uint8),
+ ("MinorLinkerVersion", c_uint8),
+ ("SizeOfCode", c_uint32),
+ ("SizeOfInitializedData", c_uint32),
+ ("SizeOfUninitializedData", c_uint32),
+ ("AddressOfEntryPoint", c_uint32),
+ ("BaseOfCode", c_uint32),
+ )
+
+
+class PeOptionalHeaderMiddle(LittleEndianStructure):
+ _fields_ = (
+ ("SectionAlignment", c_uint32),
+ ("FileAlignment", c_uint32),
+ ("MajorOperatingSystemVersion", c_uint16),
+ ("MinorOperatingSystemVersion", c_uint16),
+ ("MajorImageVersion", c_uint16),
+ ("MinorImageVersion", c_uint16),
+ ("MajorSubsystemVersion", c_uint16),
+ ("MinorSubsystemVersion", c_uint16),
+ ("Win32VersionValue", c_uint32),
+ ("SizeOfImage", c_uint32),
+ ("SizeOfHeaders", c_uint32),
+ ("CheckSum", c_uint32),
+ ("Subsystem", c_uint16),
+ ("DllCharacteristics", c_uint16),
+ )
+
+
+class PeOptionalHeaderEnd(LittleEndianStructure):
+ _fields_ = (
+ ("LoaderFlags", c_uint32),
+ ("NumberOfRvaAndSizes", c_uint32),
+ ("ExportTable", PeDataDirectory),
+ ("ImportTable", PeDataDirectory),
+ ("ResourceTable", PeDataDirectory),
+ ("ExceptionTable", PeDataDirectory),
+ ("CertificateTable", PeDataDirectory),
+ ("BaseRelocationTable", PeDataDirectory),
+ ("Debug", PeDataDirectory),
+ ("Architecture", PeDataDirectory),
+ ("GlobalPtr", PeDataDirectory),
+ ("TLSTable", PeDataDirectory),
+ ("LoadConfigTable", PeDataDirectory),
+ ("BoundImport", PeDataDirectory),
+ ("IAT", PeDataDirectory),
+ ("DelayImportDescriptor", PeDataDirectory),
+ ("CLRRuntimeHeader", PeDataDirectory),
+ ("Reserved", PeDataDirectory),
+ )
+
+
+class PeOptionalHeader(LittleEndianStructure):
+ pass
+
+
+class PeOptionalHeader32(PeOptionalHeader):
+ _anonymous_ = ("Start", "Middle", "End")
+ _fields_ = (
+ ("Start", PeOptionalHeaderStart),
+ ("BaseOfData", c_uint32),
+ ("ImageBase", c_uint32),
+ ("Middle", PeOptionalHeaderMiddle),
+ ("SizeOfStackReserve", c_uint32),
+ ("SizeOfStackCommit", c_uint32),
+ ("SizeOfHeapReserve", c_uint32),
+ ("SizeOfHeapCommit", c_uint32),
+ ("End", PeOptionalHeaderEnd),
+ )
+
+
+class PeOptionalHeader32Plus(PeOptionalHeader):
+ _anonymous_ = ("Start", "Middle", "End")
+ _fields_ = (
+ ("Start", PeOptionalHeaderStart),
+ ("ImageBase", c_uint64),
+ ("Middle", PeOptionalHeaderMiddle),
+ ("SizeOfStackReserve", c_uint64),
+ ("SizeOfStackCommit", c_uint64),
+ ("SizeOfHeapReserve", c_uint64),
+ ("SizeOfHeapCommit", c_uint64),
+ ("End", PeOptionalHeaderEnd),
+ )
+
+
+class PeSection(LittleEndianStructure):
+ _fields_ = (
+ ("Name", c_char * 8),
+ ("VirtualSize", c_uint32),
+ ("VirtualAddress", c_uint32),
+ ("SizeOfRawData", c_uint32),
+ ("PointerToRawData", c_uint32),
+ ("PointerToRelocations", c_uint32),
+ ("PointerToLinenumbers", c_uint32),
+ ("NumberOfRelocations", c_uint16),
+ ("NumberOfLinenumbers", c_uint16),
+ ("Characteristics", c_uint32),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.data = bytearray()
+
+
+N_DATA_DIRECTORY_ENTRIES = 16
+
+assert sizeof(PeSection) == 40
+assert sizeof(PeCoffHeader) == 20
+assert sizeof(PeOptionalHeader32) == 224
+assert sizeof(PeOptionalHeader32Plus) == 240
+
+PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
+PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
+PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
+
+IGNORE_SECTIONS = [
+ ".eh_frame",
+ ".eh_frame_hdr",
+ ".ARM.exidx",
+]
+
+IGNORE_SECTION_TYPES = [
+ "SHT_DYNAMIC",
+ "SHT_DYNSYM",
+ "SHT_GNU_ATTRIBUTES",
+ "SHT_GNU_HASH",
+ "SHT_HASH",
+ "SHT_NOTE",
+ "SHT_REL",
+ "SHT_RELA",
+ "SHT_RELR",
+ "SHT_STRTAB",
+ "SHT_SYMTAB",
+]
+
+# EFI mandates 4KiB memory pages.
+SECTION_ALIGNMENT = 4096
+FILE_ALIGNMENT = 512
+
+# Nobody cares about DOS headers, so put the PE header right after.
+PE_OFFSET = 64
+PE_MAGIC = b"PE\0\0"
+
+
+def align_to(x: int, align: int) -> int:
+ return (x + align - 1) & ~(align - 1)
+
+
+def align_down(x: int, align: int) -> int:
+ return x & ~(align - 1)
+
+
+def next_section_address(sections: typing.List[PeSection]) -> int:
+ return align_to(
+ sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
+ )
+
+
+def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
+ pe_s = None
+
+ # This is essentially the same as copying by ELF load segments, except that we assemble them
+ # manually, so that we can easily strip unwanted sections. We try to only discard things we know
+ # about so that there are no surprises.
+
+ relro = None
+ for elf_seg in elf.iter_segments():
+ if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT:
+ raise RuntimeError("ELF segments are not properly aligned.")
+ elif elf_seg["p_type"] == "PT_GNU_RELRO":
+ relro = elf_seg
+
+ for elf_s in elf.iter_sections():
+ if (
+ elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0
+ or elf_s["sh_type"] in IGNORE_SECTION_TYPES
+ or elf_s.name in IGNORE_SECTIONS
+ ):
+ continue
+ if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]:
+ raise RuntimeError(f"Unknown section {elf_s.name}.")
+
+ if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
+ rwx = PE_CHARACTERISTICS_RX
+ elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
+ rwx = PE_CHARACTERISTICS_RW
+ else:
+ rwx = PE_CHARACTERISTICS_R
+
+ # PE images are always relro.
+ if relro and relro.section_in_segment(elf_s):
+ rwx = PE_CHARACTERISTICS_R
+
+ if pe_s and pe_s.Characteristics != rwx:
+ yield pe_s
+ pe_s = None
+
+ if pe_s:
+ # Insert padding to properly align the section.
+ pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data)
+ pe_s.data += bytearray(pad_len) + elf_s.data()
+ else:
+ pe_s = PeSection()
+ pe_s.VirtualAddress = elf_s["sh_addr"]
+ pe_s.Characteristics = rwx
+ pe_s.data = elf_s.data()
+
+ if pe_s:
+ yield pe_s
+
+
+def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
+ last_vma = 0
+ sections = []
+
+ for pe_s in iter_copy_sections(elf):
+ # Truncate the VMA to the nearest page and insert appropriate padding. This should not
+ # cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways.
+ # The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout
+ # for the PE image.
+ vma = pe_s.VirtualAddress
+ pe_s.VirtualAddress = align_down(vma, SECTION_ALIGNMENT)
+ pe_s.data = bytearray(vma - pe_s.VirtualAddress) + pe_s.data
+
+ pe_s.VirtualSize = len(pe_s.data)
+ pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT)
+ pe_s.Name = {
+ PE_CHARACTERISTICS_RX: b".text",
+ PE_CHARACTERISTICS_RW: b".data",
+ PE_CHARACTERISTICS_R: b".rodata",
+ }[pe_s.Characteristics]
+
+ # This can happen if not building with `-z separate-code`.
+ if pe_s.VirtualAddress < last_vma:
+ raise RuntimeError("Overlapping PE sections.")
+ last_vma = pe_s.VirtualAddress + pe_s.VirtualSize
+
+ if pe_s.Name == b".text":
+ opt.BaseOfCode = pe_s.VirtualAddress
+ opt.SizeOfCode += pe_s.VirtualSize
+ else:
+ opt.SizeOfInitializedData += pe_s.VirtualSize
+
+ if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32):
+ opt.BaseOfData = pe_s.VirtualAddress
+
+ sections.append(pe_s)
+
+ return sections
+
+
+def copy_sections(
+ elf: ELFFile,
+ opt: PeOptionalHeader,
+ input_names: str,
+ sections: typing.List[PeSection],
+):
+ for name in input_names.split(","):
+ elf_s = elf.get_section_by_name(name)
+ if not elf_s:
+ continue
+ if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
+ raise RuntimeError(f"ELF section {name} is not aligned.")
+ if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
+ raise RuntimeError(f"ELF section {name} is not read-only data.")
+
+ pe_s = PeSection()
+ pe_s.Name = name.encode()
+ pe_s.data = elf_s.data()
+ pe_s.VirtualAddress = next_section_address(sections)
+ pe_s.VirtualSize = len(elf_s.data())
+ pe_s.SizeOfRawData = align_to(len(elf_s.data()), FILE_ALIGNMENT)
+ pe_s.Characteristics = PE_CHARACTERISTICS_R
+ opt.SizeOfInitializedData += pe_s.VirtualSize
+ sections.append(pe_s)
+
+
+def apply_elf_relative_relocation(
+ reloc: ElfRelocation,
+ image_base: int,
+ sections: typing.List[PeSection],
+ addend_size: int,
+):
+ # fmt: off
+ [target] = [
+ pe_s for pe_s in sections
+ if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)
+ ]
+ # fmt: on
+
+ addend_offset = reloc["r_offset"] - target.VirtualAddress
+
+ if reloc.is_RELA():
+ addend = reloc["r_addend"]
+ else:
+ addend = target.data[addend_offset : addend_offset + addend_size]
+ addend = int.from_bytes(addend, byteorder="little")
+
+ value = (image_base + addend).to_bytes(addend_size, byteorder="little")
+ target.data[addend_offset : addend_offset + addend_size] = value
+
+
+def convert_elf_reloc_table(
+ elf: ELFFile,
+ elf_reloc_table: ElfRelocationTable,
+ elf_image_base: int,
+ sections: typing.List[PeSection],
+ pe_reloc_blocks: typing.Dict[int, PeRelocationBlock],
+):
+ NONE_RELOC = {
+ "EM_386": ENUM_RELOC_TYPE_i386["R_386_NONE"],
+ "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"],
+ "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_NONE"],
+ "EM_LOONGARCH": 0,
+ "EM_RISCV": 0,
+ "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_NONE"],
+ }[elf["e_machine"]]
+
+ RELATIVE_RELOC = {
+ "EM_386": ENUM_RELOC_TYPE_i386["R_386_RELATIVE"],
+ "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"],
+ "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"],
+ "EM_LOONGARCH": 3,
+ "EM_RISCV": 3,
+ "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"],
+ }[elf["e_machine"]]
+
+ for reloc in elf_reloc_table.iter_relocations():
+ if reloc["r_info_type"] == NONE_RELOC:
+ continue
+
+ if reloc["r_info_type"] == RELATIVE_RELOC:
+ apply_elf_relative_relocation(
+ reloc, elf_image_base, sections, elf.elfclass // 8
+ )
+
+ # Now that the ELF relocation has been applied, we can create a PE relocation.
+ block_rva = reloc["r_offset"] & ~0xFFF
+ if block_rva not in pe_reloc_blocks:
+ pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva)
+
+ entry = PeRelocationEntry()
+ entry.Offset = reloc["r_offset"] & 0xFFF
+ # REL_BASED_HIGHLOW or REL_BASED_DIR64
+ entry.Type = 3 if elf.elfclass == 32 else 10
+ pe_reloc_blocks[block_rva].entries.append(entry)
+
+ continue
+
+ raise RuntimeError(f"Unsupported relocation {reloc}")
+
+
+def convert_elf_relocations(
+ elf: ELFFile,
+ opt: PeOptionalHeader,
+ sections: typing.List[PeSection],
+ minimum_sections: int,
+) -> typing.Optional[PeSection]:
+ dynamic = elf.get_section_by_name(".dynamic")
+ if dynamic is None:
+ raise RuntimeError("ELF .dynamic section is missing.")
+
+ [flags_tag] = dynamic.iter_tags("DT_FLAGS_1")
+ if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]:
+ raise RuntimeError("ELF file is not a PIE.")
+
+ # This checks that the ELF image base is 0.
+ symtab = elf.get_section_by_name(".symtab")
+ if symtab:
+ exe_start = symtab.get_symbol_by_name("__executable_start")
+ if exe_start and exe_start[0]["st_value"] != 0:
+ raise RuntimeError("Unexpected ELF image base.")
+
+ opt.SizeOfHeaders = align_to(
+ PE_OFFSET
+ + len(PE_MAGIC)
+ + sizeof(PeCoffHeader)
+ + sizeof(opt)
+ + sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
+ FILE_ALIGNMENT,
+ )
+
+ # We use the basic VMA layout from the ELF image in the PE image. This could cause the first
+ # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
+ # offset relative to the PE image base when applying/converting ELF relocations. Afterwards we
+ # just have to apply the offset to the PE addresses so that the PE relocations work correctly on
+ # the ELF portions of the image.
+ segment_offset = 0
+ if sections[0].VirtualAddress < opt.SizeOfHeaders:
+ segment_offset = align_to(
+ opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT
+ )
+
+ opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
+ opt.BaseOfCode += segment_offset
+ if isinstance(opt, PeOptionalHeader32):
+ opt.BaseOfData += segment_offset
+
+ pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {}
+ for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
+ if reloc_type not in ["REL", "RELA"]:
+ raise RuntimeError("Unsupported relocation type {elf_reloc_type}.")
+ convert_elf_reloc_table(
+ elf, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks
+ )
+
+ for pe_s in sections:
+ pe_s.VirtualAddress += segment_offset
+
+ if len(pe_reloc_blocks) == 0:
+ return None
+
+ data = bytearray()
+ for rva in sorted(pe_reloc_blocks):
+ block = pe_reloc_blocks[rva]
+ n_relocs = len(block.entries)
+
+ # Each block must start on a 32-bit boundary. Because each entry is 16 bits
+ # the len has to be even. We pad by adding a none relocation.
+ if n_relocs % 2 != 0:
+ n_relocs += 1
+ block.entries.append(PeRelocationEntry())
+
+ block.PageRVA += segment_offset
+ block.BlockSize = (
+ sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
+ )
+ data += block
+ for entry in sorted(block.entries, key=lambda e: e.Offset):
+ data += entry
+
+ pe_reloc_s = PeSection()
+ pe_reloc_s.Name = b".reloc"
+ pe_reloc_s.data = data
+ pe_reloc_s.VirtualAddress = next_section_address(sections)
+ pe_reloc_s.VirtualSize = len(data)
+ pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT)
+ # CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE
+ pe_reloc_s.Characteristics = 0x42000040
+
+ sections.append(pe_reloc_s)
+ opt.SizeOfInitializedData += pe_reloc_s.VirtualSize
+ return pe_reloc_s
+
+
+def write_pe(
+ file, coff: PeCoffHeader, opt: PeOptionalHeader, sections: typing.List[PeSection]
+):
+ file.write(b"MZ")
+ file.seek(0x3C, io.SEEK_SET)
+ file.write(PE_OFFSET.to_bytes(2, byteorder="little"))
+ file.seek(PE_OFFSET, io.SEEK_SET)
+ file.write(PE_MAGIC)
+ file.write(coff)
+ file.write(opt)
+
+ offset = opt.SizeOfHeaders
+ for pe_s in sorted(sections, key=lambda s: s.VirtualAddress):
+ if pe_s.VirtualAddress < opt.SizeOfHeaders:
+ # Linker script should make sure this does not happen.
+ raise RuntimeError(f"Section {pe_s.Name} overlapping PE headers.")
+
+ pe_s.PointerToRawData = offset
+ file.write(pe_s)
+ offset = align_to(offset + len(pe_s.data), FILE_ALIGNMENT)
+
+ assert file.tell() <= opt.SizeOfHeaders
+
+ for pe_s in sections:
+ file.seek(pe_s.PointerToRawData, io.SEEK_SET)
+ file.write(pe_s.data)
+
+ file.truncate(offset)
+
+
+def elf2efi(args: argparse.Namespace):
+ elf = ELFFile(args.ELF)
+ if not elf.little_endian:
+ raise RuntimeError("ELF file is not little-endian.")
+ if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]:
+ raise RuntimeError("Unsupported ELF type.")
+
+ pe_arch = {
+ "EM_386": 0x014C,
+ "EM_AARCH64": 0xAA64,
+ "EM_ARM": 0x01C2,
+ "EM_LOONGARCH": 0x6232 if elf.elfclass == 32 else 0x6264,
+ "EM_RISCV": 0x5032 if elf.elfclass == 32 else 0x5064,
+ "EM_X86_64": 0x8664,
+ }.get(elf["e_machine"])
+ if pe_arch is None:
+ raise RuntimeError(f"Unsupported ELF arch {elf['e_machine']}")
+
+ coff = PeCoffHeader()
+ opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus()
+
+ # We relocate to a unique image base to reduce the chances for runtime relocation to occur.
+ base_name = pathlib.Path(args.PE.name).name.encode()
+ opt.ImageBase = int(hashlib.sha1(base_name).hexdigest()[0:8], 16)
+ if elf.elfclass == 32:
+ opt.ImageBase = (0x400000 + opt.ImageBase) & 0xFFFF0000
+ else:
+ opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000
+
+ sections = convert_sections(elf, opt)
+ copy_sections(elf, opt, args.copy_sections, sections)
+ pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections)
+
+ coff.Machine = pe_arch
+ coff.NumberOfSections = len(sections)
+ coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))
+ coff.SizeOfOptionalHeader = sizeof(opt)
+ # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED
+ # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
+ coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E
+
+ opt.SectionAlignment = SECTION_ALIGNMENT
+ opt.FileAlignment = FILE_ALIGNMENT
+ opt.MajorImageVersion = args.version_major
+ opt.MinorImageVersion = args.version_minor
+ opt.MajorSubsystemVersion = args.efi_major
+ opt.MinorSubsystemVersion = args.efi_minor
+ opt.Subsystem = args.subsystem
+ opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B
+ opt.SizeOfImage = next_section_address(sections)
+
+ # DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
+ opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140
+
+ # These values are taken from a natively built PE binary (although, unused by EDK2/EFI).
+ opt.SizeOfStackReserve = 0x100000
+ opt.SizeOfStackCommit = 0x001000
+ opt.SizeOfHeapReserve = 0x100000
+ opt.SizeOfHeapCommit = 0x001000
+
+ opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES
+ if pe_reloc_s:
+ opt.BaseRelocationTable = PeDataDirectory(
+ pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize
+ )
+
+ write_pe(args.PE, coff, opt, sections)
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI")
+ parser.add_argument(
+ "--version-major",
+ type=int,
+ default=0,
+ help="Major image version of EFI image",
+ )
+ parser.add_argument(
+ "--version-minor",
+ type=int,
+ default=0,
+ help="Minor image version of EFI image",
+ )
+ parser.add_argument(
+ "--efi-major",
+ type=int,
+ default=0,
+ help="Minimum major EFI subsystem version",
+ )
+ parser.add_argument(
+ "--efi-minor",
+ type=int,
+ default=0,
+ help="Minimum minor EFI subsystem version",
+ )
+ parser.add_argument(
+ "--subsystem",
+ type=int,
+ default=10,
+ help="PE subsystem",
+ )
+ parser.add_argument(
+ "ELF",
+ type=argparse.FileType("rb"),
+ help="Input ELF file",
+ )
+ parser.add_argument(
+ "PE",
+ type=argparse.FileType("wb"),
+ help="Output PE/EFI file",
+ )
+ parser.add_argument(
+ "--minimum-sections",
+ type=int,
+ default=0,
+ help="Minimum number of sections to leave space for",
+ )
+ parser.add_argument(
+ "--copy-sections",
+ type=str,
+ default="",
+ help="Copy these sections if found",
+ )
+
+ elf2efi(parser.parse_args())
+
+
+if __name__ == "__main__":
+ main()