summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 11:20:09 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 11:20:09 -0400
commit99945b39da4fb2fc0e649c93f38ee66968551efa (patch)
treea6ba62e412a657c5aa5d878ac1da8438cdb91094 /tools/guest-diag
parent2954ee131bed4f0fa6aacae46bb6f987799135e2 (diff)
downloadopen-iov-99945b39da4fb2fc0e649c93f38ee66968551efa.tar.gz
open-iov-99945b39da4fb2fc0e649c93f38ee66968551efa.zip
wall2 session 89: GMMU PT walk replicated host-side + live inject (validated: ring entries and push method streams match); gpfifo done 0->6; root cause of -4 = release-apply gates (gr_forward defer-arm, op_tr_pending opdef withholding) + unresolvable counter page 0x20400f000 (slot stays pending); counter-page {pa,va} record tables found for host-side resolve (live only)
Diffstat (limited to 'tools/guest-diag')
-rw-r--r--tools/guest-diag/wall2/README-session77.md72
-rw-r--r--tools/guest-diag/wall2/live_walk.py142
-rw-r--r--tools/guest-diag/wall2/pt_bind.py108
-rw-r--r--tools/guest-diag/wall2/walk.py178
4 files changed, 500 insertions, 0 deletions
diff --git a/tools/guest-diag/wall2/README-session77.md b/tools/guest-diag/wall2/README-session77.md
index 22247d6..d276e9e 100644
--- a/tools/guest-diag/wall2/README-session77.md
+++ b/tools/guest-diag/wall2/README-session77.md
@@ -390,3 +390,75 @@ alloc params (or scan the guest for the ring VA) and inject the ring page.
**5. gr_walk.py / gr_walk2.py** are the guest-side kcore walkers (fresh-pass
memdesc discovery; 12 records found, kptrs readable in-pass) kept as the
alternate grounding path.
+
+## Session 89 addendum - THE PT WALK WORKS (live); the completion-word gap
+
+**1. GMMU PT walk resolved LIVE (the mission's PT-binding step: DONE).**
+The service's own walker math (svc_gmmu_walk: root_bits=ilog2(entries),
+shift=49-root_bits; L1 shift38, L2 shift29, PD0 idx=(va>>21)&0xff with 16-byte
+dual big/small entries, PT idx=(va>>12)&0x1ff; PDE bit0=0, PTE bit0=1;
+pte_addr=(e>>8)<<12) was replicated host-side over /dev/shm/vgpu-guest-ram
+(walk.py / live_walk.py). The LIVE published pdb (dmesg: "page directory
+... pdb=0x... entries=4" - a NEW pdb per client, e.g. 0x105f0a000 /
+0x12a359000 / 0x107012000) resolves every client VA while the client runs:
+ring 0x44a0000 -> PA, windows 0x4480000/0x4485000/0x4ca0000..., releases
+0x4213000/0x45a0bb0, and the injection via sema_map_add (ap=1 sysmem) makes
+the service read them. VALIDATED BY CONTENT: the ring page held exactly the
+GPFIFO entries the service logged (entry[1] raw=8000040004480598 va=0x4480598
+len=4), and the push page at va=0x4480598 held the method stream
+(...0x20020047...). Post-mortem reads fail: the guest frees/zeros the
+structures and pdb pages when the client dies - ALL resolution must run
+LIVE during the client's ~11 s life (live_walk.py polls dmesg every 0.4 s,
+parses pdb + VAs, walks, injects; static family list pre-seeds the
+deterministic VAs).
+
+**2. Progress with the walk+inject live: gpfifo done=0 -> 6-9.**
+With the live injections the service decoded and CONSUMED the whole GR ring
+(entries raw logged, releases decoded: rel=0x4213000 pay=1, rel=0x45a0bb0
+pay=3, have_rel=1) and submissions reached 6. The client STILL ends
+vkCreateDevice -4 (~11 s, RM 10 s poll of control 0x20800a70) because the
+releases are decoded but NOT APPLIED and the client's completion counter
+page is unresolvable.
+
+**3. Why releases do not apply (two gates found).**
+(a) svc_fwd_defer_arm(): with gsp_phys_gr_forward=1 a GR ring candidate that
+is valid but not "offered" keeps defer armed - every GR release goes to the
+forwarded-hardware path (which never runs) instead of the inline software
+apply. Cycling with gsp_phys_gr_forward=0 removes the arm, but the release
+still does not apply - the decode's apply path is additionally gated by
+svc.op_tr_pending (a global "operand targeted scan in progress" state) via
+svc_opdef_release: while op_tr_pending=1 every release is recorded into an
+opdef slot and withheld, silently when the slot already contains it. The
+live symptom: `counter page 0x20400f000 unreadable via
+vaspaces/discovered/targeted; slot stays pending` repeated at the client's
+poll rate plus repeated `targeted resolve va=0x20400f000: full RAM scan
+found nothing`.
+
+**4. The counter/notifier page record tables (host-side resolve).**
+Searching the RAM for the counter-page VA as u64 finds 16-byte {pa,va}
+record arrays, e.g. {0x5FEDC000,0x20400D800},{0x5FEDD000,0x20400E000},
+{0x5FEDE000,0x20400E800},{0x5FEDF000,0x20400F000},... (VA step 0x800,
+PA step 0x1000) and a second variant ({0x5FEEE000,0x20400E000},
+{0x5FEEF000,0x20400F000}). These are how the counter page (and probably
+the notifier, notif=0x1xxxxxxx0 per registration) can be resolved WITHOUT
+the VA-space walk: search the VA u64, parse the neighbouring u64 as PA,
+inject. Must be done live (records vanish post-mortem). NOTE: the service's
+own targeted scan reports "found nothing" for this VA - the record layout
+the host parses is not what that scan looks for.
+
+**5. Next steps (ranked) for session 90.**
+1. Extend live_walk.py with a record-resolve fallback: for any VA the
+ service logs as unreadable/pending/stalled (regex set already widens),
+ search the RAM for its u64, parse {pa,va} records (both orders), inject
+ candidates; feed it counter-page (0x20400f000 family), notifier
+ (notif=... per registration) and release VAs BEFORE the decode (static
+ list already covers the deterministic ones: add 0x20400f000 with the
+ record-resolve because the pdb walk cannot reach the UVM/counter space).
+2. Or fix the two release gates kernel-side: (a) don't arm the GR deferral
+ when the forwarding engine has no proven channel (offered flag); (b)
+ treat op_tr_pending as per-request state (clear when no scan is actually
+ in progress for the entry) so a stuck global does not withhold every
+ release. Both are small, testable changes (gsp_sema_service.c).
+3. Watch for the first live sign: `rel-apply ... written` / `pub-adv` /
+ `counter auto/derived` lines on the client's channel; then VKDEVICE
+ should pass and the flow proceeds to vkAllocateMemory.
diff --git a/tools/guest-diag/wall2/live_walk.py b/tools/guest-diag/wall2/live_walk.py
new file mode 100644
index 0000000..48f131f
--- /dev/null
+++ b/tools/guest-diag/wall2/live_walk.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python3
+# live_walk.py - live PT-walk + inject loop for the running client.
+# Polls dmesg for the published page directory (pdb) and for the VAs the
+# service is failing to read (sema-entry: unread / entry[] lines / registered
+# gpfifo), walks each VA through the pdb with the service's own GMMU math,
+# and injects resolved pairs via sema_map_add (ap=1 sysmem).
+import mmap, re, subprocess, sys, time
+
+F = "/dev/shm/vgpu-guest-ram"
+PARAM = "/sys/module/vgsp/parameters/sema_map_add"
+SEMA_PA_MAX = 0x180000000
+
+WINDOW_VAS = [0x44a0000, 0x4500000, 0x4560000, # rings
+ 0x4480000, 0x4485000, 0x4ca0000, 0x4ca5000,
+ 0x4d00000, 0x4b17000, 0x4020000, 0x4100000, 0x4160000,
+ 0x4213000, 0x45a0bb0, # ctx-init releases (deterministic)
+ 0x124935060, 0x102e34060, 0x108d4f060, 0x12a3a4060, 0x116c0b060, # notif families
+ 0x20400f000, 0x20400ffc0, 0x20480fef0, 0x204827f7c] # counter pages / tracker VAs
+
+mm = None
+def off(pa): return pa if pa < 0x80000000 else pa - 0x80000000
+def rd64(pa):
+ if pa + 8 > SEMA_PA_MAX: return None
+ d = mm[off(pa):off(pa)+8]
+ return int.from_bytes(d, "little") if len(d) == 8 else None
+def pde_ap(ap): return "FB" if ap == 1 else ("SYS" if ap in (2, 3) else None)
+def pte_ap(ap): return "FB" if ap == 0 else (None if ap == 1 else "SYS")
+def pte_addr(e, fb): return ((e >> 8) & 0x1FFFFFF) << 12 if fb else ((e >> 8) & 0x3FFFFFFFFFFF) << 12
+def pte_leaf(table, idx):
+ e = rd64(table + 8 * idx)
+ if e is None or not (e & 1): return None
+ ap = pte_ap((e >> 1) & 3)
+ if ap is None: return None
+ a = pte_addr(e, ap == "FB")
+ return (a, ap) if a + 8 <= SEMA_PA_MAX else None
+
+def walk(pdb, va, entries=4):
+ import math
+ root_bits = max(1, min(12, int(math.log2(entries or 1))))
+ table = pdb
+ for level in range(3):
+ if level == 0:
+ bits, shift = root_bits, 49 - root_bits
+ else:
+ bits, shift = 9, 38 if level == 1 else 29
+ idx = (va >> shift) & ((1 << bits) - 1)
+ e = rd64(table + 8 * idx)
+ if e is None or (e & 1): return None
+ ap = pde_ap((e >> 1) & 3)
+ if ap is None: return None
+ table = pte_addr(e, ap == "FB")
+ idx = (va >> 21) & 0xff
+ big = rd64(table + 16 * idx); small = rd64(table + 16 * idx + 8)
+ if big is None or small is None: return None
+ if not (small & 1):
+ sap = pde_ap((small >> 1) & 3)
+ if sap is not None:
+ r = pte_leaf(pte_addr(small, sap == "FB"), (va >> 12) & 0x1ff)
+ if r: return r[0] + (va & 0xfff), r[1]
+ if big & 1:
+ bap = pte_ap((big >> 1) & 3)
+ if bap is not None:
+ a = pte_addr(big, bap == "FB") + (va & ((1 << 21) - 1))
+ if a + 8 <= SEMA_PA_MAX: return a, bap
+ else:
+ bap = pde_ap((big >> 1) & 3)
+ if bap is not None:
+ r = pte_leaf(pte_addr(big, bap == "FB"), (va >> 16) & 0x1f)
+ if r: return r[0] + (va & 0xffff), r[1]
+ return None
+
+def inject(va, pa, ln=0x1000):
+ cmd = "echo 0x%x,0x%x,0x%x,1 > %s" % (va & ~0xfff, pa & ~0xfff, ln, PARAM)
+ r = subprocess.run(["sudo", "sh", "-c", cmd], capture_output=True)
+ print(" INJECT 0x%x -> 0x%x len=0x%x : %s" %
+ (va & ~0xfff, pa & ~0xfff, ln, "ok" if r.returncode == 0 else "FAIL"), flush=True)
+ return r.returncode == 0
+
+def main():
+ global mm
+ f = open(F, "rb")
+ mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+ dur = float(sys.argv[1]) if len(sys.argv) > 1 else 300
+ end = time.time() + dur
+ pdbs = [] # (pdb_pa, entries)
+ done = set() # (pdb, va_page)
+ seen_va = set()
+ last_lines = 0
+ print("# live_walk start (%.0fs)" % dur, flush=True)
+ while time.time() < end:
+ out = subprocess.run(["sudo", "dmesg"], capture_output=True).stdout.decode(errors="ignore")
+ lines = out.splitlines()[-500:]
+ out = "\n".join(lines)
+ # pdb offers/registrations
+ for m in re.finditer(r"page directory (?:offered as candidate )?pdb=0x([0-9a-f]+) entries=(\d+)", out):
+ pdb, ent = int(m.group(1), 16), int(m.group(2))
+ if (pdb, ent) not in pdbs:
+ pdbs.append((pdb, ent))
+ print("# pdb 0x%x entries=%u" % (pdb, ent), flush=True)
+ # VAs the service is failing on
+ vas = []
+ for m in re.finditer(r"sema-entry: unread h=0x[0-9a-f]+ idx=\d+ va=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"sema-chan: entry\[\d+\] h=0x[0-9a-f]+ eng=\d+ raw=[0-9a-f]+ va=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"registered gpfifo=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"GR ring h=0x[0-9a-f]+ rank=\d+ not at fb 0x[0-9a-f]+ yet \(e0=0x[0-9a-f]+ e1=0x[0-9a-f]+ va=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"rel=0x([0-9a-f]+) ", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"notif=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"counter page 0x([0-9a-f]+) unreadable", out):
+ vas.append(int(m.group(1), 16))
+ for m in re.finditer(r"release (?:learned|derived) h=0x[0-9a-f]+ pa=0x[0-9a-f]+ \(from release va=0x([0-9a-f]+)", out):
+ vas.append(int(m.group(1), 16))
+ vas = [v for v in vas if 0x4000000 <= v < 0x2000000000]
+ for va in WINDOW_VAS + vas:
+ seen_va.add(va)
+ # resolve + inject through every pdb
+ for (pdb, ent) in pdbs:
+ for va in list(seen_va):
+ key = (pdb, va & ~0xfff)
+ if key in done:
+ continue
+ r = walk(pdb, va, ent)
+ if not r:
+ continue
+ done.add(key)
+ pa, ap = r
+ if ap != "SYS":
+ print("# va=0x%x -> pa=0x%x ap=%s (skip non-sys)" % (va, pa, ap), flush=True)
+ continue
+ print("# walk ok pdb=0x%x va=0x%x -> pa=0x%x" % (pdb, va, pa), flush=True)
+ inject(va, pa)
+ time.sleep(0.4)
+ mm.close(); f.close()
+ print("# live_walk done", flush=True)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/guest-diag/wall2/pt_bind.py b/tools/guest-diag/wall2/pt_bind.py
new file mode 100644
index 0000000..126ed01
--- /dev/null
+++ b/tools/guest-diag/wall2/pt_bind.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# pt_bind.py - bind GMMU PT pages to VA bases and read out VA->PA for the
+# client's ring/window family. Strategy:
+# 1. ring-page candidates: pages whose 8B-aligned u64 pairs look like
+# GPFIFO entries (e0 = push VA & ~3 in the 0x44x-0x4ex families with
+# va<4G; e1 top byte 0; length bits sane).
+# 2. anchor: for each candidate PA and each family VA, check every 4KB
+# page for entry idx=(VA>>12)&0x1ff whose PA part == candidate PA.
+# A hit binds (PT page, VA, PA).
+# 3. read out: from bound PT pages, decode entries for the whole VA list.
+import mmap, struct, sys
+
+F = "/dev/shm/vgpu-guest-ram"
+FAMILY = [
+ 0x4020000, 0x4100000, 0x4160000, # r689 eng rings
+ 0x44a0000, 0x4500000, 0x4560000, # r690 ring VAs
+ 0x4480000, 0x4485000, 0x4480980, 0x4480f1c,
+ 0x4ca0000, 0x4ca5000, 0x4d00000, 0x4b17000,
+]
+
+def gpa_to_off(pa):
+ return pa if pa < 0x80000000 else pa - 0x80000000
+
+def off_to_gpa(off):
+ return off if off < 0x80000000 else off + 0x80000000
+
+def pte_pa(v):
+ return (v >> 8) << 12
+
+def main():
+ f = open(F, "rb")
+ mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+ size = len(mm)
+ print("# file size 0x%x" % size)
+
+ # ---- pass 1: ring-page candidates by content ----
+ cands = []
+ pos = 0
+ step = 4096
+ while pos + step <= size:
+ d = mm[pos:pos+step]
+ hits = []
+ for i in range(0, step, 8):
+ e0 = int.from_bytes(d[i:i+4], "little")
+ e1 = int.from_bytes(d[i+4:i+8], "little")
+ if 0x4400000 <= e0 <= 0x4e00000 and (e0 & 3) == 0 and (e1 & 0xff) == 0:
+ lw = (e1 >> 10) & 0x1fffff
+ if 0x40 <= lw <= 0x100000:
+ hits.append((i, e0, lw << 2))
+ if len(hits) >= 3:
+ cands.append((off_to_gpa(pos), hits))
+ if len(cands) >= 12:
+ break
+ pos += step
+ print("# ring-page candidates: %d" % len(cands))
+ for pa, hits in cands:
+ print("cand pa=0x%x hits=%d first=%s" %
+ (pa, len(hits), ["0x%x/0x%x" % (h[1], h[2]) for h in hits[:4]]))
+ if not cands:
+ return 1
+
+ # ---- pass 2: anchor PT pages for candidate PAs x family VAs ----
+ idxs = {}
+ for va in FAMILY:
+ idxs.setdefault((va >> 12) & 0x1ff, []).append(va)
+ cand_pas = set(pa for pa, _ in cands)
+ bound = []
+ pos = 0
+ while pos + step <= size:
+ pg = off_to_gpa(pos)
+ # skip scanning the candidate pages themselves
+ if pg not in cand_pas:
+ d = mm[pos:pos+step]
+ for idx, vas in idxs.items():
+ o = idx * 8
+ v = int.from_bytes(d[o:o+8], "little")
+ if v & 1:
+ pa = pte_pa(v)
+ if pa in cand_pas:
+ bound.append((pg, idx, vas, pa, v & 0xff))
+ pos += step
+ print("# PT anchors found: %d" % len(bound))
+ for pg, idx, vas, pa, fl in bound[:20]:
+ print("PT pa=0x%x entry[0x%x] flags=0x%x -> PA=0x%x (VA cands %s)" %
+ (pg, idx, fl, pa, ["0x%x" % x for x in vas]))
+
+ # ---- pass 3: read out PT entries for the whole family from each bound PT page ----
+ seen_pt = set()
+ for pg, idx, vas, pa, fl in bound:
+ if pg in seen_pt:
+ continue
+ seen_pt.add(pg)
+ off = gpa_to_off(pg)
+ d = mm[off:off+4096]
+ print("== PT page pa=0x%x family readout:" % pg)
+ for va in FAMILY:
+ i = (va >> 12) & 0x1ff
+ v = int.from_bytes(d[i*8:i*8+8], "little")
+ if v & 1:
+ print(" va=0x%x idx=0x%x -> pa=0x%x (raw=0x%x fl=0x%x)" %
+ (va, i, pte_pa(v), v, v & 0xff))
+ else:
+ print(" va=0x%x idx=0x%x -> absent (raw=0x%x)" % (va, i, v))
+ mm.close(); f.close()
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tools/guest-diag/wall2/walk.py b/tools/guest-diag/wall2/walk.py
new file mode 100644
index 0000000..3624007
--- /dev/null
+++ b/tools/guest-diag/wall2/walk.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+# walk.py - host-side replica of the service's svc_gmmu_walk + pd_discover,
+# run against /dev/shm/vgpu-guest-ram. Discovers the page directory that
+# reproduces the service's map[] sample pairs, then walks the ring/window
+# VAs through it.
+import mmap, struct, sys
+
+F = "/dev/shm/vgpu-guest-ram"
+SEMA_PA_MAX = 0x180000000
+
+# samples from the r690 dmesg map[] lines
+SAMPLES = [
+ (0x100000000, 0x100326000),
+ (0x100e2f000, 0x10096e000),
+ (0x100e33000, 0x10096b000),
+ (0x100e34000, 0x10096a000),
+ (0x100e35000, 0x100969000),
+ (0x100e37000, 0x102dcb000),
+ (0x100e38000, 0x102dca000),
+ (0x100e39000, 0x102dc9000),
+]
+
+TARGET_VAS = [0x44a0000, 0x4500000, 0x4560000, 0x4480000, 0x4485000,
+ 0x4ca0000, 0x4ca5000, 0x4d00000, 0x4b17000, 0x4020000,
+ 0x4100000, 0x4160000]
+
+mm = None
+
+def off(pa):
+ return pa if pa < 0x80000000 else pa - 0x80000000
+
+def rd(pa, n=8):
+ if pa + n > SEMA_PA_MAX:
+ return None
+ try:
+ d = mm[off(pa):off(pa)+n]
+ return d if len(d) == n else None
+ except Exception:
+ return None
+
+def rd64(pa):
+ d = rd(pa, 8)
+ return None if d is None else int.from_bytes(d, "little")
+
+def pde_aperture(ap):
+ if ap == 1: return "FB"
+ if ap in (2, 3): return "SYS"
+ return None
+
+def pte_aperture(ap):
+ if ap == 0: return "FB"
+ if ap == 1: return None
+ return "SYS"
+
+def pte_addr(e, fb):
+ if fb:
+ return ((e >> 8) & 0x1FFFFFF) << 12
+ return ((e >> 8) & 0x3FFFFFFFFFFF) << 12
+
+def pte_leaf(table, idx):
+ e = rd64(table + 8 * idx)
+ if e is None or not (e & 1):
+ return None
+ ap = pte_aperture((e >> 1) & 0x3)
+ if ap is None:
+ return None
+ a = pte_addr(e, ap == "FB")
+ if not fb_ok(a):
+ return None
+ return a, ap
+
+def fb_ok(a):
+ return a + 8 <= SEMA_PA_MAX
+
+def walk(va):
+ table = None
+ # root_bits = ilog2(entries=4) = 2; shift = 49-2 = 47
+ e = rd64(table) if False else None
+ # level 0
+ idx = (va >> 47) & 0x3
+ # root table is the candidate itself (passed separately)
+ return None
+
+def walk_from(pdb, va, trace=False):
+ table = pdb
+ for level in range(3):
+ if level == 0:
+ bits, shift = 2, 47
+ else:
+ bits, shift = 9, 38 if level == 1 else 29
+ idx = (va >> shift) & ((1 << bits) - 1)
+ e = rd64(table + 8 * idx)
+ if e is None:
+ return None
+ if trace:
+ print(" L%d table=0x%x idx=%u e=0x%016x" % (level, table, idx, e))
+ if e & 1:
+ return None
+ ap = pde_aperture((e >> 1) & 0x3)
+ if ap is None:
+ return None
+ table = pte_addr(e, ap == "FB")
+ if not fb_ok(table):
+ return None
+ idx = (va >> 21) & 0xff
+ big = rd64(table + 16 * idx)
+ small = rd64(table + 16 * idx + 8)
+ if big is None or small is None:
+ return None
+ if trace:
+ print(" PD0 table=0x%x idx=%u big=0x%016x small=0x%016x" %
+ (table, idx, big, small))
+ if not (small & 1):
+ sap = pde_aperture((small >> 1) & 0x3)
+ if sap is not None:
+ t = pte_addr(small, sap == "FB")
+ r = pte_leaf(t, (va >> 12) & 0x1ff)
+ if r:
+ return r[0] + (va & 0xfff), r[1]
+ if big & 1:
+ bap = pte_aperture((big >> 1) & 0x3)
+ if bap is not None:
+ a = pte_addr(big, bap == "FB") + (va & ((1 << 21) - 1))
+ if fb_ok(a):
+ return a, bap
+ else:
+ bap = pde_aperture((big >> 1) & 0x3)
+ if bap is not None:
+ t = pte_addr(big, bap == "FB")
+ r = pte_leaf(t, (va >> 16) & 0x1f)
+ if r:
+ return r[0] + (va & 0xffff), r[1]
+ return None
+
+def main():
+ global mm
+ f = open(F, "rb")
+ mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
+ size = len(mm)
+ print("# scanning for directory candidates...")
+ cands = []
+ for fo in range(0, size, 4096):
+ e = int.from_bytes(mm[fo:fo+8], "little")
+ if e and not (e & 1):
+ ap = (e >> 1) & 0x3
+ if ap in (1, 2, 3):
+ pa = fo if fo < 0x80000000 else fo + 0x80000000
+ if pa < SEMA_PA_MAX:
+ cands.append(pa)
+ print("# candidates: %d" % len(cands))
+ best = []
+ for c in cands:
+ hits = 0
+ for (sva, spa) in SAMPLES:
+ r = walk_from(c, sva)
+ if r and (r[0] & ~0xfff) == (spa & ~0xfff):
+ hits += 1
+ if hits >= 2:
+ best.append((hits, c))
+ best.sort(reverse=True)
+ print("# candidates with >=2 hits: %d" % len(best))
+ for hits, c in best[:10]:
+ print(" pdb=0x%x hits=%d/8" % (c, hits))
+ if not best:
+ return 1
+ pdb = best[0][1]
+ print("== walking targets through pdb=0x%x:" % pdb)
+ for va in TARGET_VAS:
+ r = walk_from(pdb, va)
+ if r:
+ print(" va=0x%x -> pa=0x%x ap=%s" % (va, r[0], r[1]))
+ else:
+ print(" va=0x%x -> no translation" % va)
+ mm.close(); f.close()
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())