/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: memory.c Abstract: Memory handling routines for Windows NT Setup API dll. Author: Ted Miller (tedm) 11-Jan-1995 Revision History: Jamie Hunter (jamiehun) 13-Feb-1998 Improved this further for debugging added linked list, alloc tracing, memory fills and memory leak detection jamiehun 30-April-1998 Added some more consistancy checks Put try/except around access jimschm 27-Oct-1998 Wrote fast allocation routines to speed up setupapi.dll on Win9x JamieHun Jun-26-2000 Moved to sputils Changed to use a private heap --*/ #include "precomp.h" #pragma hdrstop static BOOL Initialized = FALSE; static HANDLE _pSpUtilsHeap = NULL; #define ALLOC(x) HeapAlloc(_pSpUtilsHeap,0,x) #define FREE(x) HeapFree(_pSpUtilsHeap,0,x) #define REALLOC(x,y) HeapReAlloc(_pSpUtilsHeap,0,x,y) #define MEMSIZE(x) HeapSize(_pSpUtilsHeap,0,x) #define INITIALHEAPSIZE (0x100000) // // Internal debugging features // #if MEM_DBG #define MEMERROR(x) _pSpUtilsAssertFail(__FILE__,__LINE__,#x) DWORD _pSpUtilsDbgAllocNum = 0; DWORD _pSpUtilsMemoryFlags = 0; struct _MemHeader { struct _MemHeader * PrevAlloc; // previous on chain struct _MemHeader * NextAlloc; // next on chain DWORD MemoryTag; // tag - to pair off Malloc/Free DWORD BlockSize; // bytes of "real" data DWORD AllocNum; // number of this allocation, ie AllocCount at the time this was allocated PCSTR AllocFile; // name of file that did allocation, if set DWORD AllocLine; // line of this allocation DWORD HeadMemSig; // head-check, stop writing before actual data BYTE Data[sizeof(DWORD)]; // size allows for tail-check at end of actual data }; struct _MemStats { struct _MemHeader * FirstAlloc; // will be NULL if no allocations, else earliest malloc/realloc in chain struct _MemHeader * LastAlloc; // last alloc/realloc goes to end of chain DWORD MemoryAllocated; // bytes, excluding headers DWORD AllocCount; // incremented for every alloc DWORD ReallocCount; // incremented for every realloc DWORD FreeCount; // incremented for every free BOOL DoneInitDebugMutex; CRITICAL_SECTION DebugMutex; // We need a mutex to manage memstats, setupapi is MT } _pSpUtilsMemStats = { NULL, NULL, 0, 0, 0, 0, FALSE, 0 }; // // Checked builds have a block head/tail check // and extra statistics // #define HEAD_MEMSIG 0x4d444554 // = MDET (MSB to LSB) or TEDM (LSB to MSB) #define TAIL_MEMSIG 0x5445444d // = TEDM (MSB to LSB) or MDET (LSB to MSB) #define MEM_ALLOCCHAR 0xdd // makes sure we fill with non-null #define MEM_FREECHAR 0xee // if we see this, memory has been de-allocated #define MEM_DEADSIG 0xdeaddead #define MEM_TOOBIG 0x80000000 // use this to pick up big allocs #define MemMutexLock() EnterCriticalSection(&_pSpUtilsMemStats.DebugMutex) #define MemMutexUnlock() LeaveCriticalSection(&_pSpUtilsMemStats.DebugMutex) static BOOL MemBlockCheck( struct _MemHeader * Mem ) /*++ Routine Description: Verify a block header is valid Arguments: Mem = Header to verify Returns: TRUE if valid FALSE if not valid ++*/ { if (Mem == NULL) { return TRUE; } if (Mem->HeadMemSig != HEAD_MEMSIG) { MEMERROR("Internal heap error - HeadMemSig invalid"); return FALSE; } if (Mem->BlockSize >= MEM_TOOBIG) { MEMERROR("Internal heap error - BlockSize too big"); return FALSE; } if((Mem->PrevAlloc == Mem) || (Mem->NextAlloc == Mem)) { // // we should have failed the MEMSIG, but it's ok as an extra check // MEMERROR("Internal heap error - self link"); return FALSE; } if ((*(DWORD UNALIGNED *)(Mem->Data+Mem->BlockSize)) != TAIL_MEMSIG) { MEMERROR("Internal heap error - TailMemSig invalid"); return FALSE; } return TRUE; } static struct _MemHeader * MemBlockGet( IN PVOID Block ) /*++ Routine Description: Verify a block is valid, and return real memory pointer Arguments: Block - address the application uses ++*/ { struct _MemHeader * Mem; if((DWORD_PTR)Block < offsetof(struct _MemHeader,Data[0])) { MEMERROR("Internal heap error - Block address is invalid"); return NULL; } Mem = (struct _MemHeader *)(((PBYTE)Block) - offsetof(struct _MemHeader,Data[0])); if (MemBlockCheck(Mem)==FALSE) { // // block fails test // return NULL; } if(Mem->PrevAlloc != NULL) { if(MemBlockCheck(Mem->PrevAlloc)==FALSE) { // // back link is invalid // return NULL; } } else if (_pSpUtilsMemStats.FirstAlloc != Mem) { // // _pSpUtilsMemStats.FirstAlloc is invalid wrt Mem // MEMERROR("Internal heap error - FirstAlloc invalid"); return NULL; } if(Mem->NextAlloc != NULL) { if(MemBlockCheck(Mem->NextAlloc)==FALSE) { // // forward link is invalid // return NULL; } } else if (_pSpUtilsMemStats.LastAlloc != Mem) { // // _pSpUtilsMemStats.LastAlloc is invalid wrt Mem // MEMERROR("Internal heap error - LastAlloc invalid"); return NULL; } // // seems pretty good // return Mem; } static PVOID MemBlockLink( struct _MemHeader * Mem ) { if (Mem == NULL) { return NULL; } Mem->PrevAlloc = _pSpUtilsMemStats.LastAlloc; Mem->NextAlloc = NULL; _pSpUtilsMemStats.LastAlloc = Mem; if (Mem->PrevAlloc == NULL) { _pSpUtilsMemStats.FirstAlloc = Mem; } else { if (MemBlockCheck(Mem->PrevAlloc)) { Mem->PrevAlloc->NextAlloc = Mem; } } Mem->HeadMemSig = HEAD_MEMSIG; *(DWORD UNALIGNED *)(Mem->Data+Mem->BlockSize) = TAIL_MEMSIG; return (PVOID)(Mem->Data); } static PVOID MemBlockUnLink( struct _MemHeader * Mem ) { if (Mem == NULL) { return NULL; } if((Mem->PrevAlloc == Mem) || (Mem->NextAlloc == Mem) || (Mem->HeadMemSig == MEM_DEADSIG)) { MEMERROR("Internal heap error - MemBlockUnLink"); } if (Mem->PrevAlloc == NULL) { _pSpUtilsMemStats.FirstAlloc = Mem->NextAlloc; } else { Mem->PrevAlloc->NextAlloc = Mem->NextAlloc; } if (Mem->NextAlloc == NULL) { _pSpUtilsMemStats.LastAlloc = Mem->PrevAlloc; } else { Mem->NextAlloc->PrevAlloc = Mem->PrevAlloc; } Mem->PrevAlloc = Mem; // make pointers harmless and also adds as an exta debug check Mem->NextAlloc = Mem; // make pointers harmless and also adds as an exta debug check Mem->HeadMemSig = MEM_DEADSIG; *(DWORD UNALIGNED *)(Mem->Data+Mem->BlockSize) = MEM_DEADSIG; return Mem->Data; } static BOOL MemDebugInitialize( VOID ) { try { InitializeCriticalSection(&_pSpUtilsMemStats.DebugMutex); _pSpUtilsMemStats.DoneInitDebugMutex = TRUE; } except(EXCEPTION_EXECUTE_HANDLER) { } return _pSpUtilsMemStats.DoneInitDebugMutex; } static BOOL MemDebugUninitialize( VOID ) { struct _MemHeader *Mem; TCHAR Msg[1024]; TCHAR Process[MAX_PATH]; // // Dump the leaks // Mem = _pSpUtilsMemStats.FirstAlloc; GetModuleFileName( GetModuleHandle(NULL),Process, sizeof(Process)/sizeof(TCHAR)); while (Mem) { wsprintf (Msg, TEXT("SPUTILS: Leak (%d bytes) at %hs line %u (allocation #%d) in process %s \r\n"), Mem->BlockSize, Mem->AllocFile, Mem->AllocLine, Mem->AllocNum, Process ); pSetupDebugPrintEx(DPFLTR_WARNING_LEVEL, Msg); if (_pSpUtilsMemoryFlags != 0) { if (Mem->BlockSize > 1024) { pSetupDebugPrintEx(DPFLTR_ERROR_LEVEL, TEXT("Leak of > 1K. Calling DebugBreak.\n")); DebugBreak(); } } Mem = Mem->NextAlloc; } // // Clean up // if(_pSpUtilsMemStats.DoneInitDebugMutex) { DeleteCriticalSection(&_pSpUtilsMemStats.DebugMutex); } // // any last minute checks // return TRUE; } #endif // MEM_DBG // // published functions // PVOID pSetupDebugMallocWithTag( IN DWORD Size, IN PCSTR Filename, IN DWORD Line, IN DWORD Tag ) /*++ Routine Description: Debug version of Malloc Resulting allocated block has prefix/suffix and is filled with MEM_ALLOCCHAR Arguments: Size - size in bytes of block to be allocated. The size may be 0. Filename/Line - debugging information Tag - match malloc with free/realloc's Return Value: Pointer to block of memory, or NULL if a block could not be allocated. --*/ { #if MEM_DBG struct _MemHeader *Mem; PVOID Ptr = NULL; BOOL locked = FALSE; LPEXCEPTION_POINTERS ExceptionPointers = NULL; MYASSERT(Initialized); try { MemMutexLock(); locked = TRUE; _pSpUtilsMemStats.AllocCount++; if (Size >= MEM_TOOBIG) { MEMERROR("pSetupDebugMalloc - requested size too big (negative?)"); leave; } if((Mem = (struct _MemHeader*) ALLOC(Size+sizeof(struct _MemHeader))) == NULL) { leave; // it failed ALLOC, but prob not due to a bug } Mem->MemoryTag = Tag; Mem->BlockSize = Size; Mem->AllocNum = _pSpUtilsMemStats.AllocCount; Mem->AllocFile = Filename; Mem->AllocLine = Line; // init memory we have allocated (to make sure we don't accidently get zero's) FillMemory(Mem->Data,Size,MEM_ALLOCCHAR); _pSpUtilsMemStats.MemoryAllocated += Size; Ptr = MemBlockLink(Mem); if (_pSpUtilsMemoryFlags && (_pSpUtilsDbgAllocNum == Mem->AllocNum)) { MEMERROR("_pSpUtilsDbgAllocNum hit"); } } except(ExceptionPointers = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { MEMERROR("pSetupDebugMalloc - Exception"); Ptr = NULL; } if(locked) { MemMutexUnlock(); } return Ptr; #else return ALLOC(Size); #endif } PVOID pSetupDebugMalloc( IN DWORD Size, IN PCSTR Filename, IN DWORD Line ) /*++ Routine Description: Allocate a chunk of memory. The memory is not zero-initialized. Arguments: Size - size in bytes of block to be allocated. The size may be 0. Return Value: Pointer to block of memory, or NULL if a block could not be allocated. --*/ { MYASSERT(Initialized); #if MEM_DBG return pSetupDebugMallocWithTag(Size, Filename , Line, 0); #else return ALLOC(Size); #endif } PVOID pSetupMalloc( IN DWORD Size ) /*++ Routine Description: Allocate a chunk of memory. The memory is not zero-initialized. Arguments: Size - size in bytes of block to be allocated. The size may be 0. Return Value: Pointer to block of memory, or NULL if a block could not be allocated. --*/ { MYASSERT(Initialized); #if MEM_DBG return pSetupDebugMallocWithTag(Size, NULL , 0, 0); #else return ALLOC(Size); #endif } PVOID pSetupReallocWithTag( IN PVOID Block, IN DWORD NewSize, IN DWORD Tag ) /*++ Routine Description: Realloc routine Debug/Non-Debug versions Note that a general assumption here, is that if NewSize <= OriginalSize the reallocation *should* not fail Arguments: Block - pointer to block to be reallocated. NewSize - new size in bytes of block. If the size is 0, this function works like pSetupFree, and the return value is NULL. Tag - match realloc with malloc Return Value: Pointer to block of memory, or NULL if a block could not be allocated. In that case the original block remains unchanged. --*/ { #if MEM_DBG PVOID p; DWORD OldSize; struct _MemHeader *Mem; PVOID Ptr = NULL; BOOL locked = FALSE; LPEXCEPTION_POINTERS ExceptionPointers = NULL; MYASSERT(Initialized); try { MemMutexLock(); locked = TRUE; _pSpUtilsMemStats.ReallocCount++; if (Block == NULL) { leave; } if (NewSize >= MEM_TOOBIG) { MEMERROR("pSetupRealloc - requested size too big (negative?)"); leave; } Mem = MemBlockGet(Block); if (Mem == NULL) { leave; } if (Mem->MemoryTag != Tag) { MEMERROR("pSetupRealloc - Tag mismatch"); leave; } OldSize = Mem->BlockSize; MemBlockUnLink(Mem); if (NewSize < OldSize) { // trash memory we're about to free FillMemory(Mem->Data+NewSize,OldSize-NewSize+sizeof(DWORD),MEM_FREECHAR); } if((p = REALLOC(Mem, NewSize+sizeof(struct _MemHeader))) == NULL) { // // failed to re-alloc // MemBlockLink(Mem); leave; } Mem = (struct _MemHeader*)p; Mem->BlockSize = NewSize; if (NewSize > OldSize) { // init extra memory we have allocated FillMemory(Mem->Data+OldSize,NewSize-OldSize,MEM_ALLOCCHAR); } _pSpUtilsMemStats.MemoryAllocated -= OldSize; _pSpUtilsMemStats.MemoryAllocated += NewSize; Ptr = MemBlockLink(Mem); } except(ExceptionPointers = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { MEMERROR("pSetupRealloc - Exception"); Ptr = NULL; } if(locked) { MemMutexUnlock(); } return Ptr; #else return REALLOC(Block, NewSize); #endif } PVOID pSetupRealloc( IN PVOID Block, IN DWORD NewSize ) /*++ Routine Description: Realloc routine Debug/Non-Debug versions Note that a general assumption here, is that if NewSize <= OriginalSize the reallocation *should* not fail Arguments: Block - pointer to block to be reallocated. NewSize - new size in bytes of block. If the size is 0, this function works like pSetupFree, and the return value is NULL. Return Value: Pointer to block of memory, or NULL if a block could not be allocated. In that case the original block remains unchanged. --*/ { #if MEM_DBG return pSetupReallocWithTag(Block,NewSize,0); #else return REALLOC(Block, NewSize); #endif } VOID pSetupFreeWithTag( IN CONST VOID *Block, IN DWORD Tag ) /*++ Routine Description: Free (debug/non-debug versions) Arguments: Buffer - pointer to block to be freed. Tag - match free with malloc Return Value: None. --*/ { #if MEM_DBG DWORD OldSize; struct _MemHeader *Mem; BOOL locked = FALSE; LPEXCEPTION_POINTERS ExceptionPointers = NULL; MYASSERT(Initialized); try { MemMutexLock(); locked = TRUE; _pSpUtilsMemStats.FreeCount++; if (Block == NULL) { leave; } Mem = MemBlockGet((PVOID)Block); if (Mem == NULL) { leave; } if (Mem->MemoryTag != Tag) { MEMERROR("pSetupFree - Tag mismatch"); leave; } OldSize = Mem->BlockSize; MemBlockUnLink(Mem); _pSpUtilsMemStats.MemoryAllocated -= OldSize; // // trash memory we're about to free, so we can immediately see it has been free'd!!!! // we keep head/tail stuff to have more info available when debugging // FillMemory((PVOID)Block,OldSize,MEM_FREECHAR); Mem->MemoryTag = (DWORD)(-1); FREE(Mem); } except(ExceptionPointers = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { MEMERROR("pSetupFree - Exception"); } if(locked) { MemMutexUnlock(); } #else FREE ((void *)Block); #endif } VOID pSetupFree( IN CONST VOID *Block ) /*++ Routine Description: Free (debug/non-debug versions) Arguments: Buffer - pointer to block to be freed. Return Value: None. --*/ { #if MEM_DBG pSetupFreeWithTag(Block,0); #else FREE ((void *)Block); #endif } HANDLE pSetupGetHeap( VOID ) { MYASSERT(Initialized); return _pSpUtilsHeap; } // // initialization functions // BOOL _pSpUtilsMemoryInitialize( VOID ) { #if MEM_DBG _pSpUtilsHeap = HeapCreate(0,INITIALHEAPSIZE,0); if(_pSpUtilsHeap == NULL) { return FALSE; } MemDebugInitialize(); #else _pSpUtilsHeap = GetProcessHeap(); #endif #if MEM_DBG #endif Initialized = TRUE; return TRUE; } BOOL _pSpUtilsMemoryUninitialize( VOID ) { if(Initialized) { #if MEM_DBG MemDebugUninitialize(); if(_pSpUtilsHeap) { HeapDestroy(_pSpUtilsHeap); _pSpUtilsHeap = NULL; } #endif Initialized = FALSE; } return TRUE; }