Click here to Skip to main content
Click here to Skip to main content
Go to top

Memory leak finder

, 22 Feb 2012
Rate this:
Please Sign up or sign in to vote.
Custom memory handler with memory leak reporting and no-mans-land checking. Leaks are reported with call stack of allocation.

Sample Image - Memory_leak_finder.jpg

Introduction

Have you ever had a memory leak? Wished you knew where you allocated it and how? Is your boss cheap and refuses to buy Boundchecker or another debugging tool?

Here is the solution for you. A memory leak detector compiled directly into your code. It reports memory leaks with call stack of the allocation down to a user defined depth.

As an add-on, it does simple checks of the memory before and after the memory block, to track buffer overwrites.

Usage

Include tracealloc.cpp in your project. Define DETECT_LEAKS in the project settings. Compile. Run your application. Memory leaks are reported into your debug output window when the application terminates. Just click the memory leak and the correct file and line will be shown.

You can find further instructions in the source code.

How is it done?

The code overrides operator new and operator delete. For each allocation made, the code allocates extra space for tracking, call stack and no-mans-land. The current call stack is fetched and remembered, finally the code puts the newly allocated block in a linked list and the requested memory is returned.

When a memory block is deleted, the header is found and checked for buffer overwrites. The memory block is then removed from the linked list and deallocated.

When the program terminates, the global memory tracker object is deleted. The destructor traverses the linked list for memory blocks that isn’t deleted (= leaked memory). It then fetches symbol information for the call stacks and dumps the information in the debug console.

Limitations

The code is Microsoft Visual Studio and Win32 specific. It requires a debug build. The code is C++ specific. It handles new/delete but not malloc/free. The code will run slower with leak detection active (roughly at half normal debug build speed).

Finally

I want to thank Zoltan Csizmadia who wrote ExtendedTrace. I have used parts of his code for stack walking and symbol lookups.

I also want to thank the Code Project community. I have found many solutions or pointers in the right direction here. I hope I have given something back with this contribution.

Thank you!

History

February 22, 2012:

  • Inclusion of contributed bug fixes (author acknowledged in read me file)
  • Upgrade of solution to Visual Studio 2010.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Erik Rydgren
Software Developer (Senior)
Sweden Sweden
B.Sc in Software engineering
 
Writing software for the finance market.
Languages known: C/C++, SQL, Java, Perl, M68000 assembly and more. Give me the syntax and I'll program in it.
 
In my spare time i like to watch movies, read books and play computergames.

Comments and Discussions

 
QuestionCrash In WriteStackTrace PinmemberSivaji156517-Apr-14 22:55 
QuestionSolution updated to Visual Studio 2010 and inclusion of fixes by PEK PinmemberErik Rydgren22-Feb-12 13:07 
AnswerRe: Solution updated to Visual Studio 2010 and inclusion of fixes by PEK PinmemberRohit Dubey from Hyderabad3-Mar-12 13:39 
QuestionSomething like PinmemberMast Avalons4-Jan-12 5:14 
AnswerRe: Something like PinmemberJohnDepth20-Feb-12 6:32 
GeneralRe: Something like PinmemberErik Rydgren20-Feb-12 10:07 
GeneralRe: Something like PinmemberMember 428961327-Feb-12 3:29 
GeneralRe: Something like PinmemberSuperMegaCoder14-Mar-12 6:23 
GeneralRe: Something like PinmemberMast Avalons5-Jun-12 12:19 
GeneralRe: Something like PinmemberJohnDepth13-Jul-12 8:11 
GeneralBugfix: GetStackTrace writing data in nomans land, and some improvements. PinmemberPEK18-Mar-10 20:24 
Hi
There is a small bug in GetStackTrace (in traceallocations.cpp) that causes the code to write data in nomans land. These lines:
 
if (index <= MAXSTACK) {
      ranOffsets[index][0] = 0;
      ranOffsets[index][1] = 0;
}
Should be:
 
if (index < MAXSTACK) {
      ranOffsets[index][0] = 0;
      ranOffsets[index][1] = 0;
 }
 
In the code below this bug is fixed. I have also fixed a bug in LeakDump that corrupted the output. To the output I have also information about largest total allocation, largest single allocation and largest stack level usage.
 
New code for traceallocations.cpp:
 
/**********************************************************************
Trace alloc
-----------
Purpose:
 
  Implement a allocation check routine that reports the whole
  callstack for each leaked allocation.
 
  Based on the code for ExtendedTrace written by
  Zoltan Csizmadia, zoltan_csizmadia@yahoo.com.
  
Author:
 
  Erik Rydgren, erik@rydgrens.net.
  
Usage:
  1/ Define DETECT_LEAKS in the project settings under
     C++/preprocessor.
 
     If you want checking of overwrites then define DETECT_OVERWRITES
     in the project settings. Change the frequency of the checks by
     altering the NML_CHECK_EVERY define in tracealloc.cpp.
  
  2/ Compile.
  
     If you get multiple defined symbols (overloaded new and delete)
     add linker switch /FORCE:MULTIPLE on the exe and make sure the
     tracealloc new and delete is the ones used. If not, reorder the
     included libraries until they do.
 

Note:
  Normally RtlCaptureStackBackTrace is used but if
  STACKWALK_PRECISE is defined then StackWalk64 is used instead.
  It creates more correct positions and handles more situations
  but it is an order of several magnitudes slower.
 

 
Changes by PEK 2010-03-19
* Fixed bug in GetStackTrace (could write data in no mans land :-)
* Changed stack level size to 50
* Fixed bug in LeakDump (output was corrupted).
* Added stack level monitor
* Added defintions to leading and tail-no mans land and set these to new none-identical values
* Added defintion to magic number.
**********************************************************************/
 
#include "MemLeakFindDll.h"
 
#if defined(_DEBUG) && defined(WIN32) && defined(DETECT_LEAKS)
 
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <dbghelp.h>
#include <string>
#include <assert.h>
#include <WinNT.h>
 
using namespace std;
 
typedef std::basic_string<TCHAR, char_traits<TCHAR> > tcstring;
 
// Setup how much buffer is used for a single path fetch, increase if you get AV's during leak dump (4096 is plenty though)
#define BUFFERSIZE 4096
 
// Define how many levels of callstack that should be fetched for each allocation.
// Each level costs 2*sizof(DWORD64) bytes / allocation.
#define MAXSTACK 50
 
// Define size of no mans land
#define NO_MANS_LAND_SIZE 16
 
// Define frequency of no mans land checking
#define NML_CHECK_EVERY 1000
 
// Define number of call stack levels to ignore (usually 2, TraceAlloc and operator new)
#define NUM_LEVELS_TO_IGNORE 2
 
// Define STACKWALK_PRECISE to get more precise stackwalking at the cost of speed
//#define STACKWALK_PRECISE

// Value writing in no mans land before memory allocation ("begin")
#define LEADING_NOMANSLAND_NUMBER_BYTE		0xbe
 
// Value writing in no mans land after memory allocation ("end")
#define TAIL_NOMANSLAND_NUMBER_BYTE			0xed
 
// The magic number
#define MAGIC_NUMBER						0x55555555
 

void GetStackTrace(HANDLE hThread, DWORD64 ranOffsets[][2] );
void WriteStackTrace(DWORD64 ranOffsets[][2], tcstring& roOut);
void* TraceAlloc(size_t nSize);
void TraceDealloc(void* poMem);
 
USHORT (WINAPI *s_pfnCaptureStackBackTrace)(ULONG, ULONG, PVOID*, PULONG) = 0;  
bool bTraceAlloactions = true;
 
static int nMaxStackLevelUsage = 0;
static size_t nCurrentMemoryUsage = 0;
static size_t nLargestMemoryUsage = 0;
static size_t nLargestAllocationSize = 0;
 
void OutputDebugStringFormat( LPCTSTR lpszFormat, ... )
{
	TCHAR    lpszBuffer[BUFFERSIZE];
	va_list  fmtList;
 
	va_start( fmtList, lpszFormat );
	_vstprintf_s( lpszBuffer, BUFFERSIZE, lpszFormat, fmtList );
	va_end( fmtList );
 
   ::OutputDebugString( lpszBuffer );
}
 
// Unicode safe char* -> TCHAR* conversion
void PCSTR2LPTSTR( PCSTR lpszIn, LPTSTR lpszOut, rsize_t outSize )
{
#if defined(UNICODE)||defined(_UNICODE)
   ULONG index = 0; 
   PCSTR lpAct = lpszIn;
   
	for( ; ; lpAct++ )
	{
		lpszOut[index++] = (TCHAR)(*lpAct);
		if ( *lpAct == 0 )
			break;
	} 
#else
   // This is trivial :)
	strcpy_s( lpszOut, outSize, lpszIn );
#endif
}
 
BOOL __stdcall myReadProcMem(
    HANDLE      hProcess,
    DWORD64     qwBaseAddress,
    PVOID       lpBuffer,
    DWORD       nSize,
    LPDWORD     lpNumberOfBytesRead
) {
  SIZE_T st;
  BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st);
  *lpNumberOfBytesRead = (DWORD) st;
  //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet);
  return bRet;
}
 

// Let's figure out the path for the symbol files
// Search path= ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;" + lpszIniPath
// Note: There is no size check for lpszSymbolPath!
void InitSymbolPath( PSTR lpszSymbolPath, rsize_t symPathSize, PCSTR lpszIniPath )
{
	CHAR lpszPath[BUFFERSIZE];
 
   // Creating the default path
   // ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;"
	strcpy_s( lpszSymbolPath, symPathSize, "." );
 
	// environment variable _NT_SYMBOL_PATH
	if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", lpszPath, BUFFERSIZE ) )
	{
	  strcat_s( lpszSymbolPath, symPathSize, ";" );
		strcat_s( lpszSymbolPath, symPathSize, lpszPath );
	}
 
	// environment variable _NT_ALTERNATE_SYMBOL_PATH
	if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", lpszPath, BUFFERSIZE ) )
	{
	  strcat_s( lpszSymbolPath, symPathSize, ";" );
		strcat_s( lpszSymbolPath, symPathSize, lpszPath );
	}
 
	// environment variable SYSTEMROOT
	if ( GetEnvironmentVariableA( "SYSTEMROOT", lpszPath, BUFFERSIZE ) )
	{
	  strcat_s( lpszSymbolPath, symPathSize, ";" );
		strcat_s( lpszSymbolPath, symPathSize, lpszPath );
		strcat_s( lpszSymbolPath, symPathSize, ";" );
 
		// SYSTEMROOT\System32
		strcat_s( lpszSymbolPath, symPathSize, lpszPath );
		strcat_s( lpszSymbolPath, symPathSize, "\\System32" );
	}
 
   // Add user defined path
	if ( lpszIniPath != NULL )
		if ( lpszIniPath[0] != '\0' )
		{
		  strcat_s( lpszSymbolPath, symPathSize, ";" );
			strcat_s( lpszSymbolPath, symPathSize, lpszIniPath );
		}
}
 
// Uninitialize the loaded symbol files
BOOL UninitSymInfo()
{
	return SymCleanup( GetCurrentProcess() );
}
 
// Initializes the symbol files
BOOL InitSymInfo( PCSTR lpszInitialSymbolPath )
{
	CHAR     lpszSymbolPath[BUFFERSIZE];
   DWORD    symOptions = SymGetOptions();
 
	symOptions |= SYMOPT_LOAD_LINES; 
	symOptions &= ~SYMOPT_UNDNAME;
	SymSetOptions( symOptions );
 
   // Get the search path for the symbol files
	InitSymbolPath( lpszSymbolPath, sizeof(lpszSymbolPath), lpszInitialSymbolPath );
 
	return SymInitialize( GetCurrentProcess(), lpszSymbolPath, TRUE);
}
 
// Get the module name from a given address
BOOL GetModuleNameFromAddress( DWORD64 address, LPTSTR lpszModule, rsize_t lpszModuleSize )
{
	BOOL              ret = FALSE;
	IMAGEHLP_MODULE64   moduleInfo;
 
	::ZeroMemory( &moduleInfo, sizeof(moduleInfo) );
	moduleInfo.SizeOfStruct = sizeof(moduleInfo);
 
	if ( SymGetModuleInfo64( GetCurrentProcess(), (DWORD)address, &moduleInfo ) )
	{
	   // Got it!
		PCSTR2LPTSTR( moduleInfo.ModuleName, lpszModule, lpszModuleSize );
		ret = TRUE;
	}
	else
	   // Not found :(
		_tcscpy_s( lpszModule, lpszModuleSize, _T("?") );
	
	return ret;
}
 
// Get function prototype and parameter info from ip address and stack address
BOOL GetFunctionInfoFromAddresses( DWORD64 fnAddress, DWORD64 stackAddress, LPTSTR lpszSymbol, rsize_t lpszSymbolSize )
{
	BOOL              ret = FALSE;
	DWORD             dwDisp = 0;
	DWORD             dwSymSize = 10000;
   TCHAR             lpszUnDSymbol[BUFFERSIZE]=_T("?");
	CHAR              lpszNonUnicodeUnDSymbol[BUFFERSIZE]="?";
	LPTSTR            lpszParamSep = NULL;
	LPCTSTR           lpszParsed = lpszUnDSymbol;
	PIMAGEHLP_SYMBOL  pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, dwSymSize );
 
	::ZeroMemory( pSym, dwSymSize );
	pSym->SizeOfStruct = dwSymSize;
	pSym->MaxNameLength = dwSymSize - sizeof(IMAGEHLP_SYMBOL);
 
   // Set the default to unknown
	_tcscpy_s( lpszSymbol, lpszSymbolSize, _T("?") );
 
	// Get symbol info for IP
	if ( SymGetSymFromAddr( GetCurrentProcess(), (ULONG)fnAddress, &dwDisp, pSym ) )
	{
	   // Make the symbol readable for humans
		UnDecorateSymbolName( pSym->Name, lpszNonUnicodeUnDSymbol, BUFFERSIZE, 
			UNDNAME_COMPLETE | 
			UNDNAME_NO_THISTYPE |
			UNDNAME_NO_SPECIAL_SYMS |
			UNDNAME_NO_MEMBER_TYPE |
			UNDNAME_NO_MS_KEYWORDS |
			UNDNAME_NO_ACCESS_SPECIFIERS );
 
      // Symbol information is ANSI string
		PCSTR2LPTSTR( lpszNonUnicodeUnDSymbol, lpszUnDSymbol, sizeof(lpszUnDSymbol) );
 
      // I am just smarter than the symbol file :)
		if ( _tcscmp(lpszUnDSymbol, _T("_WinMain@16")) == 0 )
			_tcscpy_s(lpszUnDSymbol, sizeof(lpszUnDSymbol), _T("WinMain(HINSTANCE,HINSTANCE,LPCTSTR,int)"));
		else
		if ( _tcscmp(lpszUnDSymbol, _T("_main")) == 0 )
			_tcscpy_s(lpszUnDSymbol, sizeof(lpszUnDSymbol), _T("main(int,TCHAR * *)"));
		else
		if ( _tcscmp(lpszUnDSymbol, _T("_mainCRTStartup")) == 0 )
			_tcscpy_s(lpszUnDSymbol, sizeof(lpszUnDSymbol), _T("mainCRTStartup()"));
		else
		if ( _tcscmp(lpszUnDSymbol, _T("_wmain")) == 0 )
			_tcscpy_s(lpszUnDSymbol, sizeof(lpszUnDSymbol), _T("wmain(int,TCHAR * *,TCHAR * *)"));
		else
		if ( _tcscmp(lpszUnDSymbol, _T("_wmainCRTStartup")) == 0 )
			_tcscpy_s(lpszUnDSymbol, sizeof(lpszUnDSymbol), _T("wmainCRTStartup()"));
 
		lpszSymbol[0] = _T('\0');
 
    // Skip all templates
    if (_tcschr( lpszParsed, _T('<') ) == NULL) {
      // Let's go through the stack, and modify the function prototype, and insert the actual
      // parameter values from the stack
		  if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == NULL && _tcsstr( lpszUnDSymbol, _T("()") ) == NULL)
		  {
			  ULONG index = 0;
			  for( ; ; index++ )
			  {
				  lpszParamSep = const_cast<LPTSTR>(_tcschr( lpszParsed, _T(',') ));
				  if ( lpszParamSep == NULL )
					  break;
 
				  *lpszParamSep = _T('\0');
 
				  _tcscat_s( lpszSymbol, lpszSymbolSize, lpszParsed );
				  _stprintf_s( lpszSymbol + _tcslen(lpszSymbol), lpszSymbolSize-_tcslen(lpszSymbol), _T("=0x%08X,"), *((ULONG*)(stackAddress) + 2 + index) );
 
				  lpszParsed = lpszParamSep + 1;
			  }
 
			  lpszParamSep = const_cast<LPTSTR>(_tcschr( lpszParsed, _T(')') ));
			  if ( lpszParamSep != NULL )
			  {
				  *lpszParamSep = _T('\0');
 
				  _tcscat_s( lpszSymbol, lpszSymbolSize, lpszParsed );
				  _stprintf_s( lpszSymbol + _tcslen(lpszSymbol), lpszSymbolSize - _tcslen(lpszSymbol), _T("=0x%08X)"), *((ULONG*)(stackAddress) + 2 + index) );
 
				  lpszParsed = lpszParamSep + 1;
			  }
		  }
    }
 
		_tcscat_s( lpszSymbol, lpszSymbolSize, lpszParsed );
   
		ret = TRUE;
	}
 
	GlobalFree( pSym );
 
	return ret;
}
 
// Get source file name and line number from IP address
// The output format is: "sourcefile(linenumber)" or
//                       "modulename!address" or
//                       "address"

BOOL GetSourceInfoFromAddress( DWORD64 address, LPTSTR lpszSourceInfo, rsize_t lpszSourceInfoSize )
{
	BOOL            ret = FALSE;
	IMAGEHLP_LINE64 lineInfo;
	DWORD           dwDisp;
	TCHAR           lpszFileName[BUFFERSIZE] = _T("");
	TCHAR           lpModuleInfo[BUFFERSIZE] = _T("");
 
	_tcscpy_s( lpszSourceInfo, lpszSourceInfoSize, _T("?(?)") );
 
	::ZeroMemory( &lineInfo, sizeof( lineInfo ) );
	lineInfo.SizeOfStruct = sizeof( lineInfo );
 
	if ( SymGetLineFromAddr64( GetCurrentProcess(), address, &dwDisp, &lineInfo ) )
	{
	   // Got it. Let's use "sourcefile(linenumber)" format
		PCSTR2LPTSTR( lineInfo.FileName, lpszFileName, sizeof(lpszFileName) );
		_stprintf_s( lpszSourceInfo, lpszSourceInfoSize, _T("%s(%d)"), lpszFileName, lineInfo.LineNumber );
		ret = TRUE;
	}
	else
	{
      // There is no source file information. :(
      // Let's use the "modulename!address" format
	  	GetModuleNameFromAddress( address, lpModuleInfo, sizeof(lpModuleInfo) );
 
		if ( lpModuleInfo[0] == _T('?') || lpModuleInfo[0] == _T('\0'))
		   // There is no modulename information. :((
         // Let's use the "address" format
			_stprintf_s( lpszSourceInfo, lpszSourceInfoSize, _T("0x%08X"), lpModuleInfo, address );
		else
			_stprintf_s( lpszSourceInfo, lpszSourceInfoSize, _T("%s!0x%08X"), lpModuleInfo, address );
 
		ret = FALSE;
	}
	
	return ret;
}
 

void GetStackTrace(HANDLE hThread, DWORD64 ranOffsets[][2])
{
#ifndef STACKWALK_PRECISE
  if (s_pfnCaptureStackBackTrace) {
    void* stacktrace[MAXSTACK+1];
    int capcount = s_pfnCaptureStackBackTrace(NUM_LEVELS_TO_IGNORE, MAXSTACK, stacktrace, NULL);
    int index;
    for (index = 0; index < capcount; index++) {
      ranOffsets[index][0] = (DWORD64) stacktrace[index];
      ranOffsets[index][1] = (DWORD64) stacktrace[index];
    }
    
	if (index < MAXSTACK) {
      ranOffsets[index][0] = 0;
      ranOffsets[index][1] = 0;
    }
 
	if( capcount > nMaxStackLevelUsage )
		nMaxStackLevelUsage = capcount;
  }
#else
	STACKFRAME64   callStack;
	BOOL           bResult;
	CONTEXT        context;
	HANDLE         hProcess = GetCurrentProcess();
 
  memset(&context, 0, sizeof(CONTEXT));
  context.ContextFlags = CONTEXT_FULL;
  if (hThread == GetCurrentThread())
  {
    RtlCaptureContext(&context);
  }
  else
  {
		if ( SuspendThread( hThread ) == -1 )
		{
		   // whaaat ?!
		   OutputDebugStringFormat( _T("Call stack info(thread=0x%X) SuspendThread failed.\n") );
			return;
		}
 
    if (GetThreadContext(hThread, &context) == FALSE)
    {
      OutputDebugStringFormat( _T("Call stack info(thread=0x%X) GetThreadContext failed.\n") );
      ResumeThread(hThread);
      return;
    }
  }
	
	::ZeroMemory( &callStack, sizeof(callStack) );
 
  DWORD imageType;
#ifdef _M_IX86
  // normally, call ImageNtHeader() and use machine info from PE header
  imageType = IMAGE_FILE_MACHINE_I386;
  callStack.AddrPC.Offset = context.Eip;
  callStack.AddrPC.Mode = AddrModeFlat;
  callStack.AddrFrame.Offset = context.Ebp;
  callStack.AddrFrame.Mode = AddrModeFlat;
  callStack.AddrStack.Offset = context.Esp;
  callStack.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
  imageType = IMAGE_FILE_MACHINE_AMD64;
  callStack.AddrPC.Offset = context.Rip;
  callStack.AddrPC.Mode = AddrModeFlat;
  callStack.AddrFrame.Offset = context.Rsp;
  callStack.AddrFrame.Mode = AddrModeFlat;
  callStack.AddrStack.Offset = context.Rsp;
  callStack.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
  imageType = IMAGE_FILE_MACHINE_IA64;
  callStack.AddrPC.Offset = context.StIIP;
  callStack.AddrPC.Mode = AddrModeFlat;
  callStack.AddrFrame.Offset = context.IntSp;
  callStack.AddrFrame.Mode = AddrModeFlat;
  callStack.AddrBStore.Offset = context.RsBSP;
  callStack.AddrBStore.Mode = AddrModeFlat;
  callStack.AddrStack.Offset = context.IntSp;
  callStack.AddrStack.Mode = AddrModeFlat;
#else
#error "Platform not supported!"
#endif
 
	for( ULONG index = 0;; index++ ) 
	{
		bResult = StackWalk64(
			imageType,
			hProcess,
			hThread,
	    &callStack,
			&context, 
			NULL, //myReadProcMem,
			SymFunctionTableAccess64,
			SymGetModuleBase64,
			NULL);
 
    // Ignore the first two levels (it's only TraceAlloc and operator new anyhow)
		if ( index < NUM_LEVELS_TO_IGNORE )
	    continue;
 
    // Break if we have fetched MAXSTACK levels
    if ( index-NUM_LEVELS_TO_IGNORE == MAXSTACK)
      break;
 
    // If we are at the top of the stackframe then break.
		if( !bResult || callStack.AddrFrame.Offset == 0) {
      ranOffsets[index-NUM_LEVELS_TO_IGNORE][0] = 0;
      ranOffsets[index-NUM_LEVELS_TO_IGNORE][1] = 0;
			break;
    }
    	
    // Remember program counter and frame pointer
    ranOffsets[index-NUM_LEVELS_TO_IGNORE][0] = callStack.AddrPC.Offset;
    ranOffsets[index-NUM_LEVELS_TO_IGNORE][1] = callStack.AddrFrame.Offset;
	}
 
	if ( hThread != GetCurrentThread() )
		ResumeThread( hThread );
#endif
}
 

void WriteStackTrace(DWORD64 ranOffsets[][2], tcstring& roOut)
{
	TCHAR symInfo[BUFFERSIZE] = _T("?");
	TCHAR srcInfo[BUFFERSIZE] = _T("?");
 
  for (ULONG index = 0; index < MAXSTACK && ranOffsets[index][0] != 0 && ranOffsets[index][1] != 0; index++) {
  	GetFunctionInfoFromAddresses( ranOffsets[index][0], ranOffsets[index][1], symInfo, sizeof(symInfo) );
	  GetSourceInfoFromAddress( ranOffsets[index][0], srcInfo, sizeof(srcInfo) );
 
    roOut += _T("     ");
    roOut += srcInfo;
    roOut += _T(" : ");
    roOut += symInfo;
    roOut += _T("\n");
  }
}
 

struct sdAllocBlock {
  unsigned long nMagicNumber;
  sdAllocBlock* poNext;
  sdAllocBlock* poPrev;
  size_t nSize;
  DWORD64 anStack[MAXSTACK][2];
  char pzNoMansLand[NO_MANS_LAND_SIZE];
 
  sdAllocBlock()
  {
    Init();
  }
 
  void Init() {
    poNext = this;
    poPrev = this;
    nMagicNumber = MAGIC_NUMBER;
  }
 
  void Disconnect() {
    if (poNext != this) {
      poNext->poPrev = poPrev;
      poPrev->poNext = poNext;
      poNext = this;
      poPrev = this;
    }
  }
 
  void ConnectTo(sdAllocBlock* poPos) {
    Disconnect();
    poPrev = poPos;
    poNext = poPos->poNext;
    poPos->poNext->poPrev = this;
    poPos->poNext = this;
  }
};
 

void LeakDump(tcstring& roOut);
 

class CS {
private:
  CRITICAL_SECTION cs;
 
public:
  CS() { InitializeCriticalSection(&cs); }
  ~CS() { }
  operator CRITICAL_SECTION& () { return cs; }
};
 

class Guard {
private:
  CRITICAL_SECTION& rcs;
 
public:
  Guard(CRITICAL_SECTION& rcs)
  : rcs(rcs) { EnterCriticalSection(&rcs); }
  ~Guard() { LeaveCriticalSection(&rcs); }
};
 

class cLeakDetector
{
public:
  cLeakDetector() {
    if (s_pfnCaptureStackBackTrace == 0) {  
      const HMODULE hNtDll = ::GetModuleHandle("ntdll.dll");  
      reinterpret_cast<void*&>(s_pfnCaptureStackBackTrace) = ::GetProcAddress(hNtDll, "RtlCaptureStackBackTrace");  
    }  
    InitSymInfo(NULL);
  }
 
  ~cLeakDetector() {
    bTraceAlloactions = false; // All subsequent allocations will be plain malloc/free
    tcstring leaks;    
    LeakDump(leaks);    
    OutputDebugString(leaks.c_str());    
    Sleep(1000);
    UninitSymInfo();  
  }
};
 

static unsigned int nNumAllocs = 0;
static unsigned int nCurrentAllocs = 0;
static unsigned int nMaxConcurrent = 0;
 

 
CS& Gate() {
  static CS cs;
  return cs;
}
 

sdAllocBlock& Head()
{
  static cLeakDetector oDetector;
  static sdAllocBlock oHead;
  return oHead;
}
 

class cInitializer { 
  public: cInitializer() { Head(); }; 
} oInitalizer;
 

void LeakDump(tcstring& roOut)
{
  Guard at(Gate());
  
  TCHAR buffer[65];
  int leakCount = 0;
 
  sdAllocBlock* poBlock = Head().poNext;
  while (poBlock != &Head()) {
    tcstring stack;
    WriteStackTrace(poBlock->anStack, stack);
 
    bool bIsKnownLeak = false;
 
    // afxMap leaks is MFC. Not ours.
    if (stack.find(_T(": afxMap")) != tcstring::npos)
      bIsKnownLeak = true;
 
    if (!bIsKnownLeak) {
      roOut += _T("\nLeak of ");
      _itot_s(poBlock->nSize, buffer, sizeof(buffer), 10);
      roOut += buffer;
      roOut += _T(" bytes detected (");
      _itot_s(poBlock->nSize + sizeof(sdAllocBlock) + NO_MANS_LAND_SIZE, buffer, sizeof(buffer), 10);
      roOut += buffer;
      roOut += _T(" bytes with headers and no mans land)\n");
      roOut += stack;
 
	  leakCount++;
    }
 
    poBlock = poBlock->poNext;
  }
 
  roOut += _T("\nMemory statistics\n-----------------\n"); 
  if( leakCount > 0 )
  {
	  roOut += _T("Number of leaks: ");
	  _itot_s(leakCount, buffer, sizeof(buffer), 10);
	  roOut += buffer;
	  roOut += _T("\n");
  }
 
  roOut += _T("Total allocations: ");
  _itot_s(nNumAllocs, buffer, sizeof(buffer), 10);
  roOut += buffer;
  roOut += _T("\n");
  roOut += _T("Max concurrent allocations: ");
  _itot_s(nMaxConcurrent, buffer, sizeof(buffer), 10);
  roOut += buffer;
  roOut += _T("\n");
  roOut += _T("Largest total allocations: ");
  _itot_s(nLargestMemoryUsage, buffer, sizeof(buffer), 10);
  roOut += buffer;
  roOut += _T("\n");
  roOut += _T("Largest single allocation: ");
  _itot_s(nLargestAllocationSize, buffer, sizeof(buffer), 10);
  roOut += buffer;
  roOut += _T("\n");
  roOut += _T("Max stack level usage: ");
  _itot_s(nMaxStackLevelUsage, buffer, sizeof(buffer), 10);
  roOut += buffer;
  roOut += _T("\n\n");
}
 

bool AssertMem(char* m, char c, size_t s)
{
  size_t i;
  for (i = 0; i < s; i++)
    if (m[i] != c) break;
  return i >= s;
}
 

void CheckNoMansLand()
{
  Guard at(Gate());
 
  sdAllocBlock* poBlock = Head().poNext;
  while (poBlock != &Head()) {
    if (!AssertMem(poBlock->pzNoMansLand, (char) LEADING_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE)) {
      bool MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD = false;
      tcstring stack;
      WriteStackTrace(poBlock->anStack, stack);
      assert(MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD);
    }
    char* pzNoMansLand = ((char*)poBlock) + sizeof(sdAllocBlock) + poBlock->nSize;
    if (!AssertMem(pzNoMansLand, (char) TAIL_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE)) {
      bool MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_TAIL = false;
      tcstring stack;
      WriteStackTrace(poBlock->anStack, stack);
      assert(MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_TAIL);
    }
    poBlock = poBlock->poNext;
  }
}
 

void* TraceAlloc(size_t nSize)
{
  Guard at(Gate());
  
  if (bTraceAlloactions) {
    nNumAllocs++;
#ifdef DETECT_OVERWRITES
    if (nNumAllocs % NML_CHECK_EVERY == 0) {
      CheckNoMansLand();
    }
#endif
 
    sdAllocBlock* poBlock = (sdAllocBlock*) malloc(nSize + sizeof(sdAllocBlock) + NO_MANS_LAND_SIZE);
    poBlock->Init();
    poBlock->nSize = nSize;
    char* pzNoMansLand = ((char*)poBlock) + sizeof(sdAllocBlock) + poBlock->nSize;
    memset(poBlock->pzNoMansLand, LEADING_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE);
    memset(pzNoMansLand, TAIL_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE);
 
    GetStackTrace(GetCurrentThread(), poBlock->anStack );
 
    poBlock->ConnectTo(&Head());
    nCurrentAllocs++;
    if (nCurrentAllocs > nMaxConcurrent)
      nMaxConcurrent = nCurrentAllocs;
 
	nCurrentMemoryUsage += nSize;
	if( nLargestMemoryUsage < nCurrentMemoryUsage )
		nLargestMemoryUsage = nCurrentMemoryUsage;
	if( nLargestAllocationSize < nSize )
		nLargestAllocationSize = nSize;
 
    return (void*)(((char*) poBlock) + sizeof(sdAllocBlock));
  }
  else {
    return malloc(nSize);
  }
}
 

void TraceDealloc(void* poMem)
{
  Guard at(Gate());
 
  if (!poMem) return; // delete NULL; = do nothing

  if (bTraceAlloactions) {
    sdAllocBlock* poBlock = (sdAllocBlock*) ((char*)poMem - sizeof(sdAllocBlock));
    char* pzNoMansLand = ((char*)poBlock) + sizeof(sdAllocBlock) + poBlock->nSize;
 
	nCurrentMemoryUsage -= poBlock->nSize;
 
	if (poBlock->nMagicNumber != MAGIC_NUMBER) {
		// Whupps, something fishy is going on

      // Validate the address against our list of allocated blocks
      sdAllocBlock* poLoopBlock = Head().poNext;
      while (poLoopBlock != &Head() && poLoopBlock != poBlock)
        poLoopBlock = poLoopBlock->poNext;
      if (poLoopBlock == &Head()) {
        // Hell we didn't allocate this block.
        // Just free the memory and hope for the best.
        free(poMem);
      }
      else {
        bool MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD = false;
        assert(MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD);
      }
    }  
    else if (!AssertMem(poBlock->pzNoMansLand, (char) LEADING_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE)) {
      bool MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD = false;
      assert(MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_LEAD);
    }
    else if (!AssertMem(pzNoMansLand, (char) TAIL_NOMANSLAND_NUMBER_BYTE, NO_MANS_LAND_SIZE)) {
      bool MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_TAIL = false;
      assert(MEMORYERROR_STUFF_WRITTEN_IN_NOMANSLAND_TAIL);
    }
    else {
      poBlock->Disconnect();
      free(poBlock);
      nCurrentAllocs--;
    }
  }
  else {
    free(poMem);
  }
}
 
// Take over global new and delete
void* operator new(size_t s)
{
  return TraceAlloc(s);
}
 
void* operator new[](size_t s)
{
  return TraceAlloc(s);
}
 
void operator delete(void* pMem)
{
  TraceDealloc(pMem);
}
 
void operator delete[] (void* pMem)
{
  TraceDealloc(pMem);
}
 
// And then some crap for taking over MFC allocations.
void* __cdecl operator new(size_t s, LPCSTR lpszFileName, int nLine)
{
  return TraceAlloc(s);
}
 
void* __cdecl operator new[](size_t s, LPCSTR lpszFileName, int nLine)
{
  return TraceAlloc(s);
}
 
void __cdecl operator delete(void* pMem, LPCSTR /* lpszFileName */, int /* nLine */)
{
  TraceDealloc(pMem);
}
 
void __cdecl operator delete[](void* pMem, LPCSTR /* lpszFileName */, int /* nLine */)
{
  TraceDealloc(pMem);
}
 
#endif

GeneralRe: Bugfix: GetStackTrace writing data in nomans land, and some improvements. PinmemberTardi S1-Jun-10 3:16 
Generalmemory leak finder for java. Pinmemberdpmpardhu9-Jun-09 22:11 
GeneralRe: memory leak finder for java. PinmemberErik Rydgren9-Jun-09 23:37 
GeneralWorks great with VS 6 C++ Pinmemberfeanorgem31-May-09 3:42 
NewsCode now upgraded to Visual Studio 2008 PinmemberErik Rydgren12-Oct-08 20:33 
GeneralRe: Code now upgraded to Visual Studio 2008 PinmemberMember 307553127-Nov-08 20:02 
GeneralRe: Code now upgraded to Visual Studio 2008 PinmemberEiswuxe216-Apr-09 12:38 
GeneralRe: Code now upgraded to Visual Studio 2008 PinmemberErik Rydgren16-Apr-09 22:59 
GeneralLinker Errors PinmemberSuper Garrison14-Jan-08 14:10 
GeneralRe: Linker Errors Pinmembermatakk@126.com10-Oct-08 18:22 
GeneralRe: Linker Errors PinmemberErik Rydgren12-Oct-08 20:29 
GeneralThanks. Pinmembermark-w12-Sep-07 14:55 
GeneralCompile errors in VC8 PinmemberDavid_Leikis5-Jun-07 9:16 
GeneralRe: Compile errors in VC8 PinmemberDavid_Leikis5-Jun-07 9:26 
GeneralTrouble in tracking memory leak PinmemberChandrasekar S10-Oct-05 2:20 
GeneralMissing __cdecl PinmemberJoão Paulo Figueira4-May-05 10:05 
GeneralFixing bugs PinmemberSergey Solozhentsev5-Dec-04 21:03 
GeneralTrouble Getting Leak Details PinsussAColombi5-Nov-04 19:24 
GeneralRe: Trouble Getting Leak Details PinmemberErik Rydgren7-Nov-04 20:30 
GeneralGetLastError() after new PinmemberMisanthrop7-Oct-04 5:48 
GeneralRe: GetLastError() after new PinmemberErik Rydgren8-Oct-04 4:17 
Generalwrong line numbers pointed Pinmemberbnujos29-Sep-04 20:51 
GeneralRe: wrong line numbers pointed PinmemberErik Rydgren29-Sep-04 22:53 
GeneralCompiler error with _UNICODE Pinmemberjpteruel27-Sep-04 22:23 
GeneralRe: Compiler error with _UNICODE PinmemberErik Rydgren27-Sep-04 23:19 
GeneralRe: Compiler error with _UNICODE Pinmemberjpteruel27-Sep-04 23:35 
GeneralRe: Compiler error with _UNICODE PinmemberErik Rydgren27-Sep-04 23:40 
GeneralRe: Compiler error with _UNICODE Pinmemberjpteruel28-Sep-04 2:22 
Generalusing memory leak finder in a project with multiple dlls PinmemberKevin Cao20-Sep-04 20:24 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberErik Rydgren22-Sep-04 3:26 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberDieter Hammer23-Sep-04 5:28 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberErik Rydgren23-Sep-04 5:40 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberCurtis W23-Sep-04 7:25 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberErik Rydgren23-Sep-04 20:27 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberCurtis W23-Sep-04 21:47 
GeneralRe: using memory leak finder in a project with multiple dlls PinsussJaniOrca24-Sep-04 1:36 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberJonas O15-Oct-04 4:34 
GeneralRe: using memory leak finder in a project with multiple dlls PinmemberErik Rydgren15-Oct-04 4:41 
Generalmemory decrease PinsussAnonymous28-Jul-04 17:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140921.1 | Last Updated 22 Feb 2012
Article Copyright 2003 by Erik Rydgren
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid