#!/usr/bin/env python3 """Which guest words are still changing while the game is frozen? `mission-freeze-resume-spin.md` establishes that a frozen run is guest code SPINNING — the main thread stays in state `R` and gains ~40 % of a core over ten seconds while making not one kernel call. Spinning code is waiting for something in guest memory to change, and its own bookkeeping is the only thing still moving. So diff guest RAM against itself across a few seconds: while the game runs this is hopeless (everything moves), but while it is frozen the survivors should be a handful of words — the loop's counter, and whatever it polls. The comparison is over the file's *data extents* only (`SEEK_DATA`), so the 4.6 GB of sparse holes cost nothing, and the first snapshot is kept in memory per extent. Usage: live_delta.py [gap_s] [max_report] [--range LO HI] """ import os import sys import time import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import gmem # noqa: E402 def snapshot(fd, size, lo=None, hi=None): out = [] f0 = gmem.va_to_off(lo) if lo is not None else 0 f1 = gmem.va_to_off(hi) if hi is not None else size for a, b in gmem.extents(fd, size): a, b = max(a, f0), min(b, f1) n = (b - a) // 4 * 4 if n >= 4: out.append((a, np.frombuffer(os.pread(fd, n, a), dtype=">u4"))) return out def main(): gap = float(sys.argv[1]) if len(sys.argv) > 1 else 6.0 top = int(sys.argv[2]) if len(sys.argv) > 2 else 60 lo = hi = None if "--range" in sys.argv: i = sys.argv.index("--range") lo, hi = int(sys.argv[i + 1], 0), int(sys.argv[i + 2], 0) path = gmem.mem_path() fd = os.open(path, os.O_RDONLY) size = os.path.getsize(path) a = snapshot(fd, size, lo, hi) total = sum(len(x[1]) for x in a) * 4 print(f"# {path}: {len(a)} extents, {total/1e6:.1f} MB of data; " f"waiting {gap}s", flush=True) time.sleep(gap) b = dict((off, arr) for off, arr in snapshot(fd, size, lo, hi)) changed = [] for off, arr0 in a: arr1 = b.get(off) if arr1 is None or len(arr1) != len(arr0): continue idx = np.flatnonzero(arr0 != arr1) for i in idx: changed.append((off + int(i) * 4, int(arr0[i]), int(arr1[i]))) print(f"# {len(changed)} words changed in {gap}s " f"({len(changed)*4/max(total,1)*100:.4f} % of the data)", flush=True) # A flat list is useless at 235 973 hits. Summarise by 1 MB region first: # where the activity is says far more than which word moved. from collections import Counter reg = Counter() for o, _, _ in changed: va = gmem.primary_va(o) reg[(va >> 20) << 20 if va is not None else 0] += 1 print("# by 1 MB region (top 12):") for base, n in reg.most_common(12): print(f" {base:#010x} {n:8d} words") for o, v0, v1 in changed[:top]: va = gmem.primary_va(o) d = v1 - v0 print(f" va {va:#010x} {v0:#010x} -> {v1:#010x}" f"{f' ({v0} -> {v1}, {d:+d})' if abs(v0) < 1 << 28 and abs(v1) < 1 << 28 else ''}") if len(changed) > top: print(f" ... and {len(changed)-top} more") return 0 if __name__ == "__main__": raise SystemExit(main())