Files
sgi-indy/tools/make-elf-wrapper.py
T
opencode.energia.arti.ee 1dc99443b7 Map SGI Indy IP24 PROM 5.3 Rev B10
Fetch and verify the four MAME indy_4610 PROM dumps, wrap the newest
dump in an ELF32 BE MIPS image for Ghidra/REA, and record the resulting
structural map: image layout, exception vector table, boot flow,
function anchors and firmware feature clusters.
2026-11-02 11:04:39 +00:00

116 lines
2.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Wrap a raw SGI Indy IP24 PROM dump in a minimal ELF32 big-endian MIPS image.
REA's Ghidra MIPS lane accepts ELF32 ET_EXEC images, but not headerless MIPS
binaries. This tool produces a load-image-only wrapper so the PROM can be
opened at its real physical address (0x1FC00000) without modifying the ROM
bytes.
The wrapper deliberately declares EF_MIPS_ARCH_32 with an unspecified ABI and
provides no PT_MIPS_ABIFLAGS record. That selects REA's "recordless legacy"
admission, which pins Ghidra to MIPS:BE:32:default, a suitable match for the
R4000/R4600-era Indy PROM.
"""
from __future__ import annotations
import argparse
import os
import struct
import sys
ROM_BASE = 0x1FC00000
PAGE_SIZE = 0x1000
EM_MIPS = 8
ET_EXEC = 2
EF_MIPS_ARCH_32 = 0x50000000
EI_NIDENT = 16
def build_elf(rom: bytes, base: int, entry: int) -> bytes:
payload_offset = PAGE_SIZE
e_ident = bytearray(EI_NIDENT)
e_ident[0:4] = b"\x7fELF"
e_ident[4] = 1 # ELFCLASS32
e_ident[5] = 2 # ELFDATA2MSB
e_ident[6] = 1 # EV_CURRENT
e_ident[7] = 0 # ELFOSABI_NONE
e_entry = entry
e_phoff = 52
e_shoff = 0
e_flags = EF_MIPS_ARCH_32
e_ehsize = 52
e_phentsize = 32
e_phnum = 1
e_shentsize = 0
e_shnum = 0
e_shstrndx = 0
ehdr = struct.pack(
">16sHHIIIIIHHHHHH",
bytes(e_ident),
ET_EXEC,
EM_MIPS,
1,
e_entry,
e_phoff,
e_shoff,
e_flags,
e_ehsize,
e_phentsize,
e_phnum,
e_shentsize,
e_shnum,
e_shstrndx,
)
# PT_LOAD: file offset 0x1000 -> virtual address base, whole ROM.
phdr = struct.pack(
">IIIIIIII",
1, # PT_LOAD
payload_offset,
base,
base,
len(rom),
len(rom),
5, # PF_R | PF_X
PAGE_SIZE,
)
header = ehdr + phdr
assert len(header) <= payload_offset
return header.ljust(payload_offset, b"\x00") + rom
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("rom", help="raw PROM binary (512 KiB)")
parser.add_argument("-o", "--output", help="output ELF path")
parser.add_argument("--base", type=lambda s: int(s, 0), default=ROM_BASE)
parser.add_argument("--entry", type=lambda s: int(s, 0), default=None)
args = parser.parse_args()
with open(args.rom, "rb") as fh:
rom = fh.read()
if len(rom) != 0x80000:
print(
f"warning: {args.rom} is {len(rom)} bytes, expected 524288", file=sys.stderr
)
entry = args.base if args.entry is None else args.entry
out = args.output
if out is None:
stem, _ = os.path.splitext(args.rom)
out = stem + ".elf"
with open(out, "wb") as fh:
fh.write(build_elf(rom, args.base, entry))
print(out)
return 0
if __name__ == "__main__":
raise SystemExit(main())