Files
LithosAnanke/scripts/gen_pe_reloc.py
Claude 3bd81097e1 chore(license): an SPDX header on every source file that had none (LicenseRef-Starship-1.0) [skip ci]
The header scan (Captain Bob, 2026-11-10): 788 source files -- 351 with the
SL-1.0 header, 12 with the old name (docs/working/experiments, kept by
ruling), 31 under someone else's licence (tools/kconfig, Linux's, GPL-2.0:
kept), and 394 with none. Ruled: the SPDX form; capsules (.4th, .f18) and
proofs (.thy) left for a ruling of their own (mkcapsule's 64x16 block format,
content-addressed capsules).

253 files now begin
    SPDX-License-Identifier: LicenseRef-Starship-1.0
    Copyright (c) 2023-2026 Robert A. James
as a /* */ block (C, headers, assembly, linker scripts) or # lines (scripts,
Makefiles, Kconfig; below a shebang or a coding line); tools/mkcapsule.c,
which had the copyright line, gets the SPDX line only.

Not touched: docs/working/ (archive and old campaign material, as the
ruling kept the experiments); v4/system/post_cases.c (generated by
v4/tools/mkpost.py, "do not edit" -- the generator has the header).

Comments only: the kernel builds on amd64, aarch64, riscv64; make -C v4
test-32: 39 programs, 0 failures; every changed script parses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-10 04:12:49 +00:00

139 lines
5.2 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: LicenseRef-Starship-1.0
# Copyright (c) 2023-2026 Robert A. James
"""
gen_pe_reloc.py — Prepare a RISC-V ELF shared library for PE/EFI conversion.
Usage: gen_pe_reloc.py <input.elf> <patched.elf> <reloc.bin>
Problem:
When the GNU linker builds a shared ELF with -shared at base 0, it leaves
R_RISCV_RELATIVE target locations ZEROED. The ELF dynamic linker fills them
at load time via: *target = base + r_addend
But a PE IMAGE_REL_BASED_DIR64 entry does: *target += delta
so if *target starts at 0 the result is just delta, not addend + delta.
Fix (done here):
1. Parse .rela.dyn to get all R_RISCV_RELATIVE (offset, addend) pairs.
2. Pre-fill each target location in the ELF with its addend value.
Now *target = addend, so PE relocation produces addend + delta. ✓
3. Write the patched ELF to <patched.elf>.
4. Build a PE IMAGE_BASE_RELOCATION table from the offsets and write
it to <reloc.bin>. This is later injected as the .reloc section.
"""
import struct
import sys
from collections import defaultdict
def parse_segments(elf_data):
"""Return list of (p_type, p_offset, p_vaddr, p_filesz) from PT_LOAD."""
e_phoff = struct.unpack_from('<Q', elf_data, 32)[0]
e_phentsize = struct.unpack_from('<H', elf_data, 54)[0]
e_phnum = struct.unpack_from('<H', elf_data, 56)[0]
segs = []
for i in range(e_phnum):
base = e_phoff + i * e_phentsize
p_type = struct.unpack_from('<I', elf_data, base)[0]
p_offset = struct.unpack_from('<Q', elf_data, base + 8)[0]
p_vaddr = struct.unpack_from('<Q', elf_data, base + 16)[0]
p_filesz = struct.unpack_from('<Q', elf_data, base + 32)[0]
if p_type == 1: # PT_LOAD
segs.append((p_offset, p_vaddr, p_filesz))
return segs
def vma_to_fileoff(segs, vma):
"""Convert an ELF VMA to a file offset using PT_LOAD segments."""
for p_offset, p_vaddr, p_filesz in segs:
if p_vaddr <= vma < p_vaddr + p_filesz:
return p_offset + (vma - p_vaddr)
return None
def parse_rela_dyn(elf_data):
"""Return list of (r_offset, r_addend) for R_RISCV_RELATIVE entries."""
e_shoff = struct.unpack_from('<Q', elf_data, 40)[0]
e_shentsize = struct.unpack_from('<H', elf_data, 58)[0]
e_shnum = struct.unpack_from('<H', elf_data, 60)[0]
e_shstrndx = struct.unpack_from('<H', elf_data, 62)[0]
shdr = []
for i in range(e_shnum):
base = e_shoff + i * e_shentsize
shdr.append((
struct.unpack_from('<I', elf_data, base)[0],
struct.unpack_from('<I', elf_data, base + 4)[0],
struct.unpack_from('<Q', elf_data, base + 24)[0],
struct.unpack_from('<Q', elf_data, base + 32)[0],
))
strtab_off = shdr[e_shstrndx][2]
def sec_name(n):
end = elf_data.index(b'\0', strtab_off + n)
return elf_data[strtab_off + n:end].decode('ascii')
pairs = []
for sh_name, sh_type, sh_off, sh_sz in shdr:
if sh_type == 4 and sec_name(sh_name) == '.rela.dyn':
for j in range(0, sh_sz, 24):
r_offset = struct.unpack_from('<Q', elf_data, sh_off + j)[0]
r_info = struct.unpack_from('<Q', elf_data, sh_off + j + 8)[0]
r_addend = struct.unpack_from('<q', elf_data, sh_off + j + 16)[0]
if (r_info & 0xFFFFFFFF) == 3: # R_RISCV_RELATIVE
pairs.append((r_offset, r_addend))
return pairs
def build_pe_reloc(offsets):
"""Build PE IMAGE_BASE_RELOCATION table from a list of RVA offsets."""
pages = defaultdict(list)
for off in sorted(offsets):
pages[off & ~0xFFF].append(off & 0xFFF)
out = b''
for page_rva in sorted(pages.keys()):
words = [(0xA << 12) | o for o in pages[page_rva]] # DIR64 = 0xA
if len(words) % 2:
words.append(0) # padding
entries = struct.pack(f'<{len(words)}H', *words)
out += struct.pack('<II', page_rva, 8 + len(entries)) + entries
return out
if __name__ == '__main__':
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <input.elf> <patched.elf> <reloc.bin>",
file=sys.stderr)
sys.exit(1)
elf_data = bytearray(open(sys.argv[1], 'rb').read())
assert elf_data[:4] == b'\x7fELF', "Not an ELF file"
assert elf_data[4] == 2 and elf_data[5] == 1, "Need 64-bit LE ELF"
segs = parse_segments(elf_data)
pairs = parse_rela_dyn(elf_data)
patched = 0
skipped = 0
for r_offset, r_addend in pairs:
file_off = vma_to_fileoff(segs, r_offset)
if file_off is None:
skipped += 1
continue
# Pre-fill the 8-byte slot with the addend so that
# PE DIR64 relocation (+=delta) produces addend+delta.
struct.pack_into('<q', elf_data, file_off, r_addend)
patched += 1
print(f"gen_pe_reloc: {len(pairs)} R_RISCV_RELATIVE entries, "
f"{patched} patched, {skipped} skipped (outside LOAD segments)",
file=sys.stderr)
reloc = build_pe_reloc([off for off, _ in pairs])
print(f"gen_pe_reloc: PE .reloc = {len(reloc)} bytes", file=sys.stderr)
open(sys.argv[2], 'wb').write(elf_data)
open(sys.argv[3], 'wb').write(reloc)