mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
e936556ff0
seed_ref answers 'is there a byte-identical twin?'. This answers the weaker but far more common question: 'which matched function should I READ before drafting this?' S68 measured a ~20x swing on that variable. Every cheapest large match came from an agent finding a matched neighbour (func_800D1254 555 ins/72k; func_800D12D0 657 ins/122k FIRST COMPILE; func_8018AD9C 397 ins/87k; func_8017BEBC 753 ins/177k), while main functions with no neighbour ran 200-350k for ~80 instructions. THE FAILURE THAT MOTIVATED IT: func_8017BEBC's card asserted 'no banked twin' while a MATCHED 755-instruction near-twin sat 3,700 lines up IN ITS OWN FILE, its header comment documenting the four levers the target needed. seed_ref joins on signature hashes and the two bodies are not hash-identical, so it was structurally invisible. Three other S68 agents found their unlock the same way, unprompted. Ranks on what actually worked, not intuition: SAME TU first (solved against the same decl environment, and its header records the levers), then same binary, then shape (li-normalised skeleton / call-sequence hash / reloc-kind sequence / CFG counts / opcode-histogram cosine, all precomputed in .run/feat.*.jsonl), then instruction- count proximity, with a HARD PENALTY for opt-level mismatch (§116 — an -O2 example actively misleads an -O0 target). It surfaces the neighbour's HEADER COMMENT, which is the payload agents actually consumed. Explicitly NOT a remap claim: §168 law 1 measured cousins at 0/26. A neighbour is a worked example to READ; seed_ref remains the tool for the byte-identical case. Validated against ground truth: for main/func_80024054 (265k tokens, ended NEAR 32) the top three neighbours are func_8003A0E4, func_800242D0 and func_800241C0 -- all three MATCHED THIS SESSION, same TU, same call sequence, same reloc-kind sequence. src/800.c holds 657 matched functions and the card offered none of them. Bug fixed en route, and it is a repeat: the atlas writes addresses as hex STRINGS while corpus.Stub.addr is an int. T4's verifier already lost rows to exactly this string-vs-int mismatch (the R32 silent-no-op class). Normalised in _addr().
246 lines
10 KiB
Python
246 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""neighbor_ref.py — for an OPEN stub, the already-MATCHED functions worth reading as WORKED
|
|
EXAMPLES, ranked. (P31 S68)
|
|
|
|
WHY, WITH THE MEASUREMENT. `seed_ref` finds a BYTE-IDENTICAL twin by signature hash — when one
|
|
exists the body is free. This answers the weaker but far more common question: *"which matched
|
|
function should I READ before drafting this one?"*
|
|
|
|
S68 measured a ~20x swing on exactly that variable. Every one of the cheapest large matches came
|
|
from an agent finding a matched neighbour; the expensive ones had none:
|
|
|
|
func_800D1254 555 ins 72k tokens an -O0 SIBLING in the same binary
|
|
func_800D12D0 657 ins 122k tokens the -O0 sibling banked minutes earlier, FIRST COMPILE
|
|
func_8018AD9C 397 ins 87k tokens a banked twin, §193-A one-shot
|
|
func_80189240 551 ins 156k tokens pure symbol remap of a banked twin, first try
|
|
func_8017BEBC 753 ins 177k tokens a near-twin IN THE SAME FILE
|
|
---------------------------------------------------------------------------
|
|
main fns with no neighbour ~80 ins 200-350k tokens each
|
|
|
|
**And the failure that motivated this file: `func_8017BEBC`'s card asserted "no banked twin" while a
|
|
MATCHED 755-instruction near-twin sat 3,700 lines up IN ITS OWN DESTINATION FILE**, its header
|
|
comment documenting the four levers the target needed. `seed_ref` could not see it because it joins
|
|
on signature hashes and the two bodies are not hash-identical. Three other S68 agents found their
|
|
unlock the same way, unprompted, by reading around the destination TU.
|
|
|
|
WHAT IT RANKS ON, in priority order — derived from what actually worked, not from intuition:
|
|
1. **SAME TU.** The strongest signal by a distance. A matched function in the destination file was
|
|
solved against the SAME decl environment, the same neighbours' types, the same carve — and its
|
|
header comment usually records the levers. Every S68 same-TU unlock was cheap.
|
|
2. **Same binary.** Same overlay/module: shared globals, shared engine idioms.
|
|
3. **Shape similarity** from `.run/feat.<bin>.jsonl` (already computed for all 213 binaries, 21 s
|
|
fleet-wide): the li-normalised skeleton `h_seqn`, the call-sequence hash, the reloc-kind
|
|
sequence, the CFG counts and the 16-bucket opcode histogram.
|
|
4. **Instruction-count proximity.** A 750-instruction target learns little from a 20-instruction
|
|
example.
|
|
5. **-O0 agreement.** An -O0 target must read an -O0 example; opt level is a property of the FILE
|
|
(§116) and an -O2 example actively misleads.
|
|
|
|
WHAT THIS IS NOT. It does NOT claim the neighbour's body can be remapped — §168 law 1 measured that
|
|
at 0/26 ("a cousin is a seeded crack, never a remap"). A neighbour is a WORKED EXAMPLE to read, not
|
|
an answer to copy. `seed_ref` remains the tool for the byte-identical case.
|
|
|
|
tools/neighbor_ref.py --binary ov_SC06_010 --fn func_8017BEBC
|
|
tools/neighbor_ref.py --binary main --fn func_80024054 --json --top 5
|
|
"""
|
|
import argparse
|
|
import json
|
|
import math
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
os.chdir(REPO)
|
|
sys.path.insert(0, os.path.join(REPO, "tools"))
|
|
import corpus # noqa: E402
|
|
import overlay_src_split as oss # noqa: E402
|
|
|
|
|
|
def _addr(v):
|
|
"""int address from either an int or a "0x..." string; None if unusable."""
|
|
if isinstance(v, int):
|
|
return v
|
|
try:
|
|
return int(v, 16) if str(v).lower().startswith("0x") else int(v)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _feats(binary):
|
|
"""{addr: featrow} from the precomputed atlas features; {} if absent."""
|
|
p = os.path.join(REPO, ".run/feat.%s.jsonl" % binary)
|
|
out = {}
|
|
try:
|
|
with open(p) as fh:
|
|
for ln in fh:
|
|
ln = ln.strip()
|
|
if not ln:
|
|
continue
|
|
try:
|
|
r = json.loads(ln)
|
|
except ValueError:
|
|
continue
|
|
# ADDRESSES ARE HEX STRINGS HERE AND INTS ELSEWHERE. The atlas writes
|
|
# "0x80128158"; corpus.Stub.addr is an int. This project has already been bitten by
|
|
# exactly this once (T4's verifier compared ZERO rows because of a string-vs-int
|
|
# addr mismatch — the R32 silent-no-op class). Normalise both ways, loudly.
|
|
a = _addr(r.get("addr"))
|
|
if a is not None:
|
|
out[a] = r
|
|
except OSError:
|
|
pass
|
|
return out
|
|
|
|
|
|
def _ophist_cos(a, b):
|
|
"""cosine over the 16-bucket opcode histogram; 0.0 when either is missing."""
|
|
if not a or not b or len(a) != len(b):
|
|
return 0.0
|
|
na = math.sqrt(sum(x * x for x in a)) or 1.0
|
|
nb = math.sqrt(sum(x * x for x in b)) or 1.0
|
|
return sum(x * y for x, y in zip(a, b)) / (na * nb)
|
|
|
|
|
|
def _same_tu_matched(binary, tu_path):
|
|
"""{addr: name} of functions MATCHED in this TU — the strongest signal (see docstring)."""
|
|
out = {}
|
|
try:
|
|
syms = oss.load_ov_syms(binary) if hasattr(oss, "load_ov_syms") else {}
|
|
except Exception:
|
|
syms = {}
|
|
try:
|
|
_, items = oss.parse_overlay_c(open(os.path.join(REPO, tu_path)).read(), syms)
|
|
except Exception:
|
|
return out
|
|
for addr, name, kind, _ in items:
|
|
if kind in ("def", "define", "nonmatch") and name and addr is not None:
|
|
out[addr] = name
|
|
return out
|
|
|
|
|
|
def _header_comment(tu_path, name):
|
|
"""The comment block immediately above a definition — where the LEVERS are written down.
|
|
|
|
This is the payload an agent actually consumes. S68's func_8017BEBC was unlocked by a
|
|
neighbour's header listing four levers, all reused verbatim.
|
|
"""
|
|
try:
|
|
lines = open(os.path.join(REPO, tu_path), errors="replace").read().splitlines()
|
|
except OSError:
|
|
return ""
|
|
pat = re.compile(r"\b%s\b" % re.escape(name))
|
|
for i, ln in enumerate(lines):
|
|
if pat.search(ln) and ("(" in ln or "DEFINE_" in ln):
|
|
j, block = i - 1, []
|
|
while j >= 0 and (lines[j].lstrip().startswith(("*", "/*", "//")) or not lines[j].strip()):
|
|
if lines[j].strip():
|
|
block.append(lines[j].rstrip())
|
|
j -= 1
|
|
if len(block) > 40:
|
|
break
|
|
return "\n".join(reversed(block))
|
|
return ""
|
|
|
|
|
|
def neighbors(binary, fn, top=5):
|
|
stubs = corpus.stubs(binary)
|
|
st = next((s for s in stubs.values() if s.symbol == fn), None)
|
|
if st is None:
|
|
return {"error": "no open stub %s:%s" % (binary, fn)}
|
|
sp = os.path.join(st.asm_dir, fn + ".s")
|
|
nins = corpus.s_ins_count(sp) if os.path.exists(sp) else 0
|
|
from o0_detect import detect_o0
|
|
o0 = detect_o0(sp) if os.path.exists(sp) else False
|
|
|
|
feats = _feats(binary)
|
|
me = feats.get(st.addr, {})
|
|
same_tu = _same_tu_matched(binary, st.path)
|
|
matched = corpus.matched(binary)
|
|
|
|
cands = []
|
|
for addr, row in matched.items():
|
|
f = feats.get(addr, {})
|
|
n2 = f.get("nins") or row.get("nins") or 0
|
|
if not n2:
|
|
continue
|
|
score = 0.0
|
|
why = []
|
|
if addr in same_tu:
|
|
score += 100.0
|
|
why.append("SAME TU")
|
|
else:
|
|
score += 20.0
|
|
why.append("same binary")
|
|
if o0 == bool(f.get("o0")):
|
|
score += 15.0
|
|
if o0:
|
|
why.append("-O0 like the target")
|
|
else:
|
|
score -= 40.0
|
|
why.append("OPT-LEVEL MISMATCH — misleading (§116)")
|
|
if me:
|
|
if f.get("h_seqn") and f["h_seqn"] == me.get("h_seqn"):
|
|
score += 60.0; why.append("identical li-normalised skeleton")
|
|
if f.get("res_calls_h") and f["res_calls_h"] == me.get("res_calls_h"):
|
|
score += 30.0; why.append("same call sequence")
|
|
if f.get("rk_seq_h") and f["rk_seq_h"] == me.get("rk_seq_h"):
|
|
score += 25.0; why.append("same relocation-kind sequence")
|
|
c = _ophist_cos(f.get("ophist"), me.get("ophist"))
|
|
if c > 0.9:
|
|
score += 20.0 * c; why.append("opcode mix %.2f" % c)
|
|
for k, w, lbl in (("nblk", 8, "CFG blocks"), ("ncond", 8, "branches"),
|
|
("nback", 6, "loops")):
|
|
if f.get(k) is not None and me.get(k) is not None and f[k] == me[k]:
|
|
score += w; why.append("same %s" % lbl)
|
|
ratio = min(nins, n2) / max(nins, n2, 1)
|
|
score += 25.0 * ratio
|
|
if ratio < 0.25:
|
|
why.append("size far off (%d vs %d)" % (n2, nins))
|
|
cands.append((score, addr, row.get("name") or same_tu.get(addr) or "func_%08X" % addr,
|
|
n2, same_tu.get(addr) is not None, why))
|
|
|
|
cands.sort(key=lambda t: -t[0])
|
|
out = {"binary": binary, "fn": fn, "nins": nins, "o0": o0, "tu": st.path,
|
|
"matched_in_tu": len(same_tu), "matched_in_binary": len(matched), "neighbors": []}
|
|
for score, addr, name, n2, in_tu, why in cands[:top]:
|
|
out["neighbors"].append({
|
|
"name": name, "addr": "0x%08x" % addr, "nins": n2, "same_tu": in_tu,
|
|
"score": round(score, 1), "why": why,
|
|
"header": _header_comment(st.path, name)[:1200] if in_tu else "",
|
|
})
|
|
return out
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--binary", required=True); ap.add_argument("--fn", required=True)
|
|
ap.add_argument("--top", type=int, default=5); ap.add_argument("--json", action="store_true")
|
|
a = ap.parse_args()
|
|
r = neighbors(a.binary, a.fn, a.top)
|
|
if a.json:
|
|
print(json.dumps(r, indent=1)); return 0
|
|
if r.get("error"):
|
|
print("ERROR:", r["error"]); return 1
|
|
print("%s:%s %d ins%s\n TU %s — %d matched functions in it, %d in the binary"
|
|
% (r["binary"], r["fn"], r["nins"], " [-O0 TARGET]" if r["o0"] else "",
|
|
r["tu"], r["matched_in_tu"], r["matched_in_binary"]))
|
|
if not r["neighbors"]:
|
|
print(" no matched neighbours — this really is a cold start")
|
|
for n in r["neighbors"]:
|
|
print("\n %-18s %s %4d ins score %-6s %s"
|
|
% (n["name"], n["addr"], n["nins"], n["score"],
|
|
"<< SAME TU" if n["same_tu"] else ""))
|
|
print(" why: %s" % "; ".join(n["why"]))
|
|
if n["header"]:
|
|
first = [l for l in n["header"].splitlines() if l.strip("/* \t")][:4]
|
|
print(" its header records:")
|
|
for l in first:
|
|
print(" %s" % l.strip()[:110])
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|