aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/diff_asd.py
blob: 9c1d0896c94fbdf876e9b4f96c5299afdf921a5a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/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)