aboutsummaryrefslogtreecommitdiffstats
path: root/psp_emu/harness/ta_stack_and_map.py
blob: 77a8494d03bb56b8e9da4f3e3cef93fbe5d974e7 (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
"""Where does a TA's STACK live, and what is the TA address space's permission map?

Hardware probing cost one address per power cycle (a TA data abort wedges the sOS ring), so the
FE09 write-reach sweep could only find: shared buffer RW, image code/rodata RO, .bss RW, and
unmapped gaps. The stack and heap were never located.

The emulator settles it for free, because two things are real here:
  * `boot_sos_and_tas` boots sOS from the reset vector and the FIRMWARE'S OWN loader
    (`bl_load_ta_image`) loads+verifies the TAs, so the SP it installs is the real one.
  * The ARM MMU is real (FIDELITY-NOTES retraction: SCTLR.M=1, QEMU honours sOS's own
    short-descriptor tables). So walking TTBR0 gives the true mapping + permissions.

Method
  1. Boot + stage + run until modules are loaded and TA code executes.
  2. Range-filtered block hook over the TA window records the live SP (and CPSR mode) while
     TA code runs -> the stack VA, directly.
  3. Walk all TTBR0 L1 entries (TTBCR.N=4 => 256 entries, VA 0..0x10000000) and decode
     AP/APX/XN/domain for sections and small pages -> the full map, so we can say which
     regions are user-writable and where the stack/heap sit relative to the image.

Nothing here is a guess: SP is observed and permissions are decoded from the live tables.
"""
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_PC, UC_ARM_REG_R0, UC_ARM_REG_CPSR

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
MODES = {0x10: "USR", 0x11: "FIQ", 0x12: "IRQ", 0x13: "SVC", 0x17: "ABT", 0x1B: "UND", 0x1F: "SYS"}


def ap_str(ap, apx, xn):
    """ARMv7 short-descriptor access permissions -> (priv, user)."""
    table = {(0, 0): ("---", "---"), (0, 1): ("RW-", "---"), (0, 2): ("RW-", "R--"),
             (0, 3): ("RW-", "RW-"), (1, 0): ("---", "---"), (1, 1): ("R--", "---"),
             (1, 2): ("R--", "R--"), (1, 3): ("R--", "R--")}
    p, u = table.get((apx, ap), ("?", "?"))
    if xn:
        p = p[:2] + "-"; u = u[:2] + "-"
    else:
        p = p[:2] + "X"; u = u[:2] + ("X" if u != "---" else "-")
    return p, u


def walk(uc):
    """Full TTBR0 walk -> list of (va_lo, va_hi, kind, pa, priv, user, domain)."""
    base = armmmu.ttbr0(uc); n = armmmu.l1_entries(uc)
    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:                                   # 1 MB section
            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:                                 # coarse -> L2, 256 small pages
            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()
    print("staging images at PA %#x:" % T.IMAGE_PA)
    T.stage_all(c, verbose=False)

    loads = []; bs = set(); ta_hit = []
    # SP observations while TA code executes, bucketed by CPSR mode
    sp_by_mode = collections.defaultdict(list)
    sp_pc = []

    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_block(uc, a, s, u):
        ta_hit.append(a)
        sp = uc.reg_read(UC_ARM_REG_SP)
        mode = MODES.get(uc.reg_read(UC_ARM_REG_CPSR) & 0x1F, "?")
        sp_by_mode[mode].append(sp)
        if len(sp_pc) < 40:
            sp_pc.append((a, sp, mode))

    c.uc.hook_add(UC_HOOK_BLOCK, on_ta_block, begin=TA_LO, end=TA_HI - 1)
    c.uc.hook_add(UC_HOOK_CODE, code, begin=0x2048E0, end=0x204B70)
    c.uc.hook_add(UC_HOOK_BLOCK, lambda uc, a, s, u: bs.add(a))
    c.uc.reg_write(UC_ARM_REG_SP, 0x3F000)

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

    t = time.time()
    T.run_system(c, seconds=float(os.environ.get("RUN_SECONDS", "60")),
                 done=None if os.environ.get("RUN_FULL") else done, verbose=True)
    print("  ran %.1fs, %d TA blocks, loads ok=%d"
          % (time.time() - t, len(ta_hit), sum(1 for _w, rv in loads if rv == 0)))

    print("\n=== TA live SP observations (the stack, directly) ===")
    if not sp_by_mode:
        print("  NO TA blocks executed -> nothing to report (negative control: do not infer)")
    for mode, sps in sorted(sp_by_mode.items()):
        print("  mode %-4s n=%-7d SP min=%#010x max=%#010x  (span %#x)"
              % (mode, len(sps), min(sps), max(sps), max(sps) - min(sps)))
    print("  first TA blocks (pc, sp, mode):")
    for a, sp, m in sp_pc[:12]:
        print("     pc=%#010x sp=%#010x %s" % (a, sp, m))

    uc = c.uc
    print("\n=== MMU state ===")
    print("  SCTLR=%#010x (M=%d)  TTBCR=%#x  TTBR0=%#010x  L1 entries=%d (VA 0..%#x)"
          % (armmmu.sctlr(uc), armmmu.mmu_enabled(uc), armmmu.ttbcr(uc),
             armmmu.ttbr0(uc), armmmu.l1_entries(uc), armmmu.l1_entries(uc) << 20))

    rows = walk(uc)
    print("\n=== full TTBR0 map (priv/user perms, X=exec) ===")
    print("  %-22s %-8s %-10s %-5s %-5s %s" % ("VA range", "kind", "PA", "priv", "user", "dom"))
    for lo, hi, kind, pa, p, u, dom in rows:
        tag = ""
        if lo <= 0x400000 < hi or (0x400000 <= lo < 0x500000): tag = " <- TA image window"
        if lo <= 0x4013000 < hi: tag = " <- SHARED BUFFER (params_VA)"
        for mode, sps in sp_by_mode.items():
            if any(lo <= s < hi for s in sps): tag += " <- *** TA STACK (%s) ***" % mode
        print("  %#010x-%#010x %-8s %#010x %-5s %-5s %-3d%s" % (lo, hi, kind, pa, p, u, dom, tag))

    # Which regions are USER-writable? that's what the FE09 store can reach.
    print("\n=== user-writable regions (what a TA-context store can hit) ===")
    for lo, hi, kind, pa, p, u, dom in rows:
        if u.startswith("RW"):
            print("  %#010x-%#010x  %-8s user=%s dom=%d" % (lo, hi, kind, u, dom))
    json.dump([[lo, hi, kind, pa, p, u, dom] for lo, hi, kind, pa, p, u, dom in rows],
              open("/tmp/ta_mmu_map.json", "w"), indent=1)
    print("\nwrote /tmp/ta_mmu_map.json")
    return 0


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