summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/wall2/wall2_livecap3.py
blob: db28bc2c9f24c29a8cce7ae0d434a375f4a04015 (plain)
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
#!/usr/bin/env python3
# wall2_livecap3.py - capture the UVM GR channel-init push page via the host
# (C46F) semaphore release group emitted by uvm_hal_turing_host_semaphore_release:
#   {0x20050017 (INC_METHOD count=5 addr=0x5c), sem_lo, sem_hi, payload, 0, exec}
# The group sits at push_va + entry_len - 24 = push_va + 0x580 (entry0 len 1432),
# with the header at +0x598 (entry1's 4-byte push). The push VA comes from the
# service's own entry[0] decode in dmesg.
import subprocess, struct, time, sys, re, os

RAM = "/dev/shm/vgpu-guest-ram"
PARAM = "/sys/module/vgsp/parameters/sema_map_add"
LOG = "/tmp/opencode/wall2_livecap3.log"
MASK = ~0xfff & ((1 << 64) - 1)
WATCH_S = int(os.environ.get("W2_WATCH", "240"))
PUSH_VA_DEFAULT = 0x4480000

logf = open(LOG, "a", buffering=1)
def log(*a):
    s = " ".join(str(x) for x in a)
    print(s, flush=True)
    logf.write("[%s] %s\n" % (time.strftime("%H:%M:%S"), s))

img = open(RAM, "rb", buffering=0)
def gpa2off(pa):
    if pa < 0x80000000: return pa
    if pa < 0x180000000: return 0x80000000 + (pa - 0x100000000)
    return None
def off2gpa(off):
    return off if off < 0x80000000 else 0x100000000 + (off - 0x80000000)
def rd(pa, n):
    off = gpa2off(pa)
    if off is None: return None
    img.seek(off); return img.read(n)

def dmesg():
    return subprocess.run(["sudo", "dmesg"], capture_output=True, text=True).stdout

def push_va_now():
    # newest entry[0] line for the Vulkan client's channel
    va = None
    for ln in dmesg().splitlines():
        m = re.search(r"entry\[0\] h=0x([0-9a-f]+) eng=0 raw=\S+ va=0x([0-9a-f]+) len=(\d+)", ln)
        if m:
            va = int(m.group(2), 16)
    return va

def scan():
    img.seek(0, 2); fsz = img.tell()
    pat = struct.pack("<I", 0x20050017)
    out = []
    CH = 64 << 20
    pos = 0
    while pos < fsz:
        img.seek(pos); d = img.read(min(CH, fsz - pos))
        st = 0
        while True:
            i = d.find(pat, st)
            if i < 0: break
            st = i + 1
            if i + 24 > len(d): continue
            lo, hi, pay, w4, w5 = struct.unpack_from("<5I", d, i + 4)
            # data-word sanity: payload_hi==0, RELEASE op, sane payload
            if w4 != 0: continue
            if (w5 & 0x7) != 1: continue
            if not (0 < pay < (1 << 24)): continue
            sem = ((hi & 0xffffffff) << 32) | ((lo & 0x3fffffff) << 2)
            gpa = off2gpa(pos + i)
            page = gpa & MASK
            off = gpa - page
            out.append((page, off, sem, pay, w5))
        pos += CH
    return out

def main():
    log("=== livecap3 start watch=%ds ===" % WATCH_S)
    injected = {}
    seen = set()
    deadline = time.time() + WATCH_S
    while time.time() < deadline:
        hits = scan()
        if hits:
            for (page, off, sem, pay, w5) in hits:
                key = (page, off)
                if key in seen: continue
                seen.add(key)
                d = rd(page, 0x1000)
                head = d[:0x20].hex() if d else "?"
                log("hit page=0x%x off=0x%x sem=0x%x pay=%u exec=0x%08x head=%s"
                    % (page, off, sem, pay, w5, head))
                log("    @0x580: %s" % (d[0x580:0x5b0].hex() if d else "?"))
                if off in (0x598, 0x580, 0x590):
                    pv = push_va_now() or PUSH_VA_DEFAULT
                    if pv not in injected:
                        try:
                            with open(PARAM, "w") as f:
                                f.write("0x%x,0x%x,0x%x,1\n" % (pv, page, 0x1000))
                            log("    INJECT 0x%x -> 0x%x" % (pv, page))
                            injected[pv] = page
                        except OSError as e:
                            log("    inject FAIL", e)
        time.sleep(0.5)
    log("=== livecap3 done: %d injections ===" % len(injected))
    return 0

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