]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
elf2efi: import whole module, not individual symbols
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 19 Feb 2026 12:32:31 +0000 (13:32 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 19 Feb 2026 14:34:10 +0000 (15:34 +0100)
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

index b81423d06e7f0706399180bace6edbfecc757918..12bcdcd53b6d4722f11c4bddf2d0a27a103120f2 100755 (executable)
@@ -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