Capturing Windows Memory Leak Events with ETW VirtualAlloc and Microsoft-Windows-Kernel-Memory
This post documents how memory events are captured with ETW on Windows. It covers which signals come from the classic kernel-provider keywords, which of those keywords silently require the NT Kernel Logger session, what the Microsoft-Windows-Kernel-Memory manifest provider covers instead, and which combination the ET Ducky agent's memory-growth profile settled on for diagnosing leaks with per-process attribution. Most of these constraints are undocumented or documented only in TraceEvent source comments, and we hit each of them while building the profile.
Three routes to memory events
Windows exposes memory telemetry through three distinct mechanisms that are easy to conflate:
- Classic kernel keywords on a kernel trace session. These include
VirtualAlloc,Memory(page faults),MemoryHardFaults, andReferenceSet, among others, enabled as flag bits when the session starts. - The
Microsoft-Windows-Kernel-Memorymanifest provider, enabled by GUID like any user-mode provider, which emits periodic memory-info snapshots and working-set data rather than per-operation events. - Heap snapshots (the xperf
HeapSnapshotmechanism), which have no kernel keyword at all and require a separate capture workflow.
The choice between them is constrained. Several of the classic keywords only function on one specific session, and the manifest provider answers a different question than the keywords do.
The custom-session constraint
Since Windows 8, kernel providers can be enabled on a custom-named trace session rather than the single system-wide NT Kernel Logger. A custom session name is the default for an agent because it cannot collide with another tool claiming the NT Kernel Logger, and multiple diagnostic sessions can coexist. The ET Ducky engine creates its kernel session as ETDucky_Kernel for that reason.
Not every kernel keyword works on a custom session. In our engine the following are detected and skipped with a logged warning rather than enabled:
Profile(sampled CPU profiling) requires the NT Kernel Logger session.ReferenceSet(working-set reference tracking, the deep memory-analysis keyword) requires the NT Kernel Logger.Handledepends on theOSkeyword, which itself requires the NT Kernel Logger.
Setting these flags on a custom session produces no error, and the events are not delivered. A further set of memory-adjacent capabilities have no kernel keyword at all and can only be reached by enabling a manifest provider by GUID. These are memory compression state, Microsoft-Windows-Kernel-Power, Microsoft-Windows-Kernel-Session, job objects, and object-manager tracking beyond what Handle covers.
What the VirtualAlloc keyword captures
The VirtualAlloc keyword is the core leak signal. It emits an event per virtual-memory operation, covering reserve, commit, and free, with the allocating process attributable from the event. The data is page-granular. You see committed memory climb and whether it is ever released, which is the pattern most native leaks produce. Heap-allocation detail inside those pages is not included. A process that commits one large region and leaks small heap blocks inside it shows a single commit. Heap snapshots cover that case, and they sit outside the kernel-keyword system.
The adjacent keyword is Memory, which enables full page-fault tracing. It is unusable for multi-minute captures. Page faults on a busy machine arrive at very high volume and add little leak signal over the VirtualAlloc stream, because a leaking process faults constantly whether or not each fault is recorded. The ET Ducky memory-growth profile leaves it off. MemoryHardFaults is the better pressure signal. Hard faults, meaning paging from disk, are orders of magnitude rarer than soft faults, so the keyword is cheap. A rising hard-fault rate indicates memory pressure that is causing disk I/O.
Attribution requires more than the memory keywords
A VirtualAlloc event carries a process ID. Turning that into a per-process account over a multi-minute window requires three more keywords running alongside:
Processprovides process start and stop rundown. PIDs resolve to names even for processes that exit mid-capture, and PID reuse inside the window does not misattribute allocations.ImageLoadprovides module load events, which let a spike be attributed to a specific DLL loading rather than the host process's own code.ProcessCountersprovides per-process memory counters emitted at session rundown, giving a final working-set/commit snapshot to reconcile the delta stream against.
The complete keyword set for the ET Ducky memory-growth profile is therefore: Process + ImageLoad + ProcessCounters + VirtualAlloc + MemoryHardFaults, with page-fault tracing off and heap snapshots documented as out of scope.
Capture duration in the memory-growth profile
A leak shows up as a trend over time. A sub-minute capture cannot distinguish a leak from ordinary allocation churn, because most processes allocate and free in bursts that are much larger than a slow leak's slope. The profile runs three minutes by default. A three-minute kernel capture is heavier than a snapshot, so it is gated behind an explicit operator opt-in rather than started automatically by the diagnostic chat flow. The agent also pauses its baseline monitoring for the capture window and resumes afterward, so the capture does not measure the agent's own monitoring overhead.
What the Microsoft-Windows-Kernel-Memory provider covers
The manifest provider (Microsoft-Windows-Kernel-Memory) answers the question of what the memory state of the system is over time, rather than which process allocated what. It emits periodic memory-information snapshots, both system-wide and per-process working-set data, on an interval instead of a per-operation event stream. That makes it cheap enough to leave running continuously, which is how resource-monitoring products use it. It does not attribute individual allocations, so it cannot answer the leak question by itself. The VirtualAlloc keyword cannot cheaply answer the trend question at fleet scale. The two are used together. As a manifest provider it is enabled by GUID on an ordinary non-kernel session, so none of the custom-session keyword caveats above apply to it.
Summary table
| Signal | Mechanism | Custom session? | Use for |
|---|---|---|---|
| VirtualAlloc (reserve/commit/free) | Kernel keyword | Yes | Native leak attribution |
| Page faults (all) | Kernel keyword Memory | Yes, but volume-prohibitive | Short targeted captures only |
| Hard faults | Kernel keyword MemoryHardFaults | Yes | Memory-pressure signal, low volume |
| Working-set reference tracking | Kernel keyword ReferenceSet | No, NT Kernel Logger only | Deep working-set analysis |
| Periodic memory-info snapshots | Microsoft-Windows-Kernel-Memory by GUID | N/A (manifest provider) | Continuous trend monitoring |
| Heap allocations | Heap snapshot workflow | N/A (no kernel keyword) | Small-object leaks inside committed regions |
| Memory compression | GUID provider only | N/A | Compression-store behavior |
When a memory capability appears to be missing from a custom kernel session, check whether it is an NT-Kernel-Logger-only keyword, a GUID-only manifest provider, or a separate workflow before concluding the events do not exist. All three of these cases fail without an error.
How this is used in practice
In ET Ducky this profile backs the memory-leak investigation that an operator launches from an agent's properties panel. The agent runs the three-minute capture, the correlated event stream goes through root-cause analysis, and the result is rendered as an explanation plus a per-process allocate/free table. The same profile definitions are used by the AI investigation planner when a memory question arrives in a diagnostic session. The capture engine, profile definitions, and session-keyword handling described here are in the agent codebase. The desktop app's local monitoring uses the same engine.