/*************************************************************
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 "stdafx.h"
#include "MemLeakDetect.h"
static CMemLeakDetect* g_pMemTrace = NULL;
static _CRT_ALLOC_HOOK pfnOldCrtAllocHook = NULL;
extern _crtDgbFlag;
int MyTrace(LPCTSTR lpszFormat, ...)
{
TCHAR buffer[1024];
va_list args;
va_start( args, lpszFormat);
vsprintf( buffer, lpszFormat, args );
return _CrtDbgReport(_CRT_WARN,NULL,NULL,NULL,buffer);
}
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_pMemTrace == NULL)
{
if (pfnOldCrtAllocHook)
{
pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
}
return TRUE;
}
// protect internal mem trace allocs
if (g_pMemTrace->isLocked)
{
if (pfnOldCrtAllocHook)
{
pfnOldCrtAllocHook(allocType, userData, size, blockType, requestNumber, filename, lineNumber);
}
return( TRUE);
}
// lock the function
g_pMemTrace->isLocked = true;
//
if (allocType == _HOOK_ALLOC)
{
g_pMemTrace->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_pMemTrace->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_pMemTrace->removeMemoryTrace((void *) requestNumber, userData);
}
}
END:
// unlock the function
g_pMemTrace->isLocked = false;
return TRUE;
}
void CMemLeakDetect::addMemoryTrace(void* addr, DWORD asize, TCHAR *fname, DWORD lnum)
{
AllocBlockInfo ainfo;
//
if (m_AllocatedMemoryList.Lookup(addr, 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++;
MLD_STACKWALKER(&ainfo.traceinfo[0]);
//
if (fname)
_tcsncpy(&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(_T("ERROR!CMemLeakDetect::redoMemoryTrace() didnt find Address(0x%08X) to free\n"), oldaddr);
}
//
ainfo.address = addr;
ainfo.lineNumber = lnum;
ainfo.size = asize;
ainfo.occurance = memoccurance++;
MLD_STACKWALKER(&ainfo.traceinfo[0]);
//
if (fname)
_tcsncpy(&ainfo.fileName[0], fname, MLD_MAX_NAME_LENGTH);
else
ainfo.fileName[0] = 0;
m_AllocatedMemoryList.SetAt(addr, ainfo);
};
void CMemLeakDetect::removeMemoryTrace(void* addr, void* realdataptr)
{
AllocBlockInfo ainfo;
//
if (m_AllocatedMemoryList.Lookup(addr,(AllocBlockInfo &) ainfo))
{
m_AllocatedMemoryList.RemoveKey(addr);
}
else
{
//freeing unallocated memory
AfxTrace(_T("ERROR!CMemLeakDetect::removeMemoryTrace() didnt find Address(0x%08X) to free\n"), addr);
}
};
void CMemLeakDetect::cleanupMemoryTrace()
{
m_AllocatedMemoryList.RemoveAll();
};
void CMemLeakDetect::dumpMemoryTrace()
{
POSITION pos;
LPVOID addr;
AllocBlockInfo ainfo;
TCHAR buf[MLD_MAX_NAME_LENGTH];
TCHAR symInfo[MLD_MAX_NAME_LENGTH];
TCHAR srcInfo[MLD_MAX_NAME_LENGTH];
int totalSize = 0;
int numLeaks = 0;
STACKFRAMEENTRY* p = 0;
//
_tcscpy(symInfo, MLD_TRACEINFO_NOSYMBOL);
_tcscpy(srcInfo, MLD_TRACEINFO_NOSYMBOL);
//
pos = m_AllocatedMemoryList.GetStartPosition();
//
while(pos != m_AllocatedMemoryList.end())
{
numLeaks++;
sprintf(buf, "Memory Leak(%d)------------------->\n", numLeaks);
AfxTrace(buf);
//
m_AllocatedMemoryList.GetNextAssoc(pos, (LPVOID &) addr, (AllocBlockInfo&) ainfo);
if (ainfo.fileName[0] != NULL)
{
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);
//
p = &ainfo.traceinfo[0];
while(p[0].addrPC.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;
}
_stprintf(buf, _T("\n-----------------------------------------------------------\n"));
AfxTrace(buf);
if(!totalSize)
{
_stprintf(buf,_T("No Memory Leaks Detected for %d Allocations\n\n"), memoccurance);
AfxTrace(buf);
}
else
{
_stprintf(buf, _T("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_pMemTrace = this;
pfnOldCrtAllocHook = _CrtSetAllocHook( catchMemoryAllocHook );
}
void CMemLeakDetect::End()
{
isLocked = true;
_CrtSetAllocHook(pfnOldCrtAllocHook);
dumpMemoryTrace();
cleanupMemoryTrace();
cleanupSymInfo();
GlobalFree(m_pSymbol);
g_pMemTrace = NULL;
}
CMemLeakDetect::CMemLeakDetect()
{
Init();
}
CMemLeakDetect::~CMemLeakDetect()
{
End();
}
// PRIVATE STUFF
void CMemLeakDetect::symbolPaths( TCHAR* lpszSymbolPath)
{
TCHAR 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;"
_tcscpy( lpszSymbolPath, _T(".;..\\;..\\..\\"));
// environment variable _NT_SYMBOL_PATH
if ( GetEnvironmentVariable(_T("_NT_SYMBOL_PATH"), lpszPath, MLD_MAX_NAME_LENGTH ))
{
strcat( lpszSymbolPath, _T(";"));
strcat( lpszSymbolPath, lpszPath );
}
// environment variable _NT_ALTERNATE_SYMBOL_PATH
if ( GetEnvironmentVariable( _T("_NT_ALTERNATE_SYMBOL_PATH"), lpszPath, MLD_MAX_NAME_LENGTH ))
{
_tcscat( lpszSymbolPath, _T(";"));
_tcscat( lpszSymbolPath, lpszPath );
}
// environment variable SYSTEMROOT
if ( GetEnvironmentVariableA( "SYSTEMROOT", lpszPath, MLD_MAX_NAME_LENGTH ) )
{
_tcscat( lpszSymbolPath, _T(";"));
_tcscat( lpszSymbolPath, lpszPath);
_tcscat( lpszSymbolPath, _T(";"));
// SYSTEMROOT\System32
_tcscat( lpszSymbolPath, lpszPath );
_tcscat( lpszSymbolPath, _T("\\System32"));
}
}
BOOL CMemLeakDetect::cleanupSymInfo()
{
return SymCleanup( GetCurrentProcess() );
}
// Initializes the symbol files
BOOL CMemLeakDetect::initSymInfo( TCHAR* 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)
{
_tcscat(lpszSymbolPath, _T(";"));
_tcscat(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));
}
//
// This code is still under investigation
// I have to test this code and make sure it is compatible
// with the other stack walker!
//
void CMemLeakDetect::symStackTrace2(STACKFRAMEENTRY* pStacktrace )
{
ADDR FramePtr = NULL;
ADDR InstructionPtr = NULL;
ADDR OriFramePtr = NULL;
ADDR PrevFramePtr = NULL;
long StackIndex = NULL;
// Get frame pointer
_asm mov DWORD PTR [OriFramePtr], ebp
FramePtr = OriFramePtr;
//
while (FramePtr)
{
InstructionPtr = ((ADDR *)FramePtr)[1];
pStacktrace[StackIndex].addrPC.Offset = InstructionPtr;
pStacktrace[StackIndex].addrPC.Segment = NULL;
pStacktrace[StackIndex].addrPC.Mode = AddrModeFlat;
//
StackIndex++;
PrevFramePtr = FramePtr;
FramePtr = ((ADDR *)FramePtr)[0];
}
}
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, MLD_TRACEINFO_NOSYMBOL);
// Get symbol info for IP
if ( SymGetSymFromAddr( m_hProcess, (ULONG)fnAddress, &dwDisp, m_pSymbol ) )
{
_tcscpy(lpszSymbol, m_pSymbol->Name);
return TRUE;
}
//create the symbol using the address because we have no symbol
_stprintf(lpszSymbol, "0x%08X", fnAddress);
return FALSE;
}
BOOL CMemLeakDetect::symSourceInfoFromAddress(UINT address, TCHAR* lpszSourceInfo)
{
BOOL ret = FALSE;
IMAGEHLP_LINE lineInfo;
DWORD dwDisp;
TCHAR lpModuleInfo[MLD_MAX_NAME_LENGTH] = MLD_TRACEINFO_EMPTY;
_tcscpy( lpszSourceInfo, MLD_TRACEINFO_NOSYMBOL);
memset( &lineInfo, NULL, sizeof( IMAGEHLP_LINE ) );
lineInfo.SizeOfStruct = sizeof( IMAGEHLP_LINE );
if ( SymGetLineFromAddr( m_hProcess, address, &dwDisp, &lineInfo ) )
{
// Using the "sourcefile(linenumber)" format
_stprintf( lpszSourceInfo, _T("%s(%d): 0x%08X"), lineInfo.FileName, lineInfo.LineNumber, address );
ret = TRUE;
}
else
{
// Using the "modulename!address" format
symModuleNameFromAddress( address, lpModuleInfo );
if ( lpModuleInfo[0] == _T('?') || lpModuleInfo[0] == _T('\0'))
{
// Using the "address" format
_stprintf(lpszSourceInfo, _T("0x%08X"), lpModuleInfo, address );
}
else
{
_stprintf(lpszSourceInfo, _T("%sdll! 0x%08X"), lpModuleInfo, address );
}
ret = FALSE;
}
//
return ret;
}
BOOL CMemLeakDetect::symModuleNameFromAddress( UINT address, TCHAR* lpszModule )
{
BOOL ret = FALSE;
IMAGEHLP_MODULE moduleInfo;
::ZeroMemory( &moduleInfo, sizeof(IMAGEHLP_MODULE) );
moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
if ( SymGetModuleInfo( m_hProcess, (DWORD)address, &moduleInfo ) )
{
_tcscpy(moduleInfo.ModuleName, lpszModule);
ret = TRUE;
}
else
{
_tcscpy( lpszModule, MLD_TRACEINFO_NOSYMBOL);
}
return ret;
}