#!/usr/bin/env python3 """Cross-version structural diff + old<->new function pairing. Functions are matched first by address-invariant signature (identical = unchanged). Leftovers are paired greedily on (bb, calls, n, size) proximity, which survives code motion and gives candidate old->new pairs to decompile side by side. """ import json, glob, os, re, sys, collections def load(comp): out = {} for f in sorted(glob.glob(f'sigs/{comp}_*.json')): v = re.search(rf'{comp}_([\d.]+)_', os.path.basename(f)).group(1) out[v] = json.load(open(f)) return out, sorted(out, key=lambda s: [int(x) for x in s.split('.')]) def pair(a, b): """a,b: {addr:{...}} -> (unchanged, pairs, only_a, only_b)""" ha = collections.defaultdict(list) for ad, d in a.items(): ha[d['h']].append(ad) hb = collections.defaultdict(list) for ad, d in b.items(): hb[d['h']].append(ad) same = set(ha) & set(hb) unchanged = sum(min(len(ha[h]), len(hb[h])) for h in same) ra = [ad for h in ha if h not in same for ad in ha[h]] rb = [ad for h in hb if h not in same for ad in hb[h]] used, pairs = set(), [] for ad in sorted(ra, key=lambda x: -a[x]['size']): da = a[ad]; best = None; bs = 1e18 for bd in rb: if bd in used: continue db = b[bd] s = (abs(da['bb']-db['bb'])*6 + abs(da['calls']-db['calls'])*4 + abs(da['n']-db['n']) + abs(da['size']-db['size'])/8.0) if s < bs: bs, best = s, bd # only accept a pairing that is structurally close if best is not None and bs < max(40, 0.35*(da['bb']*6 + da['calls']*4 + da['n'])): used.add(best); pairs.append((ad, best, round(bs, 1))) only_a = [x for x in ra if x not in {p[0] for p in pairs}] only_b = [x for x in rb if x not in used] return unchanged, pairs, only_a, only_b for comp in ('asd',): sigs, vers = load(comp) if not vers: continue print(f"\n{'='*72}\n{comp.upper()} ({len(vers)} versions)\n{'='*72}") print(f"{'transition':<26s} {'unchanged':>9s} {'modified':>8s} {'removed':>7s} {'added':>6s}") for x, y in zip(vers, vers[1:]): u, p, oa, ob = pair(sigs[x], sigs[y]) print(f" {x} -> {y:<10s} {u:>9d} {len(p):>8d} {len(oa):>7d} {len(ob):>6d}") u, p, oa, ob = pair(sigs[vers[0]], sigs[vers[-1]]) print(f"\n {vers[0]} vs {vers[-1]}: {u} unchanged, {len(p)} modified, " f"{len(oa)} removed, {len(ob)} added") if p: print(f"\n modified functions ({vers[0]} -> {vers[-1]}), by size:") print(f" {'old':>9s} {'new':>9s} {'size':>6s} {'bb':>4s} {'calls':>5s} {'dist':>5s}") for ad, bd, s in sorted(p, key=lambda t: -sigs[vers[0]][t[0]]['size'])[:25]: da, db = sigs[vers[0]][ad], sigs[vers[-1]][bd] print(f" {ad:>9s} {bd:>9s} {da['size']:>6d} " f"{da['bb']:>2d}->{db['bb']:<2d} {da['calls']:>2d}->{db['calls']:<2d} {s:>5.1f}") json.dump({'pairs': p, 'only_old': oa, 'only_new': ob}, open(f'sigs/{comp}_diff_{vers[0]}_vs_{vers[-1]}.json', 'w'), indent=1)