csgo-2018-source/tier0/memprocessheap.cpp
2021-07-24 21:11:47 -07:00

683 lines
24 KiB
C++

//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Memory allocation!
//
// $NoKeywords: $
//=============================================================================//
#include "pch_tier0.h"
#include "tier0/dbg.h"
#include "tier0/memalloc.h"
#include "memstd.h"
#if !defined(NO_MALLOC_OVERRIDE)
#if defined( _WIN32 )
#define OVERRIDE override
// warning C4481: nonstandard extension used: override specifier 'override'
#pragma warning( disable : 4481 )
#ifdef _WIN64
// Set the new-style define that indicates a a 64-bit Windows PC
#define PLATFORM_WINDOWS_PC64 1
LONGLONG
FORCEINLINE
InterlockedExchangeAdd64(
__inout LONGLONG volatile *Addend,
__in LONGLONG Value
);
#else
// Set the new-style define that indicates a a 32-bit Windows PC
#define PLATFORM_WINDOWS_PC32 1
#endif
// Support for CHeapMemAlloc for easy switching to using the process heap.
// Track this to decide how to handle out-of-memory.
static bool s_bPageHeapEnabled = false;
//-----------------------------------------------------------------------------
// IMemAlloc must guarantee 16-byte alignment for 16n-byte allocations, so we just
// force 16-byte alignment under win32 (the win64 system heap already 16-byte aligns).
// TODO: this padding negates some of the buffer-overrun protection provided by pageheap, so...
// we should fill padding bytes with a known pattern which is checked in realloc/free
#ifdef PLATFORM_WINDOWS_PC32
#define FORCED_ALIGNMENT 16
#else
#define FORCED_ALIGNMENT 0
#endif
// Round a size up to a multiple of 4 KB to aid in calculating how much
// memory is required if full pageheap is enabled.
static size_t RoundUpToPage( size_t nSize )
{
nSize += 0xFFF;
nSize &= ~0xFFF;
return nSize;
}
static void InterlockedAddSizeT( size_t volatile *Addend, size_t Value )
{
#ifdef PLATFORM_WINDOWS_PC32
// Convenience function to deal with the necessary type-casting
InterlockedExchangeAdd( ( LONG* )Addend, LONG( Value ) );
#else
InterlockedExchangeAdd64( ( LONGLONG* )Addend, LONGLONG( Value ) );
#endif
}
// CHeapDefault supplies default implementations for as many functions as
// possible so that a heap implementation can be as simple as possible.
class CHeapDefault : public IMemAlloc
{
// Since we define the debug versions of Alloc/Realloc/Free in this class but
// not the release versions we implicitly hide the release implementations, which
// makes it impossible for us to call them in order to implement the debug
// versions. These using directives pull these three names into this namespace
// so that we can call them.
using IMemAlloc::Alloc;
using IMemAlloc::Realloc;
using IMemAlloc::Free;
// Release versions
// Alloc, Realloc, and Free must be implemented
virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ) OVERRIDE { return 0; }
// Debug versions
virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ) OVERRIDE { return Alloc( nSize ); }
virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) OVERRIDE { return Realloc(pMem, nSize); }
virtual void Free( void *pMem, const char *pFileName, int nLine ) OVERRIDE { Free( pMem ); }
virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) OVERRIDE { return 0; }
// GetSize must be implemented
// Force file + line information for an allocation
virtual void PushAllocDbgInfo( const char *pFileName, int nLine ) OVERRIDE {}
virtual void PopAllocDbgInfo() OVERRIDE {}
// FIXME: Remove when we have our own allocator
// these methods of the Crt debug code is used in our codebase currently
virtual int32 CrtSetBreakAlloc( int32 lNewBreakAlloc ) OVERRIDE { return 0; }
virtual int CrtSetReportMode( int nReportType, int nReportMode ) OVERRIDE { return 0; }
virtual int CrtIsValidHeapPointer( const void *pMem ) OVERRIDE { return 0; }
virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ) OVERRIDE { return 0; }
virtual int CrtCheckMemory( void ) OVERRIDE { return 0; }
virtual int CrtSetDbgFlag( int nNewFlag ) OVERRIDE { return 0; }
virtual void CrtMemCheckpoint( _CrtMemState *pState ) OVERRIDE {}
// FIXME: Make a better stats interface
virtual void DumpStats() OVERRIDE {}
virtual void DumpStatsFileBase( char const *pchFileBase, DumpStatsFormat_t nFormat = FORMAT_TEXT ) OVERRIDE { DumpStats(); }
virtual size_t ComputeMemoryUsedBy( char const *pchSubStr ) OVERRIDE { return 0; }
// FIXME: Remove when we have our own allocator
virtual void* CrtSetReportFile( int nRptType, void* hFile ) OVERRIDE { return 0; }
virtual void* CrtSetReportHook( void* pfnNewHook ) OVERRIDE { return 0; }
virtual int CrtDbgReport( int nRptType, const char * szFile,
int nLine, const char * szModule, const char * pMsg ) OVERRIDE { return 0; }
virtual int heapchk() OVERRIDE { return _HEAPOK; }
virtual bool IsDebugHeap() OVERRIDE { return 0; }
virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ) OVERRIDE { pFileName = ""; nLine = 0; }
virtual void RegisterAllocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) OVERRIDE {}
virtual void RegisterDeallocation( const char *pFileName, int nLine, size_t nLogicalSize, size_t nActualSize, unsigned nTime ) OVERRIDE {}
virtual int GetVersion() OVERRIDE { return 0; }
virtual void CompactHeap() OVERRIDE {}
virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) OVERRIDE { return 0; }
virtual void DumpBlockStats( void * ) OVERRIDE {}
virtual void SetStatsExtraInfo( const char *pMapName, const char *pComment ) OVERRIDE {}
// Returns 0 if no failure, otherwise the size_t of the last requested chunk
virtual size_t MemoryAllocFailed() OVERRIDE { return 0; }
virtual void CompactIncremental() OVERRIDE {}
virtual void OutOfMemory( size_t nBytesAttempted = 0 ) OVERRIDE {}
// Region-based allocations
virtual void *RegionAlloc( int region, size_t nSize ) OVERRIDE { return 0; }
virtual void *RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ) OVERRIDE { return 0; }
// Replacement for ::GlobalMemoryStatus which accounts for unused memory in our system
virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) OVERRIDE {}
// Obtain virtual memory manager interface
virtual IVirtualMemorySection * AllocateVirtualMemorySection( size_t numMaxBytes ) OVERRIDE { return 0; }
// Request 'generic' memory stats (returns a list of N named values; caller should assume this list will change over time)
virtual int GetGenericMemoryStats( GenericMemoryStat_t **ppMemoryStats ) { return 0; }
// handles storing allocation info for coroutines
virtual uint32 GetDebugInfoSize() { return 0; }
virtual void SaveDebugInfo( void *pvDebugInfo ) {}
virtual void RestoreDebugInfo( const void *pvDebugInfo ) {}
virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {}
};
class CHeapMemAlloc : public CHeapDefault
{
public:
CHeapMemAlloc()
{
// Do all allocations with the shared process heap so that we can still
// allocate from one DLL and free in another.
m_heap = GetProcessHeap();
}
// minimal IMemAlloc implementation
// Release API
public:
virtual void *Alloc( size_t nSize ) OVERRIDE;
virtual void *Realloc( void *pMem, size_t nSize ) OVERRIDE;
virtual void Free( void *pMem ) OVERRIDE;
// Returns size of a particular allocation
// BUGBUG: this function should be 'const'
virtual size_t GetSize( void *pMem ) OVERRIDE;
// Return 1 to indicate a healthy heap.
// BUGBUG: this function should be 'const'
virtual int CrtCheckMemory( void ) OVERRIDE;
// BUGBUG: this function should be 'const'
virtual void DumpStats() OVERRIDE;
void Init(bool bZeroMemory);
private:
void OutOfMemory( size_t nBytesAttempted = 0 );
// Internal allocation calls used to support alignment
void * Alloc_Unaligned( size_t nSize );
void * Realloc_Unaligned( void *pMem, size_t nSize );
void Free_Unaligned( void *pMem );
size_t GetSize_Unaligned( void *pMem ) const;
// Handle to the process heap.
HANDLE m_heap;
uint32 m_HeapFlags;
// Total outstanding bytes allocated.
volatile size_t m_nOutstandingBytes;
// Total outstanding committed bytes assuming that all allocations are
// put on individual 4-KB pages (true when using full PageHeap from
// App Verifier).
volatile size_t m_nOutstandingPageHeapBytes;
// Total outstanding allocations. With PageHeap enabled each allocation
// requires an extra 4-KB page of address space.
volatile LONG m_nOutstandingAllocations;
LONG m_nOldOutstandingAllocations;
// Total allocations without subtracting freed memory.
volatile LONG m_nLifetimeAllocations;
LONG m_nOldLifetimeAllocations;
};
void CHeapMemAlloc::Init( bool bZeroMemory )
{
m_HeapFlags = bZeroMemory ? HEAP_ZERO_MEMORY : 0;
// Can't use Msg here because it isn't necessarily initialized yet.
if ( s_bPageHeapEnabled )
{
OutputDebugStringA("PageHeap is on. Memory use will be larger than normal.\n" );
}
else
{
OutputDebugStringA("PageHeap is off. Memory use will be normal.\n" );
}
if( bZeroMemory )
{
OutputDebugStringA( " HEAP_ZERO_MEMORY is specified.\n" );
}
}
inline size_t CHeapMemAlloc::GetSize_Unaligned( void *pMem ) const
{
return HeapSize( m_heap, 0, pMem ) - FORCED_ALIGNMENT;
}
inline void *CHeapMemAlloc::Alloc_Unaligned( size_t nSize )
{
// Ensure that the constructor has run already. Poorly defined
// order of construction can result in the allocator being used
// before it is constructed. Which could be bad.
if ( !m_heap )
__debugbreak();
size_t nAdjustedSize = nSize + FORCED_ALIGNMENT;
void* pMem = HeapAlloc( m_heap, m_HeapFlags, nAdjustedSize );
if ( !pMem )
{
OutOfMemory( nSize );
}
InterlockedAddSizeT( &m_nOutstandingBytes, nSize );
InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, RoundUpToPage( nAdjustedSize ) );
InterlockedIncrement( &m_nOutstandingAllocations );
InterlockedIncrement( &m_nLifetimeAllocations );
return pMem;
}
inline void *CHeapMemAlloc::Realloc_Unaligned( void *pMem, size_t nSize )
{
size_t nOldSize = GetSize_Unaligned( pMem );
size_t nOldAdjustedSize = nOldSize + FORCED_ALIGNMENT;
size_t nAdjustedSize = nSize + FORCED_ALIGNMENT;
void* pNewMem = HeapReAlloc( m_heap, m_HeapFlags, pMem, nAdjustedSize );
if ( !pNewMem )
{
OutOfMemory( nSize );
}
InterlockedAddSizeT( &m_nOutstandingBytes, nSize - nOldSize );
InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, RoundUpToPage( nAdjustedSize ) );
InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, 0 - RoundUpToPage( nOldAdjustedSize ) );
// Outstanding allocation count isn't affected by Realloc, but lifetime allocation count is
InterlockedIncrement( &m_nLifetimeAllocations );
return pNewMem;
}
inline void CHeapMemAlloc::Free_Unaligned( void *pMem )
{
size_t nOldSize = GetSize_Unaligned( pMem );
size_t nOldAdjustedSize = nOldSize + FORCED_ALIGNMENT;
InterlockedAddSizeT( &m_nOutstandingBytes, 0 - nOldSize );
InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, 0 - RoundUpToPage( nOldAdjustedSize ) );
InterlockedDecrement( &m_nOutstandingAllocations );
HeapFree( m_heap, 0, pMem );
}
inline void CHeapMemAlloc::OutOfMemory( size_t nBytesAttempted )
{
// It is crucial to stop here, before calling DumpStats, because if an OOM failure happens
// in the logging system then DumpStats will trigger it again. This is made more complicated
// because CUtlBuffer will have updated its size but not its pointer, leading to a buffer
// that thinks it has more room than it actually does.
DebuggerBreakIfDebugging();
// Having PageHeap enabled leads to lots of allocation failures. These
// these crashes we either need to halt immediately on allocation failures,
// or print a message and exit. Printing a message and exiting is better
// for stress testing purposes.
DumpStats();
char buffer[256];
_snprintf( buffer, sizeof( buffer ), FILE_LINE_STRING "***** OUT OF MEMORY! attempted allocation size: %I64d ****\n", (uint64)nBytesAttempted );
buffer[ ARRAYSIZE(buffer) - 1 ] = 0;
// Can't use Msg() in a situation like this.
Plat_DebugString( buffer );
// If page heap is enabled then exit cleanly to simplify stress testing.
if ( !s_bPageHeapEnabled )
{
DebuggerBreakIfDebugging();
}
Plat_ExitProcess( EXIT_FAILURE );
}
inline void CHeapMemAlloc::DumpStats()
{
const size_t MB = 1024 * 1024;
Msg( "Sorry -- no stats saved to file memstats.txt when the heap allocator is enabled.\n" );
// Print requested memory.
Msg( "%u MB allocated.\n", ( unsigned )( m_nOutstandingBytes / MB ) );
// Print memory after rounding up to pages.
Msg( "%u MB memory used assuming maximum PageHeap overhead.\n", ( unsigned )( m_nOutstandingPageHeapBytes / MB ));
// Print memory after adding in reserved page after every allocation. Do 64-bit calculations
// because the pageHeap required memory can easily go over 4 GB.
__int64 pageHeapBytes = m_nOutstandingPageHeapBytes + m_nOutstandingAllocations * 4096LL;
Msg( "%u MB address space used assuming maximum PageHeap overhead.\n", ( unsigned )( pageHeapBytes / MB ));
Msg( "%u outstanding allocations (%d delta).\n", ( unsigned )m_nOutstandingAllocations, ( int )( m_nOutstandingAllocations - m_nOldOutstandingAllocations ) );
Msg( "%u lifetime allocations (%u delta).\n", ( unsigned )m_nLifetimeAllocations, ( unsigned )( m_nLifetimeAllocations - m_nOldLifetimeAllocations ) );
// Update the numbers on outstanding and lifetime allocation counts so
// that we can print out deltas.
m_nOldOutstandingAllocations = m_nOutstandingAllocations;
m_nOldLifetimeAllocations = m_nLifetimeAllocations;
}
int CHeapMemAlloc::CrtCheckMemory( void )
{
#ifdef _WIN32
// HeapValidate is supposed to check the entire heap for validity. However testing with
// intentional heap corruption suggests that it does not. If a block is corrupted and
// then HeapValidate is called on that block then this is detected, but the same corruption
// is not detected when passing NULL as the pointer. But, better to have this functionality
// supported than not.
BOOL result = HeapValidate( m_heap, 0, NULL );
return (result != 0) ? 1 : 0;
#else
// HeapValidate does not exist on the Xbox 360.
return 1;
#endif
}
// Alignment-enforcing wrappers
#if ( FORCED_ALIGNMENT > 0 )
ASSERT_INVARIANT( FORCED_ALIGNMENT < 256 ); // Alignment offset has to fit in 1 byte
inline void *AlignPointer( void *pUnaligned )
{
// Offset the pointer to align it and store the offset in the previous byte
byte nOffset = FORCED_ALIGNMENT - ( ((uintptr_t)pUnaligned) & ( FORCED_ALIGNMENT - 1 ) );
byte *pAligned = ((byte*)pUnaligned) + nOffset;
pAligned[ -1 ] = nOffset;
return pAligned;
}
inline void *UnalignPointer( void *pAligned )
{
// Get the original unaligned pointer, using the offset stored by AlignPointer()
byte *pUnaligned = (byte *)pAligned;
byte nOffset = pUnaligned[ -1 ];
pUnaligned -= nOffset;
// Detect corruption of the offset byte (valid offsets range from 1 to FORCED_ALIGNMENT):
if ( ((uintptr_t)pAligned) % FORCED_ALIGNMENT ) DebuggerBreakIfDebugging();
if ( ( nOffset < 1 ) || ( nOffset > FORCED_ALIGNMENT ) ) DebuggerBreakIfDebugging();
return pUnaligned;
}
#else // FORCED_ALIGNMENT
inline void *AlignPointer( void *pUnaligned ) { return pUnaligned; }
inline void *UnalignPointer( void *pAligned ) { return pAligned; }
#endif // FORCED_ALIGNMENT
inline void *CHeapMemAlloc::Alloc( size_t nSize )
{
// NOTE: see IMemAlloc 'API Rules'
//if ( !nSize )
// return NULL;
if ( !nSize )
nSize = 1;
return AlignPointer( Alloc_Unaligned( nSize ) );
}
inline void CHeapMemAlloc::Free( void *pMem )
{
// NOTE: see IMemAlloc 'API Rules'
if ( !pMem )
return;
return Free_Unaligned( UnalignPointer( pMem ) );
}
inline void *CHeapMemAlloc::Realloc( void *pMem, size_t nSize )
{
// NOTE: see IMemAlloc 'API Rules'
if ( !pMem )
{
return Alloc( nSize );
}
if ( !nSize )
{
Free( pMem );
return NULL;
}
#if ( FORCED_ALIGNMENT == 0 )
return Realloc_Unaligned( pMem, nSize );
#else // FORCED_ALIGNMENT
// Can't use ReAlloc_Unaligned because the leading padding varies, so it will memcpy incorrectly.
void * pUnaligned = UnalignPointer( pMem );
size_t nOldSize = GetSize_Unaligned( pUnaligned );
void * pAligned = AlignPointer( Alloc_Unaligned( nSize ) );
memcpy( pAligned, pMem, MIN( nSize, nOldSize ) );
Free_Unaligned( pUnaligned );
return pAligned;
#endif // FORCED_ALIGNMENT
}
inline size_t CHeapMemAlloc::GetSize( void *pMem )
{
// NOTE: see IMemAlloc 'API Rules'
if ( !pMem )
return 0;
return GetSize_Unaligned( UnalignPointer( pMem ) );
}
void EnableHeapMemAlloc( bool bZeroMemory )
{
// Place this here to guarantee it is constructed
// before we call Init.
static CHeapMemAlloc s_HeapMemAlloc;
static bool s_initCalled = false;
if ( !s_initCalled )
{
s_HeapMemAlloc.Init( bZeroMemory );
SetAllocatorObject( &s_HeapMemAlloc );
s_initCalled = true;
}
}
void ReserveBottomMemory()
{
// If we are running a 64-bit build then reserve all addresses below the
// 4 GB line to push as many pointers as possible above the line.
#ifdef PLATFORM_WINDOWS_PC64
// Avoid the cost of calling this multiple times.
static bool s_initialized = false;
if ( s_initialized )
return;
s_initialized = true;
// Start by reserving large blocks of memory. When those reservations
// have exhausted the bottom 4 GB then halve the size and try again.
// The granularity for reserving address space is 64 KB so if we wanted
// to reserve every single page we would need to continue down to 64 KB.
// However stopping at 1 MB is sufficient because it prevents the Windows
// heap (and dlmalloc and the small block heap) from grabbing address space
// from the bottom 4 GB, while still allowing Steam to allocate a few pages
// for setting up detours.
const size_t LOW_MEM_LINE = 0x100000000LL;
size_t totalReservation = 0;
size_t numVAllocs = 0;
size_t numHeapAllocs = 0;
for ( size_t blockSize = 256 * 1024 * 1024; blockSize >= 1024 * 1024; blockSize /= 2 )
{
for (;;)
{
void* p = VirtualAlloc( 0, blockSize, MEM_RESERVE, PAGE_NOACCESS );
if ( !p )
break;
if ( (size_t)p >= LOW_MEM_LINE )
{
// We don't need this memory, so release it completely.
VirtualFree( p, 0, MEM_RELEASE );
break;
}
totalReservation += blockSize;
++numVAllocs;
}
}
// Now repeat the same process but making heap allocations, to use up the
// already committed heap blocks that are below the 4 GB line. Now we start
// with 64-KB allocations and proceed down to 16-byte allocations.
HANDLE heap = GetProcessHeap();
for ( size_t blockSize = 64 * 1024; blockSize >= 16; blockSize /= 2 )
{
for (;;)
{
void* p = HeapAlloc( heap, 0, blockSize );
if ( !p )
break;
if ( (size_t)p >= LOW_MEM_LINE )
{
// We don't need this memory, so release it completely.
HeapFree( heap, 0, p );
break;
}
totalReservation += blockSize;
++numHeapAllocs;
}
}
// Print diagnostics showing how many allocations we had to make in order to
// reserve all of low memory. In one test run it took 55 virtual allocs and
// 85 heap allocs. Note that since the process may have multiple heaps (each
// CRT seems to have its own) there is likely to be a few MB of address space
// that was previously reserved and is available to be handed out by some allocators.
//char buffer[1000];
//sprintf_s( buffer, "Reserved %1.3f MB (%d vallocs, %d heap allocs) to keep allocations out of low-memory.\n",
// totalReservation / (1024 * 1024.0), (int)numVAllocs, (int)numHeapAllocs );
// Can't use Msg here because it isn't necessarily initialized yet.
//OutputDebugString( buffer );
#endif
}
// Check whether PageHeap (part of App Verifier) has been enabled for this process.
// It specifically checks whether it was enabled by the EnableAppVerifier.bat
// batch file. This can be used to automatically enable -processheap when
// App Verifier is in use.
static bool IsPageHeapEnabled( bool& bETWHeapEnabled )
{
// Assume false.
bool result = false;
bETWHeapEnabled = false;
// First we get the application's name so we can look in the registry
// for App Verifier settings.
HMODULE exeHandle = GetModuleHandle( 0 );
if ( exeHandle )
{
char appName[ MAX_PATH ];
if ( GetModuleFileNameA( exeHandle, appName, ARRAYSIZE( appName ) ) )
{
// Guarantee null-termination -- not guaranteed on Windows XP!
appName[ ARRAYSIZE( appName ) - 1 ] = 0;
// Find the file part of the name.
const char* pFilePart = strrchr( appName, '\\' );
if ( pFilePart )
{
++pFilePart;
size_t len = strlen( pFilePart );
if ( len > 0 && pFilePart[ len - 1 ] == ' ' )
{
OutputDebugStringA( "Trailing space on executable name! This will cause Application Verifier and ETW Heap tracing to fail!\n" );
DebuggerBreakIfDebugging();
}
// Generate the key name for App Verifier settings for this process.
char regPathName[ MAX_PATH ];
_snprintf( regPathName, ARRAYSIZE( regPathName ),
"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\%s",
pFilePart );
regPathName[ ARRAYSIZE( regPathName ) - 1 ] = 0;
HKEY key;
LONG regResult = RegOpenKeyA( HKEY_LOCAL_MACHINE,
regPathName,
&key );
if ( regResult == ERROR_SUCCESS )
{
// If PageHeapFlags exists then that means that App Verifier is enabled
// for this application. The StackTraceDatabaseSizeInMB is only
// set by Valve's enabling batch file so this indicates that
// a developer at Valve is using App Verifier.
if ( RegQueryValueExA( key, "StackTraceDatabaseSizeInMB", 0, NULL, NULL, NULL ) == ERROR_SUCCESS &&
RegQueryValueExA( key, "PageHeapFlags", 0, NULL, NULL, NULL) == ERROR_SUCCESS )
{
result = true;
}
if ( RegQueryValueExA( key, "TracingFlags", 0, NULL, NULL, NULL) == ERROR_SUCCESS )
bETWHeapEnabled = true;
RegCloseKey( key );
}
}
}
}
return result;
}
// Check for various allocator overrides such as -processheap and -reservelowmem.
// Returns true if -processheap is enabled, by a command line switch or other method.
bool CheckWindowsAllocSettings( const char* upperCommandLine )
{
// Are we doing ETW heap profiling?
bool bETWHeapEnabled = false;
s_bPageHeapEnabled = IsPageHeapEnabled( bETWHeapEnabled );
// Should we reserve the bottom 4 GB of RAM in order to flush out pointer
// truncation bugs? This helps ensure 64-bit compatibility.
// However this needs to be off by default to avoid causing compatibility problems,
// with Steam detours and other systems. It should also be disabled when PageHeap
// is on because for some reason the combination turns into 4 GB of working set, which
// can easily cause problems.
if ( strstr( upperCommandLine, "-RESERVELOWMEM" ) && !s_bPageHeapEnabled )
ReserveBottomMemory();
// Uninitialized data, including pointers, is often set to 0xFFEEFFEE.
// If we reserve that block of memory then we can turn these pointer
// dereferences into crashes a little bit earlier and more reliably.
// We don't really care whether this allocation succeeds, but it's
// worth trying. Note that we do this in all cases -- whether we are using
// -processheap or not.
VirtualAlloc( (void*)0xFFEEFFEE, 1, MEM_RESERVE, PAGE_NOACCESS );
// Enable application termination (breakpoint) on heap corruption. This is
// better than trying to patch it up and continue, both from a security and
// a bug-finding point of view. Do this always on Windows since the heap is
// used by video drivers and other in-proc components.
//HeapSetInformation( NULL, HeapEnableTerminationOnCorruption, NULL, 0 );
// The HeapEnableTerminationOnCorruption requires a recent platform SDK,
// so fake it up.
#if defined(PLATFORM_WINDOWS_PC)
HeapSetInformation( NULL, (HEAP_INFORMATION_CLASS)1, NULL, 0 );
#endif
bool bZeroMemory = false;
bool bProcessHeap = false;
// Should we force using the process heap? This is handy for gathering memory
// statistics with ETW/xperf. When using App Verifier -processheap is automatically
// turned on.
if ( strstr( upperCommandLine, "-PROCESSHEAP" ) )
{
bProcessHeap = true;
bZeroMemory = !!strstr( upperCommandLine, "-PROCESSHEAPZEROMEM" );
}
// Unless specifically disabled, turn on -processheap if pageheap or ETWHeap tracing
// are enabled.
if ( !strstr( upperCommandLine, "-NOPROCESSHEAP" ) && ( s_bPageHeapEnabled || bETWHeapEnabled ) )
bProcessHeap = true;
if ( bProcessHeap )
{
// Now all allocations will go through the system heap.
EnableHeapMemAlloc( bZeroMemory );
}
return bProcessHeap;
}
#endif // _WIN32
#endif // !NO_MALLOC_OVERRIDE