diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-31 08:59:10 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-31 08:59:10 -0400 |
| commit | c0a62cfcec2b09fff7bf0f86a129f7a04a27d70a (patch) | |
| tree | ad024765ee10d2c5d2c6157639c3839a6bea8e68 /tools/guest-diag/guest_pin_stream.py | |
| parent | 520ad3e2f99b4e15c8778874e1b4b188c6d16b77 (diff) | |
| download | open-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/guest_pin_stream.py')
| -rw-r--r-- | tools/guest-diag/guest_pin_stream.py | 211 |
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: |
