source-engine/tier0/memdbg.cpp
2022-07-28 19:27:13 +03:00

1968 lines
53 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Memory allocation!
//
// $NoKeywords: $
//=============================================================================//
#include "pch_tier0.h"
#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE)
#ifdef OSX
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#include <string.h>
#include "tier0/dbg.h"
#include "tier0/memalloc.h"
#include "mem_helpers.h"
#ifdef _WIN32
#include <crtdbg.h>
#endif
#ifdef OSX
#include <malloc/malloc.h>
#include <mach/mach.h>
#include <stdlib.h>
#endif
#include <map>
#include <set>
#include <limits.h>
#include "tier0/threadtools.h"
#ifdef _X360
#include "xbox/xbox_console.h"
#endif
#if ( !defined(_DEBUG) && defined(USE_MEM_DEBUG) )
#pragma message ("USE_MEM_DEBUG is enabled in a release build. Don't check this in!")
#endif
#if (defined(_DEBUG) || defined(USE_MEM_DEBUG))
#if defined(_WIN32) && ( !defined(_X360) && !defined(_WIN64) )
// #define USE_STACK_WALK
// or:
// #define USE_STACK_WALK_DETAILED
#endif
//-----------------------------------------------------------------------------
#ifndef _X360
#define DebugAlloc malloc
#define DebugFree free
#else
#define DebugAlloc DmAllocatePool
#define DebugFree DmFreePool
#endif
#ifdef WIN32
int g_DefaultHeapFlags = _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF );
#endif
#if defined( _MEMTEST )
static char s_szStatsMapName[32];
static char s_szStatsComment[256];
#endif
//-----------------------------------------------------------------------------
#if defined( USE_STACK_WALK ) || defined( USE_STACK_WALK_DETAILED )
#include <dbghelp.h>
#pragma comment(lib, "Dbghelp.lib" )
#pragma auto_inline(off)
__declspec(naked) DWORD GetEIP()
{
__asm
{
mov eax, [ebp + 4]
ret
}
}
int WalkStack( void **ppAddresses, int nMaxAddresses, int nSkip = 0 )
{
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
DWORD valEsp, valEbp;
__asm
{
mov [valEsp], esp;
mov [valEbp], ebp
}
frame.AddrPC.Offset = GetEIP();
frame.AddrStack.Offset = valEsp;
frame.AddrFrame.Offset = valEbp;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
// Walk the stack.
int nWalked = 0;
nSkip++;
while ( nMaxAddresses - nWalked > 0 )
{
if ( !StackWalk64(IMAGE_FILE_MACHINE_I386, hProcess, hThread, &frame, NULL, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL ) )
{
break;
}
if ( nSkip == 0 )
{
if (frame.AddrFrame.Offset == 0)
{
// End of stack.
break;
}
*ppAddresses++ = (void *)frame.AddrPC.Offset;
nWalked++;
if (frame.AddrPC.Offset == frame.AddrReturn.Offset)
{
// Catching a stack loop
break;
}
}
else
{
nSkip--;
}
}
if ( nMaxAddresses )
{
memset( ppAddresses, 0, ( nMaxAddresses - nWalked ) * sizeof(*ppAddresses) );
}
return nWalked;
}
bool GetModuleFromAddress( void *address, char *pResult )
{
IMAGEHLP_MODULE moduleInfo;
moduleInfo.SizeOfStruct = sizeof(moduleInfo);
if ( SymGetModuleInfo( GetCurrentProcess(), (DWORD)address, &moduleInfo ) )
{
strcpy( pResult, moduleInfo.ModuleName );
return true;
}
return false;
}
bool GetCallerModule( char *pDest )
{
static bool bInit;
if ( !bInit )
{
PSTR psUserSearchPath = NULL;
psUserSearchPath = "u:\\data\\game\\bin\\;u:\\data\\game\\episodic\\bin\\;u:\\data\\game\\hl2\\bin\\;\\\\perforce\\symbols";
SymInitialize( GetCurrentProcess(), psUserSearchPath, true );
bInit = true;
}
void *pCaller;
WalkStack( &pCaller, 1, 2 );
return ( pCaller != 0 && GetModuleFromAddress( pCaller, pDest ) );
}
#if defined( USE_STACK_WALK_DETAILED )
//
// 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 **ppAddresses, int nMaxAddresses )
{
static char s_chStackDescription[ 32 * 1024 ];
static char s_chSymbolBuffer[ sizeof( IMAGEHLP_SYMBOL64 ) + 1024 ];
IMAGEHLP_SYMBOL64 &hlpSymbol = * ( IMAGEHLP_SYMBOL64 * ) s_chSymbolBuffer;
hlpSymbol.SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64 );
hlpSymbol.MaxNameLength = 1024;
DWORD64 hlpSymbolOffset = 0;
IMAGEHLP_LINE64 hlpLine;
hlpLine.SizeOfStruct = sizeof( IMAGEHLP_LINE64 );
DWORD hlpLineOffset = 0;
s_chStackDescription[ 0 ] = 0;
char *pchBuffer = s_chStackDescription;
for ( int k = 0; k < nMaxAddresses; ++ k )
{
if ( !ppAddresses[k] )
break;
pchBuffer += strlen( pchBuffer );
if ( SymGetLineFromAddr64( GetCurrentProcess(), ( DWORD64 ) ppAddresses[k], &hlpLineOffset, &hlpLine ) )
{
char const *pchFileName = hlpLine.FileName ? hlpLine.FileName + strlen( hlpLine.FileName ) : NULL;
for ( size_t numSlashesAllowed = 2; pchFileName > hlpLine.FileName; -- pchFileName )
{
if ( *pchFileName == '\\' )
{
if ( numSlashesAllowed -- )
continue;
else
break;
}
}
sprintf( pchBuffer, hlpLineOffset ? "%s:%d+0x%I32X" : "%s:%d", pchFileName, hlpLine.LineNumber, hlpLineOffset );
}
else if ( SymGetSymFromAddr64( GetCurrentProcess(), ( DWORD64 ) ppAddresses[k], &hlpSymbolOffset, &hlpSymbol ) )
{
sprintf( pchBuffer, ( hlpSymbolOffset > 0 && !( hlpSymbolOffset >> 63 ) ) ? "%s+0x%I64X" : "%s", hlpSymbol.Name, hlpSymbolOffset );
}
else
{
sprintf( pchBuffer, "#0x%08p", ppAddresses[k] );
}
pchBuffer += strlen( pchBuffer );
sprintf( pchBuffer, "<--" );
}
*pchBuffer = 0;
return s_chStackDescription;
}
#endif // #if defined( USE_STACK_WALK_DETAILED )
#else
inline int WalkStack( void **ppAddresses, int nMaxAddresses, int nSkip = 0 )
{
memset( ppAddresses, 0, nMaxAddresses * sizeof(*ppAddresses) );
return 0;
}
#define GetModuleFromAddress( address, pResult ) ( ( *pResult = 0 ), 0)
#define GetCallerModule( pDest ) 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 DbgMemHeader_t
#if !defined( _DEBUG ) || defined( POSIX )
: CrtDbgMemHeader_t
#endif
{
size_t nLogicalSize;
byte reserved[12]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on
};
//-----------------------------------------------------------------------------
#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
#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 )
{
#ifdef OSX
void *pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) );
if (!pAllocedMem)
{
return NULL;
}
DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem );
pInternalMem->m_pFileName = pFileName;
pInternalMem->m_nLineNumber = nLine;
pInternalMem->nLogicalSize = nSize;
*((int*)pInternalMem->m_Reserved) = 0xf00df00d;
return pAllocedMem;
#else // LINUX || WIN32
DbgMemHeader_t *pInternalMem;
#if defined( POSIX ) || !defined( _DEBUG )
pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) );
if (!pInternalMem)
{
return NULL;
}
pInternalMem->m_pFileName = pFileName;
pInternalMem->m_nLineNumber = nLine;
*((int*)pInternalMem->m_Reserved) = 0xf00df00d;
#else
pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine );
#endif // defined( POSIX ) || !defined( _DEBUG )
pInternalMem->nLogicalSize = nSize;
return pInternalMem + 1;
#endif // LINUX || WIN32
}
inline void *InternalRealloc( void *pMem, size_t nNewSize, const char *pFileName, int nLine )
{
if ( !pMem )
return InternalMalloc( nNewSize, pFileName, nLine );
#ifdef OSX
void *pNewAllocedMem = NULL;
pNewAllocedMem = (void *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) );
DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem );
pInternalMem->m_pFileName = pFileName;
pInternalMem->m_nLineNumber = nLine;
pInternalMem->nLogicalSize = static_cast<unsigned int>( nNewSize );
*((int*)pInternalMem->m_Reserved) = 0xf00df00d;
return pNewAllocedMem;
#else // LINUX || WIN32
DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1;
#if defined( POSIX ) || !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 // LINUX || WIN32
}
inline void InternalFree( void *pMem )
{
if ( !pMem )
return;
DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1;
#if !defined( _DEBUG ) || defined( POSIX )
#ifdef OSX
malloc_zone_free( malloc_default_zone(), pMem );
#elif LINUX
free( pInternalMem );
#else
free( pInternalMem );
#endif
#else
_free_dbg( pInternalMem, _NORMAL_BLOCK );
#endif
}
inline size_t InternalMSize( void *pMem )
{
//$ TODO. For Linux, we could use 'int size = malloc_usable_size( pMem )'...
#if 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 T>
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 <class U> CNoRecurseAllocator(const CNoRecurseAllocator<U>&) {}
~CNoRecurseAllocator(){}
// rebind allocator to type U
template <class U > struct rebind { typedef CNoRecurseAllocator<U> 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 <class T1, class T2>
bool operator==(const CNoRecurseAllocator<T1>&, const CNoRecurseAllocator<T2>&)
{
return true;
}
template <class T1, class T2>
bool operator!=(const CNoRecurseAllocator<T1>&, const CNoRecurseAllocator<T2>&)
{
return false;
}
class CStringLess
{
public:
bool operator()(const char *pszLeft, const char *pszRight ) const
{
return ( 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 );
// 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 );
// Returns size of a particular allocation
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 long CrtSetBreakAlloc( long 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 MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) { return NULL; } // debug heap doesn't attempt retries
#if defined( _MEMTEST )
void SetStatsExtraInfo( const char *pMapName, const char *pComment )
{
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_1024,
BYTE_COUNT_GREATER,
NUM_BYTE_COUNT_BUCKETS
};
void Shutdown();
private:
struct MemInfo_t
{
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
size_t m_nCurrentCount;
size_t m_nPeakCount;
size_t m_nTotalCount;
// Count in terms of # of allocations of a particular size
size_t m_pCount[NUM_BYTE_COUNT_BUCKETS];
// Time spent allocating + deallocating (microseconds)
int64 m_nTime;
};
struct MemInfoKey_t
{
MemInfoKey_t( const char *pFileName, int line ) : m_pFileName(pFileName), m_nLine(line) {}
bool operator<( const MemInfoKey_t &key ) const
{
int iret = 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_t, MemInfo_t, std::less<MemInfoKey_t>, CNoRecurseAllocator<std::pair<const MemInfoKey_t, MemInfo_t> > > StatMap_t;
typedef StatMap_t::iterator StatMapIter_t;
typedef StatMap_t::value_type StatMapEntry_t;
typedef std::set<const char *, CStringLess, CNoRecurseAllocator<const char *> > Filenames_t;
// Heap reporting method
typedef void (*HeapReportFunc_t)( char const *pFormat, ... );
private:
// Returns the actual debug info
void GetActualDbgInfo( const char *&pFileName, int &nLine );
void Initialize();
// Finds the file in our map
MemInfo_t &FindOrCreateEntry( const char *pFileName, int line );
const char *FindOrCreateFilename( const char *pFileName );
// Updates stats
void RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime );
void RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime );
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();
void DumpStats();
void DumpStatsFileBase( char const *pchFileBase );
void DumpBlockStats( void *p );
virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory );
private:
StatMap_t *m_pStatMap;
MemInfo_t m_GlobalInfo;
CFastTimer m_Timer;
bool m_bInitialized;
Filenames_t *m_pFilenames;
HeapReportFunc_t m_OutputFunc;
static int 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";
//-----------------------------------------------------------------------------
const int DBG_INFO_STACK_DEPTH = 32;
struct DbgInfoStack_t
{
const char *m_pFileName;
int m_nLine;
};
CThreadLocalPtr<DbgInfoStack_t> g_DbgInfoStack CONSTRUCT_EARLY;
CThreadLocalInt<> g_nDbgInfoStackDepth CONSTRUCT_EARLY;
//-----------------------------------------------------------------------------
// Singleton...
//-----------------------------------------------------------------------------
static CDbgMemAlloc s_DbgMemAlloc CONSTRUCT_EARLY;
#ifndef TIER0_VALIDATE_HEAP
IMemAlloc *g_pMemAlloc = &s_DbgMemAlloc;
#else
IMemAlloc *g_pActualAlloc = &s_DbgMemAlloc;
#endif
//-----------------------------------------------------------------------------
CThreadMutex g_DbgMemMutex CONSTRUCT_EARLY;
#define HEAP_LOCK() AUTO_LOCK( g_DbgMemMutex )
//-----------------------------------------------------------------------------
// Byte count buckets
//-----------------------------------------------------------------------------
int CDbgMemAlloc::s_pCountSizes[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] =
{
16, 32, 128, 1024, INT_MAX
};
const char *CDbgMemAlloc::s_pCountHeader[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] =
{
"<=16 byte allocations",
"17-32 byte allocations",
"33-128 byte allocations",
"129-1024 byte allocations",
">1024 byte allocations"
};
//-----------------------------------------------------------------------------
// 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 )
{
// Make sure that we return 64-bit addresses in 64-bit builds.
ReserveBottomMemory();
m_OutputFunc = DefaultHeapReportFunc;
m_bInitialized = false;
if ( !IsDebug() && !IsX360() )
{
Plat_DebugString( "USE_MEM_DEBUG is enabled in a release build. Don't check this in!\n" );
}
}
CDbgMemAlloc::~CDbgMemAlloc()
{
Shutdown();
}
void CDbgMemAlloc::Initialize()
{
if ( !m_bInitialized )
{
m_pFilenames = new Filenames_t;
m_pStatMap= new StatMap_t;
m_bInitialized = true;
}
}
//-----------------------------------------------------------------------------
// Release versions
//-----------------------------------------------------------------------------
void CDbgMemAlloc::Shutdown()
{
if ( m_bInitialized )
{
Filenames_t::const_iterator iter = m_pFilenames->begin();
while ( iter != m_pFilenames->end() )
{
char *pFileName = (char*)(*iter);
free( pFileName );
iter++;
}
m_pFilenames->clear();
m_bInitialized = false;
delete m_pFilenames;
m_pFilenames = nullptr;
delete m_pStatMap;
m_pStatMap = nullptr;
}
m_bInitialized = false;
}
#ifdef WIN32
extern "C" BOOL APIENTRY MemDbgDllMain( HMODULE hDll, DWORD dwReason, PVOID pvReserved )
{
UNREFERENCED_PARAMETER( pvReserved );
// Check if we are shutting down
if ( dwReason == DLL_PROCESS_DETACH )
{
// CDbgMemAlloc is a global object and destructs after the _Lockit object in the CRT runtime,
// so we can't actually operate on the STL object in a normal destructor here as its support libraries have been turned off already
s_DbgMemAlloc.Shutdown();
}
return TRUE;
}
#endif
//-----------------------------------------------------------------------------
// 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 ) )
{
return Alloc( nSize, szModule, 0 );
}
else
{
return Alloc( nSize, g_pszUnknown, 0 );
}
// return malloc( nSize );
}
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 ) )
{
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 )
{
if ( g_DbgInfoStack == (int)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()
{
if ( g_DbgInfoStack == (int)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 )
{
if ( g_DbgInfoStack == (int)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 )
{
if ( g_DbgInfoStack == (int)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_WALK_DETAILED )
return;
#endif
if ( g_DbgInfoStack == (int)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 )
{
Initialize();
// 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_WALK_DETAILED )
{
// Walk the stack to determine what's causing the allocation
void *arrStackAddresses[ 10 ] = { 0 };
int numStackAddrRetrieved = WalkStack( arrStackAddresses, 10, 0 );
char *szStack = StackDescribe( arrStackAddresses, numStackAddrRetrieved );
if ( szStack && *szStack )
{
pFileName = szStack; // Use the stack description for the allocation
}
}
#endif // #if defined( USE_STACK_WALK_DETAILED )
char *pszFilenameCopy;
Filenames_t::const_iterator iter = m_pFilenames->find( pFileName );
if ( iter == m_pFilenames->end() )
{
int nLen = strlen(pFileName) + 1;
pszFilenameCopy = (char *)DebugAlloc( nLen );
memcpy( pszFilenameCopy, pFileName, nLen );
m_pFilenames->insert( pszFilenameCopy );
}
else
{
pszFilenameCopy = (char *)(*iter);
}
return pszFilenameCopy;
}
//-----------------------------------------------------------------------------
// Finds the file in our map
//-----------------------------------------------------------------------------
CDbgMemAlloc::MemInfo_t &CDbgMemAlloc::FindOrCreateEntry( const char *pFileName, int line )
{
Initialize();
// 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<StatMapIter_t, bool> retval;
if ( m_pStatMap )
{
retval = m_pStatMap->insert( StatMapEntry_t( MemInfoKey_t( pFileName, line ), MemInfo_t() ) );
}
return retval.first->second;
}
//-----------------------------------------------------------------------------
// 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 );
}
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;
}
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 )
{
// Check for decrementing these counters below zero. The checks
// must be done here because these unsigned counters will wrap-around and
// still be positive.
Assert( info.m_nCurrentCount != 0 );
// It is technically legal for code to request allocations of zero bytes, and there are a number of places in our code
// that do. So only assert that nLogicalSize >= 0. http://stackoverflow.com/questions/1087042/c-new-int0-will-it-allocate-memory
Assert( nLogicalSize >= 0 );
Assert( info.m_nCurrentSize >= nLogicalSize );
--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;
}
}
Assert( info.m_nPeakCount >= info.m_nCurrentCount );
Assert( info.m_nPeakSize >= info.m_nCurrentSize );
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 ( !m_bInitialized )
return InternalMalloc( nSize, pFileName, nLine );
if ( pFileName != g_pszUnknown )
pFileName = FindOrCreateFilename( pFileName );
GetActualDbgInfo( pFileName, nLine );
/*
if ( strcmp( pFileName, "class CUtlVector<int,class CUtlMemory<int> >" ) == 0)
{
GetActualDbgInfo( pFileName, nLine );
}
*/
m_Timer.Start();
void *pMem = InternalMalloc( nSize, pFileName, nLine );
m_Timer.End();
ApplyMemoryInitializations( pMem, nSize );
if ( pMem )
{
RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() );
}
else
{
SetCRTAllocFailed( nSize );
}
return pMem;
}
void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine )
{
HEAP_LOCK();
pFileName = FindOrCreateFilename( pFileName );
if ( !m_bInitialized )
return InternalRealloc( pMem, nSize, pFileName, nLine );
if ( pMem != 0 )
{
RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem), InternalMSize( pMem ), 0 );
}
GetActualDbgInfo( pFileName, nLine );
m_Timer.Start();
pMem = InternalRealloc( pMem, nSize, pFileName, nLine );
m_Timer.End();
if ( pMem )
{
RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() );
}
else
{
SetCRTAllocFailed( nSize );
}
return pMem;
}
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 );
const char *pOldFileName = GetAllocatonFileName( pMem );
int oldLine = GetAllocatonLineNumber( pMem );
m_Timer.Start();
InternalFree( pMem );
m_Timer.End();
RegisterDeallocation( pOldFileName, oldLine, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() );
}
void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine )
{
return NULL;
}
//-----------------------------------------------------------------------------
// Returns size of a particular allocation
//-----------------------------------------------------------------------------
size_t CDbgMemAlloc::GetSize( void *pMem )
{
HEAP_LOCK();
if ( !pMem )
return CalcHeapUsed();
return InternalMSize( pMem );
}
//-----------------------------------------------------------------------------
// FIXME: Remove when we make our own heap! Crt stuff we're currently using
//-----------------------------------------------------------------------------
long CDbgMemAlloc::CrtSetBreakAlloc( long 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;
#else
if ( !_CrtCheckMemory())
{
Msg( "Memory check failed!\n" );
return 0;
}
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
return _HEAPOK;
#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, %llu bytes\n", p, pFileName, line, (uint64)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",
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
);
for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i)
{
m_OutputFunc( "\t%d", info.m_pCount[i] );
}
m_OutputFunc("\n");
}
//-----------------------------------------------------------------------------
// Stat output
//-----------------------------------------------------------------------------
void CDbgMemAlloc::DumpFileStats()
{
if ( !m_pStatMap )
return;
StatMapIter_t iter = m_pStatMap->begin();
while ( iter != m_pStatMap->end() )
{
DumpMemInfo( iter->first.m_pFileName, iter->first.m_nLine, iter->second );
iter++;
}
}
void CDbgMemAlloc::DumpStatsFileBase( char const *pchFileBase )
{
HEAP_LOCK();
char szFileName[MAX_PATH];
static int s_FileCount = 0;
if (m_OutputFunc == DefaultHeapReportFunc)
{
char *pPath = "";
if ( IsX360() )
{
pPath = "D:\\";
}
#if defined( _MEMTEST ) && defined( _X360 )
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.txt", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount );
#else
_snprintf( szFileName, sizeof( szFileName ), "%s%s%d.txt", pPath, pchFileBase, s_FileCount );
#endif
szFileName[ ARRAYSIZE(szFileName) - 1 ] = 0;
++s_FileCount;
s_DbgFile = fopen(szFileName, "wt");
if (!s_DbgFile)
return;
}
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");
for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i)
{
m_OutputFunc( "\t%s", s_pCountHeader[i] );
}
m_OutputFunc("\n");
DumpMemInfo( "Totals", 0, m_GlobalInfo );
#ifdef WIN32
if ( IsX360() )
{
// add a line that has free memory
size_t usedMemory, freeMemory;
GlobalMemoryStatus( &usedMemory, &freeMemory );
MemInfo_t info;
// OS takes 32 MB, report our internal allocations only
info.m_nCurrentSize = usedMemory;
DumpMemInfo( "Used Memory", 0, info );
}
#endif
DumpFileStats();
if (m_OutputFunc == DefaultHeapReportFunc)
{
fclose(s_DbgFile);
#if defined( _X360 ) && !defined( _RETAIL )
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;
#else
// no data
*pFreeMemory = 0;
*pUsedMemory = 0;
#endif
}
//-----------------------------------------------------------------------------
// Stat output
//-----------------------------------------------------------------------------
void CDbgMemAlloc::DumpStats()
{
DumpStatsFileBase( "memstats" );
}
void CDbgMemAlloc::SetCRTAllocFailed( size_t nSize )
{
m_sMemoryAllocFailed = nSize;
MemAllocOOMError( nSize );
}
size_t CDbgMemAlloc::MemoryAllocFailed()
{
return m_sMemoryAllocFailed;
}
#if defined( LINUX ) && !defined( NO_HOOK_MALLOC )
//
// 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 __attribute__((constructor)) 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_HOOK_VOLATILE __malloc_initialize_hook)(void) __attribute__((visibility("default")))= override_init_hook;
#endif // LINUX
#if defined( OSX ) && !defined( NO_HOOK_MALLOC )
//
// 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)
{
}
*/
static inline void unprotect_malloc_zone( malloc_zone_t *malloc_zone )
{
// Starting in OS X 10.7 the default zone defaults to read-only, version 8.
// The version check may not be necessary, but we know it was RW before that.
if ( malloc_zone->version >= 8 )
{
#ifdef __aarch64__
// MoeMod : this is required for Apple Silicon
pthread_jit_write_protect_np(false);
#endif
vm_protect( mach_task_self(), (uintptr_t)malloc_zone, sizeof( malloc_zone_t ), 0, VM_PROT_READ | VM_PROT_WRITE );
}
}
static inline void protect_malloc_zone( malloc_zone_t *malloc_zone )
{
if ( malloc_zone->version >= 8 )
{
vm_protect( mach_task_self(), (uintptr_t)malloc_zone, sizeof( malloc_zone_t ), 0, VM_PROT_READ );
#ifdef __aarch64__
// MoeMod : this is required for Apple Silicon
pthread_jit_write_protect_np(true);
#endif
}
}
//
// 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();
unprotect_malloc_zone( malloc_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;
protect_malloc_zone( malloc_zone );
// 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();
AssertMsg( malloc_zone, "No malloc zone returned by malloc_default_zone" );
unprotect_malloc_zone( malloc_zone );
malloc_zone->malloc = override_malloc_hook;
malloc_zone->realloc = override_realloc_hook;
malloc_zone->free = override_free_hook;
protect_malloc_zone( malloc_zone );
// 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 // defined( OSX ) && !defined( NO_HOOK_MALLOC )
#endif // (defined(_DEBUG) || defined(USE_MEM_DEBUG))
#endif // !STEAM && !NO_MALLOC_OVERRIDE