/*************************************************************
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;
}