// EMERGENT GAME TECHNOLOGIES PROPRIETARY INFORMATION // // This software is supplied under the terms of a license agreement or // nondisclosure agreement with Emergent Game Technologies and may not // be copied or disclosed except in accordance with the terms of that // agreement. // // Copyright (c) 1996-2007 Emergent Game Technologies. // All Rights Reserved. // // Emergent Game Technologies, Chapel Hill, North Carolina 27517 // http://www.emergent.net //--------------------------------------------------------------------------- // Precompiled Header #include "NiSystemPCH.h" #include "NiMemTracker.h" #include "NiLog.h" const NiFLF NiFLF::UNKNOWN("",0,""); size_t NiMemTracker::ms_stBreakOnAllocID = INT_MAX; void* NiMemTracker::ms_pvBreakOnAllocRangeStart = NULL; void* NiMemTracker::ms_pvBreakOnAllocRangeEnd = NULL; const char* NiMemTracker::ms_pcBreakOnFunctionName = "@*"; NiMemTracker* NiMemTracker::ms_pkTracker = NULL; bool NiMemTracker::ms_bOutputLeaksToDebugStream = true; size_t NiMemTracker::ms_stBreakOnSizeRequested = INT_MAX; // These defines determine which channel is used for logging #define MEM_LOG_COMPLETE NIMESSAGE_MEMORY_0 #define MEM_LOG_LEAK NIMESSAGE_MEMORY_1 // Use this setting to determine whether or not to flush the log file // every time a line is sent or wait until the internal buffer is // full to flush the log file. #define MEM_FLUSH_LOG_ON_WRITE false //--------------------------------------------------------------------------- #ifdef __GNUC__ #define POSSIBLY_UNUSED __attribute__ ((__unused__)) #else #define POSSIBLY_UNUSED #endif //--------------------------------------------------------------------------- void NiMemTracker::Initialize() { m_stCurrentAllocID = 0; ResetSummaryStats(); #ifndef NI_LOGGER_DISABLE if (m_bWriteToLog) { char acLogPath[NI_MAX_PATH]; NIVERIFY(NiPath::GetExecutableDirectory(acLogPath, NI_MAX_PATH)); char acOverviewPath[NI_MAX_PATH]; char acLeakPath[NI_MAX_PATH]; NiSprintf(acOverviewPath, NI_MAX_PATH, "%sNiMemory-Overview.xml", acLogPath); NiSprintf(acLeakPath, NI_MAX_PATH, "%sNiMemory-Leaks.xml", acLogPath); m_iLoggerCompleteChannel = NiLogger::OpenLog(acOverviewPath, NiLogger::OPEN_OVERWRITE, MEM_FLUSH_LOG_ON_WRITE); m_iLoggerLeakChannel = NiLogger::OpenLog(acLeakPath, NiLogger::OPEN_OVERWRITE, MEM_FLUSH_LOG_ON_WRITE); if (m_iLoggerCompleteChannel != -1) { NiLogger::SetOutputToLogFile(MEM_LOG_COMPLETE, m_iLoggerCompleteChannel); } if (m_iLoggerLeakChannel != -1) { NiLogger::SetOutputToLogFile(MEM_LOG_LEAK, m_iLoggerLeakChannel); } #ifdef _PS3 time_t tCurrentTime = 0; struct tm* pkSysTime; struct tm* pkLocalTime; pkSysTime = gmtime(&tCurrentTime); pkLocalTime = gmtime(&tCurrentTime); #else #if defined(_MSC_VER) && (_MSC_VER >= 1400) __time64_t tCurrentTime; _time64(&tCurrentTime); struct tm kSysTime; struct tm* pkSysTime = &kSysTime; _gmtime64_s(pkSysTime, &tCurrentTime); struct tm kLocalTime; struct tm* pkLocalTime = &kLocalTime; _localtime64_s(pkLocalTime, &tCurrentTime); #else //#if defined(_MSC_VER) && (_MSC_VER >= 1400) time_t tCurrentTime; time(&tCurrentTime); struct tm* pkSysTime; pkSysTime = gmtime(&tCurrentTime); struct tm* pkLocalTime; pkLocalTime = localtime(&tCurrentTime); #endif //#if defined(_MSC_VER) && (_MSC_VER >= 1400) #endif NILOG(MEM_LOG_COMPLETE, "\n"); NILOG(MEM_LOG_COMPLETE, "\n"); NILOG(MEM_LOG_COMPLETE, "tm_mon+1, pkSysTime->tm_mday, 1900+pkSysTime->tm_year, pkSysTime->tm_hour, pkSysTime->tm_min, pkSysTime->tm_sec, pkLocalTime->tm_hour, pkLocalTime->tm_min, pkLocalTime->tm_sec); NILOG(MEM_LOG_COMPLETE, "\">\n"); NILOG(MEM_LOG_COMPLETE, "\n", NiGetCurrentTimeInSec()); NILOG(MEM_LOG_LEAK, "\n"); NILOG(MEM_LOG_LEAK, "\n"); NILOG(MEM_LOG_LEAK, "tm_mon+1, pkSysTime->tm_mday, 1900+pkSysTime->tm_year, pkSysTime->tm_hour, pkSysTime->tm_min, pkSysTime->tm_sec, pkLocalTime->tm_hour, pkLocalTime->tm_min, pkLocalTime->tm_sec); NILOG(MEM_LOG_LEAK, "\">\n"); } #endif // #ifndef NI_LOGGER_DISABLE if (m_pkActualAllocator) m_pkActualAllocator->Initialize(); #ifndef NI_LOGGER_DISABLE // Must create these after the allocators get initialized if (m_bWriteToLog) { CreateXSLForLeaks(); CreateXSLForFreeReport(); } #endif // #ifndef NI_LOGGER_DISABLE } //--------------------------------------------------------------------------- void NiMemTracker::CreateXSLForLeaks() { if (!m_bWriteToLog) return; char* pcXSLFile = "\n" "\n" "\n" "\n" " \n" "\n" "\n" " Memory Leak Report\n" "\n" "\n" "\n" "

Memory Leak Report

\n" "

Date:

\n" "

Summary:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
StatisticValue
Total Active Size bytes
Active Alloc Count
\n" " \n" "

Leaked Allocations:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Allocation #Size in bytesBirth TimeFileLineFunctionLong Filename
" "" "file:///\n" "
\n" " \n" "\n" "\n" "\n" "\n" "
\n" "\n"; char acLogPath[NI_MAX_PATH]; NIVERIFY(NiPath::GetExecutableDirectory(acLogPath, NI_MAX_PATH)); char acFilename[NI_MAX_PATH]; NiSprintf(acFilename, NI_MAX_PATH, "%smemoryleak.xsl", acLogPath); // Only write the XSL file if it doesn't already exist if (!NiFile::Access(acFilename, NiFile::READ_ONLY)) { NiFile kFile(acFilename, NiFile::WRITE_ONLY); kFile.PutS(pcXSLFile); } } //--------------------------------------------------------------------------- void NiMemTracker::CreateXSLForFreeReport() { if (!m_bWriteToLog) return; char* pcXSLFile = "\n" "\n" "\n" "\n" "\n" "\n" "\n" " \n" "\n" "\n" " Memory Report\n" "\n" "\n" "\n" "

Memory Report

\n" "

Date:

\n" "

Summary:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
StatisticValue
Application Lifetime seconds
Total Active Size bytes
Peak Active Size bytes
Accumulated Size bytes
Allocated But Unused Size bytes
Percent of Allocations Unused %
Active Alloc Count
Peak Active Alloc Count
Total Alloc Count
Total Tracker Overhead bytes
Peak Tracker Overhead bytes
Accumulated Overhead bytes
Total Active External Size bytes
Peak Active External Size bytes
Accumulated External Size bytes
Active External Alloc Count
Peak Active External Alloc Count
Total External Alloc Count
Allocation Sizes < 1024 bytes
Allocation Life Spans < 1 second
Percent Life Spans < 1 second %
Percent Sizes < 1024 bytes %" "
\n" " \n" "

Top 50 Largest Allocations:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Allocation #Size in bytesLife SpanFileLineFunction
" "
\n" " \n" "

Top 50 Smallest Allocations:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Allocation #Size in bytesLife SpanFileLineFunction
" "
\n" " \n" "

Top 50 Shortest-lived Allocations:

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Allocation #Size in bytesLife SpanFileLineFunction
" "
\n" " \n" "

Number of Allocations by File

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" " \n" " \n" " \n" "\n" " \n" "
FilenameCount
\n" " \n" "

Number of Allocations by Function

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" "
FunctionCount
\n" " \n" "

Number of Allocations by Size

\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" " \n" "
Size in bytesCount
\n" "\n" "\n" "\n" "\n" "\n" "
\n" "\n" "\n"; char acLogPath[NI_MAX_PATH]; NIVERIFY(NiPath::GetExecutableDirectory(acLogPath, NI_MAX_PATH)); char acFilename[NI_MAX_PATH]; NiSprintf(acFilename, NI_MAX_PATH, "%smemoryreport.xsl", acLogPath); // Only write the XSL file if it doesn't already exist if (!NiFile::Access(acFilename, NiFile::READ_ONLY)) { NiFile kFile(acFilename, NiFile::WRITE_ONLY); kFile.PutS(pcXSLFile); } } //--------------------------------------------------------------------------- void NiMemTracker::Shutdown() { if (m_stActiveAllocationCount != 0) { char acString[256]; NiSprintf(acString, 256, " There are %d leaked allocations" ".\n", m_stActiveAllocationCount); NiOutputDebugString("\n\n//----------------------------------------" "-----------------------------------\n"); NiOutputDebugString("// NiMemTracker Report:\n"); NiOutputDebugString("//-------------------------------------------" "--------------------------------\n"); NiOutputDebugString(acString); NiOutputDebugString("//-------------------------------------------" "--------------------------------\n"); OutputLeakedMemoryToDebugStream(); NiOutputDebugString("//-------------------------------------------" "--------------------------------\n"); } #ifndef NI_LOGGER_DISABLE if (m_bWriteToLog) { NILOG(MEM_LOG_COMPLETE, "
\n"); LogMemoryReport(); LogSummaryStats(); NILOG(MEM_LOG_COMPLETE, "
\n"); NILOG(MEM_LOG_LEAK, "\n"); NiLogger::CloseLog(m_iLoggerLeakChannel); NiLogger::CloseLog(m_iLoggerCompleteChannel); } #endif // #ifndef NI_LOGGER_DISABLE if (m_pkActualAllocator) m_pkActualAllocator->Shutdown(); } //--------------------------------------------------------------------------- void NiMemTracker::ResetSummaryStats() { m_fPeakMemoryTime = 0.0f; m_fPeakAllocationCountTime = 0.0f; m_stActiveMemory = 0; m_stPeakMemory = 0; m_stAccumulatedMemory = 0; m_stUnusedButAllocatedMemory = 0; m_stActiveAllocationCount = 0; m_stPeakAllocationCount = 0; m_stAccumulatedAllocationCount = 0; m_stActiveExternalMemory = 0; m_stPeakExternalMemory = 0; m_stAccumulatedExternalMemory = 0; m_stActiveExternalAllocationCount = 0; m_stPeakExternalAllocationCount = 0; m_stAccumulatedExternalAllocationCount = 0; } //--------------------------------------------------------------------------- NiMemTracker::NiMemTracker(NiAllocator* pkActualAllocator, bool bWriteToLog, unsigned int uiInitialSize, unsigned int uiGrowBy, bool bAlwaysValidateAll, bool bCheckArrayOverruns) : m_fPeakMemoryTime(0.0f), m_fPeakAllocationCountTime(0.0f), m_bAlwaysValidateAll(bAlwaysValidateAll), m_stReservoirGrowBy(uiGrowBy), m_bCheckArrayOverruns(bCheckArrayOverruns), m_ucFillChar(0xbd), m_bWriteToLog(bWriteToLog) { ms_pkTracker = this; m_pkActualAllocator = pkActualAllocator; memset(m_pkActiveMem, 0, ms_uiHashSize*sizeof(NiAllocUnit*)); if (uiInitialSize > 0) { // create initial set of tracking data structures m_stReservoirBufferSize = 1; m_pkReservoir = (NiAllocUnit*)calloc(uiInitialSize, sizeof(NiAllocUnit)); // If you hit this NIMEMASSERT, then the memory manager failed to // allocate internal memory for tracking the allocations NIMEMASSERT(m_pkReservoir != NULL); // throw an exception in this situation if (m_pkReservoir == NULL) throw "Unable to allocate RAM for internal memory tracking data"; // initialize the allocation units for (unsigned int i = 0; i < uiInitialSize-1; i++) { m_pkReservoir[i].m_kFLF = NiFLF::UNKNOWN; m_pkReservoir[i].m_pkNext = &m_pkReservoir[i+1]; } m_pkReservoir[uiInitialSize-1].m_kFLF = NiFLF::UNKNOWN; m_pkReservoir[uiInitialSize-1].m_pkNext = NULL; m_ppkReservoirBuffer = (NiAllocUnit**) NiExternalMalloc(sizeof(NiAllocUnit*)); // If you hit this NIMEMASSERT, then the memory manager failed to // allocate internal memory for tracking the allocations NIMEMASSERT(m_ppkReservoirBuffer != NULL); // throw an exception in this situation if (m_ppkReservoirBuffer == NULL) throw "Unable to allocate RAM for internal memory tracking data"; m_ppkReservoirBuffer[0] = m_pkReservoir; unsigned int uiInitialBytes = uiInitialSize*sizeof(NiAllocUnit) + sizeof(NiAllocUnit*); m_stActiveTrackerOverhead = uiInitialBytes; m_stPeakTrackerOverhead = uiInitialBytes; m_stAccumulatedTrackerOverhead = uiInitialBytes; } else { m_stReservoirBufferSize = 0; } } //--------------------------------------------------------------------------- NiMemTracker::~NiMemTracker() { // Free all memory used by the hash table of alloc units if (m_ppkReservoirBuffer) { for (unsigned int i = 0; i < m_stReservoirBufferSize; i++) { NiExternalFree(m_ppkReservoirBuffer[i]); } NiExternalFree(m_ppkReservoirBuffer); } ms_pkTracker = NULL; NiExternalDelete m_pkActualAllocator; } //--------------------------------------------------------------------------- void NiMemTracker::GrowReservoir() { NIMEMASSERT(!m_pkReservoir); m_pkReservoir = NiExternalAlloc(NiAllocUnit, m_stReservoirGrowBy); // If you hit this NIMEMASSERT, then the memory manager failed to allocate // internal memory for tracking the allocations NIMEMASSERT(m_pkReservoir != NULL); // throw an exception in this situation if (m_pkReservoir == NULL) throw "Unable to allocate RAM for internal memory tracking data"; m_stActiveTrackerOverhead += sizeof(NiAllocUnit) * m_stReservoirGrowBy + sizeof(NiAllocUnit*); if (m_stActiveTrackerOverhead > m_stPeakTrackerOverhead) { m_stPeakTrackerOverhead = m_stActiveTrackerOverhead; } m_stAccumulatedTrackerOverhead += sizeof(NiAllocUnit) * m_stReservoirGrowBy + sizeof(NiAllocUnit*); // Build a linked-list of the elements in the reservoir // Initialize the allocation units for (unsigned int i = 0; i < m_stReservoirGrowBy-1; i++) { m_pkReservoir[i].m_kFLF = NiFLF::UNKNOWN; m_pkReservoir[i].m_pkNext = &m_pkReservoir[i+1]; } m_pkReservoir[m_stReservoirGrowBy-1].m_kFLF = NiFLF::UNKNOWN; m_pkReservoir[m_stReservoirGrowBy-1].m_pkNext = NULL; // Add this address to the reservoir buffer so it can be freed later NiAllocUnit **pkTemp = (NiAllocUnit**) NiExternalRealloc( m_ppkReservoirBuffer, (m_stReservoirBufferSize + 1) * sizeof(NiAllocUnit*)); // If you hit this NIMEMASSERT, then the memory manager failed to allocate // internal memory for tracking the allocations NIMEMASSERT(pkTemp != NULL); if (pkTemp) { m_ppkReservoirBuffer = pkTemp; m_ppkReservoirBuffer[m_stReservoirBufferSize] = m_pkReservoir; m_stReservoirBufferSize++; } else { // throw an exception in this situation throw "Unable to allocate RAM for internal memory tracking data"; } } //--------------------------------------------------------------------------- void* NiMemTracker::Allocate( size_t& stSizeInBytes, size_t& stAlignment, NiMemEventType eEventType, bool bProvideAccurateSizeOnDeallocate, const char* pcFile, int iLine, const char* pcFunction) { m_kCriticalSection.Lock(); size_t stSizeOriginal = stSizeInBytes; float fTime = NiGetCurrentTimeInSec(); if (m_bCheckArrayOverruns) { stSizeInBytes= PadForArrayOverrun(stAlignment, stSizeInBytes); } // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // function name. NIMEMASSERT(strcmp(ms_pcBreakOnFunctionName, pcFunction) != 0); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation ID. NIMEMASSERT(ms_stBreakOnAllocID != m_stCurrentAllocID); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation request size. NIMEMASSERT(ms_stBreakOnSizeRequested != stSizeOriginal); // Actually perform the allocation. Note that size and alignment // may be adjusted by the call. void* pvMem = m_pkActualAllocator->Allocate(stSizeInBytes, stAlignment, eEventType, bProvideAccurateSizeOnDeallocate, pcFile, iLine, pcFunction); if (pvMem == NULL) { // If you hit this, your memory request was not satisfied m_kCriticalSection.Unlock(); return NULL; } // update summary statistics m_stActiveAllocationCount++; m_stAccumulatedAllocationCount++; if (m_stActiveAllocationCount > m_stPeakAllocationCount) { m_stPeakAllocationCount = m_stActiveAllocationCount; m_fPeakAllocationCountTime = fTime; } m_stActiveMemory += stSizeInBytes; m_stAccumulatedMemory += stSizeInBytes; if (m_stActiveMemory > m_stPeakMemory) { m_stPeakMemory = m_stActiveMemory; m_fPeakMemoryTime = fTime; } // If you hit this NIMEMASSERT, your memory request result was smaller // than the input. NIMEMASSERT(stSizeInBytes >= stSizeOriginal); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation address range. NIMEMASSERT(pvMem < ms_pvBreakOnAllocRangeStart || pvMem > ms_pvBreakOnAllocRangeEnd); // Pad the start and end of the allocation with the pad character // so that we can check for array under and overruns. Note that the // address is shifted to hide the padding before the allocation. if (m_bCheckArrayOverruns) { MemoryFillForArrayOverrun(pvMem, stAlignment, stSizeOriginal); } // Fill the originally requested memory size with the pad character. // This will allow us to see how much of the allocation was // actually used. MemoryFillWithPattern(pvMem, stSizeOriginal); #ifdef NIMEMORY_ENABLE_EXCEPTIONS try { #endif // If you hit this NIMEMASSERT, the somehow you have allocated a memory // unit to an address that already exists. This should never happen // and is an indicator that something has gone wrong in the sub-allocator. NIMEMASSERT(FindAllocUnit(pvMem) == NULL); // Grow the tracking unit reservoir if necessary if (!m_pkReservoir) GrowReservoir(); // If you hit this NIMEMASSERT, the free store for allocation units // does not exist. This should only happen if the reservoir // needed to grow and was unable to satisfy the request. In other words, // you may be out of memory. NIMEMASSERT (m_pkReservoir != NULL); // Get an allocation unit from the reservoir NiAllocUnit* pkUnit = m_pkReservoir; m_pkReservoir = pkUnit->m_pkNext; // fill in the known information pkUnit->Reset(); pkUnit->m_stAllocationID = m_stCurrentAllocID; pkUnit->m_stAlignment = stAlignment; pkUnit->m_ulAllocThreadId = NiGetCurrentThreadId(); pkUnit->m_eAllocType = eEventType; pkUnit->m_fAllocTime = fTime; pkUnit->m_pvMem = pvMem; pkUnit->m_stSizeRequested = stSizeOriginal; pkUnit->m_stSizeAllocated = stSizeInBytes; pkUnit->m_kFLF.Set(pcFile, iLine, pcFunction); // If you hit this NIMEMASSERT, then this allocation was made from a // source that isn't setup to use this memory tracking software, use the // stack frame to locate the source and include NiMemManager.h. NIMEMASSERTUNIT(eEventType != NI_UNKNOWN, pkUnit); // Insert the new allocation into the hash table InsertAllocUnit(pkUnit); // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } #ifdef NIMEMORY_ENABLE_EXCEPTIONS } catch(const char *err) { // Deal with the errors // Deal with the errors } #endif ++m_stCurrentAllocID; m_kCriticalSection.Unlock(); return pvMem; } //--------------------------------------------------------------------------- void NiMemTracker::InsertAllocUnit(NiAllocUnit* pkUnit) { NIMEMASSERT(pkUnit != NULL && pkUnit->m_pvMem != NULL); unsigned int uiHashIndex = AddressToHashIndex(pkUnit->m_pvMem); // Remap the new allocation unit to the head of the hash entry if (m_pkActiveMem[uiHashIndex]) { NIMEMASSERTUNIT(m_pkActiveMem[uiHashIndex]->m_pkPrev == NULL, pkUnit); m_pkActiveMem[uiHashIndex]->m_pkPrev = pkUnit; } NIMEMASSERTUNIT(pkUnit->m_pkNext == NULL, pkUnit); pkUnit->m_pkNext = m_pkActiveMem[uiHashIndex]; pkUnit->m_pkPrev = NULL; m_pkActiveMem[uiHashIndex] = pkUnit; } //--------------------------------------------------------------------------- void NiMemTracker::RemoveAllocUnit(NiAllocUnit* pkUnit) { NIMEMASSERT(pkUnit != NULL && pkUnit->m_pvMem != NULL); unsigned int uiHashIndex = AddressToHashIndex(pkUnit->m_pvMem); // If you hit this NIMEMASSERT, somehow we have emptied the // hash table for this bucket. This should not happen // and is indicative of a serious error in the memory // tracking infrastructure. NIMEMASSERTUNIT(m_pkActiveMem[uiHashIndex] != NULL, pkUnit); if (m_pkActiveMem[uiHashIndex] == pkUnit) { NIMEMASSERTUNIT(pkUnit->m_pkPrev == NULL, pkUnit); m_pkActiveMem[uiHashIndex] = pkUnit->m_pkNext; if (m_pkActiveMem[uiHashIndex]) m_pkActiveMem[uiHashIndex]->m_pkPrev = NULL; } else { if (pkUnit->m_pkPrev) { NIMEMASSERTUNIT(pkUnit->m_pkPrev->m_pkNext == pkUnit, pkUnit); pkUnit->m_pkPrev->m_pkNext = pkUnit->m_pkNext; } if (pkUnit->m_pkNext) { NIMEMASSERTUNIT(pkUnit->m_pkNext->m_pkPrev == pkUnit, pkUnit); pkUnit->m_pkNext->m_pkPrev = pkUnit->m_pkPrev; } } } //--------------------------------------------------------------------------- void* NiMemTracker::Reallocate( void* pvMemory, size_t& stSizeInBytes, size_t& stAlignment, NiMemEventType eEventType, bool bProvideAccurateSizeOnDeallocate, size_t stSizeCurrent, const char* pcFile, int iLine, const char* pcFunction) { // Store the original request size for later use. size_t stSizeOriginal = stSizeInBytes; // If the address is null, then this should function just // like an allocation routine. if (pvMemory == NULL) { return Allocate(stSizeInBytes, stAlignment, eEventType, bProvideAccurateSizeOnDeallocate, pcFile, iLine, pcFunction); } // If the requested size is 0, then this should function // just like a deallocation routine. if (stSizeInBytes== 0) { Deallocate(pvMemory, eEventType, NI_MEM_DEALLOC_SIZE_DEFAULT); return NULL; } m_kCriticalSection.Lock(); float fTime = NiGetCurrentTimeInSec(); #ifdef NIMEMORY_ENABLE_EXCEPTIONS try { #endif // A reallocation is tracked with two allocation units. The first // is the old unit. The second unit tracks the new allocation, which // may or may not have the same address as the old allocation. // Its allocation type is set to the event type passed into this function. NiAllocUnit* pkUnit = FindAllocUnit(pvMemory); if (pkUnit == NULL) { // If you hit this NIMEMASSERT, you tried to reallocate RAM that // wasn't allocated by this memory manager. NIMEMASSERT(pkUnit != NULL); #ifdef NIMEMORY_ENABLE_EXCEPTIONS throw "Request to reallocate RAM that was never allocated"; #endif m_kCriticalSection.Unlock(); return NULL; } // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // function name. NIMEMASSERT(strcmp(ms_pcBreakOnFunctionName, pcFunction) != 0); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation ID. NIMEMASSERT(ms_stBreakOnAllocID != pkUnit->m_stAllocationID); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation request size. NIMEMASSERT(ms_stBreakOnSizeRequested != pkUnit->m_stSizeRequested); // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation address range. NIMEMASSERT(pkUnit->m_pvMem < ms_pvBreakOnAllocRangeStart || pkUnit->m_pvMem > ms_pvBreakOnAllocRangeEnd); // If you hit this NIMEMASSERT, then the allocation unit that is about // to be deleted requested an initial size that doesn't match // what is currently the 'size' argument for deallocation. // This is most commonly caused by the lack of a virtual destructor // for a class that is used polymorphically in an array. if (stSizeCurrent != NI_MEM_DEALLOC_SIZE_DEFAULT) NIMEMASSERT(stSizeCurrent == pkUnit->m_stSizeRequested); if (m_bCheckArrayOverruns) { // If you hit this NIMEMASSERT, you have code that overwrites // either before or after the range of an allocation. // Check the pkUnit for information about which allocation // is being overwritten. NIMEMASSERT(!CheckForArrayOverrun(pvMemory, pkUnit->m_stAlignment, pkUnit->m_stSizeRequested)); stSizeInBytes= PadForArrayOverrun(stAlignment, stSizeInBytes); if (stSizeCurrent != NI_MEM_DEALLOC_SIZE_DEFAULT) { stSizeCurrent = PadForArrayOverrun(pkUnit->m_stAlignment, stSizeCurrent); } } // If you hit this NIMEMASSERT, then the allocation unit that is about to // be reallocated is damaged. NIMEMASSERT(ValidateAllocUnit(pkUnit)); // Alignment should be the same between reallocations NIMEMASSERT(pkUnit->m_stAlignment == stAlignment); // Determine how much memory was actually set size_t stSizeUnused = MemoryBytesWithPattern(pkUnit->m_pvMem, pkUnit->m_stSizeRequested); m_stUnusedButAllocatedMemory += stSizeUnused; // Save the thread id that freed the memory unsigned long ulFreeThreadId = NiGetCurrentThreadId(); // Perform the actual memory reallocation void* pvNewMemory = m_pkActualAllocator->Reallocate(pvMemory, stSizeInBytes, stAlignment, eEventType, bProvideAccurateSizeOnDeallocate, stSizeCurrent, pcFile, iLine, pcFunction); // If you hit this NIMEMASSERT, then the reallocation was unable to // be satisfied NIMEMASSERT(pvNewMemory != NULL); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation address range. NIMEMASSERT(pvNewMemory < ms_pvBreakOnAllocRangeStart || pvNewMemory > ms_pvBreakOnAllocRangeEnd); // update summary statistics size_t stPreReallocSize = pkUnit->m_stSizeRequested; m_stAccumulatedAllocationCount++; int iDeltaSize = stSizeInBytes - pkUnit->m_stSizeAllocated; m_stActiveMemory += iDeltaSize; if (iDeltaSize > 0) m_stAccumulatedMemory += iDeltaSize; if (m_stActiveMemory > m_stPeakMemory) { m_stPeakMemory = m_stActiveMemory; m_fPeakMemoryTime = fTime; } // Pad the start and end of the allocation with the pad character // so that we can check for array under and overruns. if (m_bCheckArrayOverruns) { MemoryFillForArrayOverrun(pvNewMemory, stAlignment, stSizeOriginal); } // Fill the originally requested memory size with the pad character. // This will allow us to see how much of the allocation was // actually used. // For reallocation, we only want to fill the unused section // that was just allocated. if (iDeltaSize > 0) { MemoryFillWithPattern((char*)pvNewMemory + stPreReallocSize, iDeltaSize); } // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation ID NIMEMASSERT(m_stCurrentAllocID != ms_stBreakOnAllocID); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation request size. NIMEMASSERT(ms_stBreakOnSizeRequested != stSizeOriginal); // If you hit this NIMEMASSERT, then this reallocation was made from a // that isn't setup to use this memory tracking software, use the stack // source frame to locate the source and include NiMemManager.h. NIMEMASSERT(eEventType != NI_UNKNOWN); // If you hit this NIMEMASSERT, you were trying to reallocate RAM that was // not allocated in a way that is compatible with realloc. In other // words, you have a allocation/reallocation mismatch. NIMEMASSERT(pkUnit->m_eAllocType == NI_MALLOC || pkUnit->m_eAllocType == NI_REALLOC || pkUnit->m_eAllocType == NI_ALIGNEDMALLOC || pkUnit->m_eAllocType == NI_ALIGNEDREALLOC); // update allocation unit NiMemEventType eDeallocType = eEventType; float fDeallocTime = fTime; // Write out the freed memory to the memory log LogAllocUnit(pkUnit, MEM_LOG_COMPLETE, "\t", eDeallocType, fDeallocTime, ulFreeThreadId, stSizeUnused); // Remove this allocation unit from the hash table RemoveAllocUnit(pkUnit); --m_stActiveAllocationCount; if (m_bAlwaysValidateAll) NIMEMASSERT(ValidateAllAllocUnits()); // Recycle the allocation unit // Add it to the front of the reservoir pkUnit->m_kFLF = NiFLF::UNKNOWN; pkUnit->m_pkNext = m_pkReservoir; m_pkReservoir = pkUnit; pkUnit = NULL; // Grow the tracking unit reservoir if necessary if (!m_pkReservoir) GrowReservoir(); // If you hit this NIMEMASSERT, the free store for allocation units // does not exist. This should only happen if the reservoir // needed to grow and was unable to satisfy the request. In other words, // you may be out of memory. NIMEMASSERT (m_pkReservoir != NULL); // Get an allocation unit from the reservoir NiAllocUnit* pkNewUnit = m_pkReservoir; m_pkReservoir = pkNewUnit->m_pkNext; // fill in the known information pkNewUnit->Reset(); pkNewUnit->m_stAllocationID = m_stCurrentAllocID; pkNewUnit->m_stAlignment = stAlignment; pkNewUnit->m_ulAllocThreadId = NiGetCurrentThreadId(); pkNewUnit->m_eAllocType = eEventType; pkNewUnit->m_fAllocTime = fTime; pkNewUnit->m_pvMem = pvNewMemory; pkNewUnit->m_stSizeAllocated = stSizeInBytes; pkNewUnit->m_stSizeRequested = stSizeOriginal; pkNewUnit->m_kFLF.Set(pcFile, iLine, pcFunction); // If you hit this NIMEMASSERT, then this allocation was made from a source // that isn't setup to use this memory tracking software, use the // stack frame to locate the source and include NiMemManager.h. NIMEMASSERT(eEventType != NI_UNKNOWN); // Insert the new allocation into the hash table InsertAllocUnit(pkNewUnit); ++m_stActiveAllocationCount; ++m_stCurrentAllocID; if (m_bAlwaysValidateAll) NIMEMASSERT(ValidateAllAllocUnits()); #ifdef NIMEMORY_ENABLE_EXCEPTIONS } catch(const char *err) { } #endif m_kCriticalSection.Unlock(); return pvNewMemory; } //--------------------------------------------------------------------------- void NiMemTracker::Deallocate( void* pvMemory, NiMemEventType eEventType, size_t stSizeInBytes) { if (pvMemory) { m_kCriticalSection.Lock(); #ifdef NIMEMORY_ENABLE_EXCEPTIONS try { #endif float fTime = NiGetCurrentTimeInSec(); // Search the tracking unit hash table to find the address NiAllocUnit* pkUnit = FindAllocUnit(pvMemory); if (pkUnit == NULL) { // If you hit this NIMEMASSERT, you tried to deallocate RAM that // wasn't allocated by this memory manager. This may also // be indicative of a double deletion. Please check the pkUnit // FLF for information about the allocation. NIMEMASSERT(pkUnit != NULL); #ifdef NIMEMORY_ENABLE_EXCEPTIONS throw "Request to deallocate RAM that was never allocated"; #endif m_kCriticalSection.Unlock(); return; } // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation ID. NIMEMASSERTUNIT(ms_stBreakOnAllocID != pkUnit->m_stAllocationID, pkUnit); // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation request size. NIMEMASSERTUNIT(ms_stBreakOnSizeRequested != pkUnit->m_stSizeRequested, pkUnit); // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation address range. NIMEMASSERTUNIT(pkUnit->m_pvMem < ms_pvBreakOnAllocRangeStart || pkUnit->m_pvMem > ms_pvBreakOnAllocRangeEnd, pkUnit); // If you hit this NIMEMASSERT, then the allocation unit that is about // to be deleted requested an initial size that doesn't match // what is currently the 'size' argument for deallocation. // This is most commonly caused by the lack of a virtual destructor // for a class that is used polymorphically in an array. if (stSizeInBytes != NI_MEM_DEALLOC_SIZE_DEFAULT) NIMEMASSERTUNIT(stSizeInBytes== pkUnit->m_stSizeRequested, pkUnit); // If you hit this NIMEMASSERT, then the allocation unit that is about // to be deallocated is damaged. bool POSSIBLY_UNUSED bValidateAllocUnit = ValidateAllocUnit(pkUnit); NIMEMASSERTUNIT(bValidateAllocUnit, pkUnit); // If you hit this NIMEMASSERT, then this deallocation was made from a // source that isn't setup to use this memory tracking software, // use the stack frame to locate the source and include // NiMemManager.h NIMEMASSERTUNIT(eEventType != NI_UNKNOWN, pkUnit); // If you hit this NIMEMASSERT, you were trying to deallocate RAM that // was not allocated in a way that is compatible with the // deallocation method requested. In other words, you have a // allocation/deallocation mismatch. NIMEMASSERTUNIT((eEventType == NI_OPER_DELETE && pkUnit->m_eAllocType == NI_OPER_NEW) || (eEventType == NI_OPER_DELETE_ARRAY && pkUnit->m_eAllocType == NI_OPER_NEW_ARRAY) || (eEventType == NI_FREE && pkUnit->m_eAllocType == NI_MALLOC) || (eEventType == NI_FREE && pkUnit->m_eAllocType == NI_REALLOC) || (eEventType == NI_REALLOC && pkUnit->m_eAllocType == NI_MALLOC) || (eEventType == NI_REALLOC && pkUnit->m_eAllocType == NI_REALLOC) || (eEventType == NI_ALIGNEDFREE && pkUnit->m_eAllocType == NI_ALIGNEDMALLOC) || (eEventType == NI_ALIGNEDFREE && pkUnit->m_eAllocType == NI_ALIGNEDREALLOC) || (eEventType == NI_ALIGNEDREALLOC && pkUnit->m_eAllocType == NI_ALIGNEDMALLOC) || (eEventType == NI_ALIGNEDREALLOC && pkUnit->m_eAllocType == NI_ALIGNEDREALLOC) || (eEventType == NI_UNKNOWN), pkUnit); // update allocation unit NiMemEventType eDeallocType = eEventType; float fDeallocTime = fTime; // Determine how much memory was actually set size_t stSizeUnused = MemoryBytesWithPattern(pvMemory, pkUnit->m_stSizeRequested); m_stUnusedButAllocatedMemory += stSizeUnused; // Save the thread id that freed the memory unsigned long ulFreeThreadId = NiGetCurrentThreadId(); if (m_bCheckArrayOverruns) { // If you hit this NIMEMASSERT, you have code that overwrites // either before or after the range of an allocation. // Check the pkUnit for information about which allocation // is being overwritten. bool POSSIBLY_UNUSED bCheckForArrayOverrun = CheckForArrayOverrun(pvMemory, pkUnit->m_stAlignment, pkUnit->m_stSizeRequested); NIMEMASSERTUNIT(!bCheckForArrayOverrun, pkUnit); if (stSizeInBytes != NI_MEM_DEALLOC_SIZE_DEFAULT) { stSizeInBytes= PadForArrayOverrun(pkUnit->m_stAlignment, stSizeInBytes); } } // Perform the actual deallocation m_pkActualAllocator->Deallocate(pvMemory, eEventType, stSizeInBytes); // Remove this allocation unit from the hash table RemoveAllocUnit(pkUnit); // update summary statistics --m_stActiveAllocationCount; m_stActiveMemory -= pkUnit->m_stSizeAllocated; // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } // Write out the freed memory to the memory log LogAllocUnit(pkUnit, MEM_LOG_COMPLETE, "\t", eDeallocType, fDeallocTime, ulFreeThreadId, stSizeUnused); // Recycle the allocation unit // Add it to the front of the reservoir pkUnit->m_kFLF = NiFLF::UNKNOWN; pkUnit->m_pkNext = m_pkReservoir; m_pkReservoir = pkUnit; // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } m_kCriticalSection.Unlock(); #ifdef NIMEMORY_ENABLE_EXCEPTIONS } catch(const char *err) { // Deal with the errors } #endif } } //--------------------------------------------------------------------------- bool NiMemTracker::TrackAllocate( const void* const pvMemory, size_t stSizeInBytes, NiMemEventType eEventType, const char* pcFile, int iLine, const char* pcFunction) { m_kCriticalSection.Lock(); size_t stSizeOriginal = stSizeInBytes; float fTime = NiGetCurrentTimeInSec(); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // function name. NIMEMASSERT(strcmp(ms_pcBreakOnFunctionName, pcFunction) != 0); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation ID. NIMEMASSERT(ms_stBreakOnAllocID != m_stCurrentAllocID); // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation request size. NIMEMASSERT(ms_stBreakOnSizeRequested != stSizeOriginal); // Actually perform the allocation. Note that size and alignment // may be adjusted by the call. m_pkActualAllocator->TrackAllocate(pvMemory, stSizeInBytes, eEventType, pcFile, iLine, pcFunction); // update overall summary statistics m_stActiveAllocationCount++; m_stAccumulatedAllocationCount++; if (m_stActiveAllocationCount > m_stPeakAllocationCount) { m_stPeakAllocationCount = m_stActiveAllocationCount; m_fPeakAllocationCountTime = fTime; } m_stActiveMemory += stSizeInBytes; m_stAccumulatedMemory += stSizeInBytes; if (m_stActiveMemory > m_stPeakMemory) { m_stPeakMemory = m_stActiveMemory; m_fPeakMemoryTime = fTime; } // update external-specific summary statistics m_stActiveExternalAllocationCount++; m_stAccumulatedExternalAllocationCount++; if (m_stActiveExternalAllocationCount > m_stPeakExternalAllocationCount) { m_stPeakExternalAllocationCount = m_stActiveExternalAllocationCount; } m_stActiveExternalMemory += stSizeInBytes; m_stAccumulatedExternalMemory += stSizeInBytes; if (m_stActiveExternalMemory > m_stPeakExternalMemory) { m_stPeakExternalMemory = m_stActiveExternalMemory; } if (pvMemory == NULL) { // If you hit this, your memory request was not satisfied m_kCriticalSection.Unlock(); return true; } // If you hit this NIMEMASSERT, you requested a breakpoint on a specific // allocation address range. NIMEMASSERT(pvMemory < ms_pvBreakOnAllocRangeStart || pvMemory > ms_pvBreakOnAllocRangeEnd); #ifdef NIMEMORY_ENABLE_EXCEPTIONS try { #endif // If you hit this NIMEMASSERT, the somehow you have allocated a memory // unit to an address that already exists. This should never happen // and is an indicator that something has gone wrong in the sub-allocator. NIMEMASSERT(FindAllocUnit(pvMemory) == NULL); // Grow the tracking unit reservoir if necessary if (!m_pkReservoir) GrowReservoir(); // If you hit this NIMEMASSERT, the free store for allocation units // does not exist. This should only happen if the reservoir // needed to grow and was unable to satisfy the request. In other words, // you may be out of memory. NIMEMASSERT (m_pkReservoir != NULL); // Get an allocation unit from the reservoir NiAllocUnit* pkUnit = m_pkReservoir; m_pkReservoir = pkUnit->m_pkNext; // fill in the known information pkUnit->Reset(); pkUnit->m_stAllocationID = m_stCurrentAllocID; pkUnit->m_stAlignment = 0; pkUnit->m_ulAllocThreadId = NiGetCurrentThreadId(); pkUnit->m_eAllocType = eEventType; pkUnit->m_fAllocTime = fTime; pkUnit->m_pvMem = (void*) pvMemory; pkUnit->m_stSizeRequested = stSizeOriginal; pkUnit->m_stSizeAllocated = stSizeInBytes; pkUnit->m_kFLF.Set(pcFile, iLine, pcFunction); // If you hit this NIMEMASSERT, then this allocation was made from a // source that isn't setup to use this memory tracking software, use the // stack frame to locate the source and include NiMemManager.h. NIMEMASSERTUNIT(eEventType != NI_UNKNOWN, pkUnit); // Insert the new allocation into the hash table InsertAllocUnit(pkUnit); // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } #ifdef NIMEMORY_ENABLE_EXCEPTIONS } catch(const char *err) { // Deal with the errors // Deal with the errors } #endif ++m_stCurrentAllocID; m_kCriticalSection.Unlock(); return true; } //--------------------------------------------------------------------------- bool NiMemTracker::TrackDeallocate( const void* const pvMemory, NiMemEventType eEventType) { if (pvMemory) { m_kCriticalSection.Lock(); #ifdef NIMEMORY_ENABLE_EXCEPTIONS try { #endif float fTime = NiGetCurrentTimeInSec(); // Search the tracking unit hash table to find the address NiAllocUnit* pkUnit = FindAllocUnit(pvMemory); if (pkUnit == NULL) { // If you hit this NIMEMASSERT, you tried to deallocate RAM that // wasn't allocated by this memory manager. This may also // be indicative of a double deletion. Please check the pkUnit // FLF for information about the allocation. NIMEMASSERT(pkUnit != NULL); #ifdef NIMEMORY_ENABLE_EXCEPTIONS throw "Request to deallocate RAM that was never allocated"; #endif m_kCriticalSection.Unlock(); return true; } // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation ID. NIMEMASSERTUNIT(ms_stBreakOnAllocID != pkUnit->m_stAllocationID, pkUnit); // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation request size. NIMEMASSERTUNIT(ms_stBreakOnSizeRequested != pkUnit->m_stSizeRequested, pkUnit); // If you hit this NIMEMASSERT, you requested a breakpoint on a // specific allocation address range. NIMEMASSERTUNIT(pkUnit->m_pvMem < ms_pvBreakOnAllocRangeStart || pkUnit->m_pvMem > ms_pvBreakOnAllocRangeEnd, pkUnit); // If you hit this NIMEMASSERT, then the allocation unit that is about // to be deallocated is damaged. bool POSSIBLY_UNUSED bValidateAllocUnit = ValidateAllocUnit(pkUnit); NIMEMASSERTUNIT(bValidateAllocUnit, pkUnit); // If you hit this NIMEMASSERT, then this deallocation was made from a // source that isn't setup to use this memory tracking software, // use the stack frame to locate the source and include // NiMemManager.h NIMEMASSERTUNIT(eEventType == NI_EXTERNALFREE, pkUnit); // If you hit this NIMEMASSERT, you were trying to deallocate RAM that // was not allocated in a way that is compatible with the // deallocation method requested. In other words, you have a // allocation/deallocation mismatch. NIMEMASSERTUNIT(pkUnit->m_eAllocType == NI_EXTERNALALLOC, pkUnit); // update allocation unit NiMemEventType eDeallocType = eEventType; float fDeallocTime = fTime; // because we don't touch the tracked external address, // simply state the the unused amount is zero size_t stSizeUnused = 0; // Save the thread id that freed the memory unsigned long ulFreeThreadId = NiGetCurrentThreadId(); // Perform the actual deallocation m_pkActualAllocator->TrackDeallocate(pvMemory, eEventType); // Remove this allocation unit from the hash table RemoveAllocUnit(pkUnit); // update overall summary statistics --m_stActiveAllocationCount; m_stActiveMemory -= pkUnit->m_stSizeAllocated; // update external-specific summary statistics --m_stActiveExternalAllocationCount; m_stActiveExternalMemory -= pkUnit->m_stSizeAllocated; // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } // Write out the freed memory to the memory log LogAllocUnit(pkUnit, MEM_LOG_COMPLETE, "\t", eDeallocType, fDeallocTime, ulFreeThreadId, stSizeUnused); // Recycle the allocation unit // Add it to the front of the reservoir pkUnit->m_kFLF = NiFLF::UNKNOWN; pkUnit->m_pkNext = m_pkReservoir; m_pkReservoir = pkUnit; // Validate every single allocated unit in memory if (m_bAlwaysValidateAll) { bool POSSIBLY_UNUSED bValidateAllAllocUnits = ValidateAllAllocUnits(); NIMEMASSERTUNIT(bValidateAllAllocUnits, pkUnit); } m_kCriticalSection.Unlock(); #ifdef NIMEMORY_ENABLE_EXCEPTIONS } catch(const char *err) { // Deal with the errors } #endif } return true; } //--------------------------------------------------------------------------- NiAllocUnit* NiMemTracker::FindAllocUnit(const void* pvMem) const { // Just in case... NIMEMASSERT(pvMem != NULL); // Use the address to locate the hash index. Note that we shift off the // lower four bits. This is because most allocated addresses will be on // four-, eight- or even sixteen-byte boundaries. If we didn't do this, // the hash index would not have very good coverage. unsigned int uiHashIndex = AddressToHashIndex(pvMem); NiAllocUnit* pkUnit = m_pkActiveMem[uiHashIndex]; while(pkUnit) { if (pkUnit->m_pvMem == pvMem) return pkUnit; pkUnit = pkUnit->m_pkNext; } return NULL; } //--------------------------------------------------------------------------- bool NiMemTracker::VerifyAddress(const void* pvMemory) { m_kCriticalSection.Lock(); // Handle both regular pointers and array offset values. // from NiStandardAllocator. NiAllocUnit* pkUnit = FindAllocUnit(pvMemory); if (pkUnit) { if (m_bCheckArrayOverruns && pkUnit->m_eAllocType != NI_EXTERNALALLOC) { void* pvMemStore = pkUnit->m_pvMem; if(CheckForArrayOverrun(pvMemStore, pkUnit->m_stAlignment, pkUnit->m_stSizeRequested)) { NiOutputDebugString("Memory Overrun Found At " "Allocation:\n"); OutputAllocUnitToDebugStream(pkUnit); m_kCriticalSection.Unlock(); return false; } } m_kCriticalSection.Unlock(); return true; } // The Xbox 360 inserts an alignment-sized header into the allocation // when a destructor needs to be called. This value holds the count of // elements that were allocated. However, it will be unknown to this // function if such a header were actually added since all we have is // an address. Therefore, we must search backwards to some reasonable // degree of certainty to find the matching allocation unit. // 4 is the minimum offset/alignment for all supported compilers. size_t stOffset = 4; // The maximum offset that is supported by this function is 128. // Anything larger is probably unreasonable on modern systems. while (stOffset <= 128) { unsigned char* pcAdjustedMemory = ((unsigned char*)pvMemory) - stOffset; pkUnit = FindAllocUnit(pcAdjustedMemory); // We must be careful that the the allocation unit exists and is // big enough to actually encompass the address and isn't actually // right in front of it. if (pkUnit && (pcAdjustedMemory + pkUnit->m_stSizeRequested) >= (unsigned char*)pvMemory) { if (m_bCheckArrayOverruns && pkUnit->m_eAllocType != NI_EXTERNALALLOC) { void* pvMemStore = pkUnit->m_pvMem; if(CheckForArrayOverrun(pvMemStore, pkUnit->m_stAlignment, pkUnit->m_stSizeRequested)) { NiOutputDebugString("Memory Overrun Found At " "Allocation:\n"); OutputAllocUnitToDebugStream(pkUnit); m_kCriticalSection.Unlock(); return false; } } m_kCriticalSection.Unlock(); return true; } // If we've found an allocation unit that exists before the // address we are looking up, there is no need to keep searching, // this address is unknown to us. else if (pkUnit) { m_kCriticalSection.Unlock(); return false; } // Find the next alignment that is permissible. stOffset *= 2; } m_kCriticalSection.Unlock(); return false; } //--------------------------------------------------------------------------- void NiMemTracker::MemoryFillForArrayOverrun(void*& pvMemory, size_t stAlignment, size_t stSizeOriginal) { char* pcMemArray = (char*) pvMemory; pvMemory = pcMemArray + stAlignment; MemoryFillWithPattern(pcMemArray, stAlignment); pcMemArray = pcMemArray + stAlignment + stSizeOriginal; MemoryFillWithPattern(pcMemArray, stAlignment); } //--------------------------------------------------------------------------- void NiMemTracker::MemoryFillWithPattern(void* pvMemory, size_t stSizeInBytes) { unsigned char* pcMemArray = (unsigned char*) pvMemory; for (unsigned int ui = 0; ui < stSizeInBytes; ui++) { pcMemArray[ui] = m_ucFillChar; } } //--------------------------------------------------------------------------- size_t NiMemTracker::MemoryBytesWithPattern(void* pvMemory, size_t stSizeInBytes) const { unsigned char* pcMemArray = (unsigned char*) pvMemory; size_t numBytes = 0; for (unsigned int ui = 0; ui < stSizeInBytes; ui++) { if (pcMemArray[ui] == m_ucFillChar) { numBytes++; } } return numBytes; } //--------------------------------------------------------------------------- bool NiMemTracker::CheckForArrayOverrun(void*& pvMemory, size_t stAlignment, size_t stSizeOriginal) const { NIASSERT(m_bCheckArrayOverruns); char* pcMemArray = (char*) pvMemory; pcMemArray -= stAlignment; pvMemory = pcMemArray; if (stAlignment != MemoryBytesWithPattern(pcMemArray, stAlignment)) return true; pcMemArray = pcMemArray + stAlignment + stSizeOriginal; if (stAlignment != MemoryBytesWithPattern(pcMemArray, stAlignment)) return true; return false; } //--------------------------------------------------------------------------- size_t NiMemTracker::PadForArrayOverrun( size_t stAlignment, size_t stSizeOriginal) { return stSizeOriginal + 2 * stAlignment; } //--------------------------------------------------------------------------- bool NiMemTracker::ValidateAllocUnit(const NiAllocUnit* pkUnit) const { if (pkUnit->m_stAllocationID > m_stCurrentAllocID) return false; if (pkUnit->m_stSizeAllocated < pkUnit->m_stSizeRequested) return false; if (pkUnit->m_stSizeAllocated == 0 || pkUnit->m_stSizeRequested == 0) return false; if (pkUnit->m_pvMem == NULL) return false; if (pkUnit->m_pkNext != NULL && pkUnit->m_pkNext->m_pkPrev != pkUnit) return false; return true; } //--------------------------------------------------------------------------- bool NiMemTracker::ValidateAllAllocUnits() const { unsigned int uiActiveCount = 0; for (unsigned int uiHashIndex = 0; uiHashIndex < ms_uiHashSize; uiHashIndex++) { NiAllocUnit* pkUnit = m_pkActiveMem[uiHashIndex]; NiAllocUnit* pkPrev = NULL; while(pkUnit) { if (!ValidateAllocUnit(pkUnit)) return false; if (pkUnit->m_pkPrev != pkPrev) return false; if (m_bCheckArrayOverruns) { void* pvMemStore = pkUnit->m_pvMem; if (CheckForArrayOverrun(pvMemStore, pkUnit->m_stAlignment, pkUnit->m_stSizeRequested)) { NiOutputDebugString("Memory Overrun Found At " "Allocation:\n"); OutputAllocUnitToDebugStream(pkUnit); return false; } } // continue to the next unit pkPrev = pkUnit; pkUnit = pkUnit->m_pkNext; uiActiveCount++; } } if (uiActiveCount != this->m_stActiveAllocationCount) return false; return true; } //--------------------------------------------------------------------------- void NiMemTracker::LogAllocUnit( const NiAllocUnit *pkUnit, int iChannel, const char* pcPrefix, NiMemEventType eDeallocType, float fDeallocTime, unsigned long ulDeallocThreadId, size_t stSizeUnused) const { if (!m_bWriteToLog) return; NIMEMASSERT(pcPrefix != NULL); NILOG(iChannel, pcPrefix); NILOG(iChannel, "m_stAllocationID); NILOG(iChannel, "alloc_thread_id='%lX' ", pkUnit->m_ulAllocThreadId); NILOG(iChannel, "dealloc_thread_id='%lX' ", ulDeallocThreadId); NILOG(iChannel, "alloc_time='%08f' ", pkUnit->m_fAllocTime); NILOG(iChannel, "dealloc_time='%08f' ", fDeallocTime); NILOG(iChannel, "life_span='%08f' " , fDeallocTime - pkUnit->m_fAllocTime); NILOG(iChannel, "addr='0x%p' ", pkUnit->m_pvMem); NILOG(iChannel, "size='%d' ", pkUnit->m_stSizeAllocated); NILOG(iChannel, "size_requested='%d' ", pkUnit->m_stSizeRequested); NILOG(iChannel, "size_unused='%d' ", stSizeUnused); NILOG(iChannel, "alignment='%d' ", pkUnit->m_stAlignment); NILOG(iChannel, "alloc_type='%s' ", NiMemManager::NiMemEventTypeToString(pkUnit->m_eAllocType)); NILOG(iChannel, "dealloc_type='%s' ", NiMemManager::NiMemEventTypeToString(eDeallocType)); NILOG(iChannel, "file='%s' ", pkUnit->m_kFLF.SourceFileStripper()); NILOG(iChannel, "line='%d' ", pkUnit->m_kFLF.m_uiLine); NILOG(iChannel, "func='%s' ", FormatForXML(pkUnit->m_kFLF.m_pcFunc)); NILOG(iChannel, "long_file='%s' ", pkUnit->m_kFLF.m_pcFile); NILOG(iChannel, ">\n"); } //--------------------------------------------------------------------------- void NiMemTracker::LogMemoryReport() const { if (!m_bWriteToLog) return; LogActiveMemoryReport(); } //--------------------------------------------------------------------------- const char* NiMemTracker::FormatForXML(const char* pcInString) { static char acOutString[1024]; NIMEMASSERT(pcInString != NULL); int iLen = strlen(pcInString); NIMEMASSERT(iLen < 1024); unsigned int uiIdx = 0; for (int i = 0; i < iLen; i++) { acOutString[uiIdx] = pcInString[i]; if (acOutString[uiIdx] == '<') { acOutString[uiIdx++] = '&'; acOutString[uiIdx++] = 'l'; acOutString[uiIdx++] = 't'; acOutString[uiIdx++] = ';'; } else if (acOutString[uiIdx] == '>') { acOutString[uiIdx++] = '&'; acOutString[uiIdx++] = 'g'; acOutString[uiIdx++] = 't'; acOutString[uiIdx++] = ';'; } else if (acOutString[uiIdx] == '&') { acOutString[uiIdx++] = '&'; acOutString[uiIdx++] = 'a'; acOutString[uiIdx++] = 'm'; acOutString[uiIdx++] = 'p'; acOutString[uiIdx++] = ';'; } else if (acOutString[uiIdx] == '`') { acOutString[uiIdx++] = ' '; } else if (acOutString[uiIdx] == '\'') { acOutString[uiIdx++] = ' '; } else { uiIdx++; } NIMEMASSERT(uiIdx < 1024); } NIMEMASSERT(uiIdx < 1024); acOutString[uiIdx] = '\0'; return acOutString; } //--------------------------------------------------------------------------- void NiMemTracker::LogActiveMemoryReport() const { if (!m_bWriteToLog) return; NILOG(MEM_LOG_LEAK, "\n", NiGetCurrentTimeInSec()); for (unsigned int uiHashIndex = 0; uiHashIndex < ms_uiHashSize; uiHashIndex++) { NiAllocUnit* pkUnit = m_pkActiveMem[uiHashIndex]; while(pkUnit) { LogAllocUnit(pkUnit, MEM_LOG_LEAK, "\t"); // continue to the next unit pkUnit = pkUnit->m_pkNext; } } NILOG(MEM_LOG_LEAK, "\n"); } //--------------------------------------------------------------------------- void NiMemTracker::OutputLeakedMemoryToDebugStream() const { if (!ms_bOutputLeaksToDebugStream) return; NiOutputDebugString("\nLeaked Memory Report:\n"); for (unsigned int uiHashIndex = 0; uiHashIndex < ms_uiHashSize; uiHashIndex++) { NiAllocUnit* pkUnit = m_pkActiveMem[uiHashIndex]; while(pkUnit) { OutputAllocUnitToDebugStream(pkUnit); // continue to the next unit pkUnit = pkUnit->m_pkNext; } } } //--------------------------------------------------------------------------- void NiMemTracker::OutputAllocUnitToDebugStream(NiAllocUnit* pkUnit) const { NIMEMASSERT(pkUnit->m_pvMem != 0); NILOG("%s(%d) : id = %d\tsize = %d\t" "addr='0x%p'\n", pkUnit->m_kFLF.m_pcFile, pkUnit->m_kFLF.m_uiLine, pkUnit->m_stAllocationID, pkUnit->m_stSizeAllocated, pkUnit->m_pvMem); } //--------------------------------------------------------------------------- void NiMemTracker::LogSummaryStats() const { if (!m_bWriteToLog) return; int iChannel = MEM_LOG_COMPLETE; NILOG(iChannel, "\t\n"); } //---------------------------------------------------------------------------