#!/usr/bin/env python3 """Extract canary tid=6 timeline at and just before the same point ours wedges. The matched-prefix endpoint is when ours's tid=1 calls NtWaitForSingleObjectEx on tid=13.handle at host_ns=1.727s. In canary, tid=6's analog wait is the sub_82173990 KeWaitForSingleObject INFINITE — but in canary it completes when the spawned worker (tid=17 = sub_821748F0 body) terminates. Need to find that wait in canary's stream. Output: ordered timeline of canary tid=6 from spawn-of-sub_821748F0 through install-epoch. """ import json import os from collections import Counter INPUT = "/home/fabi/RE - Project Sylpheed/xenia-canary/build-cross/bin/Windows/Debug/canary-jitter-1.jsonl" OUTDIR = os.path.dirname(os.path.abspath(__file__)) TARGET_TID = 6 # Capture canary tid=6 from t=1.5s through t=11s (sub_821748F0 spawn through worker fan-out). T_LO = 1_500_000_000 T_HI = 11_000_000_000 kernel_calls = [] kernel_returns = [] import_calls = [] handle_creates = [] thread_events = [] wait_events = [] other = [] with open(INPUT, "r") as f: for line in f: if '"host_ns":' not in line: continue try: i = line.index('"host_ns":') + len('"host_ns":') j = i while j < len(line) and (line[j].isdigit() or line[j] == '-'): j += 1 host_ns = int(line[i:j]) except (ValueError, IndexError): continue if host_ns < T_LO: continue if host_ns >= T_HI: break # Quick tid filter. if f'"tid":{TARGET_TID},' not in line: continue try: ev = json.loads(line) except json.JSONDecodeError: continue if ev.get("tid") != TARGET_TID: continue kind = ev.get("kind", "") if kind == "kernel.call": kernel_calls.append(ev) elif kind == "kernel.return": kernel_returns.append(ev) elif kind == "handle.create": handle_creates.append(ev) elif kind in ("thread.create", "thread.exit"): thread_events.append(ev) elif kind == "import.call": import_calls.append(ev) elif kind in ("wait.begin", "wait.end", "wait.wake"): wait_events.append(ev) else: other.append(ev) print(f"canary tid={TARGET_TID} in window [{T_LO/1e9}..{T_HI/1e9}s]") print(f" kernel.call: {len(kernel_calls)}") print(f" kernel.return: {len(kernel_returns)}") print(f" handle.create: {len(handle_creates)}") print(f" thread.create/exit: {len(thread_events)}") print(f" import.call: {len(import_calls)}") print(f" wait.* {len(wait_events)}") # Save full timeline. all_evts = [] for ev in kernel_calls + kernel_returns + handle_creates + thread_events + wait_events: all_evts.append((ev["host_ns"], ev["tid_event_idx"], ev["kind"], ev["payload"])) all_evts.sort() # Find anchor points: # 1. ExCreateThread with entry=0x821748f0 (the matched spawn site). # 2. NtWaitForSingleObjectEx on the resulting handle (the analog of ours's wedge). # 3. Wait return time. # 4. Subsequent calls that lead to sub_825070F0 fan-out at host_ns ~10.383s. print("\n=== Looking for anchor: ExCreateThread on entry 0x821748f0 ===") anchor_idx = -1 anchor_ns = -1 anchor_handle = None for i, (host_ns, idx, kind, payload) in enumerate(all_evts): if kind == "thread.create": entry = payload.get("entry_pc", "") if entry == "0x821748f0" or entry == "0x821748F0": print(f" Found at idx={idx} host_ns={host_ns} ({host_ns/1e9:.3f}s)") print(f" payload={json.dumps(payload)}") anchor_idx = i anchor_ns = host_ns anchor_handle = payload.get("handle_semantic_id") break # Locate the next NtWaitForSingleObjectEx on tid=6 - that's the join wait. print("\n=== Finding the join-wait on tid=6 after ExCreateThread ===") for i in range(anchor_idx, min(anchor_idx + 200, len(all_evts))): host_ns, idx, kind, payload = all_evts[i] if kind == "wait.begin": if anchor_handle and anchor_handle in payload.get("handles_semantic_ids", []): print(f" Join wait.begin at idx={idx} host_ns={host_ns} ({host_ns/1e9:.3f}s)") print(f" timeout_ns={payload.get('timeout_ns')}") join_wait_start_ns = host_ns join_wait_start_eidx = i break elif kind == "kernel.call" and payload.get("name") == "KeWaitForSingleObject": print(f" KeWait at idx={idx} host_ns={host_ns} ({host_ns/1e9:.3f}s)") # Look for wait.end / wait.wake. print("\n=== Finding the join-wait completion ===") for i in range(anchor_idx, len(all_evts)): host_ns, idx, kind, payload = all_evts[i] if kind in ("wait.end", "wait.wake"): if anchor_handle and anchor_handle in payload.get("handles_semantic_ids", []): print(f" Wait wake at idx={idx} host_ns={host_ns} ({host_ns/1e9:.3f}s) kind={kind}") print(f" payload={json.dumps(payload)[:300]}") join_wait_end_ns = host_ns join_wait_end_eidx = i wait_duration_ns = host_ns - join_wait_start_ns print(f" DURATION: {wait_duration_ns/1e9:.3f} s") break # Save the full timeline window from join-wait spawn (anchor) through end. with open(os.path.join(OUTDIR, "canary-tid6-from-anchor.csv"), "w") as f: f.write("host_ns,tid_event_idx,kind,name,detail\n") for host_ns, idx, kind, payload in all_evts[anchor_idx:]: name = payload.get("name", "") detail = json.dumps(payload)[:400].replace('"', '""') f.write(f'{host_ns},{idx},{kind},{name},"{detail}"\n') print(f"\nWrote canary-tid6-from-anchor.csv with {len(all_evts) - anchor_idx} events.")