// Determine whether an address is part of a live packet, or a live packet // header. Intended for interactive use in the debugger, outputs to debugger // console. DbgAllocHeader *DbgCheckAddress(unsigned ptr) { STATIC_CONTRACT_NOTHROW; DbgAllocHeader *h = g_AllocListFirst; WCHAR output[1024]; void *p = (void *)(UINT_PTR)ptr; while (h) { void *head = (void *)h; void *start = (void *)CDA_HEADER_TO_DATA(h); void *end = (void *)&CDA_DATA(h, h->m_Length); void *tail = (void *)&CDA_DATA(h, h->m_Length + CDA_OPT_GUARD_BYTES); if ((p >= head) && (p < start)) { wsprintfW(output, L"0x%08X is in packet header at 0x%08X\n", p, h); WszOutputDebugString(output); return h; } else if ((p >= start) && (p < end)) { wsprintfW(output, L"0x%08X is in data portion of packet at 0x%08X\n", p, h); WszOutputDebugString(output); return h; } else if ((p >= end) && (p < tail)) { wsprintfW(output, L"0x%08X is in guard portion of packet at 0x%08X\n", p, h); WszOutputDebugString(output); return h; } h = h->m_Next; } wsprintfW(output, L"%08X not located in any live packet\n", p); WszOutputDebugString(output); return NULL; }
// Routine to check that an allocation header looks valid. Asserts on failure. void DbgValidateHeader(DbgAllocHeader *h) { _ASSERTE((h->m_Magic1 == CDA_MAGIC_1) && (*CDA_MAGIC2(h) == CDA_MAGIC_2) && ((unsigned)h->m_Next != CDA_INV_PATTERN) && ((unsigned)h->m_Prev != CDA_INV_PATTERN)); if (g_AllocGuard) for (unsigned i = 0; i < CDA_GUARD_BYTES; i++) _ASSERTE(CDA_DATA(h, h->m_Length + i) == CDA_GUARD_PATTERN); }
// Routine to check all active packets to see if they still look valid. // Optionally, also check that the non-NULL address passed does not lie within // any of the currently allocated packets. void DbgValidateActivePackets(void *Start, void *End) { DbgAllocHeader *h = g_AllocListFirst; while (h) { DbgValidateHeader(h); if (Start) { void *head = (void *)h; void *tail = (void *)&CDA_DATA(h, h->m_Length + CDA_OPT_GUARD_BYTES); _ASSERTE((End <= head) || (Start >= tail)); } h = h->m_Next; } }
// Allocate a block of memory at least n bytes big. void * __stdcall DbgAlloc(size_t n, void **ppvCallstack, BOOL isArray) { STATIC_CONTRACT_NOTHROW; // Initialize if necessary (DbgAllocInit takes care of the synchronization). if (!g_HeapInitialized) DbgAllocInit(); if (!g_DbgEnabled) return ClrAllocInProcessHeap(0, n); CDA_LOCK(); // Count calls to this routine and the number that specify 0 bytes of // allocation. This needs to be done under the lock since the counters // themselves aren't synchronized. CDA_STATS_INC(Allocs); if (n == 0) CDA_STATS_INC(ZeroAllocs); CDA_UNLOCK(); // Allocate enough memory for the caller, our debugging header and possibly // some guard bytes. unsigned length = CDA_SIZEOF_HEADER() + (unsigned)n + CDA_OPT_GUARD_BYTES; DbgAllocHeader *h; if (g_PagePerAlloc) { // In page per alloc mode we allocate a number of whole pages. The // actual packet is placed at the end of the second to last page and the // last page is reserved but never commited (so will cause an access // violation if touched). This will catch heap crawl real quick. unsigned pages = ((length + (g_PageSize - 1)) / g_PageSize) + 1; h = (DbgAllocHeader *)ClrVirtualAlloc(NULL, pages * g_PageSize, MEM_RESERVE, PAGE_NOACCESS); if (h) { ClrVirtualAlloc(h, (pages - 1) * g_PageSize, MEM_COMMIT, PAGE_READWRITE); h = (DbgAllocHeader *)((BYTE *)h + (g_PageSize - (length % g_PageSize))); } } else h = (DbgAllocHeader *)ClrHeapAlloc(g_HeapHandle, 0, length); CDA_LOCK(); if (h == NULL) { // Whoops, allocation failure. Record it. CDA_STATS_INC(AllocFailures); LOG((LF_DBGALLOC, LL_ALWAYS, "DbgAlloc: alloc fail for %u bytes\n", n)); } else { // Check all active packets still look OK. if (g_ConstantRecheck) DbgValidateActivePackets(h, &CDA_DATA(h, n + CDA_OPT_GUARD_BYTES)); // Count the total number of bytes we've allocated so far. CDA_STATS_ADD(AllocBytes, n); // Record the largest amount of concurrent allocations we ever see // during the life of the process. if((g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes) > g_AllocStats.m_MaxAlloc) g_AllocStats.m_MaxAlloc = g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes; // Fill in the packet debugging header. for (unsigned i = 0; i < g_CallStackDepth; i++) { CDA_ALLOC_STACK(h, i) = ppvCallstack[i]; CDA_DEALLOC_STACK(h, i) = NULL; } h->m_hmod = NULL; h->m_SN = g_NextSN++; h->m_Length = (unsigned)n; h->m_IsArray = isArray; h->m_Prev = g_AllocListLast; h->m_Next = NULL; h->m_Magic1 = CDA_MAGIC_1; *CDA_MAGIC2(h) = CDA_MAGIC_2; // If the user wants to breakpoint on the allocation of a specific // packet, do it now. if (g_BreakOnAlloc && (h->m_SN == g_BreakOnAllocNumber)) _ASSERTE(!"Hit memory allocation # for breakpoint"); // Link the packet into the queue of live packets. if (g_AllocListLast != NULL) { g_AllocListLast->m_Next = h; g_AllocListLast = h; } if (g_AllocListFirst == NULL) { _ASSERTE(g_AllocListLast == NULL); g_AllocListFirst = h; g_AllocListLast = h; } if (g_PoisonPackets) memset(CDA_HEADER_TO_DATA(h), CDA_ALLOC_PATTERN, n); // Write a guard pattern after the user data to trap overwrites. if (g_AllocGuard) memset(&CDA_DATA(h, n), CDA_GUARD_PATTERN, CDA_GUARD_BYTES); // See if our allocator makes the list of most frequent allocators. if (g_UsageByAllocator) { // Look for an existing entry in the table for our EIP, or for the // first empty slot (the table is kept in sorted order, so the first // empty slot marks the end of the table). unsigned i; for (i = 0; i < g_TopAllocatorsSlots; i++) { if (g_TopAllocators[i].m_EIP == ppvCallstack[0]) { // We already have an entry for this allocator. Incrementing // the count may allow us to move the allocator up the // table. g_TopAllocators[i].m_Count++; g_TopAllocators[i].m_TotalBytes += n; if ((i > 0) && (g_TopAllocators[i].m_Count > g_TopAllocators[i - 1].m_Count)) { DbgAllocTop tmp = g_TopAllocators[i - 1]; g_TopAllocators[i - 1] = g_TopAllocators[i]; g_TopAllocators[i] = tmp; } break; } if (g_TopAllocators[i].m_EIP == NULL) { // We've found an empty slot, we weren't in the table. This // is the right place to put the entry though, since we've // only done a single allocation. g_TopAllocators[i].m_EIP = ppvCallstack[0]; g_TopAllocators[i].m_Count = 1; g_TopAllocators[i].m_TotalBytes = n; break; } } if (i == g_TopAllocatorsSlots) { // Ran out of space in the table, need to expand it. unsigned slots = g_TopAllocatorsSlots ? g_TopAllocatorsSlots * 2 : CDA_TOP_ALLOCATORS; DbgAllocTop *newtab = (DbgAllocTop*)ClrAllocInProcessHeap(0, slots*sizeof(DbgAllocTop)); if (newtab) { // Copy old contents over. if (g_TopAllocatorsSlots) { memcpy(newtab, g_TopAllocators, sizeof(DbgAllocTop) * g_TopAllocatorsSlots); delete [] g_TopAllocators; } // Install new table. g_TopAllocators = newtab; g_TopAllocatorsSlots = slots; // Add new entry to tail. g_TopAllocators[i].m_EIP = ppvCallstack[0]; g_TopAllocators[i].m_Count = 1; g_TopAllocators[i].m_TotalBytes = n; // And initialize the rest of the entries to empty. memset(&g_TopAllocators[i + 1], 0, sizeof(DbgAllocTop) * (slots - (i + 1))); } } } // Count how many allocations of each size range we get. Allocations // above a certain size are all dumped into one bucket. if (g_LogDist) { if (n > CDA_MAX_DIST_SIZE) g_LargeAllocs++; else { for (unsigned i = CDA_DIST_BUCKET_SIZE - 1; i <= CDA_MAX_DIST_SIZE; i += CDA_DIST_BUCKET_SIZE) if (n <= i) { g_AllocBuckets[i/CDA_DIST_BUCKET_SIZE]++; break; } } } } CDA_UNLOCK(); return h ? CDA_HEADER_TO_DATA(h) : NULL; }
// Called just before process exit to report stats and check for memory // leakages etc. void __stdcall DbgAllocReport(__in_z __in_opt char * pString, BOOL fDone, BOOL fDoPrintf, unsigned snapShot) { STATIC_CONTRACT_NOTHROW; if (!g_HeapInitialized) return; CDA_LOCK(); if (g_LogStats || g_LogDist || g_DetectLeaks || g_UsageByAllocator) LOG((LF_DBGALLOC, LL_ALWAYS, "------ Allocation Stats ------\n")); // Print out basic statistics. if (g_LogStats) { LOG((LF_DBGALLOC, LL_ALWAYS, "\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc calls : %u\n", (int)g_AllocStats.m_Allocs)); LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc failures : %u\n", (int)g_AllocStats.m_AllocFailures)); LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc 0s : %u\n", (int)g_AllocStats.m_ZeroAllocs)); LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc bytes : %u\n", (int)g_AllocStats.m_AllocBytes)); LOG((LF_DBGALLOC, LL_ALWAYS, "Free calls : %u\n", (int)g_AllocStats.m_Frees)); LOG((LF_DBGALLOC, LL_ALWAYS, "Free NULLs : %u\n", (int)g_AllocStats.m_NullFrees)); LOG((LF_DBGALLOC, LL_ALWAYS, "Free bytes : %u\n", (int)g_AllocStats.m_FreeBytes)); LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked allocs : %u\n", (int)(g_AllocStats.m_Allocs - g_AllocStats.m_AllocFailures) - (g_AllocStats.m_Frees - g_AllocStats.m_NullFrees))); LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked bytes : %u\n", (int)g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes)); LOG((LF_DBGALLOC, LL_ALWAYS, "Max allocation : %u\n", (int)g_AllocStats.m_MaxAlloc)); } // Print out allocation size distribution statistics. if (g_LogDist) { LOG((LF_DBGALLOC, LL_ALWAYS, "\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc distrib :\n")); for (unsigned i = 0; i < CDA_DIST_BUCKETS; i++) LOG((LF_DBGALLOC, LL_ALWAYS, " [%3u,%3u] : %u\n", i * CDA_DIST_BUCKET_SIZE, (i * CDA_DIST_BUCKET_SIZE) + (CDA_DIST_BUCKET_SIZE - 1), (int)g_AllocBuckets[i])); LOG((LF_DBGALLOC, LL_ALWAYS, " [%3u,---] : %u\n", CDA_MAX_DIST_SIZE + 1, (int)g_LargeAllocs)); } // Print out the table of top allocators. Table is pre-sorted, the first // NULL entry indicates the end of the valid list. if (g_UsageByAllocator && g_TopAllocators) { LOG((LF_DBGALLOC, LL_ALWAYS, "\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "Top allocators :\n")); for (unsigned i = 0; i < min(CDA_TOP_ALLOCATORS, g_TopAllocatorsSlots); i++) { if (g_TopAllocators[i].m_EIP == NULL) break; LOG((LF_DBGALLOC, LL_ALWAYS, " %2u: %08X %s\n", i + 1, g_TopAllocators[i].m_EIP, DbgSymbolize(g_TopAllocators[i].m_EIP))); LOG((LF_DBGALLOC, LL_ALWAYS, " %u allocations, %u bytes total, %u bytes average size\n", g_TopAllocators[i].m_Count, (unsigned)g_TopAllocators[i].m_TotalBytes, (unsigned)(g_TopAllocators[i].m_TotalBytes / g_TopAllocators[i].m_Count))); } } // Print out info for all leaked packets. if (g_DetectLeaks) { DbgAllocHeader *h = g_AllocListFirst; // Find first leak after snapshot point while (h) { if (h->m_SN > snapShot) { break; } else { h = h->m_Next; } } int fHaveLeaks = (h!=NULL); if (h) { // Tell the Log we had memory leaks LOG((LF_DBGALLOC, LL_ALWAYS, "\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "Detected memory leaks!\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked packets :\n")); // Tell the console we had memory leaks if (fDoPrintf) { printf("Detected memory leaks!\n"); if (pString != NULL) printf("%s\n", pString); printf("Leaked packets :\n"); } } while (h) { if (h->m_SN > snapShot) { char buffer1[132]; char buffer2[32]; sprintf(buffer1, "#%u %08p:%u ", h->m_SN, CDA_HEADER_TO_DATA(h), (unsigned int) h->m_Length); unsigned i; for (i = 0; i < 16; i++) { if (i < h->m_Length) sprintf(buffer2, "%02X", (BYTE)CDA_DATA(h, i)); else strcpy(buffer2, " "); if ((i % 4) == 3) strcat_s(buffer2, _countof(buffer2), " "); strcat_s(buffer1, _countof(buffer1), buffer2); } for (i = 0; i < min(16, h->m_Length); i++) { sprintf(buffer2, "%c", (CDA_DATA(h, i) < 32) || (CDA_DATA(h, i) > 127) ? '.' : CDA_DATA(h, i)); strcat_s(buffer1, _countof(buffer1), buffer2); } LOG((LF_DBGALLOC, LL_ALWAYS, "%s\n", buffer1)); if (fDoPrintf) printf("%s\n", buffer1); if (g_CallStackDepth == 1) { LOG((LF_DBGALLOC, LL_ALWAYS, " Allocated at %08X %s\n", CDA_ALLOC_STACK(h, 0), DbgSymbolize(CDA_ALLOC_STACK(h, 0)))); if (fDoPrintf) printf(" Allocated at %p %s\n", CDA_ALLOC_STACK(h, 0), DbgSymbolize(CDA_ALLOC_STACK(h, 0))); } else { LOG((LF_DBGALLOC, LL_ALWAYS, " Allocation call stack:\n")); if (fDoPrintf) printf(" Allocation call stack:\n"); for (unsigned i = 0; i < g_CallStackDepth; i++) { if (CDA_ALLOC_STACK(h, i) == NULL) break; LOG((LF_DBGALLOC, LL_ALWAYS, " %08X %s\n", CDA_ALLOC_STACK(h, i), DbgSymbolize(CDA_ALLOC_STACK(h, i)))); if (fDoPrintf) printf(" %p %s\n", CDA_ALLOC_STACK(h, i), DbgSymbolize(CDA_ALLOC_STACK(h, i))); } } wchar_t buf[256]; GetModuleFileNameW(h->m_hmod, buf, 256); LOG((LF_DBGALLOC, LL_ALWAYS, " Base, name: %08X %S\n\n", h->m_hmod, buf)); if (fDoPrintf) printf(" Base, name: %p %S\n\n", h->m_hmod, buf); } h = h->m_Next; } if (fDoPrintf) fflush(stdout); if (fHaveLeaks && g_AssertOnLeaks) _ASSERTE(!"Detected memory leaks!"); } if (g_LogStats || g_LogDist || g_DetectLeaks || g_UsageByAllocator) { LOG((LF_DBGALLOC, LL_ALWAYS, "\n")); LOG((LF_DBGALLOC, LL_ALWAYS, "------------------------------\n")); } CDA_UNLOCK(); if (fDone) { DbgUnloadSymbols(); UnsafeDeleteCriticalSection(&g_AllocMutex); // We won't be doing any more of our debug allocation stuff g_DbgEnabled=0; } }