mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 23:24:32 -04:00
2483fc902a
ROOT CAUSE (byte-witnessed, P30 S38 — the fifth tool with this same blindness).
A function banked under the §37/§73 DEFINITION-SIDE ASM-LABEL ALIAS form is spelled with a private
C identifier and bound to its real symbol by a GNU asm label:
void aF8018A860(s32, s16 *, u8 *, u8 *) __asm__("func_80183AF8"); <- decl, stays in preamble
void aF8018A860(s32, s16 *, u8 *, u8 *) { ... } <- THIS emits func_80183AF8
overlay_src_split.addr_of() resolves `func_<hex>` arithmetically and everything else through `syms`.
`aF8018A860` matches NEITHER, so it returned None — and partition() keeps only items with a
resolved address, so the definition was dropped from EVERY region. The file was then rewritten
without it and nothing said so. One carve of ov_SC02_028 deleted the definitions emitting BOTH
func_80183AF8 and func_80184268; the overlay stopped linking with `undefined reference`, and six
wave-6 drafts were written off against that as a plumbing/compiler wall.
TWO FIXES:
- CAUSE: overlay_src_split now builds an asm-label alias map from the source and resolves a
definition through its EMITTED SYMBOL rather than its C name (verified: aF8018A860 -> 0x80183AF8,
aF8018AFD0 -> 0x80184268 — exactly the two symbols the link was missing).
- SILENCE: partition() and jr_isolate_all._partition() now REFUSE to rewrite a file when any
construct's address does not resolve (R32), instead of discarding it. That guard alone would
have surfaced this the first time it happened.
RESULT: 3 of the 6 alias-class wave-6 drafts bank immediately, for ZERO agent tokens —
func_801884D8 (137 ins) · func_80180B04 (251) · func_801380E0 (438). R22 clean-fleet 140/140.
The other 3 (the three LARGEST: 557/513/710 ins) have a second, size-correlated cause — open.
NOTE FOR THE FLYWHEEL: family_remap._alias_decl_for ALREADY handled this exact form, and its
docstring records the identical lesson ("that blindness was the WHOLE of the h_seq sweep's 137 'no
matched unit' skips. The tool, not the compiler (R35)"). The fix was never propagated. The alias
form needs ONE shared oracle, the way §134 comment-masking ended up on cdecl._mask — five tools
have now independently rediscovered it.
89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Reproduce the wave-6 ALIAS-CLASS link failure and INSPECT the post-carve tree, then revert.
|
|
|
|
harvest_verify does carve -> build -> revert, so the interesting state (where the asm-label alias
|
|
definition ended up relative to its callers) is destroyed before anyone can look at it. This does
|
|
the same carve, dumps the evidence, builds to capture the real linker error, and reverts.
|
|
|
|
READ THE EVIDENCE, DO NOT GUESS: the hypothesis under test is that `jr_isolate_all` repartitions the
|
|
TU and separates the call site of `func_<X>` from the DEFINITION that emits it — which in this
|
|
overlay is a §37/§124 definition-side asm-label alias:
|
|
void aF8018A860(...) __asm__("func_80183AF8"); /* decl binds the symbol */
|
|
void aF8018A860(...) { ... } /* THIS is what emits func_80183AF8 */
|
|
A tool that moves code by ADDRESS cannot see that the symbol emitted at 0x80183AF8 is spelled
|
|
`aF8018A860` in the source.
|
|
|
|
Usage: w6_alias_repro.py <ov> <fn> <callee_sym>
|
|
"""
|
|
import sys, os, re, glob, subprocess, shutil
|
|
|
|
OV, FN, CALLEE = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
PY = '.venv/bin/python'
|
|
sys.path.insert(0, 'tools')
|
|
import corpus
|
|
|
|
|
|
def sh(cmd, **kw):
|
|
return subprocess.run(cmd, capture_output=True, text=True, **kw)
|
|
|
|
|
|
def snapshot(tag):
|
|
print(f"\n----- {tag} -----")
|
|
files = sorted(glob.glob(f'src/{OV}/*.c'))
|
|
print(f" TUs in src/{OV}/: {len(files)}")
|
|
for f in files:
|
|
t = open(f).read()
|
|
defs = len(re.findall(r'__asm__\s*\(\s*"' + re.escape(CALLEE) + r'"\s*\)', t))
|
|
refs = len(re.findall(r'\b' + re.escape(CALLEE) + r'\b', t))
|
|
stub = len(re.findall(r'INCLUDE_ASM\([^)]*\b' + re.escape(CALLEE) + r'\b', t))
|
|
if defs or refs or stub:
|
|
print(f" {os.path.basename(f):<46} asm-label-binds={defs} textual-refs={refs} INCLUDE_ASM={stub}")
|
|
|
|
|
|
st = corpus.stubs(OV)
|
|
addr = int(FN.split('_')[1], 16)
|
|
rec = st.get(addr)
|
|
if not rec:
|
|
sys.exit(f"{FN} is not a live stub in {OV}")
|
|
tu, stub_line = rec.path, f'INCLUDE_ASM("{rec.asm_dir}", {rec.symbol});'
|
|
draft = open(f'.run/w6/{OV}/{FN}.c').read()
|
|
|
|
orig = open(tu).read()
|
|
assert orig.count(stub_line) == 1, "stub line not unique"
|
|
snapshot("BEFORE the carve (HEAD)")
|
|
|
|
print(f"\n[1] splicing the draft over the stub in {tu}")
|
|
open(tu, 'w').write(orig.replace(stub_line, draft, 1))
|
|
|
|
print(f"[2] jtbl_carve {OV} --func {FN}")
|
|
r = sh([PY, 'tools/jtbl_carve.py', OV, '--func', FN])
|
|
out = (r.stdout or '') + (r.stderr or '')
|
|
print(f" rc={r.returncode} {out.strip().splitlines()[-1][:130] if out.strip() else ''}")
|
|
|
|
if r.returncode:
|
|
print(f"[3] carve refused -> jr_isolate_all --only {FN} (the §61b ladder: isolate WITH the body spliced)")
|
|
r2 = sh([PY, 'tools/jr_isolate_all.py', OV, '--only', FN])
|
|
print(f" isolate rc={r2.returncode}")
|
|
r3 = sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
|
|
print(f" extract rc={r3.returncode}")
|
|
r = sh([PY, 'tools/jtbl_carve.py', OV, '--func', FN])
|
|
print(f" carve retry rc={r.returncode}")
|
|
sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
|
|
|
|
snapshot("AFTER the carve/isolation — WHERE DID THE ALIAS DEFINITION GO?")
|
|
|
|
print(f"\n[4] make build BINARY={OV} — the real linker error")
|
|
rb = sh(['make', 'build', f'BINARY={OV}'])
|
|
allout = (rb.stdout or '') + (rb.stderr or '')
|
|
hits = [l for l in allout.splitlines()
|
|
if 'undefined reference' in l or 'multiple definition' in l or re.search(r'\.[ch]:\d+.*error', l)]
|
|
print(f" build rc={rb.returncode}, {len(hits)} hard diagnostic line(s):")
|
|
for l in hits[:10]:
|
|
print(" ", l.strip()[:190])
|
|
|
|
print("\n[5] reverting")
|
|
sh(['git', 'checkout', '--', f'src/{OV}', 'config'])
|
|
sh(['git', 'clean', '-fdq', f'src/{OV}'])
|
|
sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
|
|
print(" reverted + re-extracted (R22 corollary: a reverted config needs a re-extract)")
|