mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
b8e7250c78
Builds the guard SESSION-17 left as a TODO after the func_801463A0 `_s`-alias trap, where a draft invented extern aliases no symbol table defines, read MATCH under rtu_match, and could never bank. - diffs the symbols a draft's object references (reloc records) against the target .s's %hi/%lo/jal set; reports MISSING (invented-alias signature) and INVENTED separately. - fills a real hole: match_one/masked_diff compare relocation-MASKED words (object-vs-.s is symbol-agnostic BY CONSTRUCTION) and rtu_match COMPILES WITHOUT LINKING -- so both are structurally blind to this class. R34: a second oracle that can disagree with the first. - NEGATIVE-CONTROL PROVEN: with one data extern renamed to an invented alias, match_one reports the SAME 14 mismatched as the correct draft; symcheck exits 1 naming both symbols. - --c compiles via match_one so the pinned triple/flags can never drift (R33); or --obj. - applied to the live func_8014D820 close=14 draft: 12/12 symbols agree, so a match there will link cleanly -- the §65c class is ruled out for it in advance. - cookbook §67a + SETUP tooling-inventory row (R21). Necessary condition, NOT a match oracle: still finish on the whole-binary byte-gate (G3/P9).
120 lines
5.3 KiB
Python
120 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""symcheck.py — the pre-gate SYMBOL-SET guard (BFM Phase 29 SESSION-18).
|
|
|
|
Diff the set of symbols a draft's object REFERENCES against the set the target `.s` references
|
|
(`%hi`/`%lo`/`jal`). A mismatch is a link-level defect that every masked-diff oracle we own is
|
|
STRUCTURALLY BLIND TO:
|
|
|
|
* `tools/match_one.py` / `tools/masked_diff.py` compare relocation-MASKED words, so a reloc against
|
|
the wrong symbol scores as a match at that position (object-vs-.s mode is symbol-agnostic by
|
|
construction — see masked_diff's docstring).
|
|
* `tools/rtu_match.py` COMPILES but never LINKS, so an extern that no symbol table can resolve is
|
|
invisible to it *by construction*, not merely masked.
|
|
|
|
That combination produced the SESSION-17 `func_801463A0` trap: the draft invented `_s`-suffixed alias
|
|
externs (`D_80126BE0_s`) that no symbol table defines, read MATCH under rtu_match, and could never
|
|
bank. The whole-binary byte-gate caught it (G3/P9, as always) — but only after a full gate cycle, and
|
|
the *cause* took a symbol-set comparison to find. This makes that comparison a one-command check you
|
|
run BEFORE paying for a gate.
|
|
|
|
Two failure directions, both reported:
|
|
MISSING — in the target, absent from the draft: a dropped reference, or a symbol renamed/aliased
|
|
into something that resolves elsewhere. This is the invented-alias signature.
|
|
INVENTED — in the draft, absent from the target: a symbol the original function never touched.
|
|
|
|
Exit 0 iff the sets are equal. Not a match oracle and not a substitute for the byte-gate — a cheap
|
|
necessary condition (R34: a second oracle that can DISAGREE with the masked ones).
|
|
|
|
python3 tools/symcheck.py func_8014D820 --c .run/giants/s18_func_8014D820_close14.c \
|
|
--asm-subdir asm/ov_SC01_077/nonmatchings/ov_SC01_077_after
|
|
python3 tools/symcheck.py func_8014D820 --obj build/.../foo.o --asm-subdir <dir>
|
|
"""
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
OBJDUMP = "mipsel-linux-gnu-objdump"
|
|
PY = ".venv/bin/python"
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# `%hi(sym)` / `%lo(sym)` / `jal sym` in a splat .s. The target .s is RESOLVED (no reloc records), so
|
|
# the symbol names only survive in this operand syntax — which is exactly why the masked oracles,
|
|
# which work on the encoded word, cannot see them.
|
|
_S_SYM = re.compile(r"%(?:hi|lo)\(([A-Za-z_][A-Za-z0-9_]*)\)|\bjal\s+([A-Za-z_][A-Za-z0-9_]*)")
|
|
_O_SYM = re.compile(r"R_MIPS_\S+\s+([A-Za-z_][A-Za-z0-9_]*)")
|
|
|
|
|
|
def target_syms(s_path):
|
|
out = set()
|
|
with open(s_path) as fh:
|
|
for line in fh:
|
|
for a, b in _S_SYM.findall(line):
|
|
out.add(a or b)
|
|
return out
|
|
|
|
|
|
def object_syms(obj, fn=None):
|
|
"""Symbols referenced by relocations in `obj`. With `fn`, only relocs inside that function's
|
|
section slice — objects here are one-function-per-file, so the whole-object set is the same."""
|
|
txt = subprocess.run([OBJDUMP, "-drz", "-r", obj], capture_output=True, text=True).stdout
|
|
return set(m.group(1) for m in _O_SYM.finditer(txt))
|
|
|
|
|
|
def compile_draft(fn, c_path, asm_subdir, o0=False):
|
|
"""Compile via match_one so the triple/flags can never drift from the real gate path (R33:
|
|
derive, don't re-derive). Returns the object it left behind."""
|
|
work = os.path.join(".run/symcheck", f"{fn}.{os.getpid()}")
|
|
cmd = [PY, os.path.join(HERE, "match_one.py"), fn, "--c", c_path,
|
|
"--asm-subdir", asm_subdir, "--work", work]
|
|
if o0:
|
|
cmd.append("--o0")
|
|
subprocess.run(cmd, capture_output=True, text=True)
|
|
hits = glob.glob(os.path.join(work, "**", "*.o"), recursive=True)
|
|
if not hits:
|
|
sys.exit(f"symcheck: match_one produced no object for {fn} (does the draft compile?)")
|
|
return hits[0]
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("fn")
|
|
ap.add_argument("--c", help="draft C (externs + def); compiled via match_one")
|
|
ap.add_argument("--obj", help="pre-compiled object instead of --c")
|
|
ap.add_argument("--asm-subdir", required=True, help="dir holding <fn>.s")
|
|
ap.add_argument("--o0", action="store_true", help="compile at -O0 (the _o0 split subsegments)")
|
|
a = ap.parse_args()
|
|
|
|
if not (a.c or a.obj):
|
|
ap.error("one of --c or --obj is required")
|
|
|
|
s_path = os.path.join(a.asm_subdir, f"{a.fn}.s")
|
|
if not os.path.exists(s_path):
|
|
sys.exit(f"symcheck: no target .s at {s_path}")
|
|
|
|
obj = a.obj or compile_draft(a.fn, a.c, a.asm_subdir, a.o0)
|
|
tgt, mine = target_syms(s_path), object_syms(obj, a.fn)
|
|
|
|
missing = sorted(tgt - mine)
|
|
invented = sorted(mine - tgt)
|
|
|
|
if not missing and not invented:
|
|
print(f"SYMS-OK {a.fn} {len(tgt)} symbols agree")
|
|
return 0
|
|
|
|
print(f"SYMS-DIFF {a.fn} target={len(tgt)} draft={len(mine)}")
|
|
for s in missing:
|
|
print(f" MISSING {s} (target references it; draft does not -- dropped ref, or an "
|
|
f"invented alias standing in for it)")
|
|
for s in invented:
|
|
print(f" INVENTED {s} (draft references it; target does not -- if no symbol table "
|
|
f"defines it, rtu_match will still read MATCH and the gate will always reject)")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|