The brief was explicit: nothing is presented as done that has not run on the OS. This post is what ran. The scheduler, thread states and call stacks come from one Event Tracing for Windows session; manual scopes from the same shared-memory ring Linux and macOS use, in a file-backed section; function hooks from the same Frida agent and helper, over a named pipe. Everything that can be checked against the old C++ tracer is.
The kernel logger, from raw bytes
The Windows kernel publishes its scheduler, its sampling profiler and its image loader through the NT Kernel Logger. The obvious way to read it is a bindings crate; the right way, here, was a dozen advapi32 functions declared by hand and the classic kernel events decoded straight from their fixed MOF layouts. The C++ tracer went through the schema parser and lost every context switch on a Windows build whose CSwitch version the parser could not find. A byte offset cannot fail that way: new_tid at offset zero, old_tid at four, the old thread's state and wait reason a few bytes on. The decoder is pure, and every layout has a test written from the documentation.
A context switch says two things at once: a thread left a core and another took it. The Linux perf path sees those as a switch-out and a switch-in, so one ETW event becomes the same two calls on the same ContextSwitchManager the Linux path feeds. That manager is the one the C++ tracer had, ported earlier; its six-case test — a matched slice, an invalid pid, a tid mismatch, the running stats — is reproduced through the Windows feed, so the semantics match the oracle by construction, not by a hopeful differential run.
Samples are the profiler's stack walks. The kernel logs an interrupt and, because stack tracing is enabled for it, the frames of that instant — except the walk is split when the tick lands in the kernel: the kernel frames arrive at once, the user frames in a second walk once the thread is back in user mode, same thread, same timestamp, milliseconds later. The assembler keys walks by thread and instant, merges the halves, drops the kernel frames, and emits one sample innermost-first — the order the Linux unwinder produces and the report counts.
A ring in a file
Manual scopes need a shared-memory segment the service can find without the two ever having talked. Linux names it in /dev/shm; macOS uses a file in a private directory so there is something to list. Windows took the macOS route: a file-backed section under %LOCALAPPDATA%\Orbit\scopes, named by pid, so discovery is a directory read exactly as on Linux. The reader maps the rings read-only and the control page read-write — two views of one section — so a bug in the service cannot corrupt the game it is watching. The producer lease is a byte-range lock the kernel drops when the process dies, never a timeout. And because NTFS will not delete a file that still has mapped views, unlinking renames the segment out of the discovery namespace and lets delete-on-close take the bytes when the last handle goes.
The write path is the same arithmetic as everywhere else. A scope costs a ring write and a clock read: 7.9 ns for the write, about 18 ns for QueryPerformanceCounter, 26 ns together. Linux is 16 ns, almost all of the difference in the clock — the vDSO reads a few nanoseconds where QPC reads eighteen. The brief said not to expect parity on the clock. It was right.
Hooks over a pipe
Function hooks are the same native Frida agent and helper as the other two platforms. The control channel is the one Windows-shaped part: a named pipe instead of a Unix socket, with the connecting process authenticated by the pid the kernel reports for the pipe's client, the way SO_PEERCRED authenticates the socket peer on Linux. The Gum adapter reads the target's in-memory PE section table for the file-offset-to-address map, the mirror of what the service does from the file on disk. Three hooks on the sample target produce five hundred and forty scope events across three threads, correctly nested, with manual scopes alongside, and a clean detach.
One thing does not round-trip. The agent emits a detach trampolines self-profile span at teardown, over the pipe. A Windows named pipe has no half-close, so by the time a stop is seen the pipe is already closing and that one span is lost. The detach itself is correct — the hooks come out, the target runs on — and every other setup phase is on the service's own track. The honest thing was to say so, in the docs and in the test, rather than to paper it over.
Symbols and the static binary
Symbols come from the module's PDB, found by the GUID and age in its CodeView record, then its export table, then its exception table's function ranges for anything still unnamed — the three sources the C++ CoffFile read, merged by address with the named entry winning. The PE and PDB readers were already pure Rust and already cross-platform; this is where that work paid off. Disassembly works from the module file; there is no source interleave yet, because the line table lives in the PDB and the disassembler does not read it there.
“Static” on Windows means the static C runtime, not musl. Every shipped binary — the service, the helper, the agent, the instrumentation library — links it, so each depends on system DLLs alone, and the build script proves it with dumpbin /dependents and refuses anything that imports the runtime dynamically.
What is not there
Auto-profiling and the kernel hook engine ride on Linux uprobes and are refused on Windows with a sentence, not a silent no-op. The GPU helper is Linux only. Windows on ARM64 is not built. And the kernel logger needs an elevated prompt: from an ordinary one a capture still records manual scopes and Frida hooks, and the status line says why the scheduler and the samples are missing — the same honesty the Linux build shows under a high perf_event_paranoid.