#!/usr/bin/env python3 """ pe_reloc_gen.py — Patch PE .reloc from ELF .rela.dyn Usage: pe_reloc_gen.py On aarch64, ld -shared generates R_AARCH64_RELATIVE entries in .rela.dyn. GNU ld cannot produce PE output natively for aarch64, so we link to ELF then objcopy to PE. objcopy does not convert .rela.dyn to PE base relocations, so the resulting .efi has an empty .reloc section and UEFI cannot apply load-time fixups for function-pointer tables. This script reads the .rela.dyn RELATIVE entries from the ELF, groups them by 4 KB page, and writes IMAGE_REL_BASED_DIR64 (type 10) blocks into the .reloc section of the PE. The .reloc section must be pre-allocated large enough to hold the generated data (see linker script). """ import struct, sys SHT_RELA = 4 R_AARCH64_RELATIVE = 0x403 # 1027 IMAGE_REL_BASED_DIR64 = 10 def read_elf_relative(elf_data): """Return sorted list of VAs that need RELATIVE fixup.""" if elf_data[0:4] != b'\x7fELF': raise ValueError("Not an ELF file") endian = '<' if elf_data[5] == 1 else '>' if elf_data[4] != 2: raise ValueError("Only ELF64 supported") (e_shoff,) = struct.unpack_from(endian + 'Q', elf_data, 40) (e_shentsize,) = struct.unpack_from(endian + 'H', elf_data, 58) (e_shnum,) = struct.unpack_from(endian + 'H', elf_data, 60) (e_shstrndx,) = struct.unpack_from(endian + 'H', elf_data, 62) shstr_sh = e_shoff + e_shstrndx * e_shentsize (shstr_off,) = struct.unpack_from(endian + 'Q', elf_data, shstr_sh + 24) def sh_name(idx): (n,) = struct.unpack_from(endian + 'I', elf_data, e_shoff + idx * e_shentsize) return elf_data[shstr_off + n:].split(b'\x00')[0].decode() offsets = [] for i in range(e_shnum): sh = e_shoff + i * e_shentsize (stype,) = struct.unpack_from(endian + 'I', elf_data, sh + 4) (soffset,) = struct.unpack_from(endian + 'Q', elf_data, sh + 24) (ssize,) = struct.unpack_from(endian + 'Q', elf_data, sh + 32) (sentsize,)= struct.unpack_from(endian + 'Q', elf_data, sh + 56) if stype == SHT_RELA and sentsize == 24: name = sh_name(i) if '.rela' not in name: continue for j in range(ssize // 24): entry = soffset + j * 24 (r_off,) = struct.unpack_from(endian + 'Q', elf_data, entry) (r_info,) = struct.unpack_from(endian + 'Q', elf_data, entry + 8) r_type = r_info & 0xFFFFFFFF if r_type == R_AARCH64_RELATIVE: offsets.append(r_off) return sorted(set(offsets)) def build_pe_reloc(offsets): """Generate PE .reloc section bytes from list of RVAs.""" if not offsets: return bytes(10) # minimal empty block out = b'' i = 0 while i < len(offsets): page = offsets[i] & ~0xFFF entries = [] while i < len(offsets) and (offsets[i] & ~0xFFF) == page: entries.append((IMAGE_REL_BASED_DIR64 << 12) | (offsets[i] & 0xFFF)) i += 1 if len(entries) % 2: entries.append(0) # padding entry size = 8 + 2 * len(entries) out += struct.pack(' rawsize: print(f"ERROR: .reloc section too small ({rawsize} bytes); need {new_len}", file=sys.stderr) sys.exit(1) data[rawoff:rawoff + rawsize] = b'\x00' * rawsize data[rawoff:rawoff + new_len] = reloc_data struct.pack_into(' ", file=sys.stderr) sys.exit(1) elf_path, efi_in, efi_out = sys.argv[1], sys.argv[2], sys.argv[3] with open(elf_path, 'rb') as f: elf_data = f.read() with open(efi_in, 'rb') as f: pe_data = f.read() offsets = read_elf_relative(elf_data) print(f" {len(offsets)} RELATIVE relocations", flush=True) reloc_bytes = build_pe_reloc(offsets) print(f" .reloc section: {len(reloc_bytes)} bytes", flush=True) patched = patch_pe(pe_data, reloc_bytes) with open(efi_out, 'wb') as f: f.write(patched) print(f" Written: {efi_out}", flush=True)