/**************************************************************************\ * * Copyright (c) 1999 Microsoft Corporation * * Module Name: * * GDI+ memory allocation functions * * Abstract: * * This module provides GpMalloc, GpRealloc and GpFree. * * Notes: * * Office provides their own versions of these functions. * * Created: * * 07/08/1999 agodfrey * \**************************************************************************/ #include "precomp.hpp" namespace GpRuntime { HANDLE GpMemHeap = NULL; }; #if GPMEM_ALLOC_CHK #if GPMEM_FAULT_INJECTION #include #include #endif // Get the definition of Globals::CaptureStackBackTraceFunction #include "..\common\common.hpp" // Size of the start and end memory guards (DWORDS) // Probably should be QWORD aligned (even number). const unsigned int GPMEM_GUARD_START = 0x10; const unsigned int GPMEM_GUARD_END = 0x10; const unsigned int GPMEM_GS = GPMEM_GUARD_START*sizeof(DWORD); const unsigned int GPMEM_GE = GPMEM_GUARD_END*sizeof(DWORD); const unsigned char GPMEM_FILL_ALLOC = 0xbf; const unsigned char GPMEM_FILL_GS = 0xac; const unsigned char GPMEM_FILL_GE = 0xfe; const unsigned char GPMEM_FILL_FREE = 0x73; #define GPMEM_ALLOC_TRACKING 1 #define GPMEM_ALLOC_FILL 1 enum AllocTrackHeaderFlags { MemoryAllocated = 0x00000001, MemoryFreed = 0x00000002, // useful in catching double frees APIAllocation = 0x00000004 }; // Head of every tracked allocation. // From - // #define MAX_STACK_DEPTH 32 // Therefore we define our stack trace size to be 32 entries. // Number of Stack Frames stash away on every allocation. // Bear in mind that every allocation will have 4xGPMEM_STACKSIZE bytes // additional overhead. (8xGPMEM_STACKSIZE on ptr64 machines) #define GPMEM_STACKSIZE 32 struct AllocTrackHeader { struct AllocTrackHeader *flink; struct AllocTrackHeader *blink; DWORD size; PVOID caller_address[GPMEM_STACKSIZE]; DWORD flags; #if GPMEM_ALLOC_CHK_LIST char *callerFileName; INT callerLineNumber; #endif DWORD tag; DWORD padding; // padding to keep 8 byte alignment }; #define GPMEM_OVERHEAD (GPMEM_GS + GPMEM_GE + sizeof(AllocTrackHeader)) // Head of double linked list of tracked memory allocations. AllocTrackHeader *gpmemAllocList=NULL; // An allocation fails if rand() < gpmemDefFailRate (gpmemInitFailRate for // gdiplus initialization code. // set to RAND_MAX/2 if you want 50% failure rate, 0 if you want no failures. // // The system starts off failing allocations at a rate specified by // gpmemInitFailRate. Once GpDoneInitializeAllocFailureMode() is called, // allocations are failed at the rate specified by gpmemDefFailRate(). // This is so that dll initialization code can have a different fail rate // to regular code. int gpmemInitFailRate = 0; int gpmemDefFailRate = 0; // This would give a failure rate of 25% // int gpmemDefFailRate = (RAND_MAX/4) BOOL gpmemDoneInitialization = FALSE; // Some statistics struct AllocTrackStats { // Totals over the entire run long CumulativeAllocations; // The number of calls to GpMalloc or GpRealloc long CumulativeMemorySize; // Cumulative total of allocated memory long CumulativeReallocs; // The number of calls to GpRealloc long ForcedFailures; long AllocationFailures; // Current values long OutstandingAllocations; // The number of allocation requests long OutstandingMemorySize; // The amount of memory currently allocated // Maxima of the 'Outstanding' values long MaxAllocations; // The maximum of OutstandingAllocations long MaxMemorySize; // The maximum of OutstandingMemorySize void Allocated(long size) { size -= GPMEM_OVERHEAD; CumulativeMemorySize += size; OutstandingMemorySize += size; if (OutstandingMemorySize > MaxMemorySize) { MaxMemorySize = OutstandingMemorySize; } CumulativeAllocations++; OutstandingAllocations++; if (OutstandingAllocations > MaxAllocations) { MaxAllocations = OutstandingAllocations; } } void Freed(long size) { size -= GPMEM_OVERHEAD; OutstandingMemorySize -= size; OutstandingAllocations--; } }; AllocTrackStats gpmemAllocTotal = {0}; // Hash Table for tracking memory allocations sorted by callsite. // This table stores some total memory usage statistics for each // callsite. // Turn this on by setting GPMEM_DEBUG_SORT 1 #define GPMEM_DEBUG_SORT 0 #if GPMEM_DEBUG_SORT struct HashMem { long callsite; long size; long count; }; // It is very important that this hash size be larger than the number of // possible callsites for GpMalloc. // // Set HASHSIZE to some big prime number. #define HASHSIZE 1069 HashMem HashTable[HASHSIZE]; // Hashing algorithm. long Hash(long cs) { long tmp = cs % HASHSIZE; long tmploop = tmp; while( (HashTable[tmp].callsite != 0) && (HashTable[tmp].callsite != cs) ) { tmp++; if(tmp>=HASHSIZE) tmp=0; if(tmp==tmploop) return -1; } return tmp; } #endif #endif /**************************************************************************\ * * Function Description: * * Do we fail this memory allocation? * * Arguments: [NONE] * Return Value: [NONE] * * History: * * 09/20/1999 asecchia * Created it. * \**************************************************************************/ #if GPMEM_ALLOC_CHK BOOL GpFailMemoryAllocation() { #if GPMEM_FAULT_INJECTION int rndnum = rand(); if(gpmemDoneInitialization) { if(rndnumtag; lstrcpynA(str, skipGdiPlus(header->callerFileName), 200); WARNING(( "%p %8d %-3.3s %c%c%c%c %p %6d %s", header, header->size, header->flags & APIAllocation ? "API" : "", tagStr[3], tagStr[2], tagStr[1], tagStr[0], header->caller_address, header->callerLineNumber, str )); header = header->flink; i++; j++; } } #endif ASSERTMSG( gpmemAllocList==NULL, ("Memory leaks detected.\n" "List header (gdiplus!gpmemAllocList) at %p\n" "Use: dt AllocTrackHeader [address] to display the memory block headers.\n" "Use: dds [AllocTrackHeader.caller_address] to display the allocation stack.", gpmemAllocList) ); // Display the report stored in the Hash Table #if GPMEM_DEBUG_SORT for(int i=0; itag = (unsigned long) tag; if(bApi) hdr->flags |= APIAllocation; } } #endif /**************************************************************************\ * * Function Description: * * Allocates a block of memory. * * Arguments: * * [IN] size - number of bytes to allocate * * Return Value: * * A pointer to the new block, or NULL on failure. * * History: * * 09/14/1999 asecchia * Added the checked build memory guard code. * 07/08/1999 agodfrey * Created it. * \**************************************************************************/ /* Here's the structure of the memory block allocated under GPMEM_ALLOC_CHK |+AllocTrackHeader Two DWORDS - contains | |+flink | | Pointer to the next memory allocation in the tracked allocation list | |+blink | | Pointer to the previous memory allocation in the tracked allocation link | |+Guard Area | GPMEM_GUARD_START DWORDs filled with the gpmemGuardFill string. | |+Data Area | This is the location we return to the caller. It is pre-initialized to | the repeated DWORD value in gpmemAllocFillBlock (usually 0xbaadf00d) | |+Guard Area: | GPMEM_GUARD_END DWORDs filled with gpmemGuardFill string. */ #if GPMEM_ALLOC_CHK_LIST extern "C" void *GpMallocDebug(size_t size, char *fileName, INT lineNumber) #else extern "C" void *GpMalloc(size_t size) #endif { // on checked builds we add headers to the allocation and hence, really // large requests will overflow 32bits and succeed a really small // allocation. // Also allocations of 2-4Gb usually indicate a sign extension problem in // the caller when the size is computed. Note that size_t is UNSIGNED. // Instead of simply checking the high bit with an &, we check to see that // the size is < 0x7fffffff so that the check works on IA64 too - IA64 // size_t is 64bit. Note that allocations of this size will fail in the // heap manager anyway. The caller should be using VirtualAlloc for stuff // that's really this big. ASSERT(size < 0x7fffffff); #if GPMEM_ALLOC_CHK // If we're playing with the tracking headers, we need to be thread safe. GpMallocTrackingCriticalSection critsecobj; // // Memory? _Real_ programmers don't need memory! // if(GpFailMemoryAllocation()) { gpmemAllocTotal.AllocationFailures++; gpmemAllocTotal.ForcedFailures++; return NULL; } // // Make the allocation request a multiple of a QWORD // if(size & (sizeof(DWORD)*2-1)) { size = (size & ~(sizeof(DWORD)*2-1)) + sizeof(DWORD)*2; } size_t origsize = size; // // Allocate space for the FLink and BLink // size += sizeof(AllocTrackHeader); if(GPMEM_GUARD_START) { size += GPMEM_GS; } if(GPMEM_GUARD_END) { size += GPMEM_GE; } void *tmpalloc = HeapAlloc(GpMemHeap, GPMEMHEAPFLAGS, size); if(!tmpalloc) { gpmemAllocTotal.AllocationFailures++; return NULL; } ASSERTMSG(HeapSize(GpMemHeap, GPMEMHEAPFLAGS, tmpalloc) >= size, ("GpMalloc() allocated %d, but requested %d bytes", HeapSize(GpMemHeap, GPMEMHEAPFLAGS, tmpalloc), size)); // Add the per-callsite allocation statistics #if GPMEM_DEBUG_SORT long hidx = Hash(calleraddr); if(hidx>=0) { HashTable[hidx].callsite = calleraddr; HashTable[hidx].size += size-GPMEM_OVERHEAD; HashTable[hidx].count ++; } else { WARNING(("Hash Table too small - increase HASHSIZE")); } #endif gpmemAllocTotal.Allocated(size); #else // // This is the only piece of code that's executed if // GPMEM_ALLOC_CHK is turned off. // #if PROFILE_MEMORY_USAGE MC_LogAllocation(size); #endif return HeapAlloc(GpMemHeap, GPMEMHEAPFLAGS, size); #endif #if GPMEM_ALLOC_CHK // // Fill up the entire allocation with the value // set in GPMEM_FILL_ALLOC // if(GPMEM_ALLOC_FILL) { GpMemset((unsigned char *)tmpalloc + sizeof(AllocTrackHeader) + GPMEM_GS, GPMEM_FILL_ALLOC, origsize); } // // Fill up the start guard area - if we have one. // if(GPMEM_GUARD_START) { unsigned char *p = (unsigned char *)tmpalloc+sizeof(AllocTrackHeader); GpMemset(p, GPMEM_FILL_GS, GPMEM_GS); } // // Fill up the end guard area - if we have one. // if(GPMEM_GUARD_END) { unsigned char *p = (unsigned char *)tmpalloc+size-GPMEM_GE; GpMemset(p, GPMEM_FILL_GE, GPMEM_GE); } // // setup the double linked-list to track all pool allocations. // AllocTrackHeader *hdr = (AllocTrackHeader *)tmpalloc; hdr->size = size; // zero out any unused frame pointers. GpMemset(hdr->caller_address, 0, sizeof(ULONG_PTR)*GPMEM_STACKSIZE); // Lets stash away the entire stack trace if we have a // backtrace function. if(Globals::CaptureStackBackTraceFunction) { ULONG hash; // dummy hash USHORT frames = Globals::CaptureStackBackTraceFunction( 0, // don't skip any frames GPMEM_STACKSIZE, // get this many frames (PVOID*)(hdr->caller_address), // ... into here. &hash ); } hdr->flags = MemoryAllocated; hdr->tag = 'unkn'; #if GPMEM_ALLOC_CHK_LIST hdr->callerFileName = fileName; hdr->callerLineNumber = lineNumber; #endif if(GPMEM_ALLOC_TRACKING) { hdr->blink = NULL; hdr->flink = gpmemAllocList; if(gpmemAllocList) { gpmemAllocList->blink = (AllocTrackHeader *)tmpalloc; } gpmemAllocList = (AllocTrackHeader *)tmpalloc; } else { GpMemset(hdr, 0, sizeof(AllocTrackHeader)); } // // Give them a pointer just after the guard bits. // return (char *)tmpalloc+sizeof(AllocTrackHeader)+GPMEM_GS; #endif } /**************************************************************************\ * * Function Description: * Allocates memory for APIs. Used to track the memory with a separate * identifying flag so that API allocations can be distinguished from * internal allocations. * Used on debug builds. * * Arguments: * [IN] size - size to pass to GpMalloc * * Return Value: * Returns the memory with the appropriately hacked up caller address * * History: * * 4/30/2000 asecchia * Created it. * \**************************************************************************/ #if DBG #if GPMEM_ALLOC_CHK #if GPMEM_ALLOC_CHK_LIST extern "C" void * __stdcall GpMallocAPIDebug(size_t size, char *fileName, INT lineNumber) #else extern "C" void *GpMallocAPI(size_t size) #endif { // If we're playing with the tracking headers, we need to be thread safe. GpMallocTrackingCriticalSection critsecobj; #if GPMEM_ALLOC_CHK_LIST void *p = GpMallocDebug(size, fileName, lineNumber); #else void *p = GpMalloc(size); #endif if(p) { AllocTrackHeader *hdr = (AllocTrackHeader *)( (unsigned char *)p-(GPMEM_GS+sizeof(AllocTrackHeader))); hdr->flags |= APIAllocation; } return p; } #else // !GPMEM_ALLOC_CHK extern "C" void *GpMallocAPI(size_t size, unsigned int caddr) { return GpMalloc(size); } #endif // !GPMEM_ALLOC_CHK #endif // DBG /**************************************************************************\ * * Function Description: * * Computes the original size of a memory block allocated under GPMEM_ALLOC_CHK * * Arguments: * * [IN] p - current memory block * * Return Value: * * size of the original request for a memory block (i.e. excluding guard * areas, headers, etc). The size returned is the DWORD aligned size - so it * may differ slighly from the original size requested. * * Notes: * * Returns a size of zero if called with NULL * Only compiled under GPMEM_ALLOC_CHK * * History: * * 09/14/1999 asecchia * Created it. * \**************************************************************************/ #if GPMEM_ALLOC_CHK extern "C" size_t GpSizeBlock(void *p) { if(p) { // Find the beginning of the allocated block header. p = (char *)p-(GPMEM_GS+sizeof(AllocTrackHeader)); ASSERT( HeapSize(GpMemHeap, GPMEMHEAPFLAGS, p) >= ((AllocTrackHeader *)p)->size ); // Compute the size of the allocated block's data area. return (((AllocTrackHeader *)p)->size - (GPMEM_GS+GPMEM_GE+sizeof(AllocTrackHeader))); } else { return 0; } } #else // Non-debug build, just call HeapSize #define GpSizeBlock(p) HeapSize(GpMemHeap, GPMEMHEAPFLAGS, p) #endif /**************************************************************************\ * * Function Description: * * Reallocates a memory block. * * Arguments: * * [IN] memblock - current memory block * [IN] size - new allocation size * * Return Value: * * A pointer to the new block, or NULL on failure. * * Notes: * * If size is 0, frees the block. * If memblock is NULL, allocates a new block. * (If both, does nothing.) * * LocalReAlloc only grows if it can expand the current allocation * - otherwise it fails. * * History: * * 09/14/1999 asecchia * Added the checked build memory guard code. * 07/08/1999 agodfrey * Created it. * \**************************************************************************/ extern "C" void *GpRealloc(void *memblock, size_t size) { #if GPMEM_ALLOC_CHK gpmemAllocTotal.CumulativeReallocs++; #endif // Free. if (!size) { if (memblock) GpFree(memblock); return NULL; } // Alloc. if (!memblock) { return GpMalloc(size); } // Realloc - Use GpMalloc/GpMemcpy/GpFree for debug so that the // extra buffers line up (would require duplicating the code // from the GpMalloc/GpFree functions otherwise #if GPMEM_ALLOC_CHK VOID * p = GpMalloc(size); if (p != NULL) { size_t oldSize = GpSizeBlock(memblock); // Are we shrinking the block? if (oldSize > size) { oldSize = size; } GpMemcpy(p, memblock, oldSize); GpFree(memblock); } #else // !GPMEM_ALLOC_CHK #if PROFILE_MEMORY_USAGE MC_LogAllocation(size); #endif VOID *p = HeapReAlloc(GpMemHeap, GPMEMHEAPFLAGS, memblock, size); #endif // !GPMEM_ALLOC_CHK return p; } /**************************************************************************\ * * Function Description: * * Frees a block of memory. * * Arguments: * * [IN] memblock - block to free * * Notes: * * If memblock is NULL, does nothing. * * History: * * 09/14/1999 asecchia * Added the checked build memory guard code. * 07/08/1999 agodfrey * Created it. * \**************************************************************************/ extern "C" void GpFree(void *memblock) { // Do nothing if the pointer is NULL. if(memblock) { #if GPMEM_ALLOC_CHK // If we're playing with the tracking headers, we need to be thread safe. GpMallocTrackingCriticalSection critsecobj; memblock = (unsigned char *)memblock-(GPMEM_GS+sizeof(AllocTrackHeader)); // Let's do the header stuff. AllocTrackHeader *hdr = (AllocTrackHeader *)memblock; DWORD size = hdr->size; gpmemAllocTotal.Freed(size); ASSERTMSG( (hdr->flags & MemoryAllocated) && !(hdr->flags & MemoryFreed), ("GpFree() already freed memory %p (freed by GpFree())", memblock) ); hdr->flags &= ~MemoryAllocated; hdr->flags |= MemoryFreed; ASSERTMSG( HeapSize(GpMemHeap, GPMEMHEAPFLAGS, memblock) >= hdr->size, ( "GpFree() already freed memory %p (freed somewhere else?)" " local size=%d, size=%d", memblock, HeapSize(GpMemHeap, GPMEMHEAPFLAGS, memblock), hdr->size ) ); if(GPMEM_ALLOC_TRACKING) { // Useful on checked Win2k builds because they fill guard // area with 0xFEEEFEEE ASSERTMSG((hdr->flink == NULL) || ((DWORD)((ULONG_PTR)(hdr->flink->blink) & 0xFFFFFFFF) != 0xFEEEFEEE), ("GpFree() updating forward link to freed page, header %p", memblock)); ASSERTMSG((hdr->blink == NULL) || ((DWORD)((ULONG_PTR)(hdr->blink->flink) & 0xFFFFFFFF) != 0xFEEEFEEE), ("GpFree() updating backward link to freed page, header %p", memblock)); if(hdr->flink) hdr->flink->blink = hdr->blink; if(hdr->blink) hdr->blink->flink = hdr->flink; if(gpmemAllocList==memblock) gpmemAllocList = hdr->flink; } else { ASSERTMSG(hdr->flink==NULL, ("GpFree() corrupt header %p", memblock)); ASSERTMSG(hdr->blink==NULL, ("GpFree() corrupt header %p", memblock)); } int i; unsigned char *p; // Check the start guard area if(GPMEM_GUARD_START) { p = (unsigned char *)memblock+sizeof(AllocTrackHeader); for(i=0; i