From a04eb90d9821fde5f0bdac9a456f8ad3c9c1b635 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 2 Nov 2026 12:26:37 -0500 Subject: guest-diag: global two-pass client-mem candidate order (near offsets across ALL maps first) The r1398 client-mem sweep failed on a window that parses: with 74 qualifying rw mappings ahead of the nvidiactl mapping holding the MME 0x114 pack, their full per-map sweeps burned 37 808 of the global 8 192-candidate budget before the target map was ever reached, while a manual _window_accept of that map accepted at idx 1/2/0/3. Near-first PER MAP is not enough when the budget is global; near-first ACROSS maps is. Collect the candidate maps first and run two phases: phase 0 tries the 19 neighbourhood offsets for every map (cheap, not charged against the budget), phase 1 runs the remaining full sweep under the historical 8192 bound. Content gates unchanged (_window_accept on every candidate before any pin). k==None/0 (unknown family / family base) keeps its single bounded full-sweep phase exactly as before. SESSION 153 r1398; validation on the next cycle (r1399): the strict client-mem parse of 0x4001004's 60216 B window should now succeed and re-emit its 15 pins. --- tools/guest-diag/guest_pin_stream.py | 198 ++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 85 deletions(-) (limited to 'tools') diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py index d8566c2..96c1e43 100644 --- a/tools/guest-diag/guest_pin_stream.py +++ b/tools/guest-diag/guest_pin_stream.py @@ -692,7 +692,20 @@ def resolve_va_client_mem(pid, ent_va, off, ln, out): maps = open('/proc/%d/maps' % pid).read().splitlines() except OSError: return False - tried = 0 + # SESSION 153-r1398: collect the candidate maps first and run a + # GLOBAL two-pass candidate order - the neighbourhood offsets for + # EVERY map, then the full sweep. The r1398 client (glxgears) had + # 74 qualifying rw mappings ahead of the nvidiactl mapping that + # holds the MME 0x114 pack; their full sweeps burned 37 808 of the + # 8 192-candidate budget before the target map was reached, so the + # strict client-mem parse answered False while a manual parse of + # the same map accepted at idx 1 (near-first PER MAP is not enough + # when the budget is global; near-first ACROSS maps is). The + # content gates are unchanged: only _window_accept passes emit. + near_def = [0, 1, -1, 2, -2, 4, -4, 8, -8, 16, -16, 32, -32, + 64, -64, 128, -128, 256, -256] + near_set = set(near_def) + cand_maps = [] for line in maps: parts = line.split() if len(parts) < 5 or 'rw' not in parts[1]: @@ -707,92 +720,107 @@ def resolve_va_client_mem(pid, ent_va, off, ln, out): continue if hi - lo < (need + 4) * 0x1000 or hi - lo > (64 << 20): continue - pages = (hi - lo) >> 12 - 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: - # 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: - break - try: - first = os.pread(mfd, 0x1000 - off, win) - except OSError: - continue - 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 - a = win - ok = True - while left > 0: - n = min(left, 0x1000 - (a & 0xFFF)) + cand_maps.append((lo, (hi - lo) >> 12)) + tried = 0 + 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). + phases = (0,) + else: + phases = (0, 1) + for phase in phases: + tried = 0 + for (lo, pages) in cand_maps: + if k is None or k == 0: + adjs = range(0, max(1, pages - need + 1)) + capped = True + elif phase == 0: + # 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. + # The near pass is cheap (<=19 windows per map) and is + # not charged against the r1398 global budget. + adjs = near_def + capped = False + else: + adjs = [a for a in range(-k, pages - need + 1 - k) + if a not in near_set] + capped = True + 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 capped and tried > 8192: + break try: - d = os.pread(mfd, n, a) + first = os.pread(mfd, 0x1000 - off, win) except OSError: - ok = False - break - if not d or len(d) != n: - ok = False - break - buf += d - a += n - left -= n - if not ok: - continue - acc, mode = _window_accept(buf, ln) - if not acc: - continue - pas = [] - for j in range(need): - vpg = (ent_va & ~0xFFF) + j * 0x1000 - cpg = lo + (idx + j) * 0x1000 - os.lseek(pfd, (cpg >> 12) * 8, 0) - e = int.from_bytes(os.read(pfd, 8), 'little') - if not (e >> 63) & 1: - pas = None - break - pas.append(((e & ((1 << 55) - 1)) << 12)) - if not pas: - continue - 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 (%s client-mem parse" - " %u B ok pages=%u map=0x%x idx=%d)" - % (ent_va & ~0xFFF, pas[0], mode, ln, need, lo, idx)) - return True - if tried > 8192: + continue + 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 + a = win + ok = True + while left > 0: + n = min(left, 0x1000 - (a & 0xFFF)) + try: + d = os.pread(mfd, n, a) + except OSError: + ok = False + break + if not d or len(d) != n: + ok = False + break + buf += d + a += n + left -= n + if not ok: + continue + acc, mode = _window_accept(buf, ln) + if not acc: + continue + pas = [] + for j in range(need): + vpg = (ent_va & ~0xFFF) + j * 0x1000 + cpg = lo + (idx + j) * 0x1000 + os.lseek(pfd, (cpg >> 12) * 8, 0) + e = int.from_bytes(os.read(pfd, 8), 'little') + if not (e >> 63) & 1: + pas = None + break + pas.append(((e & ((1 << 55) - 1)) << 12)) + if not pas: + continue + 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 (%s client-mem parse" + " %u B ok pages=%u map=0x%x idx=%d)" + % (ent_va & ~0xFFF, pas[0], mode, ln, need, lo, idx)) + return True + # The near pass (phase 0, family anchors) is cheap per map and + # completes for every map; only the full-sweep pass keeps the + # historical 8192-candidate bound (r1398: prior maps burned + # 37 808 candidates before the content map, so the bound must + # never apply to the near pass or it dies before the target). + if (k is None or k == 0 or phase == 1) and tried > 8192: break return False finally: -- cgit v1.2.3