aboutsummaryrefslogtreecommitdiffstats
path: root/psp_emu/harness/ta_stack_rule.py
blob: f8a5e233fcfad63697644feebf21ae953db4c1b6 (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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
"""Derive the loader's TA memory-layout RULE: where do code/data/stack/heap land?

`ta_stack_and_map.py` showed one TA's live SP (0x40c000) and that the stack sits just above the
image behind a guard page. To PREDICT another TA's stack (e.g. GCP, which this boot does not
load) the rule must be derived, not guessed -- so group observations by process.

Each TA process has its own L1 table, so TTBR0 identifies the process. For every TA basic block
we record (TTBR0, SP). Then each distinct TTBR0's table is walked independently, giving that
process's regions + permissions, and the stack is identified as the run containing its observed
SP values. With four TAs of different image sizes (hdcp/asd/ras/dtm) the layout arithmetic is
determined rather than inferred from a single instance.
"""
import sys, os, time, collections, json
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import unicorn
from unicorn import UC_HOOK_BLOCK, UC_HOOK_CODE
from unicorn.arm_const import UC_ARM_REG_SP, UC_ARM_REG_R0, UC_ARM_REG_CPSR, UC_ARM_REG_CP_REG

import importlib.util
_s = importlib.util.spec_from_file_location(
    "tasmod", os.path.join(os.path.dirname(__file__), "boot_sos_and_tas.py"))
T = importlib.util.module_from_spec(_s); _s.loader.exec_module(T)
from pspemu import armmmu

TA_LO, TA_HI = 0x400000, 0x500000


def ap_str(ap, apx, xn):
    table = {(0, 0): ("---", "---"), (0, 1): ("RW-", "---"), (0, 2): ("RW-", "R--"),
             (0, 3): ("RW-", "RW-"), (1, 1): ("R--", "---"), (1, 2): ("R--", "R--"),
             (1, 3): ("R--", "R--"), (1, 0): ("---", "---")}
    p, u = table.get((apx, ap), ("?", "?"))
    p = p[:2] + ("-" if xn else "X")
    u = u[:2] + ("-" if (xn or u == "---") else "X")
    return p, u


def walk_base(uc, base, n):
    rd = lambda a: int.from_bytes(uc.mem_read(a, 4), "little")
    out = []
    for i in range(n):
        e = rd(base + 4 * i); t = e & 3; va = i << 20
        if t == 0:
            continue
        dom = (e >> 5) & 0xF
        if t == 2:
            ap = (e >> 10) & 3; apx = (e >> 15) & 1; xn = (e >> 4) & 1
            p, u = ap_str(ap, apx, xn)
            out.append([va, va + 0x100000, "section", e & 0xFFF00000, p, u, dom])
        elif t == 1:
            l2b = e & 0xFFFFFC00; run = None
            for j in range(256):
                e2 = rd(l2b + 4 * j); pva = va + (j << 12)
                if e2 & 3:
                    ap = (e2 >> 4) & 3; apx = (e2 >> 9) & 1; xn = e2 & 1
                    p, u = ap_str(ap, apx, xn); key = (p, u, dom)
                    if run and run[2] == key and run[1] == pva:
                        run = (run[0], pva + 0x1000, key, run[3])
                    else:
                        if run: out.append([run[0], run[1], "pages", run[3], *run[2]])
                        run = (pva, pva + 0x1000, key, e2 & 0xFFFFF000)
                else:
                    if run: out.append([run[0], run[1], "pages", run[3], *run[2]]); run = None
            if run: out.append([run[0], run[1], "pages", run[3], *run[2]])
    return out


def main():
    c, ccp, exc_log = T.build()
    T.stage_all(c, verbose=False)
    loads = []; ta_hit = []
    obs = collections.defaultdict(lambda: collections.Counter())   # ttbr0 -> Counter(sp)
    pcs = collections.defaultdict(set)

    def code(uc, a, sz, u):
        if a in T.LOAD_RETS:
            loads.append((T.LOAD_RETS[a], uc.reg_read(UC_ARM_REG_R0)))

    def on_ta(uc, a, s, u):
        ta_hit.append(a)
        if (uc.reg_read(UC_ARM_REG_CPSR) & 0x1F) != 0x10:
            return                                   # USR mode only = the TA itself
        t0 = uc.reg_read(UC_ARM_REG_CP_REG, (15, 0, 0, 2, 0, 0, 0, 0))
        obs[t0][uc.reg_read(UC_ARM_REG_SP)] += 1
        pcs[t0].add(a)

    c.uc.hook_add(UC_HOOK_BLOCK, on_ta, begin=TA_LO, end=TA_HI - 1)
    c.uc.hook_add(UC_HOOK_CODE, code, begin=0x2048E0, end=0x204B70)
    c.uc.reg_write(UC_ARM_REG_SP, 0x3F000)

    def done():
        return sum(1 for _w, rv in loads if rv == 0) >= 4 and len(ta_hit) > 150

    T.run_system(c, seconds=float(os.environ.get("RUN_SECONDS", "90")),
                 done=None if os.environ.get("RUN_FULL") else done, verbose=True)
    print("TA blocks=%d  loads ok=%d  distinct TA processes (TTBR0)=%d"
          % (len(ta_hit), sum(1 for _w, rv in loads if rv == 0), len(obs)))

    n = armmmu.l1_entries(c.uc)
    report = {}
    for t0, spc in sorted(obs.items()):
        base = t0 & ~((1 << (14 - (armmmu.ttbcr(c.uc) & 7))) - 1)
        rows = [r for r in walk_base(c.uc, base, n) if TA_LO <= r[0] < TA_HI]
        sps = sorted(spc)
        print("\n=== TA process TTBR0=%#010x (%d blocks, %d distinct SP) ===" % (t0, sum(spc.values()), len(sps)))
        print("    SP observed: %s%s" % (', '.join(hex(s) for s in sps[:6]),
                                         ' ...' if len(sps) > 6 else ''))
        print("    %-24s %-7s %-5s %-5s %s" % ("VA range", "kind", "priv", "user", "note"))
        stack = None
        for lo, hi, kind, pa, p, u, dom in rows:
            note = ""
            if any(lo <= s <= hi for s in sps):
                note = "<== STACK (top %#x, size %#x)" % (hi, hi - lo); stack = (lo, hi)
            if p.endswith("X"):
                note = note or "code"
            print("    %#010x-%#010x %-7s %-5s %-5s %s" % (lo, hi, kind, p, u, note))
        # image extent = lowest exec page .. highest RW page below the stack
        execs = [r for r in rows if r[4].endswith("X")]
        img_lo = min(r[0] for r in execs) if execs else None
        pre = [r for r in rows if stack and r[1] <= stack[0]]
        img_hi = max(r[1] for r in pre) if pre else None
        if stack and img_hi:
            print("    RULE: image/data end %#x -> guard %#x -> stack [%#x,%#x) size %#x"
                  % (img_hi, stack[0] - img_hi, stack[0], stack[1], stack[1] - stack[0]))
            report[hex(t0)] = dict(img_lo=hex(img_lo), data_end=hex(img_hi),
                                   guard=hex(stack[0] - img_hi), stack_lo=hex(stack[0]),
                                   stack_hi=hex(stack[1]), stack_size=hex(stack[1] - stack[0]))
    json.dump(report, open("/tmp/ta_stack_rule.json", "w"), indent=1)
    print("\nwrote /tmp/ta_stack_rule.json")
    return 0


if __name__ == "__main__":
    sys.exit(main())