aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/vdiff_func.py
blob: 6f097d97538b1cd2e28c4ccf44b95a87e0e001a4 (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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
"""Locate a DIVERGED function in an older build and print a side-by-side
instruction diff.

A diverged function cannot be found by mnemonic match (that is what made it
diverged), so we locate it positionally: find the nearest surrounding functions
that DID match, derive the local code shift from them, and disassemble the old
image at the implied address.

usage: vdiff_func.py <new.elf> <funcs.json> <old.elf> <func_name>
"""
import json, sys, os
from collections import defaultdict
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB, CS_MODE_LITTLE_ENDIAN

OFF = 0x1000
NGRAM = 8
CODE_RANGES = [(0x0, 0x7022), (0x12000, 0x17dd0)]

md = Cs(CS_ARCH_ARM, CS_MODE_THUMB | CS_MODE_LITTLE_ENDIAN)


def seq(buf, va, maxn=None):
    out = []
    for ins in md.disasm(buf, va):
        out.append(ins.mnemonic)
        if maxn and len(out) >= maxn:
            break
    return tuple(out)


def build_index(old):
    idx = defaultdict(list)
    for lo, hi in CODE_RANGES:
        for va in range(lo, hi, 2):
            fo = va + OFF
            if fo + 64 > len(old):
                continue
            s = seq(old[fo:fo + 64], va, NGRAM)
            if len(s) == NGRAM:
                idx[s].append(va)
    return idx


def main():
    new_p, funcs_p, old_p, target = sys.argv[1:5]
    new = open(new_p, 'rb').read()
    old = open(old_p, 'rb').read()
    funcs = json.load(open(funcs_p))
    F = sorted(((int(k, 16), v[0], v[1]) for k, v in funcs.items()))
    idx = build_index(old)

    # map every matchable function to its old address -> local shift
    shifts = {}
    tgt = None
    for st, nm, en in F:
        if nm == target:
            tgt = (st, en)
        size = en - st
        body = new[st + OFF:st + OFF + size]
        s_new = seq(body, st)
        if len(s_new) < NGRAM:
            continue
        for cand in idx.get(s_new[:NGRAM], ()):
            if seq(old[cand + OFF:cand + OFF + size], cand) == s_new:
                shifts[st] = cand - st
                break
    if tgt is None:
        print("function not found:", target); return
    st, en = tgt
    before = [a for a in shifts if a < st]
    after = [a for a in shifts if a > st]
    db = shifts[max(before)] if before else 0
    da = shifts[min(after)] if after else db
    print("target %s @0x%05x size=%d" % (target, st, en - st))
    print("nearest matched before: 0x%05x shift=%+d" % (max(before), db) if before else "none")
    print("nearest matched after : 0x%05x shift=%+d" % (min(after), da) if after else "none")

    size = en - st
    for shift in sorted({db, da}):
        oa = st + shift
        print("\n===== OLD @0x%05x (shift %+d) vs NEW @0x%05x =====" % (oa, shift, st))
        A = list(md.disasm(new[st + OFF:st + OFF + size], st))
        B = list(md.disasm(old[oa + OFF:oa + OFF + size + 16], oa))
        for i in range(max(len(A), len(B))):
            l = "%-9s %-7s %s" % (hex(A[i].address), A[i].mnemonic, A[i].op_str) if i < len(A) else ""
            r = "%-9s %-7s %s" % (hex(B[i].address), B[i].mnemonic, B[i].op_str) if i < len(B) else ""
            mark = ""
            if i < len(A) and i < len(B) and A[i].mnemonic != B[i].mnemonic:
                mark = "  <<<"
            print("%-46s | %-46s%s" % (l, r, mark))


if __name__ == "__main__":
    main()