summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/wall2/wall2_livecap.py
blob: af68c0357def5c32898c09736ae0924ffcb45da2 (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
107
108
109
110
111
112
#!/usr/bin/env python3
# wall2_livecap.py - live capture of the GR channel-init push page.
#
# The release group in the push is {0x20038090, sem_va_hi, sem_va_lo, payload,
# continuation}: sem_va = (hi<<32)|lo, and for the channel-init entry the sem
# sits at push_va + entry_len (entry0: len=1432=0x598, sem at +0x598), with the
# group itself at push_va + entry_len - 24 = +0x580.
#
# This watcher scans guest RAM for 0x20038090 groups during the client's stall,
# classifies them, and injects push_va -> page_pa via sema_map_add when the
# group's sem VA and offset identify the push page. Prints full evidence.
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_livecap.log"
MASK = ~0xfff & ((1 << 64) - 1)

ENTRY_LEN = int(os.environ.get("W2_LEN", "1432"))
WATCH_S = int(os.environ.get("W2_WATCH", "120"))

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 cont_ok(v):
    op = (v >> 29) & 7
    cnt = (v >> 16) & 0xfff
    return (op in (1, 3, 5) and cnt != 0) or op in (4, 7)

def scan():
    img.seek(0, 2); fsz = img.tell()
    pat = b"\x90\x80\x03\x20"
    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
            g = d[i:i+24]
            hdr, hi, lo, pay, w5, v5 = struct.unpack("<6I", g)
            gpa = off2gpa(pos + i)
            page = gpa & MASK
            off = gpa - page
            out.append((page, off, hi, lo, pay, w5, v5, g))
        pos += CH
    return out

def main():
    log("=== livecap start len=%d watch=%ds ===" % (ENTRY_LEN, WATCH_S))
    injected = {}
    deadline = time.time() + WATCH_S
    while time.time() < deadline:
        hits = scan()
        cands = []
        for (page, off, hi, lo, pay, w5, v5, g) in hits:
            sem = (hi << 32) | lo
            # classify
            pool = (hi == 1 and lo < 0x10000 and 0 < pay < (1 << 24)
                    and (w5 >> 29) == 1 and ((w5 >> 13) & 7) == 4
                    and ((w5 & 0x1fff) << 2) == 0x300 and (v5 & 3) == 0)
            if pool:
                continue  # CE pool release family, not the push
            if not (0x4000000 <= sem < 0x100000000):
                continue
            if not (0 < pay < (1 << 24)):
                continue
            if not cont_ok(w5):
                continue
            push_va = sem - ENTRY_LEN
            cands.append((page, off, sem, pay, push_va, w5, v5, g))
        if cands:
            # prefer the canonical group offset 0x580 / sem-consistent push VA
            cands.sort(key=lambda c: (c[1] != (ENTRY_LEN - 24), -c[3]))
            page, off, sem, pay, push_va, w5, v5, g = cands[0]
            d = rd(page, 0x100)
            log("livecap: %d cand(s); page=0x%x off=0x%x sem=0x%x pay=%u push_va=0x%x w5=0x%08x v5=0x%08x"
                % (len(cands), page, off, sem, pay, push_va, w5, v5))
            log("         page head: %s" % d[:0x40].hex())
            if (push_va & 0xfff) == 0 and push_va not in injected:
                try:
                    with open(PARAM, "w") as f:
                        f.write("0x%x,0x%x,0x%x,1\n" % (push_va, page, 0x1000))
                    log("         INJECT 0x%x -> 0x%x (off=0x%x)" % (push_va, page, off))
                    injected[push_va] = page
                except OSError as e:
                    log("         inject FAIL", e)
        time.sleep(1)
    log("=== livecap done: %d injection(s) ===" % len(injected))
    return 0

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