summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/memprobe.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-25 01:48:37 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-25 01:48:37 -0400
commit6fdbe6219f540f23070949648d09c9fd86029d79 (patch)
treefc0cf9dc1a40d389c95917ddb542056745c6cf74 /tools/guest-diag/memprobe.py
parent93431bdd896d9e6d9a25bebef0fb1af308dd64cd (diff)
downloadopen-iov-6fdbe6219f540f23070949648d09c9fd86029d79.tar.gz
open-iov-6fdbe6219f540f23070949648d09c9fd86029d79.zip
gsp_sema_service: add content rescue for unmapped push-buffer pool VAs
- Add expected_sem_payload tracking to svc_channel for CE channels - Implement svc_rescue_push_page() to scan guest RAM for push with expected semaphore release when VA translation fails - Add svc_init_expected_payload() to initialize expected payload from pool - Add rate-limited logging of distinct stalled VAs (svc_log_stalled_va) - Advance expected_sem_payload on successful push decode - Pushes SEMA_MAP_MAX to 65536, SVC_PIN_SLOTS to 32768 for scale
Diffstat (limited to 'tools/guest-diag/memprobe.py')
-rw-r--r--tools/guest-diag/memprobe.py224
1 files changed, 224 insertions, 0 deletions
diff --git a/tools/guest-diag/memprobe.py b/tools/guest-diag/memprobe.py
new file mode 100644
index 0000000..4fe000b
--- /dev/null
+++ b/tools/guest-diag/memprobe.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+# memprobe.py - read/write another process's memory through /proc/<pid>/mem
+# (root only, no gdb/ptrace attach needed).
+#
+# scan PID [--min 0x204800000 --max 0x204c0000] [--limit 8]
+# find tracker objects: qwords inside [min,max) (the client's sem VAs),
+# then objects whose +0x10 points at them (sem objects), then objects
+# whose +0x9430 points at those (the libcuda tracker at p+0x9410).
+# dump PID 0xP
+# dump the tracker at 0xP (fields sem/mirror/completed, sem value,
+# progress object at p+0x48 and its indexed slots).
+# poke PID 0xADDR=0xVAL,...
+# watch PID 0xADDR,... [--ms 2] [--secs 20]
+import sys, os, time, array, struct
+
+WS = 8
+
+def rd(fd, addr, n):
+ try:
+ return os.pread(fd, n, addr)
+ except OSError:
+ return None
+
+def wr(fd, addr, data):
+ return os.pwrite(fd, data, addr)
+
+def r32(buf, off=0):
+ return struct.unpack_from("<I", buf, off)[0]
+def r64(buf, off=0):
+ return struct.unpack_from("<Q", buf, off)[0]
+
+def regions(pid, writable=True):
+ out = []
+ with open("/proc/%d/maps" % pid) as f:
+ for line in f:
+ p = line.split()
+ try:
+ lo, hi = [int(x, 16) for x in p[0].split("-")]
+ except Exception:
+ continue
+ perm = p[1]
+ if writable and "w" not in perm:
+ continue
+ out.append((lo, hi, perm, " ".join(p[5:])))
+ return out
+
+def scan_qwords(pid, fd, lo, hi, needle_lo=None, needle_hi=None, needle_set=None, chunk=1 << 20):
+ hits = []
+ for rlo, rhi, perm, name in regions(pid):
+ a = max(rlo, 0)
+ while a < min(rhi, 1 << 47):
+ n = min(chunk, rhi - a)
+ buf = rd(fd, a, n)
+ if not buf:
+ a += n
+ continue
+ buf = buf[:n - (n % WS)]
+ arr = array.array("Q")
+ arr.frombytes(buf)
+ if needle_set is not None:
+ for i, v in enumerate(arr):
+ if v in needle_set:
+ hits.append((a + i * WS, v))
+ else:
+ for i, v in enumerate(arr):
+ if needle_lo is not None and v < needle_lo:
+ continue
+ if needle_hi is not None and v >= needle_hi:
+ continue
+ hits.append((a + i * WS, v))
+ a += n
+ return hits
+
+def sem_objs(pid, fd, lo, hi):
+ # qword in [lo,hi) at address A -> a sem object holds it at +0x10
+ first = scan_qwords(pid, fd, None, None, needle_lo=lo, needle_hi=hi)
+ out = {}
+ for addr, val in first:
+ so = addr - 0x10
+ out.setdefault(val, set()).add(so)
+ return out
+
+def find_trackers(pid, fd, lo, hi, limit=8):
+ sems = sem_objs(pid, fd, lo, hi)
+ print("sem VAs found: %d" % len(sems))
+ for va, sos in sorted(sems.items())[:8]:
+ print(" va=0x%x -> %d sem-obj candidate(s)" % (va, len(sos)))
+ needles = set()
+ for sos in sems.values():
+ needles |= sos
+ if not needles:
+ return []
+ hits = scan_qwords(pid, fd, None, None, needle_set=needles)
+ pset = set()
+ for addr, val in hits:
+ cand = addr - 0x9430
+ if cand >= 0:
+ pset.add(cand)
+ print("tracker candidates: %d" % len(pset))
+ res = []
+ for p in sorted(pset):
+ buf = rd(fd, p + 0x9410, 0x40)
+ if not buf or len(buf) < 0x40:
+ continue
+ sub = r64(buf, 0x08); last = r64(buf, 0x10); cur = r64(buf, 0x18); sem = r64(buf, 0x20)
+ if sem not in needles:
+ continue
+ mem = None
+ sob = rd(fd, sem, 0x40)
+ if sob:
+ mem = r64(sob, 0x10)
+ res.append((p, sub, last, cur, sem, mem))
+ if len(res) >= limit:
+ break
+ return res
+
+def dump_tracker(pid, fd, p):
+ t = p + 0x9410
+ buf = rd(fd, t, 0x40)
+ if not buf:
+ print("tracker unreadable at 0x%x" % t); return
+ print("TRK=0x%x sub=0x%x last=0x%x cur=0x%x sem_obj=0x%x"
+ % (t, r64(buf, 8), r64(buf, 0x10), r64(buf, 0x18), r64(buf, 0x20)))
+ sem = r64(buf, 0x20)
+ sob = rd(fd, sem, 0x40)
+ if sob:
+ print(" sem_obj[0:0x40]=%s" % sob.hex())
+ mem = r64(sob, 0x10)
+ mvb = rd(fd, mem & ~0xfff, 0x40)
+ print(" sem_mem=0x%x val@+0x%x=0x%x" % (mem, mem & 0xfff, r32(mvb, mem & 0xfff) if mvb else -1))
+ q = r64(rd(fd, p + 0x48, 8) or b"\0" * 8)
+ print(" prog_obj=0x%x next=0x%x cblist=0x%x idx95bc=0x%x p34=0x%x"
+ % (q, r64(rd(fd, p + 0x9470, 8) or b"\0" * 8),
+ r64(rd(fd, p + 0x9458, 8) or b"\0" * 8),
+ r32(rd(fd, p + 0x95bc, 4) or b"\0" * 4),
+ r32(rd(fd, p + 0x34, 4) or b"\0" * 4)))
+ if q:
+ qb = rd(fd, q, 0x50)
+ if qb:
+ size = r32(qb, 8); idx = r32(qb, 0x48); cur = r32(qb, 0x4c); arr = r64(qb, 0x40)
+ print(" size=%d idx=0x%x cursor=0x%x arr=0x%x" % (size, idx, cur, arr))
+ for s in (idx, cur):
+ slot = arr + (s % size if size else s) * 0x38
+ sb = rd(fd, slot, 0x38)
+ print(" slot[%d]@0x%x=%s" % (s, slot, sb.hex() if sb else "unreadable"))
+
+def main():
+ if len(sys.argv) < 3:
+ print(__doc__); return 1
+ mode = sys.argv[1]
+ pid = int(sys.argv[2])
+ fd = os.open("/proc/%d/mem" % pid, os.O_RDWR)
+ try:
+ if mode == "scan":
+ lo, hi = 0x204800000, 0x204c0000
+ limit = 8
+ for i, a in enumerate(sys.argv[3:]):
+ if a == "--min": lo = int(sys.argv[4 + i], 16)
+ if a == "--max": hi = int(sys.argv[5 + i], 16)
+ if a == "--limit": limit = int(sys.argv[4 + i])
+ res = find_trackers(pid, fd, lo, hi, limit)
+ for p, sub, last, cur, sem, mem in res:
+ print("P=0x%x sub=0x%x last=0x%x cur=0x%x sem_obj=0x%x sem_mem=0x%x" %
+ (p, sub, last, cur, sem, mem or 0))
+ dump_tracker(pid, fd, p)
+ elif mode == "dump":
+ dump_tracker(pid, fd, int(sys.argv[3], 16))
+ elif mode == "hex":
+ a = int(sys.argv[3], 16)
+ n = int(sys.argv[4], 16) if len(sys.argv) > 4 else 0x100
+ d = rd(fd, a, n) or b""
+ for off in range(0, len(d), 16):
+ c = d[off:off + 16]
+ q = [int.from_bytes(c[i:i + 8], "little") for i in (0, 8)]
+ print("0x%x: %s %s" % (a + off, c.hex(), " ".join("0x%x" % x for x in q)))
+ elif mode == "ctx":
+ ctx = int(sys.argv[3], 16)
+ count = r32(rd(fd, ctx + 0x10, 4) or b"\0" * 4)
+ ent = r64(rd(fd, ctx + 0x18, 8) or b"\0" * 8)
+ print("waitobj ctx=0x%x count=%d entries=0x%x" % (ctx, count, ent))
+ for i in range(min(count, 48)):
+ e = ent + i * 0x28
+ eb = rd(fd, e, 0x28)
+ if not eb:
+ print(" e%d unreadable" % i); continue
+ typ = r32(eb, 0); p = r64(eb, 8); v = r64(eb, 0x10)
+ x18 = r64(eb, 0x18); x20 = r64(eb, 0x20)
+ print(" e%d type=0x%x p=0x%x v=0x%x x18=0x%x x20=0x%x" % (i, typ, p, v, x18, x20))
+ if typ == 1 and p:
+ dump_tracker(pid, fd, p)
+ elif mode == "poke":
+ for spec in sys.argv[3].split(","):
+ a, v = spec.split("=")
+ a = int(a, 16); v = int(v, 16)
+ n = wr(fd, a, v.to_bytes(8, "little"))
+ print("poke 0x%x <- 0x%x (%s)" % (a, v, "ok" if n == 8 else "FAIL"))
+ elif mode == "watch":
+ addrs = [int(x, 16) for x in sys.argv[3].split(",")]
+ ms = 2; secs = 20
+ for i in range(4, len(sys.argv)):
+ if sys.argv[i] == "--ms" and i + 1 < len(sys.argv):
+ ms = int(sys.argv[i + 1])
+ if sys.argv[i] == "--secs" and i + 1 < len(sys.argv):
+ secs = int(sys.argv[i + 1])
+ last = {}
+ t0 = time.time()
+ while time.time() - t0 < secs:
+ for a in addrs:
+ b = rd(fd, a, 8)
+ if not b:
+ continue
+ v = r64(b)
+ if last.get(a) != v:
+ print("%.3f 0x%x = 0x%x" % (time.time() - t0, a, v))
+ last[a] = v
+ time.sleep(ms / 1000.0)
+ else:
+ print(__doc__); return 1
+ finally:
+ os.close(fd)
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main()) \ No newline at end of file