Introduction

With this utility you can simply find memory leaks in your program (CRT and COM-Leaks!). Each leak is displayed with the callstack (including source line) of the allocation. So you can also easy find leaks, while using the STL. It will also write a file with the callstack if your application crashes (it can also handle stack-overflows!). It almost has no runtime-overhead (runtime-cost). And the best: it is free (zlib/libpng license).

Finding memory leaks

It is easy to implement in your existing VC code:

  1. Add the Stackwalker.cpp and Stackwalker.h to your project
  2. Include Stackwalker.h in your main source file
  3. Call InitAllocCheck() right after the beginning of your main
  4. Call DeInitAllocCheck() just before the end of your main (Here all leaks will be reported)

All leaks will be listed in the file YouAppName.exe.mem.log in the application directory (only in debug builds; it is deactivated for release builds). This will also activate the exception-handling by default (release and debug build).

Only use the exception-handling

If you only want to use the exception handing you need to do the following:

  1. Add the Stackwalker.cpp and Stackwalker.h to your project
  2. Include Stackwalker.h in your main Source file
  3. Call OnlyInstallUnhandeldExceptionFilter() right after the beginning of your main

If an exception occurs, it will write an file with the callstack in the application directory with the name YouAppName.exe.exp.log

Example

A simple example is the following:

#include <windows.h>
#include "Stackwalker.h"

void main()
{
  // Uncomment the following if you only need the UnhandledException-Filter
  // (to log unhandled exceptions)
  // then you can remove the "(De)InitAllocCheck" lines
  //OnlyInstallUnhandeldExceptionFilter();

  InitAllocCheck();

  // This shows how the mem-leak function works
  char *pTest1 = new char[100];

  // This shows a COM-Leak
  CoTaskMemAlloc(120);

  // This shows the exception handling and log-file writing for an exception:
  // If you want to try it, please comment it out...
  //char *p = NULL;
  //*p = 'A'; // BANG!

  DeInitAllocCheck();
}

If you execute this example, you will get a file Appication-Name.exe.mem.log with the following content:

##### Memory Report ########################################
11/07/02 09:43:56

##### Leaks: ###############################################
RequestID:           42, Removed: 0, Size:          100
1: 11/07/02 09:43:56
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
                              +30 bytes (_heap_alloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(260)
                              +21 bytes (_nh_malloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(139) +21 bytes (malloc)
1: f:\vs70builds\9466\vc\crtbld\crt\src\newop.cpp(12) +9 bytes (operator new)
1: d:\privat\memory_and_exception_trace\
            memory_and_exception_trace\main.cpp(9) +7 bytes (main)
1: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259)
                               +25 bytes (mainCRTStartup)

**** Number of leaks: 1

##### COM-Leaks: ###############################################
(shortened)
**** Number of leaks: 1

Explanation

In the following I will explain the Memory-Report-File

RequestID:           42, Removed: 0, Size:          100

This line is the beginning of ONE leak. If you have more than one leak, then each leak starts with the RequestID.

1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
             +30 bytes (_heap_alloc_dbg)

This is an actual stack entry. The stack is shown from the last function on the top going through each callee until the end of the stack is reached.

More options by calling InitAllocCheck

InitAllocCheck has 3 parameters.

Parameter name Description

eAllocCheckOutput

eOutput

This is an enum for output-format. The following is possible:

  • ACOutput_Simple (default)

    This outputs the callstack as seen above.

  • ACOutput_Advanced

    This has a more detailed output of the callstack. For more info see here

  • ACOutput_XML

    This outputs the leaks in an XML file so you can read it easily from other applications or use some XSLT to transform it in an more readable format you want. For more info see here

BOOL

bSetUnhandledExeptionFilter (default: TRUE)

If this is set, an UnhandledExceptionFilter will be installed. If an (unhandled) exception occurs it will write the callstack in an log file and terminates. For more info see here

ULONG

ulShowStackAtAlloc (default: 0)

Notice: This works only for CRT-allocs

Here you can specify the level of mallocs/frees logging. Per default nothing will be logged in the log file at runtime. If you need to know what happens while executing the program you can specify an value. Then the malloc/free action will be logged to the file (either with or without callstack).

Valid values are:

  • 0 = Do not write any output during runtime-alloc-call (default)
  • 1 = Write only the alloc action (malloc, realloc, free)
  • 2 = Write alloc action and callstack only for malloc/realloc
  • 3 = Write alloc action and callstack for all actions

Log-Output with more info

You can also get an output with more info about each stack entry. For this you have to call InitAllocCheck with the first parameter set to ACOutput_Advanced. If you execute the following sample you get a file Appication-Name.exe.mem.log with more info:

#include <windows.h>
#include "Stackwalker.h"

void main()
{
  InitAllocCheck(ACOutput_Advanced);
  // This shows how the mem-leak function works
  char *pTest1 = new char[100];
  DeInitAllocCheck();
}

And here is the (shortened) output:

##### Memory Report ########################################
11/04/02 09:04:04

##### Leaks: ###############################################
RequestID:           45, Removed: 0, Size:          100
1: 11/04/02 09:04:04
// ...
1:   5     main +49 bytes
1:     Decl: main
1:     Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1:     Mod:  Memory_and_Exception_Trace, base: 00400000h

1:   6     mainCRTStartup +363 bytes
1:     Decl: mainCRTStartup
1:     Line: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259) +25 bytes
1:     Mod:  Memory_and_Exception_Trace, base: 00400000h

1:   7     _BaseProcessStart@4 +35 bytes
1:     Decl: _BaseProcessStart@4
1:     Mod:  kernel32, base: 77e40000h

**** Number of leaks: 1
// ...

Explanation

In the following I will explain the Memory-Report-File

RequestID:           45, Removed: 0, Size:          100

This line is the same as above.

1:   5     main +49 bytes
1:     Decl: main
1:     Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1:     Mod:  Memory_and_Exception_Trace, base: 00400000h

XML Output

If you set the first parameter to ACOutput_XML an XML file will be produced. It has the following contents:

<MEMREPORT date="11/08/02" time="10:43:47">
  <LEAK requestID="47" size="100">
    <!-- shortened -->
    <STACKENTRY decl="main" decl_offset="+100"
                    srcfile="d:\...\main.cpp" line="16"
      line_offset="+7" module="Memory_and_Exception_Trace" base="00400000"/>
    <STACKENTRY decl="mainCRTStartup" decl_offset="+363"
                    srcfile="f:\...\crt0.c" line="259"
      line_offset="+25" module="Memory_and_Exception_Trace" base="00400000"/>
  </LEAK>
</MEMREPORT>

It is pretty self explaining if you took a look at the "advanced log output".

Mem-leak-analyse tool

If you are using the XML-Output format than you can use my MemLeakTool to display the leaks in a sorted order (sorted by callstack). Just select the "xml-leak"-File and press "Read". The callstack will be displayed in an tree view. If you select a node, the source code will be shown in the right part (if it could be found).

Information: This program requires the .NET Framework 1.0!

A word to leaks

You should be aware, that some leaks might be only the result of other leaks, for example the following code throws two leaks, but if you remove the "originator" of the leaks, the other leak will also disappear. Example:

#include <windows.h>
#include <stdlib.h>
#include "stackwalker.h"
class MyTest
{
  public:
    MyTest(const char *szName)
    {
      // The following is the second resulting leak
      m_pszName = strdup(szName);
    }
    ~MyTest()
    {
      if (m_pszName != NULL)
        free(m_pszName);
      m_pszName = NULL;
    }
  protected:
    char *m_pszName;
};

void main()
{
  InitAllocCheck();

  // This is the "main" leak
  MyTest *pTest = new MyTest("This is an example");

  DeInitAllocCheck();
}

How it works (CRT)

The basic of the memory leak logger is a hash table with information about all allocated memory (including callstack). Basically _CrtSetAllocHook is called to hook all the memory allocations / frees. Therefore only C/C++ allocations are logged. On every allocation a portion of the callstack and the Instruction-Pointer is caught and stored in the hash-table, with some other information about the allocation.

If the application calls DeinitAllocCheck then the hash-table will be iterated and the (the saved) callstack of all entries will be listed in the file. For this we provide a pointer to our ProcessMemoryRoutine function to the StackWalk function.

In detail

Hash table

The hash table contains by default 1024 entries. You can change this value if you are doing many allocations and want to reduce the collisions. Just change the ALLOC_HASH_ENTRIES define.

As hash-key, the lRequestID for each allocations is used. This ID is passed to the AllocHook function (at least for allocs). If it is not passed (for example for freeing), then an (valid) address is passed. Having this address it is also possible to get the lRequestID, by looking into the _CrtMemBlockHeader of the allocated block.

For hashing a very simple and fast hash-function is used:

static inline ULONG AllocHashFunction(long lRequestID) {
  return lRequestID % ALLOC_HASH_ENTRIES;
}  // AllocHashFunction

Insert an allocation into the hash-table

If an new allocation should be inserted into the hash-table, first a thread context for the actual thread is made by calling GetThreadContext. This function requires an "real" thread handle and not a pseudo handle which is returned by GetCurrentThred. So for this I have to create a "real" handle by calling DuplicateHandle.

Actually I only need the current Ebp and Eip registers. This could also be done by just reading the registers with inline assembler. Now having the registers, I read the memory at the specified address. For Eip I only need to read 4 bytes. I do not known why StackWalk needs to read the Eip values, but if this values could not be read from StackWalk it fails to build the callstack. The real important part is the callstack which is stored in the memory pointing from Ebp (or Esp).

At the moment I just try to read 0x500 bytes by calling the ReadProcessMemory function. I do not read the complete stack, because it might use to much memory for many allocations. So I reduced the maximum size to 0x500. If you need a deeper callstack, you can change the MAX_ESP_LEN_BUF define.

If the callstack is not 0x500 bytes deep, then the ReadProcessMemory will fail with ERROR_PARTIAL_COPY. If this happens, I need to ask how many is possible to read without error. For this I need to query this value by calling VirtualQuery. Then I try to read as many bytes as possible.

Having the callstack I can simple insert the entry into the hash-table. If the given hash-entry is already occupied, I make a linked list and append this entry on the end.

Building the leak-list

If you call DeInitAllocCheck I simply walk through the hash-table and output every entry which was not freed. For this I call StackWalk with a pointer to my own Memory-Reading-Function (ReadProcMemoryFromHash). This function is called from the internals of StackWalk. If it is called it looks up the hash-table for the given lRequestID and returns the memory which was stored in the hash-table. The lRequestID is passed in the hProcess parameter of the StackWalk function (as stated in the documentation of StackWalk).

Ignoring allocations

Allocations/frees for _CRT_BLOCK are ignored (for more info see here). This is because the CRT allocates dynamically some memory for "special purpose". The tool also checks the _CRTDBG_ALLOC_MEM_DF flag of the _crtDbgFlag variable. If it is off, then all allocations are ignored. For more details see _CrtSetDbgFlag.

How it works (COM)

To track COM-memory-leaks you have to provide an IMallocSpy interface. This interface must be registered with CoRegisterMallocSpy. After that the (own) IMallocSpy instance is called for every memory (re)allocation/free. So you can track all memory actions.

The storage of the callstack is done the same way as for CRT-allocs (in an hash-table). So for more info please read the CRT-section.

A word to COM-Leaks

Normally there is nothing to say, but...

If you are using the msxml 3 or 4 implementation you have to be aware that this parser uses an "smart" pseudo-garbage collector. This means that they allocate memory and will not free it after it is used! So you may see some leaks which are only "cached memory". If you first call CoUninitialize then all cached memory is freed and the "real" COM-Leaks will be reported.

For more info see: Understanding the MSXML Garbage Collection Mechanism.

MFC usage

The problem with the MFC is that the derived CWinApp class is instantiated by the C-runtime, because it is a global variable. The easiest solution to implement the Leak-Finder is to declare the following static struct inside your MainApp.cpp.

You also have to add the stackwalk.cpp and stackwalk.h to your project. You also need to add the #include <stdafx.h> on top of the stackwalk.cpp file (if you use precompiled headers). A sample of an MFC application is also available from the top of this article.

static struct _test
{
  _test()
  {
    InitAllocCheck();
  }

  ~_test()
  {
    DeInitAllocCheck();
  }
} _myLeakFinder;

Temporarily disable logging (only CRT)

Maybe sometimes you do not want to log a special allocation of your application (for whatever reason; MFC is doing this many times). Then you can simple deactivate it by disabling the CRT flag _CRTDBG_ALLOC_MEM_DF with the _CrtSetDbgFlag function. Here is an example how you can do this:

#include "Stackwalker.h"

#include <crtdbg.h>

bool EnableMemoryTracking(bool bTrack)
{
  int nOldState = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
  if (bTrack)
    _CrtSetDbgFlag(nOldState | _CRTDBG_ALLOC_MEM_DF);
  else
    _CrtSetDbgFlag(nOldState & ~_CRTDBG_ALLOC_MEM_DF);
  return nOldState & _CRTDBG_ALLOC_MEM_DF;
}

void main()
{
  InitAllocCheck();

  // The following will be logged
  char *pTest1 = new char[100];

  EnableMemoryTracking(false);  // disable logging
  // The following will NOT be logged
  char *pTest2 = new char[200];
  EnableMemoryTracking(true);  // enable logging

  // The following will be logged
  char *pTest3 = new char[300];

  DeInitAllocCheck();
}

Unhandled Exceptions

There are three ways to use this tool for unhandled exceptions.

Simple using

If you just call InitAllocCheck with no parameters or with the second parameter set to TRUE, then an unhandled exception filter will be installed. If an unhandled exception occurs, an log file with the callstack will be written, a dialog box with the exception message will be displayed and the application will be terminated with FatalAppExit.

2nd simple using

If you do not want the AllocCheck-overhead (the (small) overhead is only present in debug builds), you can simply call OnlyInstallUnhandeldExceptionFilter. This will install the UnhandledExceptionFilter which writes an log file if an (unhandled) exceptions occurs. The log file will be stored in the application directory with the name YouAppName.exe.exp.log

int main()
{
  OnlyInstallUnhandeldExceptionFilter();

  // do your main code here...
}

Advanced using

You can write your own exception filter and just call StackwalkFilter to produce the callstack. Then you can do whatever you want. Here is a short example:

static LONG __stdcall MyUnhandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
{
   LONG lRet;
   lRet = StackwalkFilter(pExPtrs,
             EXCEPTION_EXECUTE_HANDLER, _T("\\exception.log"));
   TCHAR lString[500];
   _stprintf(lString,
      _T("*** Unhandled Exception!\n")
      _T("   ExpCode: 0x%8.8X\n")
      _T("   ExpFlags: %d\n")
      _T("   ExpAddress: 0x%8.8X\n")
      _T("   Please report!"),
      pExPtrs->ExceptionRecord->ExceptionCode,
      pExPtrs->ExceptionRecord->ExceptionFlags,
      pExPtrs->ExceptionRecord->ExceptionAddress);
   FatalAppExit(-1,lString);
   return lRet;
}

int main()
{
  InitAllocCheck(ACOutput_Advanced, FALSE);
  SetUnhandledExceptionFilter(MyUnhandlerExceptionFilter);

  // do some stuff...

  DeInitAlloocCheck();
}

Common mistakes

One of the most common mistake using this tool is that you statically instantiate classes in your main function. The problem is that the destructor of the class is called after the call to DeInitAllocCheck. If some memory was allocated inside this class, this memory will appear as leaks. Example:

#include <windows.h>
#include "Stackwalker.h"
#include <string>

void main()
{
  InitAllocCheck();
  std::string szTemp;
  szTemp = "This is a really long string";
  DeInitAllocCheck();
}

There are two solutions for this. Either start a block after the call the InitAllocCheck and end it before the call to DeInitAllocCheck. With this you can be sure, that the destructors are called before the leak file is produced. Example:

#include <windows.h>
#include "Stackwalker.h"
#include <string>

void main()
{
  InitAllocCheck();
  {
    std::string szTemp;
    szTemp = "This is a really long string";
  }
  DeInitAllocCheck();
}

The second solution is to use the same technique as for MFC applications (see above).

Visual Studio 7 and W2K / NT

I found a problem with executables build with VS7 and run on W2K or NT. The problem is here an old version of the dbghelp.dll. The PDB files generated from VS7 are in an newer format (DIA). It appears that the VS installations does not update the dbghelp.dll on W2K. So the original version (5.0.*) is still on the system and will be used. But with this version it is not possible to read the new PDB format. So no callstack can be displayed...

To get it work you have to do the follwing

Download the latest Debugging Tools for Windows (which includes dbghelp.dll). You have to install it to get the files. But you only need the dbghelp.dll! Now we have an other problem. The installer does not replace the original dbghelp.dll. So we need to copy the dbghelp.dll in our EXE dir. Now to make sure the right version is loaded you have to put a file with the name appname.local in your EXE dir (please replace appname with the EXE name (without extension)). Now it should also work with NT/W2K.

Known issues

References

History