From 469879aa44c4cde5541b98be0a4421ab069c5834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Feb 2026 13:01:01 +0100 Subject: [PATCH 1/4] generate-sym-test: skip everything that is not a file The generator looks for files in the filesystem, and it sometimes fails on emacs "lock files" which are a symlink. Ignore those. --- src/test/generate-sym-test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index 8fcf175289..b8d64d623f 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -6,6 +6,7 @@ import os import re import sys +from pathlib import Path from typing import IO @@ -161,7 +162,10 @@ for dirpath, _, filenames in sorted(os.walk(sys.argv[2])): for filename in sorted(filenames): if not filename.endswith('.c') and not filename.endswith('.h'): continue - with open(os.path.join(dirpath, filename), 'r') as f: + p = Path(dirpath) / filename + if p.is_symlink(): + continue + with p.open('rt') as f: process_source_file(f) print(""" {} From 528a939c89f600adfecc7d280a1474ff2498d72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Feb 2026 13:32:31 +0100 Subject: [PATCH 2/4] elf2efi: import whole module, not individual symbols When reading the code, it was hard to figure out if the given name was imported or a local class. And the renaming of imports also made it harder to look things up online. Arguably, the deeply nested import structure and inconsistent naming in elftools is partially to blame: there is just no good way to make this look nice. But anyway, let's use the usual style of importing the module and using names prefixed with the module path so that the origin of imported names is clear. elfutils.elf.elffile is importered separately, because a) it needs to be imported separately anyway bxecause the module does lazy imports internally, a) the name already indicates the origin, c) is used in quite a few places so the shorter name is nice. --- tools/elf2efi.py | 118 ++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/tools/elf2efi.py b/tools/elf2efi.py index b81423d06e..12bcdcd53b 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -39,19 +39,8 @@ from ctypes import ( 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, -) +from elftools import elf +from elftools.elf import elffile class PeCoffHeader(LittleEndianStructure): @@ -257,7 +246,7 @@ class BadSectionError(ValueError): "One of the sections is in a bad state" -def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]: +def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: pe_s = None # This is essentially the same as copying by ELF load segments, except that we assemble them @@ -265,16 +254,16 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]: # about so that there are no surprises. relro = None - for elf_seg in elf.iter_segments(): + for elf_seg in file.iter_segments(): if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned" f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})") if elf_seg["p_type"] == "PT_GNU_RELRO": relro = elf_seg - for elf_s in elf.iter_sections(): + for elf_s in file.iter_sections(): if ( - elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0 + elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 or elf_s["sh_type"] in IGNORE_SECTION_TYPES or elf_s.name in IGNORE_SECTIONS or elf_s["sh_size"] == 0 @@ -286,9 +275,9 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]: # FIXME: figure out why those sections are inserted print("WARNING: Non-empty .got section", file=sys.stderr) - if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR: + if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR: rwx = PE_CHARACTERISTICS_RX - elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE: + elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE: rwx = PE_CHARACTERISTICS_RW else: rwx = PE_CHARACTERISTICS_R @@ -315,11 +304,14 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]: yield pe_s -def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]: +def convert_sections( + file: elffile.ELFFile, + opt: PeOptionalHeader, +) -> typing.List[PeSection]: last_vma = (0, 0) sections = [] - for pe_s in iter_copy_sections(elf): + for pe_s in iter_copy_sections(file): # 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 @@ -357,18 +349,18 @@ def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSecti def copy_sections( - elf: ELFFile, + file: elffile.ELFFile, opt: PeOptionalHeader, input_names: str, sections: typing.List[PeSection], ): for name in input_names.split(","): - elf_s = elf.get_section_by_name(name) + elf_s = file.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 BadSectionError(f"ELF section {name} is not aligned") - if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0: + if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: raise BadSectionError(f"ELF section {name} is not read-only data") pe_s = PeSection() @@ -383,7 +375,7 @@ def copy_sections( def apply_elf_relative_relocation( - reloc: ElfRelocation, + reloc: elf.relocation.Relocation, image_base: int, sections: typing.List[PeSection], addend_size: int, @@ -404,29 +396,29 @@ def apply_elf_relative_relocation( def convert_elf_reloc_table( - elf: ELFFile, - elf_reloc_table: ElfRelocationTable, + file: elffile.ELFFile, + elf_reloc_table: elf.relocation.RelocationTable, 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_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], + "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], + "EM_ARM": elf.enums.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"]] + "EM_RISCV": 0, + "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], + }[file["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_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], + "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], + "EM_ARM": elf.enums.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"]] + "EM_RISCV": 3, + "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], + }[file["e_machine"]] for reloc in elf_reloc_table.iter_relocations(): if reloc["r_info_type"] == NONE_RELOC: @@ -436,7 +428,7 @@ def convert_elf_reloc_table( apply_elf_relative_relocation(reloc, elf_image_base, sections, - elf.elfclass // 8) + file.elfclass // 8) # Now that the ELF relocation has been applied, we can create a PE relocation. block_rva = reloc["r_offset"] & ~0xFFF @@ -446,7 +438,7 @@ def convert_elf_reloc_table( entry = PeRelocationEntry() entry.Offset = reloc["r_offset"] & 0xFFF # REL_BASED_HIGHLOW or REL_BASED_DIR64 - entry.Type = 3 if elf.elfclass == 32 else 10 + entry.Type = 3 if file.elfclass == 32 else 10 pe_reloc_blocks[block_rva].entries.append(entry) continue @@ -455,21 +447,21 @@ def convert_elf_reloc_table( def convert_elf_relocations( - elf: ELFFile, + file: elffile.ELFFile, opt: PeOptionalHeader, sections: typing.List[PeSection], minimum_sections: int, ) -> typing.Optional[PeSection]: - dynamic = elf.get_section_by_name(".dynamic") + dynamic = file.get_section_by_name(".dynamic") if dynamic is None: raise BadSectionError("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"]: + if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]: raise ValueError("ELF file is not a PIE") # This checks that the ELF image base is 0. - symtab = elf.get_section_by_name(".symtab") + symtab = file.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: @@ -492,7 +484,7 @@ def convert_elf_relocations( segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT) - opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset + opt.AddressOfEntryPoint = file["e_entry"] + segment_offset opt.BaseOfCode += segment_offset if isinstance(opt, PeOptionalHeader32): opt.BaseOfData += segment_offset @@ -501,7 +493,7 @@ def convert_elf_relocations( for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): if reloc_type not in ["REL", "RELA"]: raise BadSectionError(f"Unsupported relocation type {reloc_type}") - convert_elf_reloc_table(elf, + convert_elf_reloc_table(file, reloc_table, opt.ImageBase + segment_offset, sections, @@ -578,37 +570,37 @@ def write_pe( def elf2efi(args: argparse.Namespace): - elf = ELFFile(args.ELF) - if not elf.little_endian: + file = elffile.ELFFile(args.ELF) + if not file.little_endian: raise ValueError("ELF file is not little-endian") - if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]: - raise ValueError(f"Unsupported ELF type {elf['e_type']}") + if file["e_type"] not in ["ET_DYN", "ET_EXEC"]: + raise ValueError(f"Unsupported ELF type {file['e_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_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264, + "EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064, "EM_X86_64": 0x8664, - }.get(elf["e_machine"]) + }.get(file["e_machine"]) if pe_arch is None: - raise ValueError(f"Unsupported ELF architecture {elf['e_machine']}") + raise ValueError(f"Unsupported ELF architecture {file['e_machine']}") coff = PeCoffHeader() - opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus() + opt = PeOptionalHeader32() if file.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: + if file.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) + sections = convert_sections(file, opt) + copy_sections(file, opt, args.copy_sections, sections) + pe_reloc_s = convert_elf_relocations(file, opt, sections, args.minimum_sections) coff.Machine = pe_arch coff.NumberOfSections = len(sections) @@ -616,7 +608,7 @@ def elf2efi(args: argparse.Namespace): 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 + coff.Characteristics = 0x30E if file.elfclass == 32 else 0x22E opt.SectionAlignment = SECTION_ALIGNMENT opt.FileAlignment = FILE_ALIGNMENT @@ -625,11 +617,11 @@ def elf2efi(args: argparse.Namespace): opt.MajorSubsystemVersion = args.efi_major opt.MinorSubsystemVersion = args.efi_minor opt.Subsystem = args.subsystem - opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B + opt.Magic = 0x10B if file.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 + opt.DllCharacteristics = 0x160 if file.elfclass == 64 else 0x140 # These values are taken from a natively built PE binary (although, unused by EDK2/EFI). opt.SizeOfStackReserve = 0x100000 From 3c7bb0c37b4110bd12872a08e9c2decdf7af80f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Feb 2026 14:17:29 +0100 Subject: [PATCH 3/4] elf2efi: make mypy-clean --- tools/elf2efi.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/elf2efi.py b/tools/elf2efi.py index 12bcdcd53b..27ac5375f9 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -20,6 +20,7 @@ # ELF dynamic relocations at runtime. # pylint: disable=attribute-defined-outside-init +# mypy: untyped-calls-exclude=elftools import argparse import hashlib @@ -182,7 +183,7 @@ class PeSection(LittleEndianStructure): ("Characteristics", c_uint32), ) - def __init__(self): + def __init__(self) -> None: super().__init__() self.data = bytearray() @@ -353,7 +354,7 @@ def copy_sections( opt: PeOptionalHeader, input_names: str, sections: typing.List[PeSection], -): +) -> None: for name in input_names.split(","): elf_s = file.get_section_by_name(name) if not elf_s: @@ -379,7 +380,7 @@ def apply_elf_relative_relocation( image_base: int, sections: typing.List[PeSection], addend_size: int, -): +) -> None: [target] = [pe_s for pe_s in sections if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] @@ -401,7 +402,7 @@ def convert_elf_reloc_table( elf_image_base: int, sections: typing.List[PeSection], pe_reloc_blocks: typing.Dict[int, PeRelocationBlock], -): +) -> None: NONE_RELOC = { "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], @@ -537,11 +538,11 @@ def convert_elf_relocations( def write_pe( - file, + file: typing.IO[bytes], coff: PeCoffHeader, opt: PeOptionalHeader, sections: typing.List[PeSection], -): +) -> None: file.write(b"MZ") file.seek(0x3C, io.SEEK_SET) file.write(PE_OFFSET.to_bytes(2, byteorder="little")) @@ -569,7 +570,7 @@ def write_pe( file.truncate(offset) -def elf2efi(args: argparse.Namespace): +def elf2efi(args: argparse.Namespace) -> None: file = elffile.ELFFile(args.ELF) if not file.little_endian: raise ValueError("ELF file is not little-endian") @@ -695,7 +696,7 @@ def create_parser() -> argparse.ArgumentParser: return parser -def main(): +def main() -> None: parser = create_parser() elf2efi(parser.parse_args()) From a6b328fd9572b67f794a92268cd0a2dcca6dc4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 19 Feb 2026 14:22:18 +0100 Subject: [PATCH 4/4] elf2efi: modernize typing annotations We still need Union and Optional as long as compat with Python 3.9 is needed. --- tools/elf2efi.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/elf2efi.py b/tools/elf2efi.py index 27ac5375f9..ed705beb31 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -71,7 +71,7 @@ class PeRelocationBlock(LittleEndianStructure): def __init__(self, PageRVA: int): super().__init__(PageRVA) - self.entries: typing.List[PeRelocationEntry] = [] + self.entries: list[PeRelocationEntry] = [] class PeRelocationEntry(LittleEndianStructure): @@ -238,7 +238,7 @@ def align_down(x: int, align: int) -> int: return x & ~(align - 1) -def next_section_address(sections: typing.List[PeSection]) -> int: +def next_section_address(sections: list[PeSection]) -> int: return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT) @@ -308,7 +308,7 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: def convert_sections( file: elffile.ELFFile, opt: PeOptionalHeader, -) -> typing.List[PeSection]: +) -> list[PeSection]: last_vma = (0, 0) sections = [] @@ -353,7 +353,7 @@ def copy_sections( file: elffile.ELFFile, opt: PeOptionalHeader, input_names: str, - sections: typing.List[PeSection], + sections: list[PeSection], ) -> None: for name in input_names.split(","): elf_s = file.get_section_by_name(name) @@ -378,7 +378,7 @@ def copy_sections( def apply_elf_relative_relocation( reloc: elf.relocation.Relocation, image_base: int, - sections: typing.List[PeSection], + sections: list[PeSection], addend_size: int, ) -> None: [target] = [pe_s for pe_s in sections @@ -400,8 +400,8 @@ def convert_elf_reloc_table( file: elffile.ELFFile, elf_reloc_table: elf.relocation.RelocationTable, elf_image_base: int, - sections: typing.List[PeSection], - pe_reloc_blocks: typing.Dict[int, PeRelocationBlock], + sections: list[PeSection], + pe_reloc_blocks: dict[int, PeRelocationBlock], ) -> None: NONE_RELOC = { "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], @@ -450,7 +450,7 @@ def convert_elf_reloc_table( def convert_elf_relocations( file: elffile.ELFFile, opt: PeOptionalHeader, - sections: typing.List[PeSection], + sections: list[PeSection], minimum_sections: int, ) -> typing.Optional[PeSection]: dynamic = file.get_section_by_name(".dynamic") @@ -490,7 +490,7 @@ def convert_elf_relocations( if isinstance(opt, PeOptionalHeader32): opt.BaseOfData += segment_offset - pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {} + pe_reloc_blocks: dict[int, PeRelocationBlock] = {} for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): if reloc_type not in ["REL", "RELA"]: raise BadSectionError(f"Unsupported relocation type {reloc_type}") @@ -541,7 +541,7 @@ def write_pe( file: typing.IO[bytes], coff: PeCoffHeader, opt: PeOptionalHeader, - sections: typing.List[PeSection], + sections: list[PeSection], ) -> None: file.write(b"MZ") file.seek(0x3C, io.SEEK_SET)