diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /tier0/platform_posix.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'tier0/platform_posix.cpp')
| -rw-r--r-- | tier0/platform_posix.cpp | 1083 |
1 files changed, 1083 insertions, 0 deletions
diff --git a/tier0/platform_posix.cpp b/tier0/platform_posix.cpp new file mode 100644 index 0000000..ba2783a --- /dev/null +++ b/tier0/platform_posix.cpp @@ -0,0 +1,1083 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/vcrmode.h" +#include "tier0/memalloc.h" +#include "tier0/dbg.h" +#include <algorithm> +#include <vector> + +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> + +#ifdef OSX +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <stdbool.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/sysctl.h> +#endif +#ifdef LINUX +#include <time.h> +#include <fcntl.h> +#endif + +#include "tier0/memdbgon.h" +// Benchmark mode uses this heavy-handed method + +// *** WARNING ***. On Linux gettimeofday returns the system's best guess at +// actual wall clock time and this can go backwards. You need to use +// clock_gettime( CLOCK_MONOTONIC ... ) if this isn't what you want. + +// If you want to try using rdtsc for Plat_FloatTime(), enable USE_RDTSC_FOR_FLOATTIME: +// +// Make sure you know what you're doing. This was disabled due to the long startup time, and +// in our testing, even though constant_tsc was set, we couldn't rely on the +// max frequency result returned from CalculateCPUFreq() (ie /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq). +// +// #define USE_RDTSC_FOR_FLOATTIME + +extern VCRMode_t g_VCRMode; + +static bool g_bBenchmarkMode = false; +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; + +#ifdef USE_RDTSC_FOR_FLOATTIME + +static bool s_bTimeInitted; +static bool s_bUseRDTSC; +static uint64 s_nRDTSCBase; +static float s_flRDTSCToMicroSeconds; +static double s_flRDTSCScale; + +#endif // USE_RDTSC_FOR_FLOATTIME + +bool Plat_IsInBenchmarkMode() +{ + return g_bBenchmarkMode; +} + +void Plat_SetBenchmarkMode( bool bBenchmark ) +{ + g_bBenchmarkMode = bBenchmark; +} + + +#define N_ITERATIONS_OF_RDTSC_TEST_TO_RUN 5 // should be odd +#define TEST_RDTSC_FLOATTIME 0 + +size_t ApproximateProcessMemoryUsage( void ) +{ +/* +From http://man7.org/linux/man-pages/man5/proc.5.html: + /proc/[pid]/statm + Provides information about memory usage, measured in pages. + The columns are: + + size (1) total program size + (same as VmSize in /proc/[pid]/status) + resident (2) resident set size + (same as VmRSS in /proc/[pid]/status) + share (3) shared pages (i.e., backed by a file) + text (4) text (code) + lib (5) library (unused in Linux 2.6) + data (6) data + stack + dt (7) dirty pages (unused in Linux 2.6) +*/ + +// This returns the resident memory size (RES column in 'top') in bytes. + size_t nRet = 0; + FILE *pFile = fopen( "/proc/self/statm", "r" ); + if ( pFile ) + { + size_t nSize, nResident, nShare, nText, nLib_Unused, nDataPlusStack, nDt_Unused; + if ( fscanf( pFile, "%zu %zu %zu %zu %zu %zu %zu", &nSize, &nResident, &nShare, &nText, &nLib_Unused, &nDataPlusStack, &nDt_Unused ) >= 2 ) + { + nRet = 4096 * nResident; + } + fclose( pFile ); + } + return nRet; +} + +#ifdef USE_RDTSC_FOR_FLOATTIME + +static void InitTimeSystem( void ) +{ + s_bTimeInitted = true; + + // now, see if we can use rdtsc instead. If this is one of the chips with a separate constant clock for rdtsc, we can + FILE *pCpuInfo = fopen( "/proc/cpuinfo", "r" ); + if ( pCpuInfo ) + { + bool bAnyBadCores = false; + char lbuf[2048]; + while( fgets( lbuf, sizeof( lbuf ), pCpuInfo ) ) + { + if ( memcmp( lbuf, "flags", 4 ) == 0 ) + { + if ( ! strstr( lbuf, "constant_tsc" ) ) + { + bAnyBadCores = true; + break; + } + } + } + fclose( pCpuInfo ); + if ( ! bAnyBadCores ) + { + // this system appears to have the proper cpu setup to use rdtsc from reliable timing. Let's either read the cpu frequency from an + // environment variable, or time it ourselves + char const *pEnv = getenv( "RDTSC_FREQUENCY" ); + if ( pEnv ) + { + // the environment variable is allowed to hold either a benchmark result, or a string such as "disable" + if ( pEnv && ( ( pEnv[0] > '9' ) || ( pEnv[0] < '0' ) ) ) + return; // leave rdtsc disabled + // the variable holds the number of ticks per microsecond + s_flRDTSCToMicroSeconds = atof( pEnv ); + // sanity check + if ( s_flRDTSCToMicroSeconds > 1.0 ) + { + s_bUseRDTSC = true; + s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ); + s_nRDTSCBase = Plat_Rdtsc(); + return; + } + } + else + { + printf( "Running a benchmark to measure system clock frequency...\n" ); + // run n iterations and use the median + double flRDTSCToMicroSeconds[N_ITERATIONS_OF_RDTSC_TEST_TO_RUN]; + for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ) ; i++ ) + { + uint64 stime = Plat_Rdtsc(); + struct timeval stimeval; + gettimeofday( &stimeval, NULL ); + sleep( 1 ); + uint64 etime = Plat_Rdtsc() - stime; + struct timeval etimeval; + gettimeofday( &etimeval, NULL ); + // subtract timevals to get elapsed microseconds + struct timeval elapsedtimeval; + timersub( &etimeval, &stimeval, &elapsedtimeval ); + uint64 nUs = 1000000 * elapsedtimeval.tv_sec + elapsedtimeval.tv_usec; + flRDTSCToMicroSeconds[ i ] = ( etime / nUs ); + } + std::make_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 ); + std::sort_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 ); + s_flRDTSCToMicroSeconds = flRDTSCToMicroSeconds[ARRAYSIZE( flRDTSCToMicroSeconds ) / 2 ]; + s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ); + printf( "Finished RDTSC test. To prevent the startup delay from this benchmark, set the environment variable RDTSC_FREQUENCY to %f on this system." + " This value is dependent upon the CPU clock speed and architecture and should be determined separately for each server. The use of this mechanism" + " for timing can be disabled by setting RDTSC_FREQUENCY to 'disabled'.\n", + s_flRDTSCToMicroSeconds ); + s_nRDTSCBase = Plat_Rdtsc(); + s_bUseRDTSC = true; +#if TEST_RDTSC_FLOATTIME + printf( "RDTSC test results:\n" ); + for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ); i++ ) + printf(" [%d] = %f\n", i, flRDTSCToMicroSeconds[i] ); + printf( "scale factor = %f\n", s_flRDTSCScale ); + uint64 srdtsc_time = Plat_Rdtsc(); + for( int i = 0; i < 1000 * 1000 * 10; i++ ) + { + float p = Plat_FloatTime(); + } + printf( "slow = %lld\n", Plat_Rdtsc() - srdtsc_time ); + // now, run a benchmark to see how much this optimization buys us + srdtsc_time = Plat_Rdtsc(); + for( int i = 0; i < 1000 * 1000 * 10; i++ ) + { + float p = Plat_FloatTime(); + } + printf( "sfast = %lld\n", Plat_Rdtsc() - srdtsc_time ); +#endif + } + } + } +} + +static FORCEINLINE void TestTimeSystem( void ) +{ +#if TEST_RDTSC_FLOATTIME + // now, test that plat_float time actually works + for( int t = 0 ; t < 5; t++ ) + { + float flStartT = Plat_FloatTime(); + struct timeval stime; + gettimeofday( &stime, NULL ); + sleep( 5 ); + float flElapsedT = Plat_FloatTime() - flStartT; + struct timeval etime; + gettimeofday( &etime, NULL ); + struct timeval dtime; + timersub( &etime, &stime, &dtime ); + printf( " plat_float time says %f elapsed. gettimeofday says %f\n", + flElapsedT, dtime.tv_sec + dtime.tv_usec / 1000000.0 ); + } +#endif +} + +#endif // USE_RDTSC_FOR_FLOATTIME + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + +#ifdef OSX + // OSX + static uint64 start_time = 0; + static mach_timebase_info_data_t sTimebaseInfo; + static double conversion = 0.0; + + if ( !start_time ) + { + start_time = mach_absolute_time(); + mach_timebase_info(&sTimebaseInfo); + conversion = 1e-9 * (double) sTimebaseInfo.numer / (double) sTimebaseInfo.denom; + } + + uint64 now = mach_absolute_time(); + + return ( now - start_time ) * conversion; +#else + // Linux + static struct timespec start_time = { 0, 0 }; + static bool bInitialized = false; + + if ( !bInitialized ) + { + bInitialized = true; + clock_gettime( CLOCK_MONOTONIC, &start_time ); + } + + struct timespec now; + clock_gettime( CLOCK_MONOTONIC, &now ); + + return ( now.tv_sec - start_time.tv_sec ) + ( now.tv_nsec * 1e-9 ); + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return ( (double) nTicks) * s_flRDTSCScale; + } + else + { + struct timeval tp; + gettimeofday( &tp, NULL ); + + if (VCRGetMode() == VCR_Disabled) + return (( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 ); + + return VCRHook_Sys_FloatTime( ( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 ); + } +#endif // USE_RDTSC_FOR_FLOATTIME + +#endif +} + +unsigned int Plat_MSTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned int)(g_FakeBenchmarkTime * 1000.0); + } + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return 1000.0 * nTicks * s_flRDTSCScale; + } + else +#endif // USE_RDTSC_FOR_FLOATTIME + { + return ( uint )( Plat_FloatTime() * 1000 ); + } +} + +uint64 Plat_USTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned int)(g_FakeBenchmarkTime * 1000000.0); + } + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return 1000000.0 * nTicks * s_flRDTSCScale; + } + else +#endif // USE_RDTSC_FOR_FLOATTIME + { + return ( uint64 )( Plat_FloatTime() * 1000000 ); + } +} + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize ) +{ + return ctime_r( timep, buf ); +} + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime( const time_t *timep, struct tm *result ) +{ + return gmtime_r( timep, result ); +} + +time_t Plat_timegm( struct tm *timeptr ) +{ + return timegm( timeptr ); +} + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime( const time_t *timep, struct tm *result ) +{ + return localtime_r( timep, result ); +} + +bool vtune( bool resume ) +{ + return 0; +} + + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// -------------------------------------------------------------------------------------------------- // + +#ifndef NO_HOOK_MALLOC + +PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn( unsigned long size ) +{ +} + +typedef void (*Plat_AllocErrorFn)( unsigned long size ); +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; + +PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size ) +{ + void *pRet = MemAlloc_Alloc( size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size ) +{ + void *pRet = g_pMemAlloc->Realloc( ptr, size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void Plat_Free( void *ptr ) +{ + g_pMemAlloc->Free( ptr ); +} + + +PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn ) +{ + g_AllocError = fn; +} + +#endif // !NO_HOOK_MALLOC + +#if defined( OSX ) + +// From the Apple tech note: http://developer.apple.com/library/mac/#qa/qa1361/_index.html +bool Plat_IsInDebugSession() +{ + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + static int s_IsInDebugSession = -1; + + if ( s_IsInDebugSession == -1 ) + { + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + // We're being debugged if the P_TRACED flag is set. + + s_IsInDebugSession = ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + + return !!s_IsInDebugSession; +} + +#elif defined( LINUX ) + +bool Plat_IsInDebugSession() +{ + // For linux: http://stackoverflow.com/questions/3596781/detect-if-gdb-is-running + // Don't use "if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)" as it means debuggers can't attach. + // Other solutions they mention involve forking. Ugh. + // + // Good solution from Pierre-Loup: Check TracerPid in /proc/self/status. + // from "man proc" + // TracerPid: PID of process tracing this process (0 if not being traced). + int tracerpid = -1; + + int fd = open( "/proc/self/status", O_RDONLY, S_IRUSR ); + if( fd >= 0 ) + { + char buf[ 1024 ]; + static const char s_TracerPid[] = "TracerPid:"; + + int len = read( fd, buf, sizeof( buf ) - 1 ); + if ( len > 0 ) + { + buf[ len ] = 0; + + const char *str = strstr( buf, s_TracerPid ); + tracerpid = str ? atoi( str + sizeof( s_TracerPid ) ) : -1; + } + + close( fd ); + } + + return ( tracerpid > 0 ); +} + + +#endif // defined( LINUX ) + +void Plat_DebugString( const char * psz ) +{ + printf( "%s", psz ); +} + +static char g_CmdLine[ 2048 ]; +PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine ) +{ + strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) ); + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() +{ +#ifdef LINUX + if( !g_CmdLine[ 0 ] ) + { + FILE *fp = fopen( "/proc/self/cmdline", "rb" ); + + if( fp ) + { + size_t nCharRead = 0; + + // -1 to leave room for the '\0' + nCharRead = fread( g_CmdLine, sizeof( g_CmdLine[0] ), ARRAYSIZE( g_CmdLine ) - 1, fp ); + if ( feof( fp ) && !ferror( fp ) ) // Should have read the whole command line without error + { + Assert ( nCharRead < ARRAYSIZE( g_CmdLine ) ); + + for( int i = 0; i < nCharRead; i++ ) + { + if( g_CmdLine[ i ] == '\0' ) + g_CmdLine[ i ] = ' '; + } + + g_CmdLine[ nCharRead ] = '\0'; + + } + fclose( fp ); + } + + Assert( g_CmdLine[ 0 ] ); + } +#endif // LINUX + + return g_CmdLine; +} + +PLATFORM_INTERFACE const char *Plat_GetCommandLineA() +{ + return Plat_GetCommandLine(); +} + +PLATFORM_INTERFACE bool GetMemoryInformation( MemoryInformation *pOutMemoryInfo ) +{ + #if defined( LINUX ) || defined( OSX ) + return false; + #else + #error "Need to fill out GetMemoryInformation or at least return false for this platform" + #endif +} + + +PLATFORM_INTERFACE bool Is64BitOS() +{ +#if defined OSX + return true; +#elif defined LINUX + FILE *pp = popen( "uname -m", "r" ); + if ( pp != NULL ) + { + char rgchArchString[256]; + fgets( rgchArchString, sizeof( rgchArchString ), pp ); + pclose( pp ); + if ( !strncasecmp( rgchArchString, "x86_64", strlen( "x86_64" ) ) ) + return true; + } +#else + Assert( !"implement Is64BitOS" ); +#endif + return false; +} + +PLATFORM_INTERFACE void Plat_ExitProcess( int nCode ) +{ + _exit( nCode ); +} + + +static int s_nWatchDogTimerTimeScale = 0; +static bool s_bInittedWD = false; +static int s_WatchdogTime = 0; +static Plat_WatchDogHandlerFunction_t s_pWatchDogHandlerFunction; + +static void InitWatchDogTimer( void ) +{ + if( !strstr( g_CmdLine, "-nowatchdog" ) ) + { +#ifdef _DEBUG + s_nWatchDogTimerTimeScale = 10; // debug is slow +#else + s_nWatchDogTimerTimeScale = 1; +#endif + + } + +} + +// SIGALRM handler. Used by Watchdog timer code. +static void WatchDogHandler( int s ) +{ + Plat_DebugString( "WatchDog! Server took too long to process (probably infinite loop).\n" ); + + DebuggerBreakIfDebugging(); + + if ( s_pWatchDogHandlerFunction ) + { + s_pWatchDogHandlerFunction(); + } + else + { + // force a crash + abort(); + } +} + +// watchdog timer support +PLATFORM_INTERFACE void Plat_BeginWatchdogTimer( int nSecs ) +{ + if ( !s_bInittedWD ) + { + s_bInittedWD = true; + InitWatchDogTimer(); + } + + nSecs *= s_nWatchDogTimerTimeScale; + nSecs = MIN( nSecs, 5 * 60 ); // no more than 5 minutes no matter what + if ( nSecs ) + { + s_WatchdogTime = nSecs; + signal( SIGALRM, WatchDogHandler ); + alarm( nSecs ); + } +} + +PLATFORM_INTERFACE void Plat_EndWatchdogTimer( void ) +{ + alarm( 0 ); + signal( SIGALRM, SIG_DFL ); + s_WatchdogTime = 0; +} + +PLATFORM_INTERFACE int Plat_GetWatchdogTime( void ) +{ + return s_WatchdogTime; +} + +PLATFORM_INTERFACE void Plat_SetWatchdogHandlerFunction( Plat_WatchDogHandlerFunction_t function ) +{ + s_pWatchDogHandlerFunction = function; +} + +#ifndef NO_HOOK_MALLOC + +// memory logging this functionality is portable code, except for the way in which it hooks +// malloc/free. glibc contains the ability for the app to install hooks into malloc/free. + +#include <malloc.h> +#include <tier1/utlintrusivelist.h> +#include <execinfo.h> +#include <tier1/utlvector.h> + +#define MEMALLOC_HASHSIZE 8193 +typedef uint32 ptrint_t; + + + +struct CLinuxMemStats +{ + int nNumMallocs; // total every + int nNumFrees; // total + int nNumMallocsInUse; + int nTotalMallocInUse; + +}; + +#define MAX_STACK_TRACEBACK 20 + +struct CLinuxMallocContext +{ + CLinuxMallocContext *m_pNext; + + void *pStackTraceBack[MAX_STACK_TRACEBACK]; + + int m_nCurrentAllocSize; + int m_nNumAllocsInUse; + int m_nMaximumSize; + int m_TotalNumAllocs; + int m_nLastAllocSize; + + + CLinuxMallocContext( void ) + { + memset( this, 0, sizeof( *this ) ); + } + +}; + + +static CUtlIntrusiveList<CLinuxMallocContext> s_ContextHash[MEMALLOC_HASHSIZE]; + +CLinuxMemStats g_LinuxMemStats; + + +struct RememberedAlloc_t +{ + RememberedAlloc_t *m_pNext, *m_pPrev; // all addresses that hash to the same value are linked + + CLinuxMallocContext *m_pAllocContext; + ptrint_t m_nAddress; // the address of the memory that came from malloc/realloc + size_t m_nSize; + + void AdjustSize( size_t nsize ) + { + int nDelta = nsize - m_nSize; + m_nSize = nsize; + m_pAllocContext->m_nCurrentAllocSize += nDelta; + m_pAllocContext->m_nMaximumSize = MAX( m_pAllocContext->m_nMaximumSize, m_pAllocContext->m_nCurrentAllocSize ); + } + +}; + +static inline int AddressHash( ptrint_t nAdr ) +{ + return ( nAdr % MEMALLOC_HASHSIZE ); +} + +static CUtlIntrusiveDList<RememberedAlloc_t> s_AddressData[MEMALLOC_HASHSIZE]; + +static struct RememberedAlloc_t *FindAddress( void *pAdr, int *pHash = NULL ) +{ + ptrint_t nAdr = ( ptrint_t ) pAdr; + int nHash = AddressHash( nAdr ); + if ( pHash ) + *pHash = nHash; + for( RememberedAlloc_t *i = s_AddressData[nHash].m_pHead; i; i = i->m_pNext ) + { + if ( i->m_nAddress == nAdr ) + return i; + } + return NULL; +} + +#ifdef LINUX +static void *MallocHook( size_t, const void * ); +static void FreeHook( void*, const void * ); +static void *ReallocHook( void *ptr, size_t size, const void *caller ); + +static void RemoveHooks( void ) +{ + __malloc_hook = NULL; + __free_hook = NULL; + __realloc_hook = NULL; +} + + +static void InstallHooks( void ) +{ + __malloc_hook = MallocHook; + __free_hook = FreeHook; + __realloc_hook = ReallocHook; + +} +#elif OSX + + +static void RemoveHooks( void ) +{ +} + + +static void InstallHooks( void ) +{ +} + + +#else +#error +#endif + + +static void AddMemoryAllocation( void *pResult, size_t size ) +{ + if ( pResult ) + { + g_LinuxMemStats.nNumMallocs++; + g_LinuxMemStats.nNumMallocsInUse++; + g_LinuxMemStats.nTotalMallocInUse += size; + + RememberedAlloc_t *pNew = new RememberedAlloc_t; + pNew->m_nAddress = ( ptrint_t ) pResult; + pNew->m_nSize = size; + s_AddressData[AddressHash( pNew->m_nAddress )].AddToHead( pNew ); + + + // now, find the stack traceback context for this call + void *pTraceBack[MAX_STACK_TRACEBACK]; + int nNumGot = backtrace( pTraceBack, ARRAYSIZE( pTraceBack ) ); + for( int n = MAX( 0, nNumGot - 1 ); n < MAX_STACK_TRACEBACK; n++ ) + pTraceBack[n] = NULL; + + uint32 nHash = 0; + for( int i = 0; i < MAX_STACK_TRACEBACK; i++ ) + { + nHash = ( nHash * 3 ) + ( ( ptrint_t ) pTraceBack[i] ); + } + nHash %= MEMALLOC_HASHSIZE; + + CLinuxMallocContext *pFoundCtx = NULL; + // see if we have this context + for( CLinuxMallocContext *i = s_ContextHash[nHash].m_pHead; i ; i = i->m_pNext ) + { + if ( memcmp( pTraceBack, i->pStackTraceBack, sizeof( pTraceBack ) ) == 0 ) + { + pFoundCtx = i; + break; + } + } + if ( ! pFoundCtx ) + { + pFoundCtx = new CLinuxMallocContext; + memcpy( pFoundCtx->pStackTraceBack, pTraceBack, sizeof( pTraceBack ) ); + s_ContextHash[nHash].AddToHead( pFoundCtx ); + } + pNew->m_pAllocContext = pFoundCtx; + pFoundCtx->m_nCurrentAllocSize += size; + pFoundCtx->m_nNumAllocsInUse++; + pFoundCtx->m_nMaximumSize = MAX( pFoundCtx->m_nCurrentAllocSize, pFoundCtx->m_nMaximumSize ); + pFoundCtx->m_TotalNumAllocs++; + } +} + + +static CThreadFastMutex s_MemoryMutex; + +static void *ReallocHook( void *ptr, size_t size, const void *caller ) +{ + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + void *nResult = realloc( ptr, size ); + + if ( ptr ) // did we have this memory before + { + int nHash; + RememberedAlloc_t *pBlock = FindAddress( ptr, &nHash ); + if ( pBlock ) + { + if ( ptr == nResult ) + { + // it successfully alloced, just need to update size info, etc + pBlock->AdjustSize( size ); + g_LinuxMemStats.nTotalMallocInUse += ( size - pBlock->m_nSize ); + + } + else + { + pBlock->m_pAllocContext->m_nCurrentAllocSize -= pBlock->m_nSize; + pBlock->m_pAllocContext->m_nNumAllocsInUse--; + s_AddressData[nHash].RemoveNode( pBlock ); // throw away this node + AddMemoryAllocation( nResult, size ); + } + } + else + AddMemoryAllocation( nResult, size ); + } + else + AddMemoryAllocation( nResult, size ); + InstallHooks(); + return nResult; + + + +} + + +static void *MallocHook(size_t size, const void *caller) +{ + // turn off hooking so we won't recurse + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + + + void *pResult = malloc (size); + + // now, add this memory chunk to our list + AddMemoryAllocation( pResult, size ); + + InstallHooks(); + + return pResult; +} + +static void FreeHook(void *ptr, const void *caller ) +{ + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + + // call real free + free (ptr); + + // look in our list + if ( ptr ) + { + int nHash; + RememberedAlloc_t *pFound = FindAddress( ptr, &nHash ); + if ( !pFound ) + { + //printf(" free of unallocated adr %p (maybe)\n", ptr ); + } + else + { + pFound->m_pAllocContext->m_nCurrentAllocSize -= pFound->m_nSize; + pFound->m_pAllocContext->m_nNumAllocsInUse--; + g_LinuxMemStats.nTotalMallocInUse -= pFound->m_nSize; + g_LinuxMemStats.nNumFrees++; + g_LinuxMemStats.nNumMallocsInUse--; + s_AddressData[nHash].RemoveNode( pFound ); + delete pFound; + } + } + InstallHooks(); +} + +void EnableMemoryLogging( bool bOnOff ) +{ + if ( bOnOff ) + { + InstallHooks(); +#if 0 + // simple test + char *p[10]; + for( int i =0; i < 10; i++ ) + p[i] = new char[10]; + printf( "log with memory\n" ); + DumpMemoryLog(); + for( int i = 0; i < 10; i++ ) + delete[] p[i]; + printf( "after free,\n" ); + DumpMemoryLog(); + + // now, try som realloc action + int *p1 = NULL; + int *p2; + for( int i =1 ; i < 10; i++ ) + { + p1 = (int * ) realloc( p1, i * 100 ); + if ( i == 3 ) + p2 = new int[300]; + } + printf(" after realloc loop\n" ); + DumpMemoryLog(); + delete[] p2; + free( p1 ); + printf(" after realloc frees\n" ); + DumpMemoryLog(); +#endif + } + + else + RemoveHooks(); +} + + +static inline bool SortLessFunc( CLinuxMallocContext * const &left, CLinuxMallocContext * const &right ) +{ + return left->m_nCurrentAllocSize > right->m_nCurrentAllocSize; +} + +void DumpMemoryLog( int nThresh ) +{ + AUTO_LOCK( s_MemoryMutex ); + EndWatchdogTimer(); + RemoveHooks(); + std::vector<CLinuxMallocContext *> memList; + + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + if ( p->m_nCurrentAllocSize >= nThresh ) + { + memList.push_back( p ); + } + } + } + + std::sort( memList.begin(), memList.end(), SortLessFunc ); + + for( int i = 0; i < memList.size(); i++ ) + { + CLinuxMallocContext *p = memList[i]; + char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK ); + Msg( "Context cursize=%d nallocs=%d maxsize=%d total_allocs=%d\n", p->m_nCurrentAllocSize, p->m_nNumAllocsInUse, p->m_nMaximumSize, p->m_TotalNumAllocs ); + Msg(" stack\n" ); + for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ ) + if ( p->pStackTraceBack[n] ) + Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] ); + free( strings ); + } + Msg("End of memory list\n" ); + InstallHooks(); +} + +void DumpChangedMemory( int nThresh ) +{ + AUTO_LOCK( s_MemoryMutex ); + EndWatchdogTimer(); + RemoveHooks(); + std::vector<CLinuxMallocContext *> memList; + + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + if ( p->m_nCurrentAllocSize - p->m_nLastAllocSize > nThresh ) + { + memList.push_back( p ); + } + } + } + + std::sort( memList.begin(), memList.end(), SortLessFunc ); + for( int i = 0; i < memList.size(); i++ ) + { + CLinuxMallocContext *p = memList[i]; + char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK ); + Msg( "Context cursize=%d lastsize=%d nallocs=%d maxsize=%d total_allocs=%d\n", p->m_nCurrentAllocSize, p->m_nLastAllocSize, p->m_nNumAllocsInUse, p->m_nMaximumSize, p->m_TotalNumAllocs ); + Msg(" stack\n" ); + for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ ) + if ( p->pStackTraceBack[n] ) + Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] ); + free( strings ); + } + Msg("End of memory list\n" ); + InstallHooks(); +} + +void SetMemoryMark( void ) +{ + AUTO_LOCK( s_MemoryMutex ); + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + p->m_nLastAllocSize = p->m_nCurrentAllocSize; + } + } +} + + +void DumpMemorySummary( void ) +{ + Msg( "Total memory in use = %d, NumMallocs=%d, Num Frees=%d approx process usage=%ul\n", g_LinuxMemStats.nTotalMallocInUse, g_LinuxMemStats.nNumMallocs, g_LinuxMemStats.nNumFrees, + (unsigned int)ApproximateProcessMemoryUsage() ); +} + +#endif // !NO_HOOK_MALLOC + +// Turn off memdbg macros (turned on up top) since this is included like a header +#include "tier0/memdbgoff.h" + + |