Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C++

Memory Leak Detection

Rate me:
Please Sign up or sign in to vote.
4.71/5 (55 votes)
3 Jul 2009CPOL7 min read 478.5K   14.8K   217  
Adding Memory Leak Detection in your applications
/*************************************************************
 Author		: David A. Jones
 File Name	: MemLeakDetect.h
 Date		: July 30, 2004
 Synopsis		 
			A trace memory feature for source code to trace and
			find memory related bugs. 


****************************************************************/
#define VC_EXTRALEAN		// Exclude rarely-used stuff from Windows headers
//
#pragma warning(disable:4312)
#pragma warning(disable:4313)
#pragma warning(disable:4267)
#pragma warning(disable:4100)

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxole.h>         // MFC OLE classes
#include <afxodlgs.h>       // MFC OLE dialog classes
#include <afxdisp.h>        // MFC Automation classes
#include <afxdtctl.h>		// MFC support for Internet Explorer 4 Common Controls
#include <afxcmn.h>			// MFC support for Windows Common Controls
#include <afxsock.h>		// MFC socket extensions
#include <afxdb.h>			// MFC ODBC database classes
#include <afxdao.h>			// MFC DAO database classes
#include <winreg.h>			// MFC Registry
#include <afxtempl.h>
#include <afxdlgs.h>
#include <afxcview.h>
#include <afxmt.h>
#include <oleauto.h>
#include <atlbase.h>

#include "MemLeakDetect.h"

#undef DEBUG_NEW
#undef delete
#undef new
#undef malloc
#undef free

static CMemLeakDetect *g_MemTrace = 0;
static _CRT_ALLOC_HOOK pfnOldCrtAllocHook = NULL;
extern _crtDgbFlag;

int catchMemoryAllocHook(int	allocType, 
						 void	*userData, 
						 size_t size, 
						 int	blockType, 
						 long	requestNumber, 
		  const unsigned char	*filename, 
						 int	lineNumber)
{
	_CrtMemBlockHeader *pCrtHead;
	long prevRequestNumber;

	// internal C library internal allocations
	if ( blockType == _CRT_BLOCK )
	{
		return( TRUE );
	}
	// check if someone has turned off mem tracing
	if  ((( _CRTDBG_ALLOC_MEM_DF & _crtDbgFlag) == 0) && 
		(( allocType			== _HOOK_ALLOC)		|| 
			( allocType			== _HOOK_REALLOC)))
	{
		if (pfnOldCrtAllocHook)
		{
			pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
		}
		return TRUE;
	}

	// protect if mem trace is not initialized
	if (g_MemTrace == NULL)
	{
		if (pfnOldCrtAllocHook)
		{
			pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
		}
		return TRUE;
	}

	// protect internal mem trace allocs
	if (g_MemTrace->isLocked)
	{
		if (pfnOldCrtAllocHook)
		{
			pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
		}
		return( TRUE);
	}
	// lock the function
	g_MemTrace->isLocked = true;
	//
	if (allocType == _HOOK_ALLOC)
	{
		g_MemTrace->addMemoryTrace((void *) requestNumber, size, (char*)filename, lineNumber);
	}
	else
	if (allocType == _HOOK_REALLOC)
	{
		if (_CrtIsValidHeapPointer(userData))
		{
			pCrtHead = pHdr(userData);
			prevRequestNumber = pCrtHead->lRequest;
			//
			if (pCrtHead->nBlockUse == _IGNORE_BLOCK)
			{
				if (pfnOldCrtAllocHook)
				{
					pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
				}
				goto END;
			}
	   		g_MemTrace->redoMemoryTrace((void *) requestNumber, (void *) prevRequestNumber, size, (char*)filename, lineNumber);
		}
	}
	else
	if (allocType == _HOOK_FREE)
	{
		if (_CrtIsValidHeapPointer(userData))
		{
			pCrtHead = pHdr(userData);
			requestNumber = pCrtHead->lRequest;
			//
			if (pCrtHead->nBlockUse == _IGNORE_BLOCK)
			{
				if (pfnOldCrtAllocHook)
				{
					pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
				}
				goto END;
			}
	   		g_MemTrace->removeMemoryTrace((void *) requestNumber);
		}
	}
END:
	// unlock the function
	g_MemTrace->isLocked = false;
	return TRUE;
}

void CMemLeakDetect::addMemoryTrace(void* addr,  DWORD asize,  char *fname, DWORD lnum)
{
	AllocBlockInfo ainfo;
	//
	if (m_AllocatedMemoryList.Lookup(addr,(AllocBlockInfo&) ainfo))
	{
		// already allocated
		AfxTrace("ERROR!CMemLeakDetect::addMemoryTrace() Address(0x%08X) already allocated\n", addr);
		return;
	}
	ainfo.address		= addr;
	ainfo.lineNumber	= lnum;
	ainfo.size			= asize;
	ainfo.occurance		= memoccurance++;
	symStackTrace(&ainfo.traceinfo[0]);
	//
	if (fname)
		strncpy(&ainfo.fileName[0], fname, MLD_MAX_NAME_LENGTH);
	else
	  ainfo.fileName[0] = 0;

	m_AllocatedMemoryList.SetAt(addr, ainfo);
};
void CMemLeakDetect::redoMemoryTrace(void* addr,  void* oldaddr, DWORD asize,  char *fname, DWORD lnum)
{
	AllocBlockInfo ainfo;

	if (m_AllocatedMemoryList.Lookup(oldaddr,(AllocBlockInfo &) ainfo))
	{
		m_AllocatedMemoryList.RemoveKey(oldaddr);
	}
	else
	{
		AfxTrace("ERROR!CMemLeakDetect::redoMemoryTrace() didnt find Address(0x%08X) to free\n", oldaddr);
	}
	//
	ainfo.address		= addr;
	ainfo.lineNumber	= lnum;
	ainfo.size			= asize;
	ainfo.occurance		= memoccurance++;
	symStackTrace(&ainfo.traceinfo[0]);
	//
	if (fname)
		strncpy(&ainfo.fileName[0], fname, MLD_MAX_NAME_LENGTH);
	else
	  ainfo.fileName[0] = 0;

	m_AllocatedMemoryList.SetAt(addr, ainfo);
};
void CMemLeakDetect::removeMemoryTrace(void* addr)
{
	AllocBlockInfo ainfo;
	//
	if (m_AllocatedMemoryList.Lookup(addr,(AllocBlockInfo &) ainfo))
	{
		m_AllocatedMemoryList.RemoveKey(addr);
	}
	else
	{
		AfxTrace("ERROR!CMemLeakDetect::removeMemoryTrace() didnt find Address(0x%08X) to free\n", addr);
	   //freeing unallocated memory
	}
};
void CMemLeakDetect::cleanupMemoryTrace()
{
	m_AllocatedMemoryList.RemoveAll();
};

void CMemLeakDetect::dumpMemoryTrace()
{
	POSITION pos;
	LPVOID   addr;
	AllocBlockInfo ainfo;
	char buf[MLD_MAX_NAME_LENGTH];
	int totalSize = 0;
	int numLeaks = 0;
	STACKFRAMEENTRY *p;
	
	pos = m_AllocatedMemoryList.GetStartPosition();
	while(pos)
	{
		numLeaks++;
		sprintf(buf, "Memory Leak(%d)------------------->\n", numLeaks);
		AfxTrace(buf);
		//
		m_AllocatedMemoryList.GetNextAssoc(pos, (LPVOID &) addr, (AllocBlockInfo&) ainfo);

		if (ainfo.fileName)
		{
			sprintf(buf, "Memory Leak <0x%X> bytes(%d) occurance(%d) %s(%d)\n", 
					ainfo.address, ainfo.size, ainfo.occurance, ainfo.fileName, ainfo.lineNumber);
		}
		else
		{
			sprintf(buf, "Memory Leak <0x%X> bytes(%d) occurance(%d)\n", 
					ainfo.address, ainfo.size, ainfo.occurance);
		}
		AfxTrace(buf);
		//
		char          symInfo[MLD_MAX_NAME_LENGTH] = _T("?");
		char          srcInfo[MLD_MAX_NAME_LENGTH] = _T("?");

		p = &ainfo.traceinfo[0];
		while(p[0].AddrFrame.Offset)
		{
			symFunctionInfoFromAddresses( p[0].AddrPC.Offset, p[0].AddrFrame.Offset, symInfo );
			symSourceInfoFromAddress(     p[0].AddrPC.Offset, srcInfo );
			AfxTrace("%s : %s\n", srcInfo, symInfo);
			p++;
		}
		totalSize += ainfo.size;
	}
		sprintf(buf, "\n-----------------------------------------------------------\n");
		AfxTrace(buf);
		if(!totalSize) 
		{
			sprintf(buf,"No Memory Leaks Detected for %d Allocations\n\n", memoccurance);
			AfxTrace(buf);
		}
		else
		{
		sprintf(buf, "Total %d Memory Leaks: %d bytes Total Alocations %d\n\n", numLeaks, totalSize, memoccurance);
		AfxTrace(buf);
		}
};
void CMemLeakDetect::Init()
{
	  m_dwsymBufSize = (MLD_MAX_NAME_LENGTH + sizeof(PIMAGEHLP_SYMBOL));
	  m_hProcess = GetCurrentProcess();
	  m_pSymbol = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, m_dwsymBufSize);

	  m_AllocatedMemoryList.InitHashTable(10211, TRUE);
	  initSymInfo( NULL );
	  isLocked	 = false;
	  g_MemTrace = this;
	  pfnOldCrtAllocHook = _CrtSetAllocHook( catchMemoryAllocHook ); 
}
void CMemLeakDetect::End()
{
	isLocked	 = true;
	_CrtSetAllocHook(pfnOldCrtAllocHook);
	dumpMemoryTrace();
	cleanupMemoryTrace();
	cleanupSymInfo();
	GlobalFree(m_pSymbol);
	g_MemTrace = NULL;
}
CMemLeakDetect::CMemLeakDetect()
{
	Init();
}

CMemLeakDetect::~CMemLeakDetect()
{
	End();
}

// PRIVATE STUFF
void CMemLeakDetect::symbolPaths( char* lpszSymbolPath)
{
	CHAR lpszPath[MLD_MAX_NAME_LENGTH];

   // Creating the default path where the dgbhelp.dll is located
   // ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;"
	strcpy( lpszSymbolPath, ".;..\\;..\\..\\" );

	// environment variable _NT_SYMBOL_PATH
	if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", lpszPath, MLD_MAX_NAME_LENGTH ) )
	{
	   strcat( lpszSymbolPath, ";" );
		strcat( lpszSymbolPath, lpszPath );
	}

	// environment variable _NT_ALTERNATE_SYMBOL_PATH
	if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", lpszPath, MLD_MAX_NAME_LENGTH ) )
	{
	   strcat( lpszSymbolPath, ";" );
		strcat( lpszSymbolPath, lpszPath );
	}

	// environment variable SYSTEMROOT
	if ( GetEnvironmentVariableA( "SYSTEMROOT", lpszPath, MLD_MAX_NAME_LENGTH ) )
	{
	   strcat( lpszSymbolPath, ";" );
		strcat( lpszSymbolPath, lpszPath );
		strcat( lpszSymbolPath, ";" );

		// SYSTEMROOT\System32
		strcat( lpszSymbolPath, lpszPath );
		strcat( lpszSymbolPath, "\\System32" );
	}
}

BOOL CMemLeakDetect::cleanupSymInfo()
{
	return SymCleanup( GetCurrentProcess() );
}

// Initializes the symbol files
BOOL CMemLeakDetect::initSymInfo( char* lpszUserSymbolPath )
{
	CHAR    lpszSymbolPath[MLD_MAX_NAME_LENGTH];
    DWORD   symOptions = SymGetOptions();

	symOptions |= SYMOPT_LOAD_LINES; 
	symOptions &= ~SYMOPT_UNDNAME;
	SymSetOptions( symOptions );

    // Get the search path for the symbol files
	symbolPaths( lpszSymbolPath);
	//
	if (lpszUserSymbolPath)
	{
		strcat(lpszSymbolPath, ";");
		strcat(lpszSymbolPath, lpszUserSymbolPath);
	}
	return SymInitialize( GetCurrentProcess(), lpszSymbolPath, TRUE);
}

void CMemLeakDetect::symStackTrace(STACKFRAMEENTRY* pStacktrace )
{
	STACKFRAME     callStack;
	BOOL           bResult;
	CONTEXT        context;
	HANDLE		   hThread  = GetCurrentThread();

	// get the context
	memset( &context, NULL, sizeof(context) );
	context.ContextFlags = CONTEXT_FULL;
	if ( !GetThreadContext( hThread, &context ) )
	{
       AfxTrace("Call stack info(thread=0x%X) failed.\n", hThread );
	   return;
	}
	//initialize the call stack
	memset( &callStack, NULL, sizeof(callStack) );
	callStack.AddrPC.Offset    = context.Eip;
	callStack.AddrStack.Offset = context.Esp;
	callStack.AddrFrame.Offset = context.Ebp;
	callStack.AddrPC.Mode      = AddrModeFlat;
	callStack.AddrStack.Mode   = AddrModeFlat;
	callStack.AddrFrame.Mode   = AddrModeFlat;
	//
	for( DWORD index = 0; index < MLD_MAX_TRACEINFO; index++ ) 
	{
		bResult = StackWalk(IMAGE_FILE_MACHINE_I386,
							m_hProcess,
							hThread,
							&callStack,
							NULL, 
							NULL,
							SymFunctionTableAccess,
							SymGetModuleBase,
							NULL);

		if ( index == 0 )
		   continue;

		if( !bResult || callStack.AddrFrame.Offset == 0 ) 
			break;
		//
		pStacktrace[0].AddrPC	 = callStack.AddrPC;
		pStacktrace[0].AddrFrame = callStack.AddrFrame;
		pStacktrace++;
	}
	//clear the last entry
	memset(pStacktrace, NULL, sizeof(STACKFRAMEENTRY));
}

BOOL CMemLeakDetect::symFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, LPTSTR lpszSymbol )
{
	DWORD             dwDisp = 0;

	::ZeroMemory(m_pSymbol, m_dwsymBufSize );
	m_pSymbol->SizeOfStruct	= m_dwsymBufSize;
	m_pSymbol->MaxNameLength = m_dwsymBufSize - sizeof(IMAGEHLP_SYMBOL);

    // Set the default to unknown
	_tcscpy( lpszSymbol, _T("?") );

	// Get symbol info for IP
	if ( SymGetSymFromAddr( m_hProcess, (ULONG)fnAddress, &dwDisp, m_pSymbol ) )
	{
		_tcscpy(lpszSymbol, m_pSymbol->Name);
	}
	return TRUE;
}

BOOL CMemLeakDetect::symSourceInfoFromAddress(UINT address, char* lpszSourceInfo)
{
	BOOL           ret = FALSE;
	IMAGEHLP_LINE  lineInfo;
	DWORD          dwDisp;
	char          lpModuleInfo[MLD_MAX_NAME_LENGTH] = _T("");

	strcpy( lpszSourceInfo, "?(?)");

	memset( &lineInfo, NULL, sizeof( IMAGEHLP_LINE ) );
	lineInfo.SizeOfStruct = sizeof( IMAGEHLP_LINE );

	if ( SymGetLineFromAddr( m_hProcess, address, &dwDisp, &lineInfo ) )
	{
	   // Using the "sourcefile(linenumber)" format
		sprintf( lpszSourceInfo, "%s(%d)", lineInfo.FileName, lineInfo.LineNumber );
		ret = TRUE;
	}
	else
	{
        // Using the "modulename!address" format
	  	symModuleNameFromAddress( address, lpModuleInfo );

		if ( lpModuleInfo[0] == _T('?') || lpModuleInfo[0] == _T('\0'))
		{
			// Using the "address" format
			sprintf(lpszSourceInfo, "0x%08X", lpModuleInfo, address );
		}
		else
		{
			sprintf(lpszSourceInfo, "%s!0x%08X", lpModuleInfo, address );
		}
		ret = FALSE;
	}
	//
	return ret;
}

BOOL CMemLeakDetect::symModuleNameFromAddress( UINT address, char* lpszModule )
{
	BOOL              ret = FALSE;
	IMAGEHLP_MODULE   moduleInfo;

	::ZeroMemory( &moduleInfo, sizeof(IMAGEHLP_MODULE) );
	moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);

	if ( SymGetModuleInfo( m_hProcess, (DWORD)address, &moduleInfo ) )
	{
		strcpy(moduleInfo.ModuleName, lpszModule);
		ret = TRUE;
	}
	else
	{
		strcpy( lpszModule, "?");
	}
	
	return ret;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions