//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: Memory allocation! // // $NoKeywords: $ //===========================================================================// #include "pch_tier0.h" #if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) //#include #include #include "tier0/dbg.h" #include "tier0/stackstats.h" #include "tier0/memalloc.h" #include "tier0/fasttimer.h" #include "mem_helpers.h" #ifdef PLATFORM_WINDOWS_PC #undef WIN32_LEAN_AND_MEAN #include #include #include #include #endif #ifdef OSX #include #include #endif #include #include #include #include "tier0/threadtools.h" #ifdef _X360 #include "xbox/xbox_console.h" #endif #ifdef _PS3 #include "sys/memory.h" #include "tls_ps3.h" #include "ps3/ps3_helpers.h" #include "memoverride_ps3.h" #endif #ifdef USE_LIGHT_MEM_DEBUG #undef USE_MEM_DEBUG #endif #if (!defined( POSIX ) && (defined(_DEBUG) || defined(USE_MEM_DEBUG))) #pragma message ("USE_MEM_DEBUG is enabled in a release build. Don't check this in!") #endif #include "mem_impl_type.h" #if MEM_IMPL_TYPE_DBG #if defined(_WIN32) && ( !defined(_X360) && !defined(_WIN64) ) //be sure to disable frame pointer omission for all projects. "vpc /nofpo" when using stack traces //#define USE_STACK_TRACES // or: //#define USE_STACK_TRACES_DETAILED const size_t STACK_TRACE_LENGTH = 32; #endif //prevent stupid bugs from checking one and not the other #if defined( USE_STACK_TRACES_DETAILED ) && !defined( USE_STACK_TRACES ) #define USE_STACK_TRACES //don't comment me. I'm a safety check #endif #if defined( USE_STACK_TRACES ) #define SORT_STACK_TRACE_DESCRIPTION_DUMPS #endif #if (defined( USE_STACK_TRACES )) && !(defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) #error Stack traces will not work unless FPO is disabled for every function traced through. Rebuild everything with FPO disabled "vpc /nofpo" #endif //----------------------------------------------------------------------------- #ifdef _PS3 MemOverrideRawCrtFunctions_t *g_pMemOverrideRawCrtFns; #define DebugAlloc (g_pMemOverrideRawCrtFns->pfn_malloc) #define DebugFree (g_pMemOverrideRawCrtFns->pfn_free) #elif defined( _X360 ) #define DebugAlloc DmAllocatePool #define DebugFree DmFreePool #else #define DebugAlloc malloc #define DebugFree free #endif #ifdef _WIN32 int g_DefaultHeapFlags = _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF ); #else int g_DefaultHeapFlags = 0; #endif // win32 #if defined( _MEMTEST ) static char s_szStatsMapName[32]; static char s_szStatsComment[256]; #endif #pragma optimize( "", off ) //----------------------------------------------------------------------------- #if defined( USE_STACK_TRACES ) bool GetModuleFromAddress( void *address, char *pResult, int iLength ) { return GetModuleNameFromAddress( address, pResult, iLength ); } bool GetCallerModule( char *pDest, int iLength ) { void *pCaller; GetCallStack_Fast( &pCaller, 1, 2 ); return ( pCaller != 0 && GetModuleFromAddress( pCaller, pDest, iLength ) ); } // // Note: StackDescribe function is non-reentrant: // Reason: Stack description is stored in a static buffer. // Solution: Passing caller-allocated buffers would allow the // function to become reentrant, however the current only client (FindOrCreateFilename) // is synchronized with a heap mutex, after retrieving stack description the // heap memory will be allocated to copy the text. // char * StackDescribe( void * const *ppAddresses, int nMaxAddresses ) { static char s_chStackDescription[ 32 * 1024 ]; char *pchBuffer = s_chStackDescription; #if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) //Assuming StackDescribe is called iteratively on a sorted set of stacks (as in DumpStackStats()). We can save work by skipping unchanged parts at the beginning of the string. static void *LastCallStack[STACK_TRACE_LENGTH] = { NULL }; static char *pEndPos[STACK_TRACE_LENGTH] = { NULL }; bool bUseExistingString = true; #else s_chStackDescription[ 0 ] = 0; #endif int k; for ( k = 0; k < nMaxAddresses; ++ k ) { if ( !ppAddresses[k] ) break; #if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) if( bUseExistingString && (k < STACK_TRACE_LENGTH) ) { if( ppAddresses[k] == LastCallStack[k] ) { pchBuffer = pEndPos[k]; continue; } else { //everything from here on is invalidated bUseExistingString = false; for( int clearEntries = k; clearEntries < STACK_TRACE_LENGTH; ++clearEntries ) //wipe out unused entries { LastCallStack[clearEntries] = NULL; pEndPos[clearEntries] = NULL; } //fall through to existing code if( k == 0 ) *pchBuffer = '\0'; else sprintf( pchBuffer, "<--" ); } } #endif { pchBuffer += strlen( pchBuffer ); char szTemp[MAX_PATH]; szTemp[0] = '\0'; uint32 iLine = 0; uint32 iLineDisplacement = 0; uint64 iSymbolDisplacement = 0; if ( GetFileAndLineFromAddress( ppAddresses[k], szTemp, MAX_PATH, iLine, &iLineDisplacement ) ) { char const *pchFileName = szTemp + strlen( szTemp ); for ( size_t numSlashesAllowed = 2; pchFileName > szTemp; --pchFileName ) { if ( *pchFileName == '\\' ) { if ( numSlashesAllowed-- ) continue; else break; } } sprintf( pchBuffer, iLineDisplacement ? "%s:%d+0x%I32X" : "%s:%d", pchFileName, iLine, iLineDisplacement ); } else if ( GetSymbolNameFromAddress( ppAddresses[k], szTemp, MAX_PATH, &iSymbolDisplacement ) ) { sprintf( pchBuffer, ( iSymbolDisplacement > 0 && !( iSymbolDisplacement >> 63 ) ) ? "%s+0x%llX" : "%s", szTemp, iSymbolDisplacement ); } else { sprintf( pchBuffer, "#0x%08p", ppAddresses[k] ); } pchBuffer += strlen( pchBuffer ); sprintf( pchBuffer, "<--" ); #if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) if( k < STACK_TRACE_LENGTH ) { LastCallStack[k] = ppAddresses[k]; pEndPos[k] = pchBuffer; } #endif } } *pchBuffer = 0; #if defined( SORT_STACK_TRACE_DESCRIPTION_DUMPS ) for( ; k < STACK_TRACE_LENGTH; ++k ) //wipe out unused entries { LastCallStack[k] = NULL; pEndPos[k] = NULL; } #endif return s_chStackDescription; } #else #define GetModuleFromAddress( address, pResult, iLength ) ( ( *pResult = 0 ), 0) #define GetCallerModule( pDest, iLength ) false #endif //----------------------------------------------------------------------------- // NOTE: This exactly mirrors the dbg header in the MSDEV crt // eventually when we write our own allocator, we can kill this struct CrtDbgMemHeader_t { unsigned char m_Reserved[8]; const char *m_pFileName; int m_nLineNumber; unsigned char m_Reserved2[16]; }; struct Sentinal_t { DWORD value[4]; }; Sentinal_t g_HeadSentinelAllocated = { 0xeee1beef, 0xeee1f00d, 0xbd122969, 0xbeefbeef, }; Sentinal_t g_HeadSentinelFree = { 0xdeadbeef, 0xbaadf00d, 0xbd122969, 0xdeadbeef, }; Sentinal_t g_TailSentinel = { 0xbaadf00d, 0xbd122969, 0xdeadbeef, 0xbaadf00d, }; const byte g_FreeFill = 0xdd; enum DbgMemHeaderBlockType_t { BLOCKTYPE_FREE, BLOCKTYPE_ALLOCATED }; struct DbgMemHeader_t #if !defined( _DEBUG ) || defined( _PS3 ) : CrtDbgMemHeader_t #endif { size_t nLogicalSize; #if defined( USE_STACK_TRACES ) unsigned int nStatIndex; byte reserved[ 16 - (sizeof(unsigned int) + sizeof( size_t ) ) ]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on #else byte reserved[16 - sizeof( size_t )]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on #endif Sentinal_t sentinal; }; const int g_nRecentFrees = ( IsPC() ) ? 8192 : 512; DbgMemHeader_t ** GetRecentFrees() { static DbgMemHeader_t **g_pRecentFrees = (DbgMemHeader_t**) #ifdef _PS3 g_pMemOverrideRawCrtFns->pfn_calloc #else calloc #endif ( g_nRecentFrees, sizeof(DbgMemHeader_t *) ); return g_pRecentFrees; } uint32 volatile g_iNextFreeSlot; uint32 volatile g_break_BytesFree = 0xffffffff; void LMDReportInvalidBlock( DbgMemHeader_t *pHeader, const char *pszMessage ) { char szMsg[256]; if ( pHeader ) { sprintf( szMsg, "HEAP IS CORRUPT: %s (block 0x%x, %d bytes)\n", pszMessage, (size_t)( ((byte*) pHeader) + sizeof( DbgMemHeader_t ) ), pHeader->nLogicalSize ); } else { sprintf( szMsg, "HEAP IS CORRUPT: %s\n", pszMessage ); } Assert( !"HEAP IS CORRUPT!" ); DebuggerBreak(); } void LMDValidateBlock( DbgMemHeader_t *pHeader, bool bFreeList ) { if ( memcmp( &pHeader->sentinal, bFreeList ? &g_HeadSentinelFree : &g_HeadSentinelAllocated, sizeof(Sentinal_t) ) != 0 ) { LMDReportInvalidBlock( pHeader, "Head sentinel corrupt" ); } if ( memcmp( ((Sentinal_t *)(( ((byte*) pHeader) + sizeof( DbgMemHeader_t ) + pHeader->nLogicalSize ))), &g_TailSentinel, sizeof(Sentinal_t) ) != 0 ) { LMDReportInvalidBlock( pHeader, "Tail sentinel corrupt" ); } if ( bFreeList ) { byte *pCur = (byte *)pHeader + sizeof(DbgMemHeader_t); byte *pLimit = pCur + pHeader->nLogicalSize; while ( pCur != pLimit ) { if ( *pCur++ != g_FreeFill ) { LMDReportInvalidBlock( pHeader, "Write after free" ); } } } } //----------------------------------------------------------------------------- #if defined( _DEBUG ) && !defined( POSIX ) #define GetCrtDbgMemHeader( pMem ) ((CrtDbgMemHeader_t*)((DbgMemHeader_t*)pMem - 1) - 1) #elif defined( OSX ) DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ); #else #define GetCrtDbgMemHeader( pMem ) ((DbgMemHeader_t*)(pMem) - 1) #endif #if defined( USE_STACK_TRACES ) #define GetAllocationStatIndex_Internal( pMem ) ( ((DbgMemHeader_t*)pMem - 1)->nStatIndex ) #endif #ifdef OSX DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ) { size_t msize = malloc_size( pMem ); return (DbgMemHeader_t *)( (char *)pMem + msize - sizeof(DbgMemHeader_t) ); } #endif inline void *InternalMalloc( size_t nSize, const char *pFileName, int nLine ) { #if defined( POSIX ) || defined( _PS3 ) void *pAllocedMem = NULL; #ifdef OSX pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem ); #elif defined( _PS3 ) pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_malloc)( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; *((void**)pInternalMem->m_Reserved2) = pAllocedMem; #else pAllocedMem = malloc( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; #endif pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; pInternalMem->nLogicalSize = nSize; *((int*)pInternalMem->m_Reserved) = 0xf00df00d; pInternalMem->sentinal = g_HeadSentinelAllocated; *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nSize ) ) = g_TailSentinel; LMDValidateBlock( pInternalMem, false ); #ifdef OSX return pAllocedMem; #else return pInternalMem + 1; #endif #else // WIN32 DbgMemHeader_t *pInternalMem; #if !defined( _DEBUG ) pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) ); pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; #else pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); #endif pInternalMem->nLogicalSize = nSize; return pInternalMem + 1; #endif // WIN32 } #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS inline void *InternalMallocAligned( size_t nSize, size_t align, const char *pFileName, int nLine ) { #if defined( POSIX ) || defined( _PS3 ) void *pAllocedMem = NULL; #ifdef OSX pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem ); #elif defined( _PS3 ) size_t numWastedAlignPages = ( sizeof( DbgMemHeader_t ) / align ); if ( align * numWastedAlignPages < sizeof( DbgMemHeader_t ) ) ++ numWastedAlignPages; size_t nSizeRequired = nSize + numWastedAlignPages*align + sizeof( Sentinal_t ); pAllocedMem = (g_pMemOverrideRawCrtFns->pfn_memalign)( align, nSizeRequired ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( ((char*)pAllocedMem) + numWastedAlignPages*align ); *((void**)pInternalMem->m_Reserved2) = pAllocedMem; #else pAllocedMem = malloc( nSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pAllocedMem; #endif pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; pInternalMem->nLogicalSize = nSize; *((int*)pInternalMem->m_Reserved) = 0xf00df00d; pInternalMem->sentinal = g_HeadSentinelAllocated; *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nSize ) ) = g_TailSentinel; LMDValidateBlock( pInternalMem, false ); #ifdef OSX return pAllocedMem; #else return pInternalMem + 1; #endif #else // WIN32 DbgMemHeader_t *pInternalMem; #if !defined( _DEBUG ) pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) ); pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; #else pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); #endif pInternalMem->nLogicalSize = nSize; return pInternalMem + 1; #endif // WIN32 } #endif inline void *InternalRealloc( void *pMem, size_t nNewSize, const char *pFileName, int nLine ) { if ( !pMem ) return InternalMalloc( nNewSize, pFileName, nLine ); #ifdef POSIX void *pNewAllocedMem = NULL; #ifdef OSX pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem ); #elif defined( _PS3 ) DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); pNewAllocedMem = (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_realloc)( *((void**)pInternalMem->m_Reserved2), nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; *((void**)pInternalMem->m_Reserved2) = pNewAllocedMem; #else DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); pNewAllocedMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; #endif pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; pInternalMem->nLogicalSize = static_cast( nNewSize ); *((int*)pInternalMem->m_Reserved) = 0xf00df00d; pInternalMem->sentinal = g_HeadSentinelAllocated; *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nNewSize ) ) = g_TailSentinel; LMDValidateBlock( pInternalMem, false ); #ifdef OSX return pNewAllocedMem; #else return pInternalMem + 1; #endif #else // WIN32 DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; #if !defined( _DEBUG ) pInternalMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) ); pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; #else pInternalMem = (DbgMemHeader_t *)_realloc_dbg( pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); #endif pInternalMem->nLogicalSize = nNewSize; return pInternalMem + 1; #endif // WIN32 } #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS inline void *InternalReallocAligned( void *pMem, size_t nNewSize, size_t align, const char *pFileName, int nLine ) { if ( !pMem ) return InternalMallocAligned( nNewSize, align, pFileName, nLine ); #ifdef POSIX void *pNewAllocedMem = NULL; #ifdef OSX pNewAllocedMem = (DbgMemHeader_t *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem ); #elif defined( _PS3 ) size_t numWastedAlignPages = ( sizeof( DbgMemHeader_t ) / align ); if ( align * numWastedAlignPages < sizeof( DbgMemHeader_t ) ) ++ numWastedAlignPages; size_t nSizeRequired = nNewSize + numWastedAlignPages*align + sizeof( Sentinal_t ); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); pNewAllocedMem = (DbgMemHeader_t *)(g_pMemOverrideRawCrtFns->pfn_reallocalign)( *((void**)pInternalMem->m_Reserved2), nSizeRequired, align ); pInternalMem = GetCrtDbgMemHeader( ((char*)pNewAllocedMem) + numWastedAlignPages*align ); *((void**)pInternalMem->m_Reserved2) = pNewAllocedMem; #else DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); pNewAllocedMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) + sizeof( Sentinal_t ) ); pInternalMem = (DbgMemHeader_t *)pNewAllocedMem; #endif pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; pInternalMem->nLogicalSize = static_cast( nNewSize ); *((int*)pInternalMem->m_Reserved) = 0xf00df00d; pInternalMem->sentinal = g_HeadSentinelAllocated; *( (Sentinal_t *)( ((byte*)pInternalMem) + sizeof( DbgMemHeader_t ) + nNewSize ) ) = g_TailSentinel; LMDValidateBlock( pInternalMem, false ); #ifdef OSX return pNewAllocedMem; #else return pInternalMem + 1; #endif #else // WIN32 DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; #if !defined( _DEBUG ) pInternalMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) ); pInternalMem->m_pFileName = pFileName; pInternalMem->m_nLineNumber = nLine; #else pInternalMem = (DbgMemHeader_t *)_realloc_dbg( pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); #endif pInternalMem->nLogicalSize = nNewSize; return pInternalMem + 1; #endif // WIN32 } #endif inline void InternalFree( void *pMem ) { if ( !pMem ) return; DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; #if defined( POSIX ) // Record it in recent free blocks list DbgMemHeader_t **pRecentFrees = GetRecentFrees(); uint32 iNextSlot = ThreadInterlockedIncrement( &g_iNextFreeSlot ); iNextSlot %= g_nRecentFrees; if ( memcmp( &pInternalMem->sentinal, &g_HeadSentinelAllocated, sizeof( Sentinal_t ) ) != 0 ) { Assert( !"Double Free or Corrupt Block Header!" ); DebuggerBreak(); } LMDValidateBlock( pInternalMem, false ); if ( g_break_BytesFree == pInternalMem->nLogicalSize ) { DebuggerBreak(); } pInternalMem->sentinal = g_HeadSentinelFree; memset( pMem, g_FreeFill, pInternalMem->nLogicalSize ); DbgMemHeader_t *pToFree = pInternalMem; if ( pInternalMem->nLogicalSize < 16*1024 ) { pToFree = pRecentFrees[iNextSlot]; pRecentFrees[iNextSlot] = pInternalMem; if ( pToFree ) { LMDValidateBlock( pToFree, true ); } } // Validate several last frees for ( uint32 k = iNextSlot - 1, iteration = 0; iteration < 10; ++ iteration, -- k ) { if ( DbgMemHeader_t *pLastFree = pRecentFrees[ k % g_nRecentFrees ] ) { LMDValidateBlock( pLastFree, true ); } } if ( !pToFree ) return; #ifdef OSX malloc_zone_free( malloc_default_zone(), pToFree ); #elif defined( _PS3 ) (g_pMemOverrideRawCrtFns->pfn_free)( *((void**)pToFree->m_Reserved2) ); #elif LINUX free( pToFree ); #else free( pToFree ); #endif #elif defined( _DEBUG ) _free_dbg( pInternalMem, _NORMAL_BLOCK ); #else free( pInternalMem ); #endif } inline size_t InternalMSize( void *pMem ) { #if defined( _PS3 ) DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); return pInternalMem->nLogicalSize; #elif defined(POSIX) DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); return pInternalMem->nLogicalSize; #elif !defined(_DEBUG) DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); return _msize( pInternalMem ) - sizeof(DbgMemHeader_t); #else DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; return _msize_dbg( pInternalMem, _NORMAL_BLOCK ) - sizeof(DbgMemHeader_t); #endif } inline size_t InternalLogicalSize( void *pMem ) { #if defined(POSIX) DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); #elif !defined(_DEBUG) DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; #else DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; #endif return pInternalMem->nLogicalSize; } #ifndef _DEBUG #define _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ) 0 #endif //----------------------------------------------------------------------------- // Custom allocator protects this module from recursing on operator new template class CNoRecurseAllocator { public: // type definitions typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; CNoRecurseAllocator() {} CNoRecurseAllocator(const CNoRecurseAllocator&) {} template CNoRecurseAllocator(const CNoRecurseAllocator&) {} ~CNoRecurseAllocator(){} // rebind allocator to type U template struct rebind { typedef CNoRecurseAllocator other; }; // return address of values pointer address (reference value) const { return &value; } const_pointer address (const_reference value) const { return &value;} size_type max_size() const { return INT_MAX; } pointer allocate(size_type num, const void* = 0) { return (pointer)DebugAlloc(num * sizeof(T)); } void deallocate (pointer p, size_type num) { DebugFree(p); } void construct(pointer p, const T& value) { new((void*)p)T(value); } void destroy (pointer p) { p->~T(); } }; template bool operator==(const CNoRecurseAllocator&, const CNoRecurseAllocator&) { return true; } template bool operator!=(const CNoRecurseAllocator&, const CNoRecurseAllocator&) { return false; } class CStringLess { public: bool operator()(const char *pszLeft, const char *pszRight ) const { return ( V_tier0_stricmp( pszLeft, pszRight ) < 0 ); } }; //----------------------------------------------------------------------------- #pragma warning( disable:4074 ) // warning C4074: initializers put in compiler reserved initialization area #pragma init_seg( compiler ) //----------------------------------------------------------------------------- // NOTE! This should never be called directly from leaf code // Just use new,delete,malloc,free etc. They will call into this eventually //----------------------------------------------------------------------------- class CDbgMemAlloc : public IMemAlloc { public: CDbgMemAlloc(); virtual ~CDbgMemAlloc(); // Release versions virtual void *Alloc( size_t nSize ); virtual void *Realloc( void *pMem, size_t nSize ); virtual void Free( void *pMem ); virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS virtual void *AllocAlign( size_t nSize, size_t align ); virtual void *AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ); virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align ); virtual void *ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ); #endif // Debug versions virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); virtual void Free( void *pMem, const char *pFileName, int nLine ); virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); virtual void *RegionAlloc( int region, size_t nSize ) { return Alloc( nSize ); } virtual void *RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ) { return Alloc( nSize, pFileName, nLine ); } // Returns the size of a particular allocation (NOTE: may be larger than the size requested!) virtual size_t GetSize( void *pMem ); // Force file + line information for an allocation virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); virtual void PopAllocDbgInfo(); virtual int32 CrtSetBreakAlloc( int32 lNewBreakAlloc ); virtual int CrtSetReportMode( int nReportType, int nReportMode ); virtual int CrtIsValidHeapPointer( const void *pMem ); virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); virtual int CrtCheckMemory( void ); virtual int CrtSetDbgFlag( int nNewFlag ); virtual void CrtMemCheckpoint( _CrtMemState *pState ); // handles storing allocation info for coroutines virtual uint32 GetDebugInfoSize(); virtual void SaveDebugInfo( void *pvDebugInfo ); virtual void RestoreDebugInfo( const void *pvDebugInfo ); virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ); // FIXME: Remove when we have our own allocator virtual void* CrtSetReportFile( int nRptType, void* hFile ); virtual void* CrtSetReportHook( void* pfnNewHook ); virtual int CrtDbgReport( int nRptType, const char * szFile, int nLine, const char * szModule, const char * szFormat ); virtual int heapchk(); virtual bool IsDebugHeap() { return true; } virtual int GetVersion() { return MEMALLOC_VERSION; } virtual void CompactHeap() { #if defined( _X360 ) && defined( _DEBUG ) HeapCompact( GetProcessHeap(), 0 ); #endif } virtual void CompactIncremental() {} virtual void OutOfMemory( size_t nBytesAttempted = 0 ) {} virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) { return NULL; } // debug heap doesn't attempt retries void SetStatsExtraInfo( const char *pMapName, const char *pComment ) { #if defined( _MEMTEST ) strncpy( s_szStatsMapName, pMapName, sizeof( s_szStatsMapName ) ); s_szStatsMapName[sizeof( s_szStatsMapName ) - 1] = '\0'; strncpy( s_szStatsComment, pComment, sizeof( s_szStatsComment ) ); s_szStatsComment[sizeof( s_szStatsComment ) - 1] = '\0'; #endif } virtual size_t MemoryAllocFailed(); void SetCRTAllocFailed( size_t nMemSize ); enum { BYTE_COUNT_16 = 0, BYTE_COUNT_32, BYTE_COUNT_128, BYTE_COUNT_2048, BYTE_COUNT_GREATER, NUM_BYTE_COUNT_BUCKETS }; private: struct MemInfo_t { #if defined( USE_STACK_TRACES ) DECLARE_CALLSTACKSTATSTRUCT(); DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION(); #endif MemInfo_t() { memset( this, 0, sizeof(*this) ); } // Size in bytes size_t m_nCurrentSize; size_t m_nPeakSize; size_t m_nTotalSize; size_t m_nOverheadSize; size_t m_nPeakOverheadSize; // Count in terms of # of allocations int m_nCurrentCount; int m_nPeakCount; int m_nTotalCount; int m_nSumTargetRange; int m_nCurTargetRange; int m_nMaxTargetRange; // Count in terms of # of allocations of a particular size int m_pCount[NUM_BYTE_COUNT_BUCKETS]; // Time spent allocating + deallocating (microseconds) int64 m_nTime; }; struct MemInfoKey_FileLine_t { MemInfoKey_FileLine_t( const char *pFileName, int line ) : m_pFileName(pFileName), m_nLine(line) {} bool operator<( const MemInfoKey_FileLine_t &key ) const { int iret = V_tier0_stricmp( m_pFileName, key.m_pFileName ); if ( iret < 0 ) return true; if ( iret > 0 ) return false; return m_nLine < key.m_nLine; } const char *m_pFileName; int m_nLine; }; // NOTE: Deliberately using STL here because the UTL stuff // is a client of this library; want to avoid circular dependency // Maps file name to info typedef std::map< MemInfoKey_FileLine_t, MemInfo_t, std::less, CNoRecurseAllocator > > StatMap_FileLine_t; typedef StatMap_FileLine_t::iterator StatMapIter_FileLine_t; typedef StatMap_FileLine_t::value_type StatMapEntry_FileLine_t; typedef std::set > Filenames_t; // Heap reporting method typedef void (*HeapReportFunc_t)( char const *pFormat, ... ); private: // Returns the actual debug info virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ); // Finds the file in our map MemInfo_t &FindOrCreateEntry( const char *pFileName, int line ); const char *FindOrCreateFilename( const char *pFileName ); #if defined( USE_STACK_TRACES ) int GetCallStackForIndex( unsigned int index, void **pCallStackOut, int iMaxEntriesOut ); friend int GetAllocationCallStack( void *mem, void **pCallStackOut, int iMaxEntriesOut ); #endif // Updates stats virtual void RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); virtual void RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); #if defined( USE_STACK_TRACES ) void RegisterAllocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); void RegisterDeallocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); #endif void RegisterAllocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); void RegisterDeallocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ); // Gets the allocation file name const char *GetAllocatonFileName( void *pMem ); int GetAllocatonLineNumber( void *pMem ); // FIXME: specify a spew output func for dumping stats // Stat output void DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ); void DumpFileStats(); #if defined( USE_STACK_TRACES ) void DumpMemInfo( void * const CallStack[STACK_TRACE_LENGTH], const MemInfo_t &info ); void DumpCallStackFlow( char const *pchFileBase ); #endif virtual void DumpStats(); virtual void DumpStatsFileBase( char const *pchFileBase, DumpStatsFormat_t nFormat = FORMAT_TEXT ) OVERRIDE; virtual void DumpBlockStats( void *p ); virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ); virtual size_t ComputeMemoryUsedBy( char const *pchSubStr ); virtual IVirtualMemorySection * AllocateVirtualMemorySection( size_t numMaxBytes ) { #if defined( _GAMECONSOLE ) || defined( _WIN32 ) extern IVirtualMemorySection * VirtualMemoryManager_AllocateVirtualMemorySection( size_t numMaxBytes ); return VirtualMemoryManager_AllocateVirtualMemorySection( numMaxBytes ); #else return NULL; #endif } virtual int GetGenericMemoryStats( GenericMemoryStat_t **ppMemoryStats ) { // TODO: reuse code from GlobalMemoryStatus (though this is only really useful when using CStdMemAlloc...) return 0; } private: StatMap_FileLine_t m_StatMap_FileLine; #if defined( USE_STACK_TRACES ) typedef CCallStackStatsGatherer, CNoRecurseAllocator> CallStackStatsType_t; CallStackStatsType_t m_CallStackStats; #endif MemInfo_t m_GlobalInfo; CFastTimer m_Timer; bool m_bInitialized; Filenames_t m_Filenames; HeapReportFunc_t m_OutputFunc; static size_t s_pCountSizes[NUM_BYTE_COUNT_BUCKETS]; static const char *s_pCountHeader[NUM_BYTE_COUNT_BUCKETS]; size_t m_sMemoryAllocFailed; }; static char const *g_pszUnknown = "unknown"; #if defined( USE_STACK_TRACES ) BEGIN_STATSTRUCTDESCRIPTION( CDbgMemAlloc::MemInfo_t ) WRITE_STATSTRUCT_FIELDDESCRIPTION(); END_STATSTRUCTDESCRIPTION() BEGIN_STATSTRUCTFIELDDESCRIPTION( CDbgMemAlloc::MemInfo_t ) DEFINE_STATSTRUCTFIELD( m_nCurrentSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nPeakSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nTotalSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nOverheadSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nPeakOverheadSize, BasicStatStructFieldDesc, ( BSSFT_SIZE_T, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nCurrentCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nPeakCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nTotalCount, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nSumTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nCurTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nMaxTargetRange, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_16, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_32, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_128, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_2048, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD_ARRAYENTRY( m_pCount, BYTE_COUNT_GREATER, BasicStatStructFieldDesc, ( BSSFT_INT, BSSFCM_ADD ) ) DEFINE_STATSTRUCTFIELD( m_nTime, BasicStatStructFieldDesc, ( BSSFT_INT64, BSSFCM_ADD ) ) END_STATSTRUCTFIELDDESCRIPTION() #endif //----------------------------------------------------------------------------- const int DBG_INFO_STACK_DEPTH = 32; struct DbgInfoStack_t { const char *m_pFileName; int m_nLine; }; #ifdef _PS3 #ifndef _CERT extern TLSGlobals * ( *g_pfnElfGetTlsGlobals )(); #define IfDbgInfoIsReady() if ( TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = g_pfnElfGetTlsGlobals ? g_pfnElfGetTlsGlobals() : NULL ) #else #define IfDbgInfoIsReady() if ( TLSGlobals *IfDbgInfoIsReady_pTlsGlobals = GetTLSGlobals() ) #endif #define g_DbgInfoStack ( ( DbgInfoStack_t *& ) IfDbgInfoIsReady_pTlsGlobals->pMallocDbgInfoStack ) #define g_nDbgInfoStackDepth ( IfDbgInfoIsReady_pTlsGlobals->nMallocDbgInfoStackDepth ) #else CTHREADLOCALPTR( DbgInfoStack_t) g_DbgInfoStack CONSTRUCT_EARLY; CTHREADLOCALINT g_nDbgInfoStackDepth CONSTRUCT_EARLY; #define IfDbgInfoIsReady() if (true) #endif #ifdef _PS3 struct CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early { CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early() { malloc_managed_size mms; mms.current_inuse_size = 0x12345678; mms.current_system_size = 0x09ABCDEF; mms.max_system_size = 0; int iResult = malloc_stats( &mms ); g_pMemOverrideRawCrtFns = reinterpret_cast< MemOverrideRawCrtFunctions_t * >( iResult ); } } g_CDbgMemAlloc_GetRawCrtMemOverrideFuncs_Early CONSTRUCT_EARLY; #endif //----------------------------------------------------------------------------- // Singleton... //----------------------------------------------------------------------------- static CDbgMemAlloc s_DbgMemAlloc CONSTRUCT_EARLY; #ifdef _PS3 IMemAlloc *g_pMemAllocInternalPS3 = &s_DbgMemAlloc; PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3_IMPL #else // !_PS3 #ifndef TIER0_VALIDATE_HEAP IMemAlloc *g_pMemAlloc CONSTRUCT_EARLY = &s_DbgMemAlloc; void SetAllocatorObject( IMemAlloc* pAllocator ) { g_pMemAlloc = pAllocator; } #else IMemAlloc *g_pActualAlloc = &s_DbgMemAlloc; void SetAllocatorObject( IMemAlloc* pAllocator ) { g_pActualAlloc = pAllocator; } #endif #endif // _PS3 //----------------------------------------------------------------------------- CThreadMutex g_DbgMemMutex CONSTRUCT_EARLY; #define HEAP_LOCK() AUTO_LOCK( g_DbgMemMutex ) //----------------------------------------------------------------------------- // Byte count buckets //----------------------------------------------------------------------------- size_t CDbgMemAlloc::s_pCountSizes[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = { 16, 32, 128, 2048, INT_MAX }; const char *CDbgMemAlloc::s_pCountHeader[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = { "<=16 byte allocations", "17-32 byte allocations", "33-128 byte allocations", "129-2048 byte allocations", ">2048 byte allocations" }; size_t g_TargetCountRangeMin = 0, g_TargetCountRangeMax = 0; //----------------------------------------------------------------------------- // Standard output //----------------------------------------------------------------------------- static FILE* s_DbgFile; static void DefaultHeapReportFunc( char const *pFormat, ... ) { va_list args; va_start( args, pFormat ); vfprintf( s_DbgFile, pFormat, args ); va_end( args ); } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CDbgMemAlloc::CDbgMemAlloc() : m_sMemoryAllocFailed( (size_t)0 ) { CClockSpeedInit::Init(); m_OutputFunc = DefaultHeapReportFunc; m_bInitialized = true; if ( !IsDebug() && !IsX360() ) { Plat_DebugString( "USE_MEM_DEBUG is enabled in a release build. Don't check this in!\n" ); } #ifdef _PS3 g_pMemAllocInternalPS3 = &s_DbgMemAlloc; PLATFORM_OVERRIDE_MEM_ALLOC_INTERNAL_PS3.m_pMemAllocCached = &s_DbgMemAlloc; malloc_managed_size mms; mms.current_inuse_size = 0x12345678; mms.current_system_size = 0x09ABCDEF; mms.max_system_size = reinterpret_cast< size_t >( this ); int iResult = malloc_stats( &mms ); g_pMemOverrideRawCrtFns = reinterpret_cast< MemOverrideRawCrtFunctions_t * >( iResult ); #elif IsPlatformWindowsPC() char *pStr = (char*)Plat_GetCommandLineA(); if ( pStr ) { char tempStr[512]; strncpy( tempStr, pStr, sizeof( tempStr ) - 1 ); tempStr[ sizeof( tempStr ) - 1 ] = 0; _strupr( tempStr ); CheckWindowsAllocSettings( tempStr ); } #endif } CDbgMemAlloc::~CDbgMemAlloc() { Filenames_t::const_iterator iter = m_Filenames.begin(); while(iter != m_Filenames.end()) { char *pFileName = (char*)(*iter); free( pFileName ); iter++; } m_bInitialized = false; } //----------------------------------------------------------------------------- // Release versions //----------------------------------------------------------------------------- void *CDbgMemAlloc::Alloc( size_t nSize ) { /* // NOTE: Uncomment this to find unknown allocations const char *pFileName = g_pszUnknown; int nLine; GetActualDbgInfo( pFileName, nLine ); if (pFileName == g_pszUnknown) { int x = 3; } */ char szModule[MAX_PATH]; if ( GetCallerModule( szModule, MAX_PATH ) ) { return Alloc( nSize, szModule, 0 ); } else { return Alloc( nSize, g_pszUnknown, 0 ); } // return malloc( nSize ); } #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS void *CDbgMemAlloc::AllocAlign( size_t nSize, size_t align ) { /* // NOTE: Uncomment this to find unknown allocations const char *pFileName = g_pszUnknown; int nLine; GetActualDbgInfo( pFileName, nLine ); if (pFileName == g_pszUnknown) { int x = 3; } */ char szModule[MAX_PATH]; if ( GetCallerModule( szModule, MAX_PATH ) ) { return AllocAlign( nSize, align, szModule, 0 ); } else { return AllocAlign( nSize, align, g_pszUnknown, 0 ); } // return malloc( nSize ); } #endif void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize ) { /* // NOTE: Uncomment this to find unknown allocations const char *pFileName = g_pszUnknown; int nLine; GetActualDbgInfo( pFileName, nLine ); if (pFileName == g_pszUnknown) { int x = 3; } */ // FIXME: Should these gather stats? char szModule[MAX_PATH]; if ( GetCallerModule( szModule, MAX_PATH ) ) { return Realloc( pMem, nSize, szModule, 0 ); } else { return Realloc( pMem, nSize, g_pszUnknown, 0 ); } // return realloc( pMem, nSize ); } void CDbgMemAlloc::Free( void *pMem ) { // FIXME: Should these gather stats? Free( pMem, g_pszUnknown, 0 ); // free( pMem ); } void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) { return NULL; } //----------------------------------------------------------------------------- // Force file + line information for an allocation //----------------------------------------------------------------------------- void CDbgMemAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) { IfDbgInfoIsReady() { if ( g_DbgInfoStack == NULL ) { g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); g_nDbgInfoStackDepth = -1; } ++g_nDbgInfoStackDepth; Assert( g_nDbgInfoStackDepth < DBG_INFO_STACK_DEPTH ); g_DbgInfoStack[g_nDbgInfoStackDepth].m_pFileName = FindOrCreateFilename( pFileName ); g_DbgInfoStack[g_nDbgInfoStackDepth].m_nLine = nLine; } } void CDbgMemAlloc::PopAllocDbgInfo() { IfDbgInfoIsReady() { if ( g_DbgInfoStack == NULL ) { g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); g_nDbgInfoStackDepth = -1; } --g_nDbgInfoStackDepth; Assert( g_nDbgInfoStackDepth >= -1 ); } } //----------------------------------------------------------------------------- // handles storing allocation info for coroutines //----------------------------------------------------------------------------- uint32 CDbgMemAlloc::GetDebugInfoSize() { return sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH + sizeof( int32 ); } void CDbgMemAlloc::SaveDebugInfo( void *pvDebugInfo ) { IfDbgInfoIsReady() { if ( g_DbgInfoStack == NULL ) { g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); g_nDbgInfoStackDepth = -1; } int32 *pnStackDepth = (int32*) pvDebugInfo; *pnStackDepth = g_nDbgInfoStackDepth; memcpy( pnStackDepth+1, &g_DbgInfoStack[0], sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); } } void CDbgMemAlloc::RestoreDebugInfo( const void *pvDebugInfo ) { IfDbgInfoIsReady() { if ( g_DbgInfoStack == NULL ) { g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); g_nDbgInfoStackDepth = -1; } const int32 *pnStackDepth = (const int32*) pvDebugInfo; g_nDbgInfoStackDepth = *pnStackDepth; memcpy( &g_DbgInfoStack[0], pnStackDepth+1, sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); } } void CDbgMemAlloc::InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) { int32 *pnStackDepth = (int32*) pvDebugInfo; if( pchRootFileName ) { *pnStackDepth = 0; DbgInfoStack_t *pStackRoot = (DbgInfoStack_t *)(pnStackDepth + 1); pStackRoot->m_pFileName = FindOrCreateFilename( pchRootFileName ); pStackRoot->m_nLine = nLine; } else { *pnStackDepth = -1; } } //----------------------------------------------------------------------------- // Returns the actual debug info //----------------------------------------------------------------------------- void CDbgMemAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) { #if defined( USE_STACK_TRACES_DETAILED ) return; #endif IfDbgInfoIsReady() { if ( g_DbgInfoStack == NULL ) { g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); g_nDbgInfoStackDepth = -1; } if ( g_nDbgInfoStackDepth >= 0 && g_DbgInfoStack[0].m_pFileName) { pFileName = g_DbgInfoStack[0].m_pFileName; nLine = g_DbgInfoStack[0].m_nLine; } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- const char *CDbgMemAlloc::FindOrCreateFilename( const char *pFileName ) { // If we created it for the first time, actually *allocate* the filename memory HEAP_LOCK(); // This is necessary for shutdown conditions: the file name is stored // in some piece of memory in a DLL; if that DLL becomes unloaded, // we'll have a pointer to crap memory if ( !pFileName ) { pFileName = g_pszUnknown; } #if defined( USE_STACK_TRACES_DETAILED ) { // Walk the stack to determine what's causing the allocation void *arrStackAddresses[ 10 ] = { 0 }; int numStackAddrRetrieved = GetCallStack_Fast( arrStackAddresses, 10, 2 ); //Skip this function, and either CDbgMemAlloc::Alloc() or CDbgMemAlloc::Realloc() char *szStack = StackDescribe( arrStackAddresses, numStackAddrRetrieved ); if ( szStack && *szStack ) { pFileName = szStack; // Use the stack description for the allocation } } #endif // #if defined( USE_STACK_TRACES_DETAILED ) char *pszFilenameCopy; Filenames_t::const_iterator iter = m_Filenames.find( pFileName ); if ( iter == m_Filenames.end() ) { size_t nLen = strlen(pFileName) + 1; pszFilenameCopy = (char *)DebugAlloc( nLen ); memcpy( pszFilenameCopy, pFileName, nLen ); m_Filenames.insert( pszFilenameCopy ); } else { pszFilenameCopy = (char *)(*iter); } return pszFilenameCopy; } //----------------------------------------------------------------------------- // Finds the file in our map //----------------------------------------------------------------------------- CDbgMemAlloc::MemInfo_t &CDbgMemAlloc::FindOrCreateEntry( const char *pFileName, int line ) { // Oh how I love crazy STL. retval.first == the StatMapIter_t in the std::pair // retval.first->second == the MemInfo_t that's part of the StatMapIter_t std::pair retval; retval = m_StatMap_FileLine.insert( StatMapEntry_FileLine_t( MemInfoKey_FileLine_t( pFileName, line ), MemInfo_t() ) ); return retval.first->second; } #if defined( USE_STACK_TRACES ) int CDbgMemAlloc::GetCallStackForIndex( unsigned int index, void **pCallStackOut, int iMaxEntriesOut ) { if( iMaxEntriesOut > STACK_TRACE_LENGTH ) iMaxEntriesOut = STACK_TRACE_LENGTH; CallStackStatsType_t::StackReference stackRef = m_CallStackStats.GetCallStackForIndex( index ); memcpy( pCallStackOut, stackRef, iMaxEntriesOut * sizeof( void * ) ); for( int i = 0; i != iMaxEntriesOut; ++i ) { if( pCallStackOut[i] == NULL ) return i; } return iMaxEntriesOut; } #endif //----------------------------------------------------------------------------- // Updates stats //----------------------------------------------------------------------------- void CDbgMemAlloc::RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { HEAP_LOCK(); RegisterAllocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); RegisterAllocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); } void CDbgMemAlloc::RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { HEAP_LOCK(); RegisterDeallocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); RegisterDeallocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); } #if defined( USE_STACK_TRACES ) void CDbgMemAlloc::RegisterAllocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { HEAP_LOCK(); RegisterAllocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); CCallStackStatsGatherer_StructAccessor_AutoLock entryAccessor = m_CallStackStats.GetEntry( nStatIndex ); RegisterAllocation( *entryAccessor.GetStruct(), nLogicalSize, nActualSize, nTime ); } void CDbgMemAlloc::RegisterDeallocation( unsigned int nStatIndex, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { HEAP_LOCK(); RegisterDeallocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); CCallStackStatsGatherer_StructAccessor_AutoLock entryAccessor = m_CallStackStats.GetEntry( nStatIndex ); RegisterDeallocation( *entryAccessor.GetStruct(), nLogicalSize, nActualSize, nTime ); } #endif void CDbgMemAlloc::RegisterAllocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { ++info.m_nCurrentCount; ++info.m_nTotalCount; if (info.m_nCurrentCount > info.m_nPeakCount) { info.m_nPeakCount = info.m_nCurrentCount; } info.m_nCurrentSize += nLogicalSize; info.m_nTotalSize += nLogicalSize; if (info.m_nCurrentSize > info.m_nPeakSize) { info.m_nPeakSize = info.m_nCurrentSize; } if ( nLogicalSize > g_TargetCountRangeMin && nLogicalSize <= g_TargetCountRangeMax ) { info.m_nSumTargetRange++; info.m_nCurTargetRange++; if ( info.m_nCurTargetRange > info.m_nMaxTargetRange ) { info.m_nMaxTargetRange = info.m_nCurTargetRange; } } for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { if (nLogicalSize <= s_pCountSizes[i]) { ++info.m_pCount[i]; break; } } Assert( info.m_nPeakCount >= info.m_nCurrentCount ); Assert( info.m_nPeakSize >= info.m_nCurrentSize ); info.m_nOverheadSize += (nActualSize - nLogicalSize); if (info.m_nOverheadSize > info.m_nPeakOverheadSize) { info.m_nPeakOverheadSize = info.m_nOverheadSize; } info.m_nTime += nTime; } void CDbgMemAlloc::RegisterDeallocation( MemInfo_t &info, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) { --info.m_nCurrentCount; info.m_nCurrentSize -= nLogicalSize; for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { if (nLogicalSize <= s_pCountSizes[i]) { --info.m_pCount[i]; break; } } if ( nLogicalSize > g_TargetCountRangeMin && nLogicalSize <= g_TargetCountRangeMax ) { info.m_nCurTargetRange--; } Assert( info.m_nPeakCount >= info.m_nCurrentCount ); Assert( info.m_nPeakSize >= info.m_nCurrentSize ); Assert( info.m_nCurrentCount >= 0 ); Assert( info.m_nCurrentSize >= 0 ); info.m_nOverheadSize -= (nActualSize - nLogicalSize); info.m_nTime += nTime; } //----------------------------------------------------------------------------- // Gets the allocation file name //----------------------------------------------------------------------------- const char *CDbgMemAlloc::GetAllocatonFileName( void *pMem ) { if (!pMem) return ""; CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); if ( pHeader->m_pFileName ) return pHeader->m_pFileName; else return g_pszUnknown; } //----------------------------------------------------------------------------- // Gets the allocation file name //----------------------------------------------------------------------------- int CDbgMemAlloc::GetAllocatonLineNumber( void *pMem ) { if ( !pMem ) return 0; CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); return pHeader->m_nLineNumber; } //----------------------------------------------------------------------------- // Debug versions of the main allocation methods //----------------------------------------------------------------------------- void *CDbgMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) { HEAP_LOCK(); #if defined( USE_STACK_TRACES ) unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( CCallStackStorage( m_CallStackStats.StackFunction, 1 ) ); #endif if ( !m_bInitialized ) { void *pRetval = InternalMalloc( nSize, pFileName, nLine ); #if defined( USE_STACK_TRACES ) if( pRetval ) { GetAllocationStatIndex_Internal( pRetval ) = iStatEntryIndex; } #endif return pRetval; } if ( pFileName != g_pszUnknown ) pFileName = FindOrCreateFilename( pFileName ); GetActualDbgInfo( pFileName, nLine ); /* if ( strcmp( pFileName, "class CUtlVector >" ) == 0) { GetActualDbgInfo( pFileName, nLine ); } */ m_Timer.Start(); void *pMem = InternalMalloc( nSize, pFileName, nLine ); m_Timer.End(); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif ApplyMemoryInitializations( pMem, nSize ); #if defined( USE_STACK_TRACES ) RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #else RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #endif if ( !pMem ) { SetCRTAllocFailed( nSize ); } return pMem; } #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS void *CDbgMemAlloc::AllocAlign( size_t nSize, size_t align, const char *pFileName, int nLine ) { HEAP_LOCK(); #if defined( USE_STACK_TRACES ) unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndexForCurrentCallStack( 1 ); #endif if ( !m_bInitialized ) { void *pRetval = InternalMalloc( nSize, pFileName, nLine ); #if defined( USE_STACK_TRACES ) if( pRetval ) { GetAllocationStatIndex_Internal( pRetval ) = iStatEntryIndex; } #endif return pRetval; } if ( pFileName != g_pszUnknown ) pFileName = FindOrCreateFilename( pFileName ); GetActualDbgInfo( pFileName, nLine ); /* if ( strcmp( pFileName, "class CUtlVector >" ) == 0) { GetActualDbgInfo( pFileName, nLine ); } */ m_Timer.Start(); void *pMem = InternalMallocAligned( nSize, align, pFileName, nLine ); m_Timer.End(); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif ApplyMemoryInitializations( pMem, nSize ); #if defined( USE_STACK_TRACES ) RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #else RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #endif if ( !pMem ) { SetCRTAllocFailed( nSize ); } return pMem; } #endif void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) { HEAP_LOCK(); pFileName = FindOrCreateFilename( pFileName ); #if defined( USE_STACK_TRACES ) unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndex( CCallStackStorage( m_CallStackStats.StackFunction, 1 ) ); #endif if ( !m_bInitialized ) { pMem = InternalRealloc( pMem, nSize, pFileName, nLine ); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif return pMem; } if ( pMem != 0 ) { #if defined( USE_STACK_TRACES ) RegisterDeallocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); #else RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); #endif } GetActualDbgInfo( pFileName, nLine ); m_Timer.Start(); pMem = InternalRealloc( pMem, nSize, pFileName, nLine ); m_Timer.End(); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif #if defined( USE_STACK_TRACES ) RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #else RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #endif if ( !pMem ) { SetCRTAllocFailed( nSize ); } return pMem; } #ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS void *CDbgMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align ) { /* // NOTE: Uncomment this to find unknown allocations const char *pFileName = g_pszUnknown; int nLine; GetActualDbgInfo( pFileName, nLine ); if (pFileName == g_pszUnknown) { int x = 3; } */ char szModule[MAX_PATH]; if ( GetCallerModule( szModule, MAX_PATH ) ) { return ReallocAlign( pMem, nSize, align, szModule, 0 ); } else { return ReallocAlign( pMem, nSize, align, g_pszUnknown, 0 ); } // return malloc( nSize ); } void *CDbgMemAlloc::ReallocAlign( void *pMem, size_t nSize, size_t align, const char *pFileName, int nLine ) { HEAP_LOCK(); pFileName = FindOrCreateFilename( pFileName ); #if defined( USE_STACK_TRACES ) unsigned int iStatEntryIndex = m_CallStackStats.GetEntryIndexForCurrentCallStack( 1 ); #endif if ( !m_bInitialized ) { pMem = InternalReallocAligned( pMem, nSize, align, pFileName, nLine ); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif return pMem; } if ( pMem != 0 ) { #if defined( USE_STACK_TRACES ) RegisterDeallocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); #else RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), 0 ); #endif } GetActualDbgInfo( pFileName, nLine ); m_Timer.Start(); pMem = InternalReallocAligned( pMem, nSize, align, pFileName, nLine ); m_Timer.End(); #if defined( USE_STACK_TRACES ) if( pMem ) { GetAllocationStatIndex_Internal( pMem ) = iStatEntryIndex; } #endif #if defined( USE_STACK_TRACES ) RegisterAllocation( GetAllocationStatIndex_Internal( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #else RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); #endif if ( !pMem ) { SetCRTAllocFailed( nSize ); } return pMem; } #endif void CDbgMemAlloc::Free( void *pMem, const char * /*pFileName*/, int nLine ) { if ( !pMem ) return; HEAP_LOCK(); if ( !m_bInitialized ) { InternalFree( pMem ); return; } size_t nOldLogicalSize = InternalLogicalSize( pMem ); size_t nOldSize = InternalMSize( pMem ); #if defined( USE_STACK_TRACES ) unsigned int oldStatIndex = GetAllocationStatIndex_Internal( pMem ); #else const char *pOldFileName = GetAllocatonFileName( pMem ); int oldLine = GetAllocatonLineNumber( pMem ); #endif m_Timer.Start(); InternalFree( pMem ); m_Timer.End(); #if defined( USE_STACK_TRACES ) RegisterDeallocation( oldStatIndex, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() ); #else RegisterDeallocation( pOldFileName, oldLine, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() ); #endif } void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) { return NULL; } //----------------------------------------------------------------------------- // Returns the size of a particular allocation (NOTE: may be larger than the size requested!) //----------------------------------------------------------------------------- size_t CDbgMemAlloc::GetSize( void *pMem ) { HEAP_LOCK(); if ( !pMem ) return m_GlobalInfo.m_nCurrentSize; return InternalMSize( pMem ); } //----------------------------------------------------------------------------- // FIXME: Remove when we make our own heap! Crt stuff we're currently using //----------------------------------------------------------------------------- int32 CDbgMemAlloc::CrtSetBreakAlloc( int32 lNewBreakAlloc ) { #ifdef POSIX return 0; #else return _CrtSetBreakAlloc( lNewBreakAlloc ); #endif } int CDbgMemAlloc::CrtSetReportMode( int nReportType, int nReportMode ) { #ifdef POSIX return 0; #else return _CrtSetReportMode( nReportType, nReportMode ); #endif } int CDbgMemAlloc::CrtIsValidHeapPointer( const void *pMem ) { #ifdef POSIX return 0; #else return _CrtIsValidHeapPointer( pMem ); #endif } int CDbgMemAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) { #ifdef POSIX return 0; #else return _CrtIsValidPointer( pMem, size, access ); #endif } #define DBGMEM_CHECKMEMORY 1 int CDbgMemAlloc::CrtCheckMemory( void ) { #if !defined( DBGMEM_CHECKMEMORY ) || defined( POSIX ) return 1; #elif defined( _WIN32 ) if ( !_CrtCheckMemory()) { Msg( "Memory check failed!\n" ); return 0; } return 1; #else return 1; #endif } int CDbgMemAlloc::CrtSetDbgFlag( int nNewFlag ) { #ifdef POSIX return 0; #else return _CrtSetDbgFlag( nNewFlag ); #endif } void CDbgMemAlloc::CrtMemCheckpoint( _CrtMemState *pState ) { #ifndef POSIX _CrtMemCheckpoint( pState ); #endif } // FIXME: Remove when we have our own allocator void* CDbgMemAlloc::CrtSetReportFile( int nRptType, void* hFile ) { #ifdef POSIX return 0; #else return (void*)_CrtSetReportFile( nRptType, (_HFILE)hFile ); #endif } void* CDbgMemAlloc::CrtSetReportHook( void* pfnNewHook ) { #ifdef POSIX return 0; #else return (void*)_CrtSetReportHook( (_CRT_REPORT_HOOK)pfnNewHook ); #endif } int CDbgMemAlloc::CrtDbgReport( int nRptType, const char * szFile, int nLine, const char * szModule, const char * pMsg ) { #ifdef POSIX return 0; #else return _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); #endif } int CDbgMemAlloc::heapchk() { #ifdef POSIX return 0; #else if ( CrtCheckMemory() ) return _HEAPOK; else return _HEAPBADPTR; #endif } void CDbgMemAlloc::DumpBlockStats( void *p ) { DbgMemHeader_t *pBlock = (DbgMemHeader_t *)p - 1; if ( !CrtIsValidHeapPointer( pBlock ) ) { Msg( "0x%p is not valid heap pointer\n", p ); return; } const char *pFileName = GetAllocatonFileName( p ); int line = GetAllocatonLineNumber( p ); Msg( "0x%p allocated by %s line %d, %d bytes\n", p, pFileName, line, GetSize( p ) ); } //----------------------------------------------------------------------------- // Stat output //----------------------------------------------------------------------------- void CDbgMemAlloc::DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ) { m_OutputFunc("%s, line %i\t%.1f\t%.1f\t%.1f\t%.1f\t%.1f\t%d\t%d\t%d\t%d\t%d\t%d\t%d", pAllocationName, line, info.m_nCurrentSize / 1024.0f, info.m_nPeakSize / 1024.0f, info.m_nTotalSize / 1024.0f, info.m_nOverheadSize / 1024.0f, info.m_nPeakOverheadSize / 1024.0f, (int)(info.m_nTime / 1000), info.m_nCurrentCount, info.m_nPeakCount, info.m_nTotalCount, info.m_nSumTargetRange, info.m_nCurTargetRange, info.m_nMaxTargetRange ); for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { m_OutputFunc( "\t%d", info.m_pCount[i] ); } m_OutputFunc("\n"); } //----------------------------------------------------------------------------- // Stat output //----------------------------------------------------------------------------- size_t CDbgMemAlloc::ComputeMemoryUsedBy( char const *pchSubStr) { size_t total = 0; StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); while(iter != m_StatMap_FileLine.end()) { if(!pchSubStr || strstr(iter->first.m_pFileName,pchSubStr)) { total += iter->second.m_nCurrentSize; } iter++; } return total; } void CDbgMemAlloc::DumpFileStats() { StatMapIter_FileLine_t iter = m_StatMap_FileLine.begin(); while(iter != m_StatMap_FileLine.end()) { DumpMemInfo( iter->first.m_pFileName, iter->first.m_nLine, iter->second ); iter++; } } void CDbgMemAlloc::DumpStatsFileBase( char const *pchFileBase, DumpStatsFormat_t nFormat ) { char szFileName[MAX_PATH]; static int s_FileCount = 0; if (m_OutputFunc == DefaultHeapReportFunc) { char *pPath = ""; #ifdef _X360 pPath = "D:\\"; #elif defined( _PS3 ) pPath = "/app_home/"; #endif // [mhansen] Give out a unique filename for mem dumps #if defined( _MEMTEST ) char szXboxName[32]; strcpy( szXboxName, "memdump" ); #if defined( _PS3 ) _snprintf( szFileName, sizeof( szFileName ), "%s%s_%d.txt", pPath, s_szStatsMapName, s_FileCount ); #else DWORD numChars = sizeof( szXboxName ); DmGetXboxName( szXboxName, &numChars ); char *pXboxName = strstr( szXboxName, "_360" ); if ( pXboxName ) { *pXboxName = '\0'; } SYSTEMTIME systemTime; GetLocalTime( &systemTime ); _snprintf( szFileName, sizeof( szFileName ), "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.txt", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount ); #endif #else // _MEMTEST #if defined( _WIN32 ) && !defined( _X360 ) bool fileExists = true; while (fileExists) { _snprintf( szFileName, sizeof( szFileName ), "%s%s%d.txt", pPath, pchFileBase, s_FileCount ); szFileName[ ARRAYSIZE(szFileName) - 1 ] = 0; if (_access_s(szFileName, 0) == ENOENT) { fileExists = false; } else { ++s_FileCount; } } #else // _WIN32 _snprintf( szFileName, sizeof( szFileName ), "%s%s%d.txt", pPath, pchFileBase, s_FileCount ); #endif // _WIN32 #endif // _MEMTEST szFileName[ ARRAYSIZE(szFileName) - 1 ] = 0; ++s_FileCount; s_DbgFile = fopen(szFileName, "wt"); if (!s_DbgFile) return; } { HEAP_LOCK(); m_OutputFunc("Allocation type\tCurrent Size(k)\tPeak Size(k)\tTotal Allocations(k)\tOverhead Size(k)\tPeak Overhead Size(k)\tTime(ms)\tCurrent Count\tPeak Count\tTotal Count\tTNum\tTCur\tTMax"); for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) { m_OutputFunc( "\t%s", s_pCountHeader[i] ); } m_OutputFunc("\n"); MemInfo_t totals = m_GlobalInfo; #ifdef _PS3 { // Add a line for system heap stats static malloc_managed_size mms; (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); MemInfo_t info; info.m_nCurrentSize = mms.current_inuse_size; info.m_nPeakSize = mms.max_system_size; info.m_nOverheadSize = mms.current_system_size - mms.current_inuse_size; DumpMemInfo( "||PS3 malloc_stats||", 0, info ); // Add a line for PRXs char prxFilename[256]; sys_prx_id_t prxIDs[256]; sys_prx_segment_info_t prxSegments[32]; sys_prx_get_module_list_t prxList = { sizeof( sys_prx_get_module_list_t ), ARRAYSIZE( prxIDs ), 0, prxIDs, NULL }; sys_prx_get_module_list( 0, &prxList ); Assert( prxList.count < ARRAYSIZE( prxIDs ) ); memset( &info, 0, sizeof( info ) ); for ( int i = 0; i < prxList.count; i++ ) { sys_prx_module_info_t prxInfo; prxInfo.size = sizeof( sys_prx_module_info_t ); prxInfo.filename = prxFilename; prxInfo.filename_size = sizeof( prxFilename ); prxInfo.segments = prxSegments; prxInfo.segments_num = ARRAYSIZE( prxSegments ); sys_prx_get_module_info( prxList.idlist[i], 0, &prxInfo ); Assert( prxInfo.segments_num < ARRAYSIZE( prxSegments ) ); for ( int j = 0; j < prxInfo.segments_num; j++ ) { info.m_nCurrentSize += prxInfo.segments[j].memsz; } } DumpMemInfo( "PS3 PRXs", 0, info ); // Add PRX sizes to our global tracked total: totals.m_nCurrentSize += info.m_nCurrentSize; } #endif // _PS3 // The total of all memory usage we know about: DumpMemInfo( "||Totals||", 0, totals ); if ( IsGameConsole() ) { // Add a line showing total system memory usage from the OS (if this is more than // "||Totals||", then there is unknown memory usage that we need to track down): size_t usedMemory, freeMemory; GlobalMemoryStatus( &usedMemory, &freeMemory ); MemInfo_t info; info.m_nCurrentSize = usedMemory; DumpMemInfo( "||Used Memory||", 0, info ); } #ifdef _MEMTEST { // Add lines for GPU allocations int nGPUMemSize, nGPUMemFree, nTextureSize, nRTSize, nVBSize, nIBSize, nUnknown; if ( 7 == sscanf( s_szStatsComment, "%d %d %d %d %d %d %d", &nGPUMemSize, &nGPUMemFree, &nTextureSize, &nRTSize, &nVBSize, &nIBSize, &nUnknown ) ) { int nTotalUsed = nTextureSize + nRTSize + nVBSize + nIBSize + nUnknown; int nOverhead = ( nGPUMemSize - nTotalUsed ) - nGPUMemFree; m_OutputFunc( "||PS3 RSX: total used||, line 0\t%.1f\n", nTotalUsed / 1024.0f ); m_OutputFunc( "PS3 RSX: textures, line 0\t%.1f\n", nTextureSize / 1024.0f ); m_OutputFunc( "PS3 RSX: render targets, line 0\t%.1f\n", nRTSize / 1024.0f ); m_OutputFunc( "PS3 RSX: vertex buffers, line 0\t%.1f\n", nVBSize / 1024.0f ); m_OutputFunc( "PS3 RSX: index buffers, line 0\t%.1f\n", nIBSize / 1024.0f ); m_OutputFunc( "PS3 RSX: unknown, line 0\t%.1f\n", nUnknown / 1024.0f ); m_OutputFunc( "PS3 RSX: overhead, line 0\t%.1f\n", nOverhead / 1024.0f ); } } #endif //m_OutputFunc("File/Line Based\n"); DumpFileStats(); } if (m_OutputFunc == DefaultHeapReportFunc) { fclose(s_DbgFile); #if defined( _X360 ) XBX_rMemDump( szFileName ); #endif } } void CDbgMemAlloc::GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) { if ( !pUsedMemory || !pFreeMemory ) return; #if defined ( _X360 ) // GlobalMemoryStatus tells us how much physical memory is free MEMORYSTATUS stat; ::GlobalMemoryStatus( &stat ); *pFreeMemory = stat.dwAvailPhys; // Used is total minus free (discount the 32MB system reservation) *pUsedMemory = ( stat.dwTotalPhys - 32*1024*1024 ) - *pFreeMemory; #elif defined( _PS3 ) // need to factor in how much empty space there is in the heap // (since it NEVER returns pages back to the OS after hitting a high-watermark) static malloc_managed_size mms; (g_pMemOverrideRawCrtFns->pfn_malloc_stats)( &mms ); int heapFree = mms.current_system_size - mms.current_inuse_size; Assert( heapFree >= 0 ); // sys_memory_get_user_memory_size tells us how much PPU memory is used/free static sys_memory_info stat; sys_memory_get_user_memory_size( &stat ); *pFreeMemory = stat.available_user_memory; *pFreeMemory += heapFree; *pUsedMemory = stat.total_user_memory - *pFreeMemory; // 213MB are available in retail mode, so adjust free mem to reflect that even if we're in devkit mode const size_t RETAIL_SIZE = 213*1024*1024; if ( stat.total_user_memory > RETAIL_SIZE ) *pFreeMemory -= stat.total_user_memory - RETAIL_SIZE; #else // no data *pFreeMemory = 0; *pUsedMemory = 0; #endif } #ifdef USE_STACK_TRACES void CDbgMemAlloc::DumpCallStackFlow( char const *pchFileBase ) { HEAP_LOCK(); char szFileName[MAX_PATH]; static int s_FileCount = 0; char *pPath = ""; if ( IsX360() ) { pPath = "D:\\"; } #if defined( _MEMTEST ) && defined( _WIN32 ) char szXboxName[32]; strcpy( szXboxName, "xbox" ); DWORD numChars = sizeof( szXboxName ); DmGetXboxName( szXboxName, &numChars ); char *pXboxName = strstr( szXboxName, "_360" ); if ( pXboxName ) { *pXboxName = '\0'; } SYSTEMTIME systemTime; GetLocalTime( &systemTime ); _snprintf( szFileName, sizeof( szFileName ), "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.csf", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount ); #else _snprintf( szFileName, sizeof( szFileName ), "%s%s%d.vcsf", pPath, pchFileBase, s_FileCount ); #endif ++s_FileCount; m_CallStackStats.DumpToFile( szFileName, false ); } #endif //----------------------------------------------------------------------------- // Stat output //----------------------------------------------------------------------------- void CDbgMemAlloc::DumpStats() { DumpStatsFileBase( "memstats" ); #ifdef USE_STACK_TRACES DumpCallStackFlow( "memflow" ); #endif } void CDbgMemAlloc::SetCRTAllocFailed( size_t nSize ) { m_sMemoryAllocFailed = nSize; DebuggerBreakIfDebugging(); char buffer[256]; _snprintf( buffer, sizeof( buffer ), "***** OUT OF MEMORY! attempted allocation size: %u ****\n", nSize ); buffer[ ARRAYSIZE(buffer) - 1] = 0; #if defined( _PS3 ) && defined( _DEBUG ) DebuggerBreak(); #endif // _PS3 #ifdef _X360 XBX_OutputDebugString( buffer ); if ( !Plat_IsInDebugSession() ) { XBX_CrashDump( true ); #if defined( _DEMO ) XLaunchNewImage( XLAUNCH_KEYWORD_DEFAULT_APP, 0 ); #else XLaunchNewImage( "default.xex", 0 ); #endif } #elif defined(_WIN32 ) OutputDebugString( buffer ); if ( !Plat_IsInDebugSession() ) { AssertFatalMsg( false, buffer ); abort(); } #else printf( "%s\n", buffer ); if ( !Plat_IsInDebugSession() ) { AssertFatalMsg( false, buffer ); exit( 0 ); } #endif } size_t CDbgMemAlloc::MemoryAllocFailed() { return m_sMemoryAllocFailed; } #ifdef LINUX // // Under linux we can ask GLIBC to override malloc for us // Base on code from Ryan, http://hg.icculus.org/icculus/mallocmonitor/file/29c4b0d049f7/monitor_client/malloc_hook_glibc.c // // static void *glibc_malloc_hook = NULL; static void *glibc_realloc_hook = NULL; static void *glibc_memalign_hook = NULL; static void *glibc_free_hook = NULL; /* convenience functions for setting the hooks... */ static inline void save_glibc_hooks(void); static inline void set_glibc_hooks(void); static inline void set_override_hooks(void); CThreadMutex g_HookMutex; /* * Our overriding hooks...they call through to the original C runtime * implementations and report to the monitoring daemon. */ static void *override_malloc_hook(size_t s, const void *caller) { void *retval; AUTO_LOCK( g_HookMutex ); set_glibc_hooks(); /* put glibc back in control. */ retval = InternalMalloc( s, NULL, 0 ); save_glibc_hooks(); /* update in case glibc changed them. */ set_override_hooks(); /* only restore hooks if daemon is listening */ return(retval); } /* override_malloc_hook */ static void *override_realloc_hook(void *ptr, size_t s, const void *caller) { void *retval; AUTO_LOCK( g_HookMutex ); set_glibc_hooks(); /* put glibc back in control. */ retval = InternalRealloc(ptr, s, NULL, 0); /* call glibc version. */ save_glibc_hooks(); /* update in case glibc changed them. */ set_override_hooks(); /* only restore hooks if daemon is listening */ return(retval); } /* override_realloc_hook */ static void *override_memalign_hook(size_t a, size_t s, const void *caller) { void *retval; AUTO_LOCK( g_HookMutex ); set_glibc_hooks(); /* put glibc back in control. */ retval = memalign(a, s); /* call glibc version. */ save_glibc_hooks(); /* update in case glibc changed them. */ set_override_hooks(); /* only restore hooks if daemon is listening */ return(retval); } /* override_memalign_hook */ static void override_free_hook(void *ptr, const void *caller) { AUTO_LOCK( g_HookMutex ); set_glibc_hooks(); /* put glibc back in control. */ InternalFree(ptr); /* call glibc version. */ save_glibc_hooks(); /* update in case glibc changed them. */ set_override_hooks(); /* only restore hooks if daemon is listening */ } /* override_free_hook */ /* * Convenience functions for swapping the hooks around... */ /* * Save a copy of the original allocation hooks, so we can call into them * from our overriding functions. It's possible that glibc might change * these hooks under various conditions (so the manual's examples seem * to suggest), so we update them whenever we finish calling into the * the originals. */ static inline void save_glibc_hooks(void) { glibc_malloc_hook = (void *)__malloc_hook; glibc_realloc_hook = (void *)__realloc_hook; glibc_memalign_hook = (void *)__memalign_hook; glibc_free_hook = (void *)__free_hook; } /* save_glibc_hooks */ /* * Restore the hooks to the glibc versions. This is needed since, say, * their realloc() might call malloc() or free() under the hood, etc, so * it's safer to let them have complete control over the subsystem, which * also makes our logging saner, too. */ static inline void set_glibc_hooks(void) { __malloc_hook = (void* (*)(size_t, const void*))glibc_malloc_hook; __realloc_hook = (void* (*)(void*, size_t, const void*))glibc_realloc_hook; __memalign_hook = (void* (*)(size_t, size_t, const void*))glibc_memalign_hook; __free_hook = (void (*)(void*, const void*))glibc_free_hook; } /* set_glibc_hooks */ /* * Put our hooks back in place. This should be done after the original * glibc version has been called and we've finished any logging (which * may call glibc functions, too). This sets us up for the next calls from * the application. */ static inline void set_override_hooks(void) { __malloc_hook = override_malloc_hook; __realloc_hook = override_realloc_hook; __memalign_hook = override_memalign_hook; __free_hook = override_free_hook; } /* set_override_hooks */ /* * The Hook Of All Hooks...how we get in there in the first place. */ /* * glibc will call this when the malloc subsystem is initializing, giving * us a chance to install hooks that override the functions. */ static void override_init_hook(void) { AUTO_LOCK( g_HookMutex ); /* install our hooks. Will connect to daemon on first malloc, etc. */ save_glibc_hooks(); set_override_hooks(); } /* override_init_hook */ /* * __malloc_initialize_hook is apparently a "weak variable", so you can * define and assign it here even though it's in glibc, too. This lets * us hook into malloc as soon as the runtime initializes, and before * main() is called. Basically, this whole trick depends on this. */ void (*__malloc_initialize_hook)(void) __attribute__((visibility("default")))= override_init_hook; #elif defined( OSX ) // // pointers to the osx versions of these functions static void *osx_malloc_hook = NULL; static void *osx_realloc_hook = NULL; static void *osx_free_hook = NULL; // convenience functions for setting the hooks... static inline void save_osx_hooks(void); static inline void set_osx_hooks(void); static inline void set_override_hooks(void); CThreadMutex g_HookMutex; // // Our overriding hooks...they call through to the original C runtime // implementations and report to the monitoring daemon. // static void *override_malloc_hook(struct _malloc_zone_t *zone, size_t s) { void *retval; set_osx_hooks(); retval = InternalMalloc( s, NULL, 0 ); set_override_hooks(); return(retval); } static void *override_realloc_hook(struct _malloc_zone_t *zone, void *ptr, size_t s) { void *retval; set_osx_hooks(); retval = InternalRealloc(ptr, s, NULL, 0); set_override_hooks(); return(retval); } static void override_free_hook(struct _malloc_zone_t *zone, void *ptr) { // sometime they pass in a null pointer from higher level calls, just ignore it if ( !ptr ) return; set_osx_hooks(); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( ptr ); if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) { InternalFree( ptr ); } set_override_hooks(); } /* These are func's we could optionally override right now on OSX but don't need to static size_t override_size_hook(struct _malloc_zone_t *zone, const void *ptr) { set_osx_hooks(); DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( (void *)ptr ); set_override_hooks(); if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) { return pInternalMem->nLogicalSize; } return 0; } static void *override_calloc_hook(struct _malloc_zone_t *zone, size_t num_items, size_t size ) { void *ans = override_malloc_hook( zone, num_items*size ); if ( !ans ) return 0; memset( ans, 0x0, num_items*size ); return ans; } static void *override_valloc_hook(struct _malloc_zone_t *zone, size_t size ) { return override_calloc_hook( zone, 1, size ); } static void override_destroy_hook(struct _malloc_zone_t *zone) { } */ // // Save a copy of the original allocation hooks, so we can call into them // from our overriding functions. It's possible that osx might change // these hooks under various conditions (so the manual's examples seem // to suggest), so we update them whenever we finish calling into the // the originals. // static inline void save_osx_hooks(void) { malloc_zone_t *malloc_zone = malloc_default_zone(); osx_malloc_hook = (void *)malloc_zone->malloc; osx_realloc_hook = (void *)malloc_zone->realloc; osx_free_hook = (void *)malloc_zone->free; // These are func's we could optionally override right now on OSX but don't need to // osx_size_hook = (void *)malloc_zone->size; // osx_calloc_hook = (void *)malloc_zone->calloc; // osx_valloc_hook = (void *)malloc_zone->valloc; // osx_destroy_hook = (void *)malloc_zone->destroy; } // // Restore the hooks to the osx versions. This is needed since, say, // their realloc() might call malloc() or free() under the hood, etc, so // it's safer to let them have complete control over the subsystem, which // also makes our logging saner, too. // static inline void set_osx_hooks(void) { malloc_zone_t *malloc_zone = malloc_default_zone(); malloc_zone->malloc = (void* (*)(_malloc_zone_t*, size_t))osx_malloc_hook; malloc_zone->realloc = (void* (*)(_malloc_zone_t*, void*, size_t))osx_realloc_hook; malloc_zone->free = (void (*)(_malloc_zone_t*, void*))osx_free_hook; // These are func's we could optionally override right now on OSX but don't need to //malloc_zone->size = (size_t (*)(_malloc_zone_t*, const void *))osx_size_hook; //malloc_zone->calloc = (void* (*)(_malloc_zone_t*, size_t, size_t))osx_calloc_hook; //malloc_zone->valloc = (void* (*)(_malloc_zone_t*, size_t))osx_valloc_hook; //malloc_zone->destroy = (void (*)(_malloc_zone_t*))osx_destroy_hook; } /* * Put our hooks back in place. This should be done after the original * osx version has been called and we've finished any logging (which * may call osx functions, too). This sets us up for the next calls from * the application. */ static inline void set_override_hooks(void) { malloc_zone_t *malloc_zone = malloc_default_zone(); malloc_zone->malloc = override_malloc_hook; malloc_zone->realloc = override_realloc_hook; malloc_zone->free = override_free_hook; // These are func's we could optionally override right now on OSX but don't need to //malloc_zone->size = override_size_hook; //malloc_zone->calloc = override_calloc_hook; // malloc_zone->valloc = override_valloc_hook; //malloc_zone->destroy = override_destroy_hook; } // // The Hook Of All Hooks...how we get in there in the first place. // // osx will call this when the malloc subsystem is initializing, giving // us a chance to install hooks that override the functions. // void __attribute__ ((constructor)) mem_init(void) { AUTO_LOCK( g_HookMutex ); save_osx_hooks(); set_override_hooks(); } void *operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) { set_osx_hooks(); void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); set_override_hooks(); return pMem; } void *operator new[] ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) { set_osx_hooks(); void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); set_override_hooks(); return pMem; } #endif // OSX int GetAllocationCallStack( void *mem, void **pCallStackOut, int iMaxEntriesOut ) { #if defined( USE_MEM_DEBUG ) && (defined( USE_STACK_TRACES )) return s_DbgMemAlloc.GetCallStackForIndex( GetAllocationStatIndex_Internal( mem ), pCallStackOut, iMaxEntriesOut ); #else return 0; #endif } #endif // MEM_IMPL_TYPE_DBG #endif // !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE)