Resolving Memory Leaks Using WinDbg

An approach to addressing memory leaks in native code

Introduction

Native applications written in C/C++ will suffer from memory leaks just as the sun comes up every day. It is simply part of the cost of doing business if the application has any level of sophistication. Regardless of how diligent a software engineer is proactively trying to prevent memory leaks they will inevitably still occur.

Memory leaks occur for many reasons. I’ll spare the easy ones (those well, duh! cases) for brevity sake and to avoid insulting the intelligence of readers. One common thing to miss is forgetting to make base class constructors virtual when necessary. A class may not begin its life as a base class but over time may end up that way as the application evolves. If the base class dynamically allocates memory or stores a pointer to memory that is dynamically allocated by another class on its behalf to use and to manage, there is a potential for a memory leak if the destructor isn’t virtual and needs to be.

Another common mistake by software engineers is to not free dynamically allocated memory in all control paths. For example, functA() might be 200 lines long and have several return statements depending on application state and user input. If the function dynamically allocates memory early during function execution then further near the bottom of the function there is return statement without first freeing memory, there is a potential for a memory leak. It may not be obvious to the software engineer writing or maintaining code they’re creating a memory leak. The list of causes of memory leaks is seemingly endless. 

The Visual Studio debugger and the memory diagnostic facilities provided by the CRT often times provide some information to the origin of the memory leak. Such as in

Armed with this information it may be somewhat trivial to identify the offending code to remove the memory leak. This document does not address this scenario. Rather, this document addresses the scenario where the memory allocation number varies, the CRT memory diagnostics does not provide enough information or even worse you have no source code information at all such as in

In an application that is large and complicated with hundreds of thousands of lines of code it can be nearly impossible to track down where a memory leak is originating from when source code information is unavailable. Never fear there is hope and hope is what this document attempts to provide.

Prerequisites

There are some tools required to get started isolating and removing memory leaks. Since you are reading this document it is assumed you’re a software engineer and Visual Studio is installed on your workstation. Additionally, you will need the full platform SDK installed on your workstation. At the time of writing it is version 10.0.17763.0 (Win10). There are two key applications installed with the full SDK that are required: Global Flags and WinDbg. Both applications reside in C:\Program Files (x86)\Windows Kits\10\Debuggers\x64. It may be beneficial to add this directory to your PATH environment variable or create shortcuts to make access easier. Finally, you need a debug build of the application. That’s it. You are ready to start isolating and eliminating memory leaks.

Memory Leak Isolation Process

Preparation

The first thing that must happen is to configure Global Flags for the application. Global Flags (gflags) must be executed with administrator privileges since it makes modifications to the Windows registry. Execute gflags then switch to the Image File tab. Type the name of the executable (i.e. MyApp.exe) then press the Tab key. Make sure the options "Create user mode stack trace database" and "Enable page heap" are enabled for the target executable. Configuring the application in gflags does a few things. What’s really important is it enables call stack information for all memory allocations. Feel free to reread that last sentence because that is really awesome.

 Debugging

Now the fun part. Identifying the source of the memory leak. Execute WinDbg. If you have never used WinDbg it is an extremely powerful debugger. The Visual Studio debugger is powerful in its own right but WinDbg takes debugging and process interrogation in general to a whole new level in a somewhat graphical manner. WinDbg is for all practical purposes a command line program with a GUI wrapper.   The learning curve of WinDbg is pretty steep for the simple fact that it is extremely powerful and can do a lot if you know the commands. Never fear, the exact steps and commands needed are detailed below. 

First, start the application with the WinDbg debugger (Ctrl+E) using the debug configuration (debug build) or if the application is already running you can simply attach to an existing process (F6). Both options are available in the File menu of WinDbg. 

WinDbg will launch a new process or attach to the existing process and immediately breaks stopping program execution. This is a good thing since there is a break point that is necessary to be set to prevent the application from closing and losing valuable diagnostic information necessary to locate the source of memory leaks. On the command line enter the command

The ‘bu’ command sets an unresolved breakpoint in ucrtbased.dll on the _CrtDumpMemoryLeaks function call. _CrtDumpMemoryLeaks is the function call that produces the memory leak report seen in the output window in WinDbg or Visual Studio. Program execution must be halted shortly after this function call. To ensure information is retained it is a good location for a breakpoint since the really important function call is only a few function calls away. 

Continue execution by selecting the Go menu item (F5) from the Debug menu or by entering ‘g’ on the command line and pressing ‘Enter’. 

Perform the necessary tasks in the application that creates the memory leaks then simply exit the application. WinDbg will break when the function _CrtDumpMemoryLeaks is called. Next issue the ‘pc’ command four times to get the actual memory leak report sent to the output window in WinDbg. The ‘pc’ command effectively means to step to the next function call. The following functions get called in order.  The last function call actually produces the memory leak report in the output window

At this point all leaked memory is identified and the investigation process begins. The important information is the memory address of the memory leak. Armed with the memory address of leaked memory it is possible to use the power of WinDbg to get to the call stack for that memory allocation.

Copy the memory address from the output window of WinDbg including the 0x. For example, using the output from the WinDbg image above with memory allocation {500} the memory address is 0x000002455F377F00

Type the following command in the WinDbg command line

The command will take a few seconds to evaluate. Once it completes you will magically have a call stack of the memory allocation for the memory that is leaked. Feel free to reread that last sentence because that is really awesome and it is also the source of hope, relief, disbelief and possibly outright cries of joy.  

Below is an example of output from the !heap command

In this example the source of the memory leak traces to the CMyTestClass class in MyLib DLL. There’s a call to the LeakMemory() method. What is even better is the !heap command output provides the source file name and the number after the @ symbol is the exact line of code in the source file where the memory allocation occurred. Now it’s simply a matter of executing the !heap –p –a addr command for each memory leak address in the memory leak report.

Issues Encountered

The process of getting a workstation configured properly to enable memory allocation call stacks in WinDbg is generally fairly straight forward when provided the necessary information. However, there is a potential for a couple issues that are addressed below. 

Missing Call Stack

The toughest issue one might face is the !heap -p –a addr command doesn’t produce a call stack. Viewing the call stack of a memory allocation is the entire objective of this article. If you are not getting a call stack it is often times extremely difficult, if not impossible, to eliminate memory leaks. A possible resolution to this problem resides in gflags. Execute gflags and enter the executable name in image text box on the Image File tab. Enable the "Stack Backtrace" configuration option (lower left). Experiment with the value of the backtrace setting. It is not unreasonable to have up to 250 MB set for the backtrace setting. 

Multiple Debuggers

It is possible to attach WinDbg to an existing process that is being debugged in another debugger. The primary debugger can be Microsoft Visual Studio or another instance of WinDbg. The catch is only one debugger can be attached to a process invasively. Additional debugger instances attached to a process must be attached noninvasively. A process will be halted when WinDbg is attached to a process noninvasively. It is possible to interrogate the process from WinDbg, however, it is not possible to continue execution. All additional debuggers must be detached from a process to enable continuation of process execution. 

Inaccurate Source Code Line Number

Occasionally the source code line information provided in the memory leak report is inaccurate. Rarely is the source code line number off by more than one line. Generally, the actual source code line is one line above the line number identified in the memory leak report.

Conclusion

Tracking down memory leaks is difficult and time consuming. However, armed with the proper tools and information it is possible to track down memory leaks that are seemingly impossible to find. While it takes a little effort to do it is possible to have minimal memory leaks in even the largest and most complicated native applications. Happy debugging.

To view or add a comment, sign in

More articles by Jon Krupa

  • Code Faster With Snippets

    My current assignment as a contract developer is working with a very large legacy code base. We're in the process of…

  • Concurrency Tokens In EF Core : The Hidden Details

    I ran into a situation recently fixing a defect involving an exception when trying to update the database using Entity…

    3 Comments
  • async/await in C# quick tip

    The way asynchronous code is written today has been largely influenced by C# when the async/await keywords were…

  • Are Corporate Development Standards Killing Your Project

    Corporate development or coding standards can be extremely valuable to ensure code is well written, maintainable and…

  • Radio Buttons in .NET MAUI

    One of my many side projects is porting an old utility application that currently uses Windows Forms to .NET MAUI.

  • Wait, I Don't Have to Await?

    One thing I see quite often in asynchronous C# code is unnecessary awaits. I'm not sure if it is a lack of…

  • Today Is an Important Day

    Over the past few years, I've read a lot about Azure Static Web Apps, but I've never had a reason to create one. Mainly…

  • Why Am I So Stressed?

    Over my holiday vacation I came to the realization I am stressed out. I am over stressed and I didn't realize it until…

    3 Comments
  • Structured Logging != String Interpolation

    Structured logging has been around for a while now in .NET.

  • Being Aware of .NET Performance Issues

    I recently performed a code review trying to match contacts between two different systems to build a single list of…

Others also viewed

Explore content categories