|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1999.
//
// File: alocdbg.cxx
//
// Contents: This file implements an arena that tracks memory allocations
// and frees.
//
// History: 28-Oct-92 IsaacHe Created
//
//--------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <alocdbg.hxx>
#include <basetyps.h>
#include <tracheap.h>
#include <wtypes.h>
extern "C" { #include <imagehlp.h>
#define MAX_TRANSLATED_LEN 80
}
#include "snapimg.hxx"
//+---------------------------------------------------------------------------
//
// Function: RecordStack functions(s) below...per processor type
//
// Synopsis: Record a stack backtrace into fTrace
//
// Arguments: [cFrameSkipped] -- How many stack frames to skip over and
// not record
// [fTrace] -- The recorded frames are put in here
//
// Returns: A checksum of the stack frames for fast initial lookups
//
// Notes: If we can do stack backtracing for whatever processor we're
// compiling for, the #define CANDOSTACK
//
//----------------------------------------------------------------------------
#if defined (_X86_)
static inline DWORD RecordStack( int cFrameSkipped, void *fTrace[ DEPTHTRACE ] ) {
#define CANDOSTACK
ULONG sum; USHORT cStack;
// NOTE: RtlCaptureStackBackTrace does not understand FPOs, so routines
// that have FPOs will be skipped in the backtrace. Also, there is
// a chance of an access violation for inter-module calls from an
// FPO routine, so enclose the call to RtlCaptureStackBackTrace
// in a TRY/CATCH.
__try { sum = 0; cStack = RtlCaptureStackBackTrace(cFrameSkipped + 1, DEPTHTRACE, fTrace, &sum ); } __except ( EXCEPTION_EXECUTE_HANDLER ) { //
// Checksum any addresses that may have been collected in the buffer
//
for ( cStack = 0, sum = 0; cStack < DEPTHTRACE; cStack++ ) { sum += (ULONG) (fTrace[cStack]); } }
return sum; }
#elif defined( _AMD64_ )
DWORD RecordStack( int cFrameSkipped, void *fTrace[DEPTHTRACE] )
{
#define CANDOSTACK
ULONG sum; USHORT cStack;
__try { sum = 0; cStack = RtlCaptureStackBackTrace(cFrameSkipped + 1, DEPTHTRACE, &fTrace[0], &sum);
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
//
// Checksum any addresses that may have been collected in the buffer.
//
for (cStack = 0, sum = 0; cStack < DEPTHTRACE; cStack++) { sum += (ULONG)((ULONG64)(fTrace[cStack])); } }
return sum; }
#endif // machine-specific RecordStack implementations
#if CIDBG
#pragma optimize( "y", off )
DECLARE_INFOLEVEL( heap ); DECLARE_DEBUG( heap ); #define heapDebugOut(x) heapInlineDebugOut x
DECLARE_INFOLEVEL(Cn);
/*
* The maximum number of AllocArenaCreate's we expect */ static const MAXARENAS = 5;
/*
* When printing leak dumps, the max number we will print out. Note, we keep * track of all of them, we just don't want to take forever to terminate a * process */ static const MAXDUMP = 500;
/*
* The maximum size we'll let any single debug arena get */ static const ULONG ARENASIZE = 2048*1024;
/*
* The unit of growth for the arena holding the AllocArena data. * Must be a power of 2 */ static const ALLOCRECINCR = 128;
AllocArena *AllocArenas[ MAXARENAS + 1 ];
//
// Create an arena for recording allocation statistics. Return the arena
// pointer to the caller
//
STDAPI_( AllocArena * ) AllocArenaCreate( DWORD memctx, char FAR *comment ) { // the first time through, we set up the symbol handler
static int FirstTime = TRUE; if ( FirstTime ) { //
// Snap to imagehlp dll
//
if (!SnapToImageHlp( )) { heapDebugOut(( DEB_WARN, "ci heap unable to load imagehlp!\n" )); return FALSE; } LocalSymSetOptions( SYMOPT_DEFERRED_LOADS ); LocalSymInitialize( GetCurrentProcess(), NULL, TRUE ); FirstTime = FALSE; }
struct AllocArena *paa = NULL;
if( memctx == MEMCTX_TASK ) { #if defined( CANDOSTACK )
if( heapInfoLevel & DEB_WARN ) {
paa = (struct AllocArena *)VirtualAlloc( NULL, ARENASIZE, MEM_RESERVE, PAGE_NOACCESS ); if( paa == NULL ) return NULL;
paa = (AllocArena *)VirtualAlloc( paa, sizeof(*paa)+(ALLOCRECINCR-1)*sizeof(HeapAllocRec), MEM_COMMIT, PAGE_READWRITE );
} else #endif
{ paa = (struct AllocArena *)calloc( 1, sizeof(*paa) ); } }
if( paa == NULL ) return NULL;
memcpy( paa->Signature,HEAPSIG,sizeof(HEAPSIG)); if( comment ) strncpy(paa->comment, comment, sizeof(paa->comment) );
InitializeCriticalSection( &paa->csExclusive );
for( int i=0; i < MAXARENAS; i++ ) if( AllocArenas[i] == 0 ) { AllocArenas[i] = paa; break; }
#if defined( CANDOSTACK )
if( (heapInfoLevel & DEB_WARN) == 0 ) #endif
{ paa->flags.KeepStackTrace = 0; paa->AllocRec[0].paa = paa; return paa; }
#if defined( CANDOSTACK )
paa->cRecords = ALLOCRECINCR; paa->cTotalRecords = ALLOCRECINCR; paa->flags.KeepStackTrace = 1;
return paa; #endif
}
//+---------------------------------------------------------------------------
//
// Function: AllocArenaRecordAlloc
//
// Synopsis: Keep a hash table of the stack backtraces of the allocations
// we've done.
//
// Arguments: [paa] -- Return value from AllocArenaCreate() above
// [bytes] -- the number of bytes being allocated by the caller.
// This value is recorded in the stack backtrace entry.
//
// Algorithm: The arena for the AllocArena is created with VirtualAlloc.
// pAllocArena->cRecords is the index of the next
// free record. The first ALLOCRECINCR records are heads
// of separate lists of the records.
//
// Returns: A pointer to the AllocRec structure recording the entry.
// Can return NULL if we can't record the allocation.
//
//----------------------------------------------------------------------------
STDAPI_( HeapAllocRec FAR * ) AllocArenaRecordAlloc( AllocArena *paa, size_t bytes ) { if( paa == NULL ) return NULL;
EnterCriticalSection( &paa->csExclusive );
if( bytes ) { paa->cAllocs++; paa->cBytesNow += bytes; paa->cBytesTotal += bytes; } else { paa->czAllocs++; }
//
// Record 'size' in the histogram of requests
//
for( int i=31; i>=0; i-- ) if( bytes & (1<<i) ) { ++(paa->Histogram.total[i]); if( paa->Histogram.simul[i] < ++(paa->Histogram.now[i])) paa->Histogram.simul[i] = paa->Histogram.now[i]; break; }
LeaveCriticalSection( &paa->csExclusive );
#if defined( CANDOSTACK )
if( paa->flags.KeepStackTrace == 0 ) #endif
return &paa->AllocRec[0];
#if defined( CANDOSTACK )
DWORD sum; struct HeapAllocRec *phar,*hp; void *fTrace[ DEPTHTRACE ];
//
// See if we find an existing record of this stack backtrace
//
memset( fTrace, '\0', sizeof( fTrace ) ); sum = RecordStack( 2, fTrace );
hp = &paa->AllocRec[ sum & (ALLOCRECINCR-1) ];
EnterCriticalSection( &paa->csExclusive );
for( phar = hp; phar != NULL; phar = phar->u.next ) if( phar->sum == sum && RtlEqualMemory(phar->fTrace,fTrace,sizeof(fTrace))) { phar->count++; phar->bytes += bytes; phar->total.bytes += bytes; phar->total.count++; phar->paa = paa; LeaveCriticalSection( &paa->csExclusive ); return phar; } //
// We have no record of this allocation. Make one!
//
if( hp->total.count && paa->cRecords == paa->cTotalRecords ) { //
// The arena is currently full. Grow it by ALLOCRECINCR
//
AllocArena *npHeap;
npHeap = (AllocArena *)VirtualAlloc( paa, sizeof(AllocArena)+ ((paa->cTotalRecords + ALLOCRECINCR) * sizeof(HeapAllocRec) ), MEM_COMMIT, PAGE_READWRITE );
if( npHeap != paa ) { if ( 0 == (paa->cMissed % 1000) ) { heapDebugOut(( DEB_WARN, "ci: Missed recording alloc -- couldn't grow arena 0x%x to %u bytes. Error %d\n", paa, ((paa->cTotalRecords + ALLOCRECINCR) * sizeof(HeapAllocRec)), GetLastError() )); }
paa->cMissed++; LeaveCriticalSection( &paa->csExclusive ); return NULL; }
paa->cTotalRecords += ALLOCRECINCR; }
if( hp->total.count == 0 ) { phar = hp; } else { phar = &paa->AllocRec[ paa->cRecords++ ]; phar->u.next = hp->u.next; hp->u.next = phar; }
paa->cPaths++;
memcpy( phar->fTrace, fTrace, sizeof( fTrace ) ); phar->count = phar->total.count = 1; phar->bytes = phar->total.bytes = bytes; phar->sum = sum; phar->paa = paa; LeaveCriticalSection( &paa->csExclusive ); return phar; #endif
}
//+---------------------------------------------------------------------------
//
// Function: AllocArenaRecordReAlloc
//
// Synopsis: Update the record to reflect the fact that we've ReAlloc'd
// the memory chunk.
//
// Arguments: [vp] -- Return value from AllocArenaRecordAlloc() above
// [oldbytes] -- size of the memory before ReAllocation
// [newbytes] -- new size of the memory
//
//----------------------------------------------------------------------------
STDAPI_( void ) AllocArenaRecordReAlloc( HeapAllocRec FAR *vp, size_t oldbytes, size_t newbytes) { if( vp == NULL ) return;
struct AllocArena *paa = vp->paa;
EnterCriticalSection( &paa->csExclusive );
paa->cReAllocs++; paa->cBytesNow -= oldbytes; paa->cBytesNow += newbytes;
if( newbytes > oldbytes ) paa->cBytesTotal += newbytes - oldbytes;
//
// Take 'oldbytes' out of the histogram of requests
//
for( int i=31; i>=0; i-- ) if( oldbytes & (1<<i) ) { --(paa->Histogram.now[i]); break; }
//
// Record 'newbytes' in the histogram of requests
//
for( i=31; i>=0; i-- ) if( newbytes & (1<<i) ) { ++(paa->Histogram.total[i]); if( paa->Histogram.simul[i] < ++(paa->Histogram.now[i])) paa->Histogram.simul[i] = paa->Histogram.now[i]; break; }
#if defined( CANDOSTACK )
if( paa->flags.KeepStackTrace ) { vp->bytes -= oldbytes; vp->bytes += newbytes; vp->total.count++; if( newbytes > oldbytes ) vp->total.bytes += newbytes; } #endif
LeaveCriticalSection( &paa->csExclusive ); }
//+---------------------------------------------------------------------------
//
// Function: AllocArenaRecordFree
//
// Synopsis: Caller has freed memory -- keep accounting up to date
//
// Arguments: [vp] -- Value returned by AllocArenaRecordAlloc() above
// [bytes] -- The number of bytes being freed
//
// Algorithm: AllocRec structures, once allocated, are never actually
// freed back to the Hash memory arena. This helps us
// understand historical use of the heap.
//
//----------------------------------------------------------------------------
STDAPI_( void ) AllocArenaRecordFree( HeapAllocRec FAR *vp, size_t bytes ) { if( vp == NULL ) return;
struct AllocArena *paa = vp->paa;
EnterCriticalSection( &paa->csExclusive );
//
// Record this free in the histogram
//
for( int i=31; i>=0; i-- ) if( bytes & (1<<i) ) { --(paa->Histogram.now[i]); break; }
paa->cFrees++; paa->cBytesNow -= bytes;
#if defined( CANDOSTACK )
if( paa->flags.KeepStackTrace ) { vp->count--; vp->bytes -= bytes; } #endif
LeaveCriticalSection( &paa->csExclusive ); }
STDAPI_( void ) AllocArenaDumpRecord( HeapAllocRec FAR *bp ) { #if defined( CANDOSTACK )
char achBuffer[ MAX_TRANSLATED_LEN ], *p; static int FirstTime = TRUE;
// make sure we print the nice undecorated names
if ( FirstTime ) { LocalSymSetOptions( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS ); FirstTime = FALSE; }
heapDebugOut((DEB_WARN, "*** %d allocs, %u bytes:\n", bp->count, bp->bytes ));
HANDLE hProcess = GetCurrentProcess();
for( int j=0; j<DEPTHTRACE && bp->fTrace[j]; j++ ) { BYTE symbolInfo[sizeof(IMAGEHLP_SYMBOL) + MAX_TRANSLATED_LEN]; PIMAGEHLP_SYMBOL psym = (PIMAGEHLP_SYMBOL) &symbolInfo;
DWORD_PTR dwDisplacement;
psym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); psym->MaxNameLength = MAX_TRANSLATED_LEN;
if ( LocalSymGetSymFromAddr( hProcess, (ULONG_PTR)(bp->fTrace[j]), &dwDisplacement, psym ) ) { if ( LocalSymUnDName( psym, achBuffer, MAX_TRANSLATED_LEN ) ) { heapDebugOut((DEB_WARN, " %s+0x%p (0x%p)\n", achBuffer, dwDisplacement, (ULONG_PTR)(bp->fTrace[j]) )); } else { heapDebugOut(( DEB_WARN, " %s+%#x (%#x)\n", psym->Name, dwDisplacement, bp->fTrace[j] )); } } else { heapDebugOut(( DEB_WARN, " 0x%x (symbolic name unavailable)\n", (ULONG_PTR)bp->fTrace[j] )); } } #endif
}
extern "C" ULONG DbgPrint( PCH Format, ... );
STDAPI_( void ) AllocArenaDump( AllocArena *paa ) { if( paa == NULL ) { for( int i = 0; i < MAXARENAS && AllocArenas[i]; i++ ) AllocArenaDump( AllocArenas[i] ); return; }
EnterCriticalSection( &paa->csExclusive );
char *cmdline = GetCommandLineA();
if( cmdline == NULL ) cmdline = "???";
HeapAllocRec *bp = paa->AllocRec; HeapAllocRec *ep = bp + paa->cRecords;
if( paa->cBytesNow ) heapDebugOut((DEB_WARN, "***** CiExcept.Lib: %u bytes leaked mem for %s in '%s'\n", paa->cBytesNow, paa->comment, cmdline ));
// always dump leaks
ULONG oldLevel = heapInfoLevel; heapInfoLevel |= DEB_TRACE;
#if defined( CANDOSTACK )
if( paa->cBytesNow && paa->flags.KeepStackTrace ) { int cleaks = 0;
for( ; bp < ep; bp++) { if( bp->count ) ++cleaks; }
if( cleaks ) { heapDebugOut((DEB_WARN, "***** %s %u MEM LEAKS\n", paa->comment, cleaks ));
if( heapInfoLevel & DEB_TRACE ) { HeapAllocRec *bp; UINT maxdump = MAXDUMP; for( bp = paa->AllocRec; maxdump && bp<ep; bp++) if( bp->count ) { heapDebugOut((DEB_TRACE, "\n")); AllocArenaDumpRecord( bp ); maxdump--; } } else if( cleaks ) heapDebugOut((DEB_WARN, "** Set query!heapInfoLevel to x707 for leak backtrace\n"));
} } #endif
heapInfoLevel = oldLevel;
if( (heapInfoLevel & DEB_TRACE) && paa->cBytesTotal ) { heapDebugOut((DEB_TRACE,"\n")); heapDebugOut((DEB_TRACE, "'%s' Memory Stats: %u allocations, %u frees, %u missed\n", cmdline, paa->cAllocs, paa->cFrees, paa->cMissed ));
if( paa->czAllocs ) heapDebugOut((DEB_TRACE, "\t%u zero allocs\n", paa->czAllocs ));
// i64s are not handled by debugout
char acBuf[100]; sprintf( acBuf, "\t%I64u bytes allocated\n", paa->cBytesTotal ); heapDebugOut((DEB_TRACE, acBuf ));
heapDebugOut((DEB_TRACE, "*** Histogram of Allocated Mem Sizes ***\n"));
heapDebugOut((DEB_TRACE, " Min Max Tot Simul\n" )); for( int i=0; i < 32; i++ ) { if( paa->Histogram.total[i] ) { heapDebugOut((DEB_TRACE, "%9u -> %9u\t%9u\t%9u\n", 1<<i, (1<<(i+1))-1, paa->Histogram.total[i], paa->Histogram.simul[i] )); } } } LeaveCriticalSection( &paa->csExclusive ); }
#endif // CIDBG
CStaticMutexSem g_mtxGetStackTrace;
void GetStackTrace( char * pszBuf, ULONG ccMax ) { // Trial and error shows that Imagehlp isn't thread-safe
CLock lock( g_mtxGetStackTrace );
if ( 0 == pszBuf || ccMax == 0 ) return;
*pszBuf = 0;
// If we cannot get to IMAGEHLP then no stack traces are available
if (!SnapToImageHlp( )) return;
char * pszCurrent = pszBuf;
TRY {
#if defined( CANDOSTACK )
static int FirstTime = TRUE;
// make sure we print the nice undecorated names
if ( FirstTime ) { LocalSymSetOptions( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS ); FirstTime = FALSE; }
//
// Determine the current stack.
//
void *fTrace[ DEPTHTRACE ];
//
// See if we find an existing record of this stack backtrace
//
memset( fTrace, '\0', sizeof( fTrace ) ); DWORD sum = RecordStack( 2, fTrace ); if ( 0 == sum ) return;
HANDLE hProcess = GetCurrentProcess();
for( int j=0; j<DEPTHTRACE && fTrace[j]; j++ ) { BYTE symbolInfo[sizeof(IMAGEHLP_SYMBOL) + MAX_TRANSLATED_LEN]; PIMAGEHLP_SYMBOL psym = (PIMAGEHLP_SYMBOL) &symbolInfo;
DWORD_PTR dwDisplacement;
psym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); psym->MaxNameLength = MAX_TRANSLATED_LEN;
char szTempBuf[MAX_TRANSLATED_LEN+256];
if ( LocalSymGetSymFromAddr( hProcess, (ULONG_PTR)fTrace[j], &dwDisplacement, psym ) ) sprintf( szTempBuf, " %s+0x%p (0x%p)\n", psym->Name, dwDisplacement, fTrace[j] ); else sprintf( szTempBuf, " 0x%p\n", (ULONG_PTR)fTrace[j] );
ULONG cc = strlen(szTempBuf); if ( cc+pszCurrent >= pszBuf+ccMax ) break;
RtlCopyMemory( pszCurrent, szTempBuf, cc ); pszCurrent += cc; } #endif
} CATCH( CException, e ) { pszBuf[0] = 0; } END_CATCH
*pszCurrent = 0; }
|