You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
682 lines
24 KiB
682 lines
24 KiB
//========= 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
|