Skip to content
WindowsDeep Dive Published Updated 7 min readViews unavailable

Application Verifier and Page Heap: Catch Native Windows Bugs at the Fault Site

Instrument unmanaged Windows applications with Application Verifier and targeted page heap to expose heap, handle, lock, and API misuse in test builds.

Heap corruption often becomes visible long after the code that caused it ran. A buffer overrun can damage an allocator header, and a use-after-free can remain unnoticed until the same address is reused. Application Verifier changes the runtime environment so selected API misuse and memory errors are detected closer to the offending operation. Page heap adds guard regions and allocation tracking to make certain overruns, underruns, and invalid frees reproducible under a debugger.

These tools are for testing unmanaged/native applications, not a magic production repair. Instrumentation changes timing and memory layout, can consume substantial resources, and can make the application stop at the first detected violation. Use a controlled test machine, retain an uninstrumented baseline, and scope the checks to one executable. A clean run only means that the exercised scenarios did not trigger the enabled checks.

What Application Verifier can and cannot prove

Application Verifier monitors an application’s interactions with Windows APIs, objects, heaps, handles, locks, registry, and file-system behavior through selectable verification layers. It can expose invalid handles, heap misuse, incorrect critical-section behavior, unsafe APIs, some privilege assumptions, and other defects. It does not replace static analysis, compiler sanitizers, fuzzing, concurrency tests, or review of application-specific invariants.

The tool is included in the Windows SDK; GFlags and WinDbg are available through the Debugging Tools for Windows. Install and version these tools as part of the test image rather than downloading unknown binaries. Application Verifier requires an administrative account for configuration, and some checks need a user-mode debugger because the process can break when a stop is detected.

Start with the Basics layer and a meaningful workload. Add only layers that test a hypothesis, such as Heaps for suspected buffer corruption or Handles for invalid object use. A verifier stop is diagnostic evidence: preserve its stop ID, layer, stack, process version, test input, and machine build. Do not swallow the exception or configure the debugger to continue blindly when the stop says the process cannot safely continue.

Add page heap to a bounded test

Page heap is enabled per image name through GFlags. Full page heap places a guard region beside allocations and tracks their lifetime. By default, the guard is at the end of an allocation and catches overruns; /backwards moves it to the beginning to target underruns. Invalid frees can also be diagnosed through heap tracking. It can substantially increase virtual-memory use and alter allocation behavior. Light page heap uses less overhead, but may detect some damage later, such as when a block is released. Full page heap is most useful for a focused reproduction, not as an all-day setting on a memory-constrained workstation.

From an elevated Developer Command Prompt with the Windows Debugging Tools on PATH, enable full page heap for a lab binary:

gflags /p /enable ExampleApp.exe /full
gflags /p

The command above targets overruns with the default guard placement. For a suspected underrun, use /full /backwards instead of /full; the guard placement is one-sided, so choose it from the failure hypothesis rather than assuming one run catches both directions.

Launch that image under WinDbg or another supported debugger and reproduce the smallest scenario that normally corrupts memory. Use the matching architecture of the debugger, symbols for the application and relevant components, and exact input that triggers the defect. If the guard page faults, inspect the exception address and stack to locate the read/write, not merely the later allocator failure.

After testing, disable page heap for that image and confirm it no longer appears in the page-heap list:

gflags /p /disable ExampleApp.exe
gflags /p

GFlags settings are persisted for future launches of the target image, so removing the debugger or closing the program is not cleanup. Record the configured executable name and architecture. If multiple product binaries share a basename, verify which image name the setting affects before enabling it.

Debug the first useful stop

When the process breaks, preserve the first-chance exception context, Application Verifier stop output, and relevant logs. In WinDbg, !avrf reports verifier settings and the current/previous stop; !heap -p -a <address> can inspect a page-heap allocation around an invalid access. The exact command and extension output vary with the installed debugger version and the type of heap instrumentation, so retain raw output rather than pasting only a summary into an issue.

Classify the failure before changing code:

  • Buffer overrun/underrun: inspect the accessed address relative to the allocation, the allocation size, the allocation stack, and whether a length calculation overflowed.
  • Use after free/double free: correlate allocation and release stacks, object ownership, asynchronous callbacks, and shutdown ordering. Verify that every consumer has stopped before releasing shared memory.
  • Wrong heap or mismatched allocator: confirm that allocation and deallocation APIs use the same contract and module ownership. Pairing CRT malloc with HeapFree, or freeing a block through a different heap, is invalid.
  • Invalid handle: identify the API that created the handle, its lifetime, inheritance/duplication behavior, and whether another thread closed it concurrently.
  • Lock misuse: determine which thread owns the critical section or SRW lock and whether lifecycle paths can delete or reuse the synchronization object too early.

Application Verifier’s heap log can help identify the most recent allocation operations and their stacks. It is evidence about calls observed by the tool, not necessarily a complete record of custom allocators that bypass Windows heap APIs. A private allocator, GPU heap, shared-memory arena, or third-party allocator may need its own instrumentation.

Control overhead and test contamination

Full page heap can reserve substantial virtual address space and change performance, so interpret timing results from an instrumented run cautiously. It is a fault-finding configuration, not a performance benchmark environment. If full mode prevents the application from starting or causes unrelated resource exhaustion, use light page heap or a narrower scenario; do not disable every check and conclude the bug is gone.

Keep separate test plans for memory corruption, handle misuse, concurrency, and low-resource behavior. Combining every layer can make the failure difficult to attribute and can prevent a workload from reaching the code path of interest. For intermittent concurrency bugs, repeat runs with deterministic seeds and compare normal versus instrumented outcomes. Instrumentation often changes scheduling and may hide timing-sensitive defects, so keep stress/fuzz tests that do not depend on a single instrumented run.

Before comparing a test result across machines, align the target binary, runtime libraries, verifier build, debugger architecture, and symbol set. A 32-bit process under WOW64 may need a matching 32-bit debugger context and compatible image-level settings. Record whether the application uses the process heap, private heaps, or a custom allocator; page heap can only instrument allocations that pass through the supported heap mechanisms. An apparent lack of findings can otherwise be a coverage gap rather than evidence that the allocator path is clean.

Protect test data and symbols. Dumps can contain credentials, documents, keys, or customer data from process memory. Restrict access, redact or minimize captures where possible, and set retention that matches the incident. Avoid sending full dumps through ordinary chat or issue trackers without an approved handling process.

A reproducible investigation loop

  1. Preserve the uninstrumented failure, binary hash, symbols, and exact reproduction input.
  2. Enable only the relevant Application Verifier layers and/or page heap for the target image.
  3. Run under a debugger and capture the first stop with full stack and verifier context.
  4. Fix the underlying ownership, bounds, or API-contract defect; do not merely suppress the stop.
  5. Re-run the same reproduction under instrumentation, then run broader regression tests without it.
  6. Disable image-level page heap, verify settings are cleared, and store dumps according to data policy.

Close the investigation when the same deterministic test no longer produces the violation, ordinary workloads pass, instrumentation is removed, and ownership/lifetime assumptions have tests. If the issue does not reproduce, report the tested coverage and limits rather than stating that the binary is memory-safe.

Application Verifier and page heap are most effective when they shrink the distance between cause and symptom. Use them to capture a precise first failure, then repair the program’s lifetime or bounds contract and verify the fix with both targeted and normal runs.

Related:

Sources:

Comments