#!/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())