--------- Co-authored-by: Mc-muffin <Mc-muffin@users.noreply.github.com> Co-authored-by: weirdbeardgame <weirdbeardgame@users.noreply.github.com>
389 lines
12 KiB
Python
389 lines
12 KiB
Python
# /// script
|
|
# requires-python = ">=3.12"
|
|
# dependencies = [
|
|
# "pyelftools>=0.32",
|
|
# ]
|
|
# ///
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import struct
|
|
import sys
|
|
from elftools.elf.elffile import ELFFile
|
|
from elftools.elf.relocation import RelocationSection
|
|
from elftools.elf.sections import Section, Symbol, SymbolTableSection
|
|
from elftools.elf.constants import E_FLAGS, E_FLAGS_MASKS
|
|
from elftools.elf.enums import ENUM_RELOC_TYPE_MIPS
|
|
from elftools.elf.descriptions import _DESCR_RELOC_TYPE_MIPS
|
|
from enum import IntEnum
|
|
|
|
# Elftools enums are just dicts...
|
|
class MipsReloc(IntEnum):
|
|
R_MIPS_NONE = 0
|
|
R_MIPS_32 = 2
|
|
R_MIPS_26 = 4
|
|
R_MIPS_HI16 = 5
|
|
R_MIPS_LO16 = 6
|
|
R_MIPS_GPREL16 = 7
|
|
R_MIPS_LITERAL = 8
|
|
R_MIPS15_S3 = 119
|
|
R_MIPS_DVP_11_PCREL = 120
|
|
R_MIPS_DVP_27_S4 = 121
|
|
R_MIPS_DVP_11_S4 = 122
|
|
R_MIPS_DVP_U15_S3 = 123
|
|
|
|
_U32 = struct.Struct("<I")
|
|
_S16 = struct.Struct("<h")
|
|
_U16 = struct.Struct("<H")
|
|
|
|
def read_generic(f, _s: struct.Struct, pos: int = -1) -> int:
|
|
if pos != -1:
|
|
old = f.tell()
|
|
f.seek(pos)
|
|
value = _s.unpack(f.read(_s.size))[0]
|
|
if pos != -1:
|
|
f.seek(old)
|
|
return value
|
|
|
|
def read_u32(f, pos: int = -1) -> int:
|
|
return read_generic(f, _U32, pos)
|
|
|
|
def read_u16(f, pos: int = -1) -> int:
|
|
return read_generic(f, _U16, pos)
|
|
|
|
def read_s16(f, pos: int = -1) -> int:
|
|
return read_generic(f, _S16, pos)
|
|
|
|
@dataclass
|
|
class RelInfo:
|
|
rom_off: int
|
|
addr: int
|
|
type: MipsReloc
|
|
sym_name: str
|
|
sym_addr: int
|
|
addend: int = 0
|
|
comment_out: bool = False
|
|
|
|
|
|
EF_MIPS_MACH = 0x00FF0000
|
|
E_MIPS_MACH_R5900 = 0x00920000
|
|
|
|
ELF_PATH = Path(r"config/SLUS_210.07")
|
|
|
|
# VADDR for the start of the .lit4/LITERAL section
|
|
LIT4_SECTION_START = 0x003C7C80
|
|
|
|
def main():
|
|
_monkey_patch()
|
|
|
|
with ELFFile(ELF_PATH.open("rb")) as elf:
|
|
if not check_elf(elf):
|
|
sys.exit(-1)
|
|
|
|
make_rels_file(elf)
|
|
make_syms_file(elf)
|
|
|
|
|
|
def make_syms_file(elf: ELFFile) -> None:
|
|
nono_chars = [
|
|
"<",
|
|
">",
|
|
":",
|
|
"\"",
|
|
"/",
|
|
"\\",
|
|
"|",
|
|
"?",
|
|
"*"
|
|
]
|
|
|
|
known_ldsyms = {
|
|
"__bss_start",
|
|
"__bss_end",
|
|
"__bss_size",
|
|
"_fbss",
|
|
"__data_start",
|
|
"__data_end",
|
|
"_heap_size",
|
|
"_stack_size",
|
|
"_end",
|
|
"_gp",
|
|
# Should these be here? Technically they are used by mwInit
|
|
# "__exception_table_start__",
|
|
# "__exception_table_end__",
|
|
# "__static_init",
|
|
# "__static_init_end",
|
|
# "_overlay_group_addresses",
|
|
}
|
|
|
|
# There should only be one symtab anyway
|
|
symtab = get_symtab_section(elf)
|
|
|
|
if symtab is None:
|
|
return
|
|
|
|
with open("symbols.txt", "w+") as rf:
|
|
all_syms = list(symtab.iter_symbols())
|
|
|
|
seen_symbols = set()
|
|
seen_addr = set()
|
|
duped_symbols = set()
|
|
duped_addr = set()
|
|
|
|
for sym in all_syms:
|
|
name = sym.name
|
|
addr = sym.entry["st_value"]
|
|
if name in seen_symbols:
|
|
duped_symbols.add(name)
|
|
elif addr in seen_addr:
|
|
duped_addr.add(addr)
|
|
else:
|
|
seen_symbols.add(name)
|
|
seen_addr.add(addr)
|
|
|
|
for sym in all_syms:
|
|
addr = sym.entry["st_value"]
|
|
size = sym.entry["st_size"]
|
|
# vis seems to always be STV_DEFAULT
|
|
vis = sym.entry["st_other"]["visibility"]
|
|
# local seems to always be 0
|
|
local = sym.entry["st_other"]["local"]
|
|
bind = sym.entry["st_info"]["bind"]
|
|
type = sym.entry["st_info"]["type"]
|
|
|
|
# assert (vis == "STV_DEFAULT" and local == 0)
|
|
|
|
# Skip common autogenerated symbols
|
|
if sym.name in ("__gnu_compiled_c", "gcc2_compiled."):
|
|
continue
|
|
|
|
# Anything below 0x100000 shouldn't exist
|
|
if addr < 0x100000:
|
|
continue
|
|
|
|
# Skip section symbols
|
|
if type == "STT_SECTION":
|
|
continue
|
|
|
|
comment = ""
|
|
|
|
# Skip local ASM labels, VU microcode labels and known linker symbols
|
|
if (sym.name.startswith("$")
|
|
or sym.name.startswith("_$")
|
|
or sym.name.startswith(".vif")
|
|
or sym.name.startswith(".dma")
|
|
or sym.name.startswith(".gif")
|
|
or sym.name.startswith(".vu")
|
|
or sym.name in known_ldsyms
|
|
):
|
|
remove = "// "
|
|
else:
|
|
remove = ""
|
|
|
|
if size != 0:
|
|
comment += f" size:0x{size:X}"
|
|
|
|
match (bind):
|
|
case "STB_GLOBAL":
|
|
# The default
|
|
comment += " visibility:global"
|
|
pass
|
|
case "STB_LOCAL":
|
|
comment += " visibility:local"
|
|
case "STB_WEAK":
|
|
comment += " visibility:weak"
|
|
case "STB_LOPROC":
|
|
# Custom MWCC thing, it's called "MULTIDEF"
|
|
# appears exactly once in Code Veronica
|
|
# due to `inline TYPE function();` declaration
|
|
# maps roughly to `weak`, but it's distinct
|
|
comment += " visibility:weak"
|
|
case _:
|
|
raise ValueError(f"Unknown symbol binding {bind} ({sym.name})")
|
|
|
|
if sym.name in duped_symbols or addr in duped_addr:
|
|
comment += " allow_duplicated:True"
|
|
if len(sym.name) > 128:
|
|
comment += f" filename:long_symbol_{addr:08X}"
|
|
elif any(char in sym.name for char in nono_chars):
|
|
comment += f" filename:bad_symbol_{addr:08X}"
|
|
|
|
rf.write(f"{remove}{sym.name:<50} = 0x{addr:08X}; //{comment}\n")
|
|
|
|
|
|
def make_rels_file(elf: ELFFile) -> None:
|
|
|
|
all_relocs = parse_rels(elf)
|
|
|
|
with open("relocs.txt", "w+") as rf:
|
|
for reloc in all_relocs:
|
|
addend = reloc.addend
|
|
rom_off = reloc.rom_off
|
|
|
|
if addend == 0:
|
|
addend_str = ""
|
|
else:
|
|
sign = "+" if addend > 0 else "-"
|
|
addend_str = f"addend:{sign}0x{abs(addend):X}"
|
|
|
|
if reloc.comment_out:
|
|
cmm = "// "
|
|
else:
|
|
cmm = ""
|
|
|
|
rf.write(f"{cmm}rom:0x{rom_off:06X} reloc:{reloc.type.name[2:]:<19} {addend_str:<15} symbol:{reloc.sym_name}\n")
|
|
|
|
def get_reloc_section(elf: ELFFile) -> tuple[RelocationSection | None, SymbolTableSection | None]:
|
|
relmain: RelocationSection | None = elf.get_section_by_name(".relmain")
|
|
if relmain is None:
|
|
print("No .relmain section!")
|
|
return None, None
|
|
|
|
if relmain['sh_link'] == 0:
|
|
print("Reloc section has no associated symbol table!")
|
|
sys.exit(-1)
|
|
|
|
symtable: SymbolTableSection = elf.get_section(relmain['sh_link'])
|
|
|
|
return relmain, symtable
|
|
|
|
def get_symtab_section(elf: ELFFile) -> tuple[SymbolTableSection | None]:
|
|
symtable: SymbolTableSection | None = elf.get_section_by_name(".symtab")
|
|
if symtable is None:
|
|
print("No .symtab section!")
|
|
|
|
return symtable
|
|
|
|
def parse_rels(elf: ELFFile) -> list[RelInfo]:
|
|
# Factor this out?
|
|
main_section: Section | None = elf.get_section_by_name("main")
|
|
if main_section is None:
|
|
print("No main section!")
|
|
sys.exit(-1)
|
|
|
|
vaddr_diff = main_section.header["sh_addr"] - main_section.header["sh_offset"]
|
|
|
|
relmain, symtab = get_reloc_section(elf)
|
|
|
|
if relmain is None or symtab is None:
|
|
return list()
|
|
|
|
gp_value = _get_gp_value(elf)
|
|
|
|
pending_hi: list[RelInfo] = []
|
|
seen_hi: dict[str, int] = {}
|
|
all_relocs: list[RelInfo] = []
|
|
for r in relmain.iter_relocations():
|
|
s: Symbol = symtab.get_symbol(r['r_info_sym'])
|
|
reloc = RelInfo(
|
|
r["r_offset"] - vaddr_diff,
|
|
0,
|
|
MipsReloc(r["r_info_type"]),
|
|
s.name,
|
|
s.entry["st_value"]
|
|
)
|
|
|
|
instr = read_u32(elf.stream, reloc.rom_off)
|
|
simm = read_s16(elf.stream, reloc.rom_off)
|
|
uimm = instr & 0xFFFF
|
|
|
|
match reloc.type:
|
|
case MipsReloc.R_MIPS_NONE:
|
|
# NONE-thing to do
|
|
pass
|
|
case MipsReloc.R_MIPS_32:
|
|
reloc.addr = instr
|
|
reloc.addend = instr - reloc.sym_addr
|
|
case MipsReloc.R_MIPS_26:
|
|
reloc.addr = (instr & 0x03FFFFFF) * 4
|
|
reloc.addend = reloc.addr - reloc.sym_addr
|
|
case MipsReloc.R_MIPS_HI16:
|
|
reloc.addr = uimm << 0x10
|
|
pending_hi.append(reloc)
|
|
case MipsReloc.R_MIPS_LO16:
|
|
# This logic roughly matches GNU AS (_bfd_mips_elf_hi16_reloc)
|
|
# and produces corrected addends for all the "orphaned" LO16s
|
|
# in Code Veronica save for one ambiguous entry at ROM:14AA8
|
|
# so I guess it's good enough.
|
|
for hi in pending_hi:
|
|
hi.addr += simm
|
|
hi.addend = hi.addr - hi.sym_addr
|
|
if simm < 0:
|
|
seen_hi[hi.sym_name] = (hi.addr + 0x10000) & 0xFFFF0000
|
|
else:
|
|
seen_hi[hi.sym_name] = hi.addr & 0xFFFF0000
|
|
|
|
reloc.addr = seen_hi[reloc.sym_name] + simm
|
|
reloc.addend = reloc.addr - reloc.sym_addr
|
|
pending_hi.clear()
|
|
case MipsReloc.R_MIPS_GPREL16:
|
|
reloc.addr = gp_value + simm
|
|
reloc.addend = reloc.addr - reloc.sym_addr
|
|
case MipsReloc.R_MIPS_LITERAL:
|
|
# Literals are also not supported, as GNU AS
|
|
# doesn't have a reloc operator for them...
|
|
# We can lie and say it's a gprel reloc instead but meh
|
|
reloc.addend = gp_value + simm - LIT4_SECTION_START
|
|
# reloc.type = MipsReloc.R_MIPS_GPREL16
|
|
# reloc.sym_name = f"D_{gp_value + simm:08X}"
|
|
reloc.comment_out = True
|
|
case MipsReloc.R_MIPS15_S3:
|
|
# Needs upstream support...
|
|
reloc.comment_out = True
|
|
case (MipsReloc.R_MIPS_DVP_U15_S3 | MipsReloc.R_MIPS_DVP_27_S4):
|
|
# Nothing to do, it's only for VU1 microcode
|
|
reloc.comment_out = True
|
|
case _:
|
|
raise ValueError(f"Unimplemented reloc {reloc.type}")
|
|
|
|
all_relocs.append(reloc)
|
|
return all_relocs
|
|
|
|
def _get_gp_value(elf: ELFFile):
|
|
reginfo = elf.get_section_by_name(".reginfo")
|
|
|
|
# It better not use GP
|
|
if reginfo is None:
|
|
return 0
|
|
|
|
addr = reginfo.header["sh_offset"]
|
|
return read_u32(elf.stream, addr + 0x14)
|
|
|
|
def check_elf(elf: ELFFile) -> bool:
|
|
flags = elf.header["e_flags"]
|
|
type = elf.header["e_type"]
|
|
|
|
if type != "ET_EXEC":
|
|
print("Not a static file!")
|
|
return False
|
|
|
|
|
|
if ((flags & E_FLAGS_MASKS.EFM_MIPS_ABI) == E_FLAGS_MASKS.EFM_MIPS_ABI_EABI64
|
|
and (flags & E_FLAGS.EF_MIPS_ARCH) != E_FLAGS.EF_MIPS_ARCH_3
|
|
and(flags & EF_MIPS_MACH) != E_MIPS_MACH_R5900):
|
|
print("Not an R5900 elf file!")
|
|
return False
|
|
|
|
if elf.header["e_shnum"] == 0:
|
|
print("No section headers!")
|
|
return False
|
|
|
|
return True
|
|
|
|
def _monkey_patch():
|
|
# Add DVP reloc types, though splat doesn't support them as of yet
|
|
# TODO: ask for R_MIPS15_S3 support at some point, the others
|
|
# are only for the VU microcode, which is out of scope
|
|
ENUM_RELOC_TYPE_MIPS["R_MIPS15_S3"] = 119
|
|
ENUM_RELOC_TYPE_MIPS["R_MIPS_DVP_11_PCREL"] = 120
|
|
ENUM_RELOC_TYPE_MIPS["R_MIPS_DVP_27_S4"] = 121
|
|
ENUM_RELOC_TYPE_MIPS["R_MIPS_DVP_11_S4"] = 122
|
|
ENUM_RELOC_TYPE_MIPS["R_MIPS_DVP_U15_S3"] = 123
|
|
_DESCR_RELOC_TYPE_MIPS[119] = "R_MIPS15_S3"
|
|
_DESCR_RELOC_TYPE_MIPS[120] = "R_MIPS_DVP_11_PCREL"
|
|
_DESCR_RELOC_TYPE_MIPS[121] = "R_MIPS_DVP_27_S4"
|
|
_DESCR_RELOC_TYPE_MIPS[122] = "R_MIPS_DVP_11_S4"
|
|
_DESCR_RELOC_TYPE_MIPS[123] = "R_MIPS_DVP_U15_S3"
|
|
|
|
if __name__ == "__main__":
|
|
main()
|