// // File: memory.cpp // // Debug memory tracking per-module #include "precomp.h" static BOOL s_fZeroInit = FALSE; #if defined(DEBUG) #define DBG_NAME_LENGTH 16 typedef struct tagMemTag { DWORD dwSignature; BOOL fActive; LPVOID callerAddress; CHAR szFileName[DBG_NAME_LENGTH]; UINT nLineNumber; UINT cbSize; struct tagMemTag *next; } MEM_TAG; static MEM_TAG *s_pDbgActiveMemPool = NULL; #define CLEAN_BYTE ((BYTE) 0xCD) static UINT s_cDbgActiveMemAlloc = 0; static UINT s_cbDbgActiveMem = 0; const DWORD MEM_TAG_SIGNATURE = 0x12345678UL; static CRITICAL_SECTION s_DbgCritSect; static char s_szDbgModuleName[DBG_NAME_LENGTH] = { 0 }; static void _GetFileName(LPSTR pszTarget, LPSTR pszSrc); static void _DbgGetFileLine(LPSTR *, UINT *); #define DBG_MEM_TRACK_DUMP_ALL ((UINT) -1) // // DbgMemTrackReverseList() // void WINAPI DbgMemTrackReverseList(void) { EnterCriticalSection(&s_DbgCritSect); if (NULL != s_pDbgActiveMemPool && NULL != s_pDbgActiveMemPool->next) { MEM_TAG *p, *q, *r;; for (q = (p = s_pDbgActiveMemPool)->next, r = q; // make sure r is not null in the beginning NULL != r; p = q, q = r) { r = q->next; q->next = p; } s_pDbgActiveMemPool->next = NULL; s_pDbgActiveMemPool = p; } LeaveCriticalSection(&s_DbgCritSect); } // // DbgMemTrackDumpCurrent() // void WINAPI DbgMemTrackDumpCurrent(void) { MEM_TAG *p; int i; char szBuf[128]; EnterCriticalSection(&s_DbgCritSect); for (p = s_pDbgActiveMemPool, i = 0; p; p = p->next, i++) { if (p->callerAddress) { // No file/line, just caller wsprintfA(szBuf, "%s: mem leak [%u]: caller address=0x%p, size=%u, ptr=0x%p\r\n", s_szDbgModuleName, i, p->callerAddress, p->cbSize, (p+1)); } else { // File & line number wsprintfA(szBuf, "%s: mem leak [%u]: file=%s, line=%u, size=%u, ptr=0x%p\r\n", s_szDbgModuleName, i, p->szFileName, p->nLineNumber, p->cbSize, (p+1)); } OutputDebugStringA(szBuf); } LeaveCriticalSection(&s_DbgCritSect); } // // DbgMemTrackFinalCheck() // // Dumps any left-around (leaked) memory blocks. Call this on // DLL_PROCESS_DETACH from your .DLL or at the end of WinMain of your .EXE // void WINAPI DbgMemTrackFinalCheck(void) { DbgMemTrackReverseList(); DbgMemTrackDumpCurrent(); if (NULL != s_pDbgActiveMemPool || NULL != s_cDbgActiveMemAlloc || NULL != s_cbDbgActiveMem) { DebugBreak(); } DeleteCriticalSection(&s_DbgCritSect); } // // _GetFileName() // static void _GetFileName(LPSTR pszTarget, LPSTR pszSrc) { LPSTR psz = pszSrc; while (*psz != '\0') { if (*psz++ == '\\') { pszSrc = psz; } } lstrcpynA(pszTarget, pszSrc, DBG_NAME_LENGTH); } // // DbgMemAlloc() // // Debug memory allocation // LPVOID WINAPI DbgMemAlloc ( UINT cbSize, LPVOID callerAddress, LPSTR pszFileName, UINT nLineNumber ) { MEM_TAG *p; UINT cbToAlloc; cbToAlloc = sizeof(MEM_TAG) + cbSize; EnterCriticalSection(&s_DbgCritSect); p = (MEM_TAG *) LocalAlloc(LPTR, cbToAlloc); if (p != NULL) { p->dwSignature = MEM_TAG_SIGNATURE; p->fActive = TRUE; p->callerAddress = callerAddress; if (pszFileName) { _GetFileName(p->szFileName, pszFileName); p->nLineNumber = nLineNumber; } p->cbSize = cbSize; p->next = s_pDbgActiveMemPool; s_pDbgActiveMemPool = p; s_cDbgActiveMemAlloc++; s_cbDbgActiveMem += p->cbSize; p++; // // If no zero-init, fill with clean byte // if (!s_fZeroInit) { FillMemory(p, cbSize, CLEAN_BYTE); } } LeaveCriticalSection(&s_DbgCritSect); return (LPVOID) p; } // // DbgMemFree() // // Debug memory free // void WINAPI DbgMemFree(LPVOID ptr) { if (ptr != NULL) { MEM_TAG *p = (MEM_TAG *) ptr; p--; if (! IsBadWritePtr(p, sizeof(MEM_TAG)) && (p->dwSignature == MEM_TAG_SIGNATURE)) { if (! p->fActive) { // // This memory has been freed already. // ERROR_OUT(("DbgMemFree called with invalid pointer 0x%08x", p)); return; } MEM_TAG *q, *q0; EnterCriticalSection(&s_DbgCritSect); for (q = s_pDbgActiveMemPool; q != NULL; q = (q0 = q)->next) { if (q == p) { if (q == s_pDbgActiveMemPool) { s_pDbgActiveMemPool = p->next; } else { q0->next = p->next; } s_cDbgActiveMemAlloc--; s_cbDbgActiveMem -= p->cbSize; p->fActive = FALSE; // // Fill app pointer data with CLEAN_BYTE, to see if // anybody tries later to access it after it's been // freed. // FillMemory(p+1, p->cbSize, CLEAN_BYTE); break; } } LeaveCriticalSection(&s_DbgCritSect); } else { ERROR_OUT(("DbgMemFree called with invalid pointer 0x%08x", p)); return; } LocalFree(p); } } // // DbgMemReAlloc() // // Debug memory reallocate // LPVOID WINAPI DbgMemReAlloc(LPVOID ptr, UINT cbSize, UINT uFlags, LPSTR pszFileName, UINT nLineNumber) { MEM_TAG *p; void *q; if (ptr == NULL) return DbgMemAlloc(cbSize, 0, pszFileName, nLineNumber); p = (MEM_TAG *) ptr; p--; if (IsBadWritePtr(p, sizeof(MEM_TAG)) || p->dwSignature != MEM_TAG_SIGNATURE) { DebugBreak(); return LocalReAlloc(ptr, cbSize, uFlags); } q = DbgMemAlloc(cbSize, 0, pszFileName, nLineNumber); if (q != NULL) { CopyMemory(q, ptr, p->cbSize); DbgMemFree(ptr); } return q; } typedef struct { DWORD dwThreadID; LPSTR pszFileName; UINT nLineNumber; } DBG_THREAD_FILE_LINE; #define DBG_MAX_THREADS 32 static DBG_THREAD_FILE_LINE s_aThreadFileLine[DBG_MAX_THREADS] = { 0 }; void WINAPI DbgSaveFileLine(LPSTR pszFileName, UINT nLineNumber) { DWORD dwThreadID = GetCurrentThreadId(); EnterCriticalSection(&s_DbgCritSect); UINT c = DBG_MAX_THREADS; DBG_THREAD_FILE_LINE *p; for (p = s_aThreadFileLine; c--; p++) { if (p->dwThreadID == 0) { p->dwThreadID = dwThreadID; p->pszFileName = pszFileName; p->nLineNumber = nLineNumber; break; } else if (p->dwThreadID == dwThreadID) { p->pszFileName = pszFileName; p->nLineNumber = nLineNumber; break; } } LeaveCriticalSection(&s_DbgCritSect); } void WINAPI DbgGetFileLine(LPSTR *ppszFileName, UINT *pnLineNumber) { *ppszFileName = NULL; *pnLineNumber = 0; DWORD dwThreadID = GetCurrentThreadId(); EnterCriticalSection(&s_DbgCritSect); UINT c = DBG_MAX_THREADS; DBG_THREAD_FILE_LINE *p; for (p = s_aThreadFileLine; c--; p++) { if (p->dwThreadID == 0) { break; } else if (p->dwThreadID == dwThreadID) { *ppszFileName = p->pszFileName; *pnLineNumber = p->nLineNumber; p->pszFileName = NULL; p->nLineNumber = 0; break; } } LeaveCriticalSection(&s_DbgCritSect); } LPVOID __cdecl ::operator new(size_t uObjSize) { LPVOID callerAddress; LPSTR pszFileName; UINT nLineNumber; DbgGetFileLine(&pszFileName, &nLineNumber); if (pszFileName) { callerAddress = NULL; } else { #ifdef _X86_ LPVOID * lpParams; // // LAURABU HACK: This doesn't work for alpha. But it's not bad // for normal debugging. We're going to grab the return address // of whomever called new() // lpParams = (LPVOID *)&uObjSize; callerAddress = *(lpParams - 1); #else callerAddress = NULL; #endif // _X86_ } return(DbgMemAlloc(uObjSize, callerAddress, pszFileName, nLineNumber)); } #else // RETAIL LPVOID __cdecl ::operator new(size_t uObjSize) { if (s_fZeroInit) { return(LocalAlloc(LPTR, uObjSize)); } else { return(LocalAlloc(LMEM_FIXED, uObjSize)); } } #endif // defined(DEBUG) // // delete() is the same for both debug and retail // void __cdecl ::operator delete(LPVOID pObj) { MemFree(pObj); } // // DbgInitMemTrack() // // Initialize debug memory tracking. Call this on DLL_PROCESS_ATTACH in // your .DLL or at beginning of WinMain of your .EXE // void WINAPI DbgInitMemTrack(HINSTANCE hDllInst, BOOL fZeroInit) { s_fZeroInit = fZeroInit; #if defined(DEBUG) InitializeCriticalSection(&s_DbgCritSect); char szPath[MAX_PATH]; if (0 != GetModuleFileNameA(hDllInst, szPath, MAX_PATH)) { _GetFileName(s_szDbgModuleName, szPath); LPSTR psz = s_szDbgModuleName; while (*psz != '\0') { if (*psz == '.') { *psz = '\0'; break; } psz++; } } else { lstrcpyA(s_szDbgModuleName, "unknown"); } #endif // DEBUG }