summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-31 08:59:10 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-31 08:59:10 -0400
commitc0a62cfcec2b09fff7bf0f86a129f7a04a27d70a (patch)
treead024765ee10d2c5d2c6157639c3839a6bea8e68 /tools/guest-diag
parent520ad3e2f99b4e15c8778874e1b4b188c6d16b77 (diff)
downloadopen-iov-c0a62cfcec2b09fff7bf0f86a129f7a04a27d70a.tar.gz
open-iov-c0a62cfcec2b09fff7bf0f86a129f7a04a27d70a.zip
vgsp+diag: strict replay/hold plumbing + pushfail/unread diagnostics + demand-record ex; guest_pin_stream vmap full-sweep resolver (unknown-family anchors), zero-head first-page acceptance, strict retry throttles - MME-PRELUDE MATURED (gen=4 words=901) but replay DROPS it (window=390 prelude=901 sa=47 exceeds block; replaying window alone) -> every launch prelude=0 timeout; GR-DEC data produced (5657 lines); retirement 0/900 no writes; next: raise replay/block capacity or two-pass prelude - SESSION 153 r1126
Diffstat (limited to 'tools/guest-diag')
-rw-r--r--tools/guest-diag/guest_pin_stream.py211
1 files changed, 186 insertions, 25 deletions
diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py
index d1595b4..ddc7098 100644
--- a/tools/guest-diag/guest_pin_stream.py
+++ b/tools/guest-diag/guest_pin_stream.py
@@ -211,7 +211,11 @@ def read_u32(kc, va):
return struct.unpack("<I", d)[0] if d else 0xFFFFFFFF
-DBG_BUDGET = int(os.environ.get("STREAM_DBG_BUDGET", "600"))
+DBG_BUDGET = int(os.environ.get("STREAM_DBG_BUDGET", "2400"))
+# SESSION 153-CONTINUED-65: minimum seconds between full-offset sweeps of
+# one strict anchor (the demand file re-sends every ~2 s; the sweep can cost
+# tens of seconds - see the throttle comment at the strict processing loop).
+ANCHOR_RETRY_S = float(os.environ.get("STREAM_ANCHOR_RETRY_S", "20"))
_dbg_count = [0]
@@ -484,6 +488,87 @@ def _stream_parse_full(buf):
and code_words >= 8)
+def _walk_measure(buf):
+ """Method-shape walk returning (end_ok, data_runs, mme_hits, code_words,
+ pair_runs, parsed_bytes). 'end_ok' mirrors _stream_parse_full's
+ exact-end + zero padding landing; 'pair_runs' counts runs addressing the
+ SA-pair method 0x11c (the client push signature); 'parsed_bytes' is how
+ far the walk got before a shape failure (or the window end)."""
+ off = 0
+ methods = 0
+ data_runs = 0
+ mme_hits = 0
+ code_words = 0
+ pair_runs = 0
+ n = len(buf)
+ end_ok = False
+ while off + 4 <= n:
+ hdr = struct.unpack_from('<I', buf, off)[0]
+ if hdr == 0:
+ off += 4
+ continue
+ op = (hdr >> 29) & 7
+ count = (hdr >> 16) & 0xFFF
+ if op in (1, 3, 5):
+ if not count or off + 4 + count * 4 > n:
+ break
+ data_runs += 1
+ addr = (hdr & 0x1FFF) << 2
+ if (0x114 <= addr <= 0x124 or addr == 0x234 or
+ 0x550 <= addr <= 0x568 or 0x3400 <= addr <= 0x34FC):
+ mme_hits += 1
+ if addr == 0x114:
+ code_words += count
+ if addr == 0x11c and count >= 2:
+ pair_runs += 1
+ off += 4 + count * 4
+ elif op in (0, 2, 4, 7):
+ off += 4
+ else:
+ break
+ methods += 1
+ if methods > 8192:
+ break
+ else:
+ o2 = off
+ while o2 < n and buf[o2] == 0:
+ o2 += 1
+ end_ok = (methods > 0 and o2 == n)
+ return end_ok, data_runs, mme_hits, code_words, pair_runs, off
+
+
+def _window_accept(buf, ln):
+ """Acceptance for a client-mapping candidate window.
+
+ SESSION 153-CONTINUED-65: the full-offset sweep of the client-mem
+ resolver needs a false-positive gate that survives pointer/event data.
+ The probe of the r1125 boot found a 60216-byte window of pointer junk
+ that walked to an exact end with one misinterpreted 0x114 run
+ (mme=1): the old accept (data>0, mme>0, code>=8) would have pinned it.
+ Large windows therefore require the client push signature - at least
+ two MME-setup runs or two SA-pair (0x11c) runs - in addition to the
+ counts; small windows keep the historical strict rule. The relaxed
+ path additionally accepts a window whose tail is unmapped/unwritten
+ (walk parses >= 0x1000) when it carries >= 64 code words (the macro
+ upload signature).
+
+ Returns (ok, mode).
+ """
+ if ln < 0x8000:
+ return _stream_parse_full(buf), "strict"
+ end_ok, data, mme, code, pairs, parsed = _walk_measure(buf)
+ # The client-push signature: SA-pair (0x11c) runs or 0x114 code runs.
+ # The r1125 probe of the live client mappings measured 10k+ pointer/float
+ # windows with data/mme hits but pairs=0, code=0 - MME-setup-family hits
+ # alone (0x124/0x34xx) are NOT a discriminator; the pair/code runs are.
+ sig = (pairs >= 2 or code >= 8)
+ if end_ok and data >= 2 and sig:
+ return True, "strict"
+ if data >= 2 and sig and parsed >= 0x1000:
+ return True, "relaxed"
+ return False, None
+
+
def _prefix_walk_ok(buf, off):
"""Cheap first-page pre-filter for a strict window candidate.
@@ -612,27 +697,48 @@ def resolve_va_client_mem(pid, ent_va, off, ln, out):
if hi - lo < (need + 4) * 0x1000 or hi - lo > (64 << 20):
continue
pages = (hi - lo) >> 12
- if k is None:
- # Unknown family (SESSION 153-CONTINUED-64e: the hold moved
- # to the 0x3200000 window): no identity page index to key
- # on, so sweep every page start of the mapping (bounded).
+ if k is None or k == 0:
+ # Unknown family, or the FAMILY-BASE page (k == 0): neither
+ # has an identity page index to key on, so sweep every page
+ # start of the mapping (bounded). SESSION 153-CONTINUED-65:
+ # the 0x3200000 macro-upload push's window sits at an
+ # arbitrary page offset inside the client's mapping (the
+ # r1124 power-of-two neighbourhood never tried it; the
+ # window content measures code=712, exact-end, under the
+ # strict walk).
adjs = range(0, max(1, pages - need + 1))
else:
- adjs = (0, 1, -1, 2, -2, 4, -4, 8, -8, 16, -16, 32, -32,
- 64, -64, 128, -128, 256, -256)
+ # SESSION 153-CONTINUED-65: neighbourhood FIRST, then the
+ # FULL page sweep. The r1125 setup push (family pages k=1
+ # and k=33, 60216 B) failed every power-of-two offset while
+ # a matching window could sit at an arbitrary mapping page;
+ # every candidate still passes _window_accept before any
+ # pin is emitted, so the widened sweep cannot pin a window
+ # the parse rejects.
+ near = [0, 1, -1, 2, -2, 4, -4, 8, -8, 16, -16, 32, -32,
+ 64, -64, 128, -128, 256, -256]
+ near_set = set(near)
+ rest = [a for a in range(-k, pages - need + 1 - k)
+ if a not in near_set]
+ adjs = near + rest
for adj in adjs:
idx = (k + adj) if k is not None else adj
if idx < 0 or idx + need > pages:
continue
win = lo + idx * 0x1000 + off
tried += 1
- if tried > (8192 if k is None else 192):
+ if tried > 8192:
break
try:
first = os.pread(mfd, 0x1000 - off, win)
except OSError:
continue
- if not first or not _mme_run_in_page(first):
+ if not first:
+ continue
+ # An all-zero first page is a legitimate push head (the
+ # method stream may start later); only a page with non-zero
+ # content and no MME-setup run is rejected outright.
+ if not _mme_run_in_page(first) and any(first):
continue
buf = b''
left = ln
@@ -651,7 +757,10 @@ def resolve_va_client_mem(pid, ent_va, off, ln, out):
buf += d
a += n
left -= n
- if not ok or not _stream_parse_full(buf):
+ if not ok:
+ continue
+ acc, mode = _window_accept(buf, ln)
+ if not acc:
continue
pas = []
for j in range(need):
@@ -668,11 +777,11 @@ def resolve_va_client_mem(pid, ent_va, off, ln, out):
for j, pa in enumerate(pas):
out.append("0x%x,0x%x,1\n"
% ((ent_va & ~0xFFF) + j * 0x1000, pa))
- dbg("demand-va: 0x%x -> pa=0x%x (strict client-mem parse"
+ dbg("demand-va: 0x%x -> pa=0x%x (%s client-mem parse"
" %u B ok pages=%u map=0x%x idx=%d)"
- % (ent_va & ~0xFFF, pas[0], ln, need, lo, idx))
+ % (ent_va & ~0xFFF, pas[0], mode, ln, need, lo, idx))
return True
- if tried > 192:
+ if tried > 8192:
break
return False
finally:
@@ -719,11 +828,26 @@ def resolve_va_strict(kc, ent_va, off, ln, out, pid=None):
fam = _gl_fam.get(gl_base)
if fam is not None and any(a_lo == fam[0] for (a_lo, _h, _p) in areas):
cands.append(fam)
- for (lo, _hi, pages) in areas:
- for adj in _STRICT_ADJ:
- c = (lo, adj)
- if c not in cands:
- cands.append(c)
+ if k == 0:
+ # SESSION 153-CONTINUED-65: a family-BASE anchor (the 0x3200000
+ # macro-upload push) has no identity page index to key on. The
+ # r1126 macro window lives in a KERNEL-mapped allocation (no
+ # process pagemap maps it: the whole-process reverse scan found
+ # none), so the kcore vmap sweep is the only resolver - and the
+ # power-of-two neighborhood missed the window's arbitrary page
+ # index. Sweep every page of every nv_vm_map_pages area; the
+ # _window_accept parse gates each candidate.
+ for (lo, _hi, pages) in areas:
+ for adj in range(0, max(1, pages - need + 1)):
+ c = (lo, adj)
+ if c not in cands:
+ cands.append(c)
+ else:
+ for (lo, _hi, pages) in areas:
+ for adj in _STRICT_ADJ:
+ c = (lo, adj)
+ if c not in cands:
+ cands.append(c)
tried = 0
for (lo, adj) in cands:
pages = next(p for (a_lo, _h, p) in areas if a_lo == lo)
@@ -731,16 +855,26 @@ def resolve_va_strict(kc, ent_va, off, ln, out, pid=None):
if idx < 0 or idx + need > pages:
continue
tried += 1
- if tried > 128:
+ if tried > (8192 if k == 0 else 128):
break
cpu = lo + idx * 0x1000
first = kc.read(cpu, 0x1000)
- if not first or not any(first):
+ if not first:
continue
- if not _prefix_walk_ok(first, off):
+ # SESSION 153-CONTINUED-65: an all-zero first page is a legitimate
+ # push head - the r1126 macro-upload window (0x3200000, 153600 B)
+ # starts with a fully zero page (the method stream lands later in
+ # the window); the old `not any(first)` skip threw exactly that
+ # candidate away (the r1128 sweep tried 166 kernel-vmap windows and
+ # rejected the true one on this check). The prefix walk trivially
+ # passes an all-zero page; the full-window accept is the gate.
+ if any(first) and not _prefix_walk_ok(first, off):
continue
buf = _read_window(kc, cpu, off, ln)
- if buf is None or not _stream_parse_full(buf):
+ if buf is None:
+ continue
+ acc, mode = _window_accept(buf, ln)
+ if not acc:
continue
pas = []
for j in range(need):
@@ -755,8 +889,8 @@ def resolve_va_strict(kc, ent_va, off, ln, out, pid=None):
out.append("0x%x,0x%x,1\n" % (page + j * 0x1000, vpa))
_area_off[lo] = adj
_gl_fam[gl_base] = (lo, adj)
- dbg("demand-va: 0x%x -> pa=0x%x (strict parse %u B ok pages=%u"
- " area=0x%x adj=%+d)" % (page, pas[0], ln, need, lo, adj))
+ dbg("demand-va: 0x%x -> pa=0x%x (%s parse %u B ok pages=%u"
+ " area=0x%x adj=%+d)" % (page, pas[0], mode, ln, need, lo, adj))
return True
dbg("demand-va: 0x%x strict parse failed (%u B, %u windows tried)"
% (page, ln, tried))
@@ -2095,7 +2229,8 @@ def main():
# deinit), so the set deliberately persists across client switches.
pool_pages = {}
pool_reemit_next = 0.0
- _want_state = {"mtime": None, "done": set(), "strict_pending": {}}
+ _want_state = {"mtime": None, "done": set(), "strict_pending": {},
+ "strict_try": {}}
loop = 0
while True:
pid = cdt_pid()
@@ -2243,6 +2378,18 @@ def main():
epage = ev & ~0xFFF
if epage in _want_state["done"]:
continue
+ # SESSION 153-CONTINUED-65: throttle anchor
+ # re-resolution. The full-offset sweep can read
+ # thousands of windows (tens of seconds of CPU);
+ # the demand file re-sends the anchor every ~2 s,
+ # so without this throttle the scanner starves the
+ # client (both measured at ~91% CPU at r1125). One
+ # attempt per anchor per window is enough - content
+ # that lands late is picked up on the next window.
+ lt = _want_state["strict_try"].get(epage, 0.0)
+ if now_d - lt < ANCHOR_RETRY_S:
+ continue
+ _want_state["strict_try"][epage] = now_d
got = []
try:
ok = resolve_va_strict(kc, ev, eoff, eln, got,
@@ -2250,6 +2397,20 @@ def main():
except Exception:
ok = False
got = []
+ if not ok and eoff:
+ # The demand's off is the entry va's in-page
+ # offset; the push content can be page-aligned
+ # instead (r1123 vs r1125 parse behaviour). A
+ # second try at page alignment is cheap next to
+ # the completed sweep and the same pins result.
+ got2 = []
+ try:
+ ok = resolve_va_strict(kc, ev & ~0xFFF, 0,
+ eln, got2, pid)
+ except Exception:
+ ok = False
+ if ok:
+ got = got2
if ok and got:
lines.extend(got)
for g in got: