/*++ Copyright (c) 1996-2001 Microsoft Corporation Module Name: dbgtrack.c Abstract: Allocation tracking implementation. Author: Jim Schmidt (jimschm) 18-Sept-2001 Revision History: --*/ // // Includes // #include "pch.h" #include "commonp.h" // // NOTE: No code should appear outside the #ifdef DEBUG // #ifdef DEBUG // // Strings // // None // // Constants // #define TRACK_BUCKETS 1501 #define BUCKET_ITEMS_PER_POOL 8192 // // Macros // // None // // Types // typedef ULONG_PTR ALLOCATION_ITEM_OFFSET; typedef struct TAG_TRACKBUCKETITEM { struct TAG_TRACKBUCKETITEM *Next; struct TAG_TRACKBUCKETITEM *Prev; ALLOCTYPE Type; PCVOID Ptr; ALLOCATION_ITEM_OFFSET ItemOffset; } TRACKBUCKETITEM, *PTRACKBUCKETITEM; typedef struct _tagBUCKETPOOL { UINT Count; TRACKBUCKETITEM Items[BUCKET_ITEMS_PER_POOL]; } TRACKBUCKETPOOL, *PTRACKBUCKETPOOL; typedef struct { ALLOCTYPE Type; PCVOID Ptr; PCSTR FileName; UINT Line; BOOL Allocated; } ALLOCATION_ITEM, *PALLOCATION_ITEM; typedef struct { PTRACKBUCKETITEM TrackBuckets[TRACK_BUCKETS]; PTRACKBUCKETITEM TrackPoolDelHead; PTRACKBUCKETPOOL TrackPool; UINT DisabledRefCount; INT UseCount; CHAR TrackComment[1024]; PCSTR TrackFile; UINT TrackLine; BOOL FreeTrackFile; } THREADTRACK, *PTHREADTRACK; // // Globals // DWORD g_TlsIndex = TLS_OUT_OF_INDEXES; CRITICAL_SECTION g_AllocListCs; GROWBUFFER g_AllocationList; PVOID g_FirstDeletedAlloc; // // Macro expansion list // // None // // Private function prototypes // // // Macro expansion definition // // None // // Code // PBYTE pUntrackedGbGrow ( IN OUT PGROWBUFFER GrowBuf, IN DWORD SpaceNeeded ) /*++ Routine Description: pUntrackedGbGrow is the same as GbGrow, but it does not cause any tracking to occur. Arguments: GrowBuf - A pointer to a GROWBUFFER structure. Initialize this structure to zero for the first call to GrowBuffer. SpaceNeeded - The number of free bytes needed in the buffer Return Value: A pointer to the SpaceNeeded bytes, or NULL if a memory allocation error occurred. --*/ { PBYTE newBuffer; DWORD totalSpaceNeeded; DWORD growTo; MYASSERT(SpaceNeeded); // // Make sure structure is initialized properly // if (!GrowBuf->Buf) { ZeroMemory (GrowBuf, sizeof (GROWBUFFER)); } if (!GrowBuf->GrowSize) { GrowBuf->GrowSize = 1024; } // // Compute new buffer size // totalSpaceNeeded = GrowBuf->End + SpaceNeeded; if (totalSpaceNeeded > GrowBuf->Size) { growTo = (totalSpaceNeeded + GrowBuf->GrowSize) - (totalSpaceNeeded % GrowBuf->GrowSize); } else { growTo = 0; } // // If no buffer, allocate one. If buffer is too small, reallocate it. // if (!GrowBuf->Buf) { GrowBuf->Buf = (PBYTE) MemAllocNeverFail (g_hHeap, 0, growTo); GrowBuf->Size = growTo; } else if (growTo) { newBuffer = MemReAllocNeverFail (g_hHeap, 0, GrowBuf->Buf, growTo); GrowBuf->Size = growTo; GrowBuf->Buf = newBuffer; } newBuffer = GrowBuf->Buf + GrowBuf->End; GrowBuf->End += SpaceNeeded; return newBuffer; } VOID pUntrackedGbFree ( IN PGROWBUFFER GrowBuf ) /*++ Routine Description: pUntrackedGbFree frees a buffer allocated by pUntrackedGbGrow. Arguments: GrowBuf - A pointer to the same structure passed to pUntrackedGbGrow Return Value: none --*/ { MYASSERT(GrowBuf); if (GrowBuf->Buf) { HeapFree (g_hHeap, 0, GrowBuf->Buf); ZeroMemory (GrowBuf, sizeof (GROWBUFFER)); } } PTHREADTRACK pGetTrackStructForThread ( VOID ) { PTHREADTRACK result; result = (PTHREADTRACK) TlsGetValue (g_TlsIndex); if (!result) { // // Need to allocate a struct // result = (PTHREADTRACK) MemAllocNeverFail ( g_hHeap, HEAP_ZERO_MEMORY, sizeof (THREADTRACK) ); TlsSetValue (g_TlsIndex, (PVOID) result); } return result; } VOID DisableTrackComment ( VOID ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); trackStruct->DisabledRefCount += 1; } VOID EnableTrackComment ( VOID ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); if (trackStruct->DisabledRefCount > 0) { trackStruct->DisabledRefCount -= 1; } } INT DbgTrackPushEx ( PCSTR Msg, PCSTR File, UINT Line, BOOL DupFileString ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); if (trackStruct->DisabledRefCount > 0) { return 0; } if (trackStruct->UseCount > 0) { trackStruct->UseCount += 1; return 0; } if (Msg) { wsprintfA (trackStruct->TrackComment, "%s line %u [%s]", File, Line, Msg); } else { wsprintfA (trackStruct->TrackComment, "%s line %u", File, Line); } if (DupFileString) { trackStruct->TrackFile = (PCSTR) MemAllocNeverFail (g_hHeap, 0, SzSizeA (File)); SzCopyA ((PSTR) trackStruct->TrackFile, File); trackStruct->FreeTrackFile = TRUE; } else { trackStruct->TrackFile = File; trackStruct->FreeTrackFile = FALSE; } trackStruct->TrackLine = Line; trackStruct->UseCount = 1; return 0; } INT DbgTrackPop ( VOID ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); if (trackStruct->DisabledRefCount > 0) { return 0; } trackStruct->UseCount -= 1; if (!(trackStruct->UseCount)) { trackStruct->TrackComment[0] = 0; if (trackStruct->FreeTrackFile && trackStruct->TrackFile) { HeapFree (g_hHeap, 0, (PVOID)trackStruct->TrackFile); } trackStruct->TrackFile = NULL; trackStruct->TrackLine = 0; trackStruct->FreeTrackFile = FALSE; } return 0; } VOID DbgTrackDump ( VOID ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); if (trackStruct->UseCount > 0) { DEBUGMSGA (( DBG_INFO, "Caller : %s line %u (%s)", trackStruct->TrackFile, trackStruct->TrackLine, trackStruct->TrackComment )); } } BOOL DbgInitTracking ( VOID ) { if (g_TlsIndex == TLS_OUT_OF_INDEXES) { g_TlsIndex = TlsAlloc(); if (g_TlsIndex == TLS_OUT_OF_INDEXES) { return FALSE; } } InitializeCriticalSection (&g_AllocListCs); ZeroMemory (&g_AllocationList, sizeof (g_AllocationList)); g_AllocationList.GrowSize = 65536; g_FirstDeletedAlloc = NULL; return TRUE; } VOID DbgTerminateTracking ( VOID ) { UINT size; UINT u; PALLOCATION_ITEM item; GROWBUFFER msg = { 0, 0, 0, 0, 0, 0 }; CHAR text[1024]; PSTR p; UINT byteCount; PTHREADTRACK trackStruct; EnterCriticalSection (&g_AllocListCs); trackStruct = pGetTrackStructForThread(); size = g_AllocationList.End / sizeof (ALLOCATION_ITEM); for (u = 0 ; u < size ; u++) { item = (PALLOCATION_ITEM) g_AllocationList.Buf + u; if (!item->FileName) { continue; } // // Append the string but not the nul // byteCount = (UINT) wsprintfA (text, "%s line %u\r\n", item->FileName, item->Line); p = (PSTR) pUntrackedGbGrow (&msg, byteCount); CopyMemory (p, text, byteCount); } pUntrackedGbFree (&g_AllocationList); g_FirstDeletedAlloc = NULL; LeaveCriticalSection (&g_AllocListCs); // // Put the message in the log // if (msg.End) { p = (PSTR) pUntrackedGbGrow (&msg, 1); *p = 0; DEBUGMSGA ((DBG_WARNING, "Leaks : %s", msg.Buf)); pUntrackedGbFree (&msg); } // Intentional leak -- who cares about track memory trackStruct->TrackPoolDelHead = NULL; trackStruct->TrackPool = NULL; TlsFree (g_TlsIndex); g_TlsIndex = TLS_OUT_OF_INDEXES; } PTRACKBUCKETITEM pAllocTrackBucketItem ( VOID ) { PTRACKBUCKETITEM bucketItem; PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); if (trackStruct->TrackPoolDelHead) { bucketItem = trackStruct->TrackPoolDelHead; trackStruct->TrackPoolDelHead = bucketItem->Next; } else { if (!trackStruct->TrackPool || trackStruct->TrackPool->Count == BUCKET_ITEMS_PER_POOL) { trackStruct->TrackPool = (PTRACKBUCKETPOOL) MemAllocNeverFail (g_hHeap, 0, sizeof (TRACKBUCKETPOOL)); trackStruct->TrackPool->Count = 0; } bucketItem = trackStruct->TrackPool->Items + trackStruct->TrackPool->Count; trackStruct->TrackPool->Count++; } return bucketItem; } VOID pFreeTrackBucketItem ( PTRACKBUCKETITEM BucketItem ) { PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); BucketItem->Next = trackStruct->TrackPoolDelHead; trackStruct->TrackPoolDelHead = BucketItem; } UINT pComputeTrackHashVal ( IN ALLOCTYPE Type, IN PCVOID Ptr ) { ULONG_PTR hash; hash = ((ULONG_PTR) Type << 16) ^ (ULONG_PTR)Ptr; return (UINT) (hash % TRACK_BUCKETS); } VOID pTrackHashTableInsert ( IN PBYTE Base, IN ALLOCATION_ITEM_OFFSET ItemOffset ) { UINT hash; PTRACKBUCKETITEM bucketItem; PALLOCATION_ITEM item; PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); item = (PALLOCATION_ITEM) (Base + ItemOffset); hash = pComputeTrackHashVal (item->Type, item->Ptr); bucketItem = pAllocTrackBucketItem(); bucketItem->Prev = NULL; bucketItem->Next = trackStruct->TrackBuckets[hash]; bucketItem->Type = item->Type; bucketItem->Ptr = item->Ptr; bucketItem->ItemOffset = ItemOffset; if (bucketItem->Next) { bucketItem->Next->Prev = bucketItem; } trackStruct->TrackBuckets[hash] = bucketItem; } VOID pTrackHashTableDelete ( IN PTRACKBUCKETITEM BucketItem ) { UINT hash; PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); hash = pComputeTrackHashVal (BucketItem->Type, BucketItem->Ptr); if (BucketItem->Prev) { BucketItem->Prev->Next = BucketItem->Next; } else { trackStruct->TrackBuckets[hash] = BucketItem->Next; } if (BucketItem->Next) { BucketItem->Next->Prev = BucketItem->Prev; } pFreeTrackBucketItem (BucketItem); } PTRACKBUCKETITEM pTrackHashTableFind ( IN ALLOCTYPE Type, IN PCVOID Ptr ) { PTRACKBUCKETITEM bucketItem; UINT hash; PTHREADTRACK trackStruct; trackStruct = pGetTrackStructForThread(); hash = pComputeTrackHashVal (Type, Ptr); bucketItem = trackStruct->TrackBuckets[hash]; while (bucketItem) { if (bucketItem->Type == Type && bucketItem->Ptr == Ptr) { return bucketItem; } bucketItem = bucketItem->Next; } return NULL; } VOID DbgRegisterAllocation ( IN ALLOCTYPE Type, IN PVOID Ptr, IN PCSTR File, IN UINT Line ) { PALLOCATION_ITEM item; PTHREADTRACK trackStruct; PCSTR fileToRecord; UINT lineToRecord; trackStruct = pGetTrackStructForThread(); if (!trackStruct->TrackFile) { fileToRecord = File; lineToRecord = Line; } else { fileToRecord = trackStruct->TrackFile; lineToRecord = trackStruct->TrackLine; } EnterCriticalSection (&g_AllocListCs); if (!g_FirstDeletedAlloc) { item = (PALLOCATION_ITEM) pUntrackedGbGrow (&g_AllocationList,sizeof(ALLOCATION_ITEM)); } else { item = (PALLOCATION_ITEM) g_FirstDeletedAlloc; g_FirstDeletedAlloc = (PVOID)item->Ptr; } item->Type = Type; item->Ptr = Ptr; item->FileName = fileToRecord; item->Line = lineToRecord; pTrackHashTableInsert ( g_AllocationList.Buf, (ALLOCATION_ITEM_OFFSET) ((PBYTE) item - g_AllocationList.Buf) ); LeaveCriticalSection (&g_AllocListCs); } VOID DbgUnregisterAllocation ( IN ALLOCTYPE Type, IN PCVOID Ptr ) { PALLOCATION_ITEM item; PTRACKBUCKETITEM bucketItem; EnterCriticalSection (&g_AllocListCs); bucketItem = pTrackHashTableFind (Type, Ptr); if (!g_AllocationList.Buf) { LeaveCriticalSection (&g_AllocListCs); DEBUGMSG ((DBG_WARNING, "Unregister allocation: Allocation buffer already freed")); return; } if (bucketItem) { item = (PALLOCATION_ITEM) (g_AllocationList.Buf + bucketItem->ItemOffset); if (item->Allocated) { HeapFree (g_hHeap, 0, (PSTR)item->FileName); } item->FileName = NULL; item->Type = (ALLOCTYPE) -1; item->Ptr = g_FirstDeletedAlloc; g_FirstDeletedAlloc = item; pTrackHashTableDelete (bucketItem); LeaveCriticalSection (&g_AllocListCs); } else { LeaveCriticalSection (&g_AllocListCs); DEBUGMSG ((DBG_WARNING, "Unregister allocation: Pointer not registered")); } } #endif