aboutsummaryrefslogtreecommitdiffstats
path: root/dmcub/dmcub_map.py
blob: 32142c211ea1b2f87e2f782ec111de0cb1bda4cc (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
#!/usr/bin/env python3
"""Recover the DMCUB address map from the image itself, without trusting the
driver headers: decode every Xtensa `l32r` and collect the literal pool it
resolves to.

`l32r at, label` is PC-relative and *backwards only*:
    literal_addr = ((PC + 3) & ~3) + (sign_extend16(imm16) << 2)
with imm16 always negative, so the arithmetic works entirely in file-offset
space -- no load base assumption is required. That makes two things possible:

  1. A validator for the ISA call: on a correct decode nearly every l32r must
     resolve *inside* the image (and 4-byte aligned by construction); on a wrong
     decode, targets scatter out of range.
  2. The address map: the literal values are the absolute addresses the firmware
     uses -- code pointers, data/stack bases, and the register aperture.

Usage: dmcub_map.py fw/dcn_3_2_0_dmcub.bin [payload_off] [payload_end]
"""
import collections
import struct
import sys


def l32r_sites(data, lo, hi):
    """Yield (site_off, literal_off) for every op0==1 slot whose literal lands
    inside [lo, hi). Linear sweep -- deliberately not a recursive-descent
    disassembly, so counts are an upper bound and the in-range *ratio* is the
    signal."""
    for off in range(lo, hi - 3):
        if data[off] & 0xF != 0x1:
            continue
        imm16 = struct.unpack_from('<H', data, off + 1)[0]
        lit = ((off + 3) & ~3) + ((imm16 - 0x10000) << 2)
        yield off, lit


def main():
    path = sys.argv[1]
    data = open(path, 'rb').read()
    lo = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x100
    hi = int(sys.argv[3], 0) if len(sys.argv) > 3 else len(data)

    total = inrange = 0
    lits = collections.Counter()
    for off, lit in l32r_sites(data, lo, hi):
        total += 1
        if lo <= lit < hi - 3:
            inrange += 1
            lits[lit] += 1

    print('%s payload 0x%x..0x%x' % (path, lo, hi))
    print('l32r slots (linear sweep): %d   literal in-image: %d (%.1f%%)' % (
        total, inrange, 100.0 * inrange / (total or 1)))
    print('distinct literal addresses: %d' % len(lits))

    vals = collections.Counter()
    for lit, n in lits.items():
        v, = struct.unpack_from('<I', data, lit)
        vals[v] += n

    top = collections.Counter()
    for v, n in vals.items():
        top[v >> 24] += n
    print('\nliteral values by high byte (0x??000000):')
    for hb, n in top.most_common(16):
        print('   0x%02x______  %6d refs' % (hb, n))

    print('\nmost-referenced literal values:')
    for v, n in vals.most_common(25):
        print('   0x%08x  %4d refs' % (v, n))


if __name__ == '__main__':
    main()