summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 13:54:29 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 13:54:29 -0400
commit28fca95140d448e900bfe95491dfbc35067852b5 (patch)
treeaa70c7381196d5a4e6bd1647838dd283c7f02a61
parent8082d0ef31e98de06d5b733dc14c9c7c560e3eaa (diff)
downloadopen-iov-28fca95140d448e900bfe95491dfbc35067852b5.tar.gz
open-iov-28fca95140d448e900bfe95491dfbc35067852b5.zip
wall2 session 96: detach-first clearer t106 PROVEN (BC-DETACHED, TracerPid 0, VKDEVICE via 5 words on r696); orphan-tracer plague root-caused (interrupted gdb attempts) with strict discipline; next t107 = clearer+diagnostic in one self-contained gdb window
-rw-r--r--tools/guest-diag/wall2/README-session77.md32
-rw-r--r--tools/guest-diag/wall2/t106.py61
-rw-r--r--tools/guest-diag/wall2/unwait2.py95
3 files changed, 188 insertions, 0 deletions
diff --git a/tools/guest-diag/wall2/README-session77.md b/tools/guest-diag/wall2/README-session77.md
index 70b5e18..9c18ab4 100644
--- a/tools/guest-diag/wall2/README-session77.md
+++ b/tools/guest-diag/wall2/README-session77.md
@@ -664,3 +664,35 @@ folding: the instance markers and the +0x80 words are guest-RAM writes a
kernel-side service can perform on the same triggers (the marker semantics
and the word semantics are understood) - that removes gdb and /proc
helpers from the loop entirely.
+
+**19. Session 96: THE DETACH-FIRST CLEARER PROVEN (t106) + the orphan-tracer plague.**
+t106.py (single +0xB33D30 BP; clears markers; detaches FROM THE HANDLER as
+soon as cleared>=5 or chits>=14: `gdb.execute("detach")` + return True so
+the batch gdb exits) on fresh r696 (mdev 241af1c1): CLEAR#1..#5,
+`BC-DETACHED cleaned=5 chits=5`, `T106-DONE chits=5 cleared=5`,
+`TracerPid: 0` - THE SLOT IS LEFT FREE. unwait.py wrote five words and the
+client (1549) reached `VKINSTANCE ok / VKDEVS 2 / VKPICK / VKDEVICE ok
+family=0`. THE CLEAN FORM OF THE CLEARER EXISTS NOW.
+
+**20. THE ORPHAN-TRACER PLAGUE (root cause of every t102 failure).** Every
+INTERRUPTED gdb attempt leaves an attached orphan: (a) piping gdb to
+`head` closes the pipe early and the gdb stays attached (hit twice); (b) an
+ssh command dying/timing out mid-gdb-add leaves the gdb running (hit
+again). Symptoms: later attaches fail with `Could not attach ... already
+traced by process N` / `ptrace: Inappropriate ioctl for device` and the
+t102 log stays empty. RECOVERY (session-95 evidence): TERM is usually
+ignored (wedged in continue); SIGKILL the orphan + `kill -CONT <client>`;
+the tracee SURVIVES when it was running/at-futex and detaches cleanly
+(TracerPid 0). STRICT DISCIPLINE: never pipe gdb output (redirect to a
+file, read it after); never let an ssh command end while a gdb may be
+attached (generous timeouts); after every gdb run verify TracerPid 0.
+
+**21. THE NEXT FORM (t107, one gdb, one window).** Combine the proven
+clearer and the diagnostic in ONE batch gdb: attach -> clears -> detach ->
+`python time.sleep(...)` idle -> RE-ATTACH (gdb can attach again after
+detach) -> read PC / bt / A8 count (t102 content) -> detach -> print -
+one ssh session, one window, everything self-contained, no orphan risk
+(every path ends in an explicit detach; wrap each phase in try/except with
+a final detach). Expected: names the post-device wait (allocation/UVM vs
+A8 count base+0x2070b7c) so the allocation can be passed and the first
+VKFRAME reached.
diff --git a/tools/guest-diag/wall2/t106.py b/tools/guest-diag/wall2/t106.py
new file mode 100644
index 0000000..ceaac44
--- /dev/null
+++ b/tools/guest-diag/wall2/t106.py
@@ -0,0 +1,61 @@
+import gdb, time
+gr = gdb.execute
+inf = gdb.selected_inferior()
+pid = inf.pid
+base = None
+for line in open("/proc/%d/maps" % pid):
+ parts = line.split()
+ try:
+ lo, hi = [int(x,16) for x in parts[0].split('-')]
+ except Exception:
+ continue
+ if base is None and "libnvidia-eglcore" in line and int(parts[2],16) == 0:
+ base = lo
+print("BASE 0x%x PID %d" % (base or 0, pid), flush=True)
+def u64(a): return int(gdb.parse_and_eval("*(unsigned long*)0x%x" % a))
+def u32(a): return int(gdb.parse_and_eval("*(unsigned int*)0x%x" % a))
+state = {"chits":0, "cleared":0, "done":False}
+class BC(gdb.Breakpoint):
+ def __init__(self):
+ super().__init__("*0x%x" % (base + 0xB33D30), internal=False)
+ self.silent = True
+ def stop(self):
+ if state["done"]:
+ return False
+ state["chits"] += 1
+ try:
+ obj = int(gdb.parse_and_eval("$rdi"))
+ n = int(gdb.parse_and_eval("$rsi"))
+ cnt = u32(obj + 0x22efc)
+ for i in range(0, min(cnt, 32)):
+ p = u64(obj + 0x1428 + i*8)
+ if not p:
+ continue
+ tgt = p + n * 0x10
+ try:
+ v16 = int(gdb.parse_and_eval("*(unsigned short*)0x%x" % (tgt + 0xe)))
+ if v16 == 0xffff:
+ gr("set {unsigned short}0x%x = 0" % (tgt + 0xe))
+ gr("set {unsigned int}0x%x = 0" % tgt)
+ gr("set {unsigned short}0x%x = 0" % (tgt + 0xc))
+ state["cleared"] += 1
+ print("CLEAR#%d obj=0x%x n=0x%x tgt=0x%x" % (state["cleared"], obj, n, tgt), flush=True)
+ except Exception:
+ pass
+ except Exception:
+ pass
+ if state["cleared"] >= 5 or state["chits"] >= 14:
+ state["done"] = True
+ try:
+ gr("detach")
+ print("BC-DETACHED cleaned=%d chits=%d" % (state["cleared"], state["chits"]), flush=True)
+ except Exception as e:
+ print("detachfail %s" % e, flush=True)
+ return True
+ return False
+try:
+ BC(); print("BPS-SET", flush=True)
+ gr("continue")
+except Exception as e:
+ print("contend %s" % e, flush=True)
+print("T106-DONE chits=%d cleared=%d" % (state["chits"], state["cleared"]), flush=True)
diff --git a/tools/guest-diag/wall2/unwait2.py b/tools/guest-diag/wall2/unwait2.py
new file mode 100644
index 0000000..cd4169b
--- /dev/null
+++ b/tools/guest-diag/wall2/unwait2.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+# unwait2.py - no-ptrace guest-side harness:
+# (a) writes 1 to any zero u32 at +0x80 of every 0x4000-byte rw-s nvidia
+# mapping of EVERY live vk_frame;
+# (b) scans each live vk_frame's anonymous/heap rw-p mappings (16-byte
+# aligned) for marker slots {u32@0==0, u16@+0xe==0xffff} and clears
+# u16@+0xe to 0 (the instance-phase pending markers the t105 gdb used
+# to clear). No ptrace anywhere.
+import os, subprocess, sys, time
+DUR = float(sys.argv[1]) if len(sys.argv) > 1 else 600
+def pids():
+ try:
+ out = subprocess.run(["pgrep", "-x", "vk_frame"], capture_output=True).stdout
+ return [int(x) for x in out.split()]
+ except Exception:
+ return []
+def rd(fd, off, n):
+ try:
+ os.lseek(fd, off, 0)
+ return os.read(fd, n)
+ except Exception:
+ return b""
+def wr(fd, off, data):
+ try:
+ os.lseek(fd, off, 0)
+ os.write(fd, data)
+ return True
+ except Exception:
+ return False
+wlog = set(); mlog = set()
+end = time.time() + DUR
+last_scan = 0.0
+while time.time() < end:
+ for pid in pids():
+ try:
+ fd = os.open("/proc/%d/mem" % pid, os.O_RDWR)
+ except Exception:
+ continue
+ try:
+ for line in open("/proc/%d/maps" % pid):
+ p = line.split()
+ if len(p) < 6 or "nvidia" not in p[-1] or "rw-s" not in p[1]:
+ continue
+ lo, hi = [int(x, 16) for x in p[0].split("-")]
+ if hi - lo != 0x4000:
+ continue
+ wa = lo + 0x80
+ v = rd(fd, wa, 4)
+ if len(v) == 4 and v == b"\x00\x00\x00\x00":
+ if wr(fd, wa, b"\x01\x00\x00\x00"):
+ k = (pid, wa)
+ if k not in wlog:
+ wlog.add(k)
+ print("WORDWROTE pid=%d va=0x%x" % (pid, wa), flush=True)
+ except Exception:
+ pass
+ if time.time() - last_scan > 1.5:
+ budget = 64
+ try:
+ for line in open("/proc/%d/maps" % pid):
+ p = line.split()
+ if len(p) < 6 or p[1] != "rw-p":
+ continue
+ path = p[-1]
+ if path.startswith("/") and "nvidia" not in path and path != "[heap]":
+ continue
+ lo, hi = [int(x, 16) for x in p[0].split("-")]
+ if hi - lo > 0x10000000:
+ hi = lo + 0x10000000
+ off = lo
+ while off < hi:
+ chunk = rd(fd, off, 0x100000)
+ if len(chunk) < 32:
+ break
+ j = 0
+ while j + 16 <= len(chunk):
+ if chunk[j:j+4] == b"\x00\x00\x00\x00" and chunk[j+0xe:j+0x10] == b"\xff\xff":
+ wa = off + j + 0xe
+ if (pid, wa) not in mlog:
+ if wr(fd, wa, b"\x00\x00"):
+ mlog.add((pid, wa))
+ print("MARKCLR pid=%d va=0x%x" % (pid, wa), flush=True)
+ j += 16
+ off += 0x100000
+ budget -= 1
+ if budget <= 0:
+ break
+ if budget <= 0:
+ break
+ except Exception:
+ pass
+ last_scan = time.time()
+ os.close(fd)
+ time.sleep(0.4)
+print("UNWAIT2-DONE", flush=True)