Troubleshooting Memory and Handle Leaks with Microsoft Application Verifier

Top 10 Application Verifier Checks Every Windows Developer Should RunApplication Verifier (AppVerifier) is a runtime verification tool from Microsoft designed to help developers find subtle programming errors in native Windows applications. It instruments your app and watches for common mistakes—memory corruption, handle leaks, incorrect API usage—that can cause crashes, security vulnerabilities, and instability in production. Running the right set of checks during development and continuous integration dramatically reduces hard-to-reproduce bugs.

Below are the top 10 AppVerifier checks every Windows developer should run, why each matters, how to interpret findings, and practical tips for using them effectively.


1. Heap (Low- and High-Integrity Heap Checks)

Why it matters:

  • Memory corruption and incorrect heap usage cause crashes and security issues such as buffer overruns and use-after-free.

What it does:

  • Detects heap overruns/underruns, double-free, invalid heap handles, and misuse of heap APIs (HeapAlloc/HeapFree).

How to use:

  • Enable the Heap checks for your process and run both short and longer test runs. AppVerifier places guard pages and additional metadata to catch corruptions.

Interpreting results:

  • An overrun/underrun report typically points to the allocation size or pointer math error near the reported allocation site. Use the stack trace AppVerifier provides to find the code path.

Tips:

  • Re-run under a debugger (WinDbg or Visual Studio) with break-on-exception to inspect corrupted memory. Address sanitizer-style tools (e.g., GFlags/UMDH) can complement the diagnosis.

2. Handles

Why it matters:

  • Leaked or invalid handles cause resource exhaustion, subtle bugs, and crashes. Commonly affected resources include files, events, mutexes, registry keys, and GDI objects.

What it does:

  • Tracks handle creation and closing, reports leaks and invalid handle usage (use-after-close), and flags mismatched handle types.

How to use:

  • Use Handle checks during long-run tests and typical user workflows. AppVerifier will list leaked handles on process exit and flag improper operations.

Interpreting results:

  • Leaked handle reports include allocation stack traces showing where the handle was created but not closed. Use these to patch missing CloseHandle() calls or implement RAII patterns.

Tips:

  • Combine with Task Manager or Process Explorer to watch handle counts while exercising features. For complex lifetimes, add logging at creation/close sites.

3. Locks / Synchronization

Why it matters:

  • Incorrect lock usage leads to deadlocks, contention, and race conditions that are often non-deterministic and hard to reproduce.

What it does:

  • Detects incorrect usage of synchronization primitives (critical sections, SRW locks, mutexes, events), potential deadlocks, lock order violations, and abandoned synchronization objects.

How to use:

  • Enable Locks checks when running multithreaded tests, stress tests, and UI interaction scenarios.

Interpreting results:

  • AppVerifier provides lock stacks and the lock order history. A reported potential deadlock indicates conflicting lock orders between threads.

Tips:

  • Enforce consistent lock acquisition order in code, prefer finer-grained locks, or use lock hierarchies. Reproduce with concurrency profilers or with Thread Sanitizers where available.

4. Handles/Resource Types — GDI and User (GUI) Objects

Why it matters:

  • Leaks of GDI/User objects (brushes, pens, device contexts, fonts, windows) degrade UI performance and eventually cause drawing failures or crashes.

What it does:

  • Monitors creation and destruction of GDI and USER objects and reports leaks and invalid usage.

How to use:

  • Run UI-heavy test cases and long-lived sessions with these checks enabled.

Interpreting results:

  • Reports include the type and creation stack. Fix by ensuring appropriate DeleteObject/ReleaseDC/DestroyWindow calls.

Tips:

  • Use tools like GDIView in parallel to confirm leaks; enforce RAII wrappers (e.g., C++ smart handles) around GDI/User resources.

5. COM (Component Object Model) Checks

Why it matters:

  • Incorrect COM reference counting or improper interface usage leads to memory leaks, use-after-free, and crashes.

What it does:

  • Tracks COM object creation, QueryInterface usage, AddRef/Release balances, and warns on mismatches.

How to use:

  • Enable COM checks for components that use COM heavily (shell extensions, in-process COM objects, Windows Runtime interop).

Interpreting results:

  • A reference count imbalance points to missing Release or extra AddRef. The stack traces identify where refcount operations occur.

Tips:

  • Prefer smart-pointer wrappers like CComPtr or Microsoft::WRL::ComPtr. Make sure to match every AddRef with a Release (or use scoped ownership).

6. RPC / Winsock / Network API Usage

Why it matters:

  • Incorrect network API usage can cause resource leaks, deadlocks, or protocol errors; it’s especially important for services and client-server apps.

What it does:

  • Validates usage patterns for RPC, Winsock, and certain network-related Win32 APIs; detects invalid parameters, improper cleanup, and protocol misuse.

How to use:

  • Test network flows, timeouts, and error handling paths with these checks enabled.

Interpreting results:

  • Look for improper socket closes, invalid buffer sizes, or misuse of async completion APIs. Stack traces show the erroneous calls.

Tips:

  • Ensure error paths correctly clean up sockets and handles. Use robust retry and timeout logic in network operations.

7. Security (Invalid Parameter Handling)

Why it matters:

  • Input validation and correct parameter handling are major sources of security vulnerabilities and stability issues.

What it does:

  • Triggers checks when Win32 APIs are called with invalid parameters or out-of-range values, highlighting poor error handling and unsafe assumptions.

How to use:

  • Enable during fuzzing, boundary testing, and when exercising unexpected inputs.

Interpreting results:

  • AppVerifier reports the invalid call and the offending parameters. Fix by adding validation and graceful error-handling.

Tips:

  • Combine with fuzzers (e.g., OSS-Fuzz, WinAFL) to explore edge cases. Harden APIs against malformed inputs.

8. C++ Runtime / CRT Checks

Why it matters:

  • Misuse of CRT features (invalid free, buffer overruns in C runtime functions, mismatched new/delete) can corrupt memory and crash the app.

What it does:

  • Monitors common CRT errors and flags misuse across allocations/freeing, string functions, and runtime assertions.

How to use:

  • Enable CRT checks in native C/C++ projects; run unit tests and integration scenarios.

Interpreting results:

  • Reports point to specific CRT functions and call stacks where misuse occurred. Fix by matching allocation/deallocation and using safer functions (strncpy_s, etc.).

Tips:

  • Use modern C++ idioms and RAII to reduce manual memory management. Enable compiler warnings and runtime checks.

9. Deadlocks (Advanced)

Why it matters:

  • Deadlocks freeze applications and services. They’re often visible only under load or specific timing.

What it does:

  • More advanced deadlock detection observes wait chains and resource acquisition patterns to detect cycles and potential deadlocks.

How to use:

  • Stress-test multithreaded components and long-running operations with this check enabled.

Interpreting results:

  • AppVerifier provides involved threads, stacks, and the wait chain that led to the deadlock. Use this to refactor locking strategy.

Tips:

  • Implement timed waits where appropriate, break complex lock regions into smaller scopes, and add logging to capture thread states in production.

10. Miscellaneous / Custom Checks (Instrumentation)

Why it matters:

  • Application-specific bugs may not be covered by default checks. Custom instrumentation and assertions catch domain-specific issues.

What it does:

  • AppVerifier supports various supplemental checks and can be combined with application assertions, ETW tracing, and custom test harnesses.

How to use:

  • Add application-specific checks, enable relevant AppVerifier modules, and integrate into automated test runs.

Interpreting results:

  • Use combined data (AppVerifier stacks, ETW traces, logs) to pinpoint root causes.

Tips:

  • Build small, reproducible tests for each class of bug. Integrate AppVerifier runs into CI for nightly or pre-release builds.

Practical Workflow and Integration Tips

  • Start local development runs with the full recommended set (Heap, Handles, Locks, GDI/User, COM, CRT). For slower tests, toggle subsets depending on the feature being exercised.
  • Integrate AppVerifier into CI for targeted builds (e.g., debug/instrumented builds). Keep a baseline of known issues to avoid noisy noise; triage and fix leaks or false positives promptly.
  • Use a native debugger (WinDbg, Visual Studio) with first-chance exceptions enabled to break at the point of violation. AppVerifier often raises exceptions at the site of error, making debugging straightforward.
  • Combine AppVerifier with other tools: ASAN (where available), static analyzers, fuzzers, ETW tracing, and heap profilers to get a full picture.

Conclusion

Running Application Verifier with these top 10 checks helps catch memory corruption, resource leaks, synchronization bugs, COM issues, and insecure parameter handling long before release. Treat AppVerifier as part of your defensive programming toolkit: run it often, integrate it into CI, and respond quickly to its reports. The time invested reduces production incidents and simplifies root-cause analysis when problems arise.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *