/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/CoreLibExe/hsExeMalloc.cpp * ***/ #include "Pch.h" #pragma hdrstop /**************************************************************************** * * Local constants * ***/ #if defined(NO_MEM_TRACKER) || !defined(HS_FIND_MEM_LEAKS) || !defined(HS_BUILD_FOR_WIN32) || !defined(_MSC_VER) // no mem debugging #else # undef MEM_DEBUG # define MEM_DEBUG #endif const unsigned kMemReallocInPlaceOnly = 1<<0; const unsigned kMemZero = 1<<1; const unsigned kMemIgnoreBlock = 1<<2; // don't track this allocation #ifndef MEM_DEBUG # define _malloc_dbg(s, t, f, l) malloc(s) # define _calloc_dbg(c, s, t, f, l) calloc(c, s) # define _realloc_dbg(p, s, t, f, l) realloc(p, s) # define _expand_dbg(p, s, t, f, l) _expand(p, s) # define _free_dbg(p, t) free(p) # define _msize_dbg(p, t) _msize(p) # ifndef _CLIENT_BLOCK # define _CLIENT_BLOCK 0 # endif # ifndef _IGNORE_BLOCK # define _IGNORE_BLOCK 0 # endif # ifndef _CRTDBG_ALLOC_MEM_DF # define _CRTDBG_ALLOC_MEM_DF 0 # endif # define SET_CRT_DEBUG_FIELD(a) # define CLEAR_CRT_DEBUG_FIELD(a) #endif // !MEM_DEBUG /***************************************************************************** * * Private data * ***/ #ifdef MEM_DEBUG #define SET_CRT_DEBUG_FIELD(a) \ _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) #define CLEAR_CRT_DEBUG_FIELD(a) \ _CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) // From dbgint.h #define nNoMansLandSize 4 typedef struct _CrtMemBlockHeader { struct _CrtMemBlockHeader * pBlockHeaderNext; struct _CrtMemBlockHeader * pBlockHeaderPrev; char * szFileName; int nLine; #ifdef _WIN64 /* These items are reversed on Win64 to eliminate gaps in the struct * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is * maintained in the debug heap. */ int nBlockUse; size_t nDataSize; #else /* _WIN64 */ size_t nDataSize; int nBlockUse; #endif /* _WIN64 */ long lRequest; unsigned char gap[nNoMansLandSize]; /* followed by: * unsigned char data[nDataSize]; * unsigned char anotherGap[nNoMansLandSize]; */ } _CrtMemBlockHeader; #define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1)) #define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1) enum EMemFile { kMemErr, kMemLeaks, kMemUsage, kMemAllocs, kNumMemFiles }; static char * s_memFilename[kNumMemFiles] = { "MemErr.log", "MemLeaks.log", "MemUsage.log", "MemAllocs.log", }; static char * s_memDlgTitle[kNumMemFiles] = { "Memory error", "Memory leak", nil, nil, }; static HANDLE s_memFile[kNumMemFiles] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, }; struct MemDumpParam { EMemFile file; bool showDialog; }; static unsigned s_memColor; static unsigned s_memCheckOff; static CCritSect * s_critsect; #endif // MEM_DEBUG namespace ExeMalloc { /***************************************************************************** * * Internal functions * ***/ //=========================================================================== #ifdef MEM_DEBUG static void ConvertFilename ( const char src[], unsigned chars, char * dst ) { // Because the filename field may point into a DLL that has been // unloaded, this code validates and converts the string into a // reasonable value __try { unsigned pos = 0; for (;;) { // If the file name is too long then assume it is bogus if (pos >= chars) { pos = 0; break; } // Get the next character unsigned chr = src[pos]; if (!chr) break; // If the character isn't valid low-ASCII // then assume that the name is bogus if ((chr < 32) || (chr > 127)) { pos = 0; break; } // Store character dst[pos++] = (char) chr; } // Ensure that name is terminated dst[pos] = 0; } __except(EXCEPTION_EXECUTE_HANDLER) { dst[0] = 0; } // Print the address of the filename; it may be of some // use given the load address and the map file of the DLL if (!dst[0]) snprintf(dst, chars, "@%p", src); } #endif // MEM_DEBUG //=========================================================================== #ifdef MEM_DEBUG static void OpenErrorFile (EMemFile file) { ASSERT(INVALID_HANDLE_VALUE == s_memFile[file]); s_memFile[file] = CreateFile( s_memFilename[file], GENERIC_WRITE, FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES) NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } #endif // MEM_DEBUG //=========================================================================== #ifdef MEM_DEBUG static void __cdecl ReportMem (EMemFile file, bool showDialog, const char fmt[], ...) { if (s_memFile[file] == INVALID_HANDLE_VALUE) { DebugBreakIfDebuggerPresent(); OpenErrorFile(file); ErrorMinimizeAppWindow(); } char buffer[512]; va_list args; va_start(args, fmt); DWORD length = hsVsnprintf(buffer, arrsize(buffer), fmt, args); va_end(args); #ifdef HS_BUILD_FOR_WIN32 OutputDebugStringA(buffer); if (s_memFile[file] != INVALID_HANDLE_VALUE) WriteFile(s_memFile[file], buffer, length, &length, NULL); static bool s_skip; if (showDialog && !s_skip && !ErrorGetOption(kErrOptNonGuiAsserts)) { s_skip = IDOK != MessageBox( NULL, buffer, s_memDlgTitle[file], MB_ICONSTOP | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL ); } #else fputs(buffer, stderr); #endif } #endif //============================================================================ #ifdef MEM_DEBUG static void __cdecl MemDumpCallback (void * mem, void * param) { REF(MemDumpCallback); const _CrtMemBlockHeader * pHead = pHdr(mem); MemDumpParam * dumpParam = (MemDumpParam *) param; char filename[MAX_PATH]; ConvertFilename( pHead->szFileName, arrsize(filename), filename ); // HACK: Don't report array memory leaks since these underly the hash // table type and may not be cleaned up until after the mem leak // checker runs. =( if (strstr(filename, "pnUtArray")) return; ReportMem( dumpParam->file, dumpParam->showDialog, "Offset %p size %u at %s:%d\r\n", mem, pHead->nDataSize, filename, pHead->nLine ); } #endif // MEM_DEBUG //============================================================================ #ifdef MEM_DEBUG static void __cdecl OnExitMemDumpCallback (void * mem, size_t) { static MemDumpParam param = { kMemLeaks, true }; if (!ErrorGetOption(kErrOptDisableMemLeakChecking)) MemDumpCallback(mem, ¶m); } #endif // MEM_DEBUG //=========================================================================== #ifdef MEM_DEBUG static void __cdecl CheckLeaksOnExit () { REF(CheckLeaksOnExit); if (!ErrorGetOption(kErrOptDisableMemLeakChecking)) { MemDumpParam param; param.file = kMemLeaks; param.showDialog = true; _CrtDoForAllClientObjects(MemDumpCallback, ¶m); } } #endif // MEM_DEBUG //============================================================================ static int __cdecl CrtAllocHook ( int method, void * pUserData, size_t nSize, int nBlockUse, long lRequest, const unsigned char * szFileName, int nLine ) { REF(method); REF(pUserData); REF(nSize); REF(nBlockUse); REF(lRequest); REF(szFileName); REF(nLine); if (nBlockUse == _NORMAL_BLOCK) { int xx = 0; REF(xx); } return 1; } //=========================================================================== #ifdef MEM_DEBUG AUTO_INIT_FUNC(hsExeMallocInit) { // The critical section has to be initialized // before program startup and never freed static byte rawMemory[sizeof CCritSect]; s_critsect = new(rawMemory) CCritSect; SET_CRT_DEBUG_FIELD(_CRTDBG_LEAK_CHECK_DF); _CrtSetAllocHook(CrtAllocHook); _CrtSetDumpClient(OnExitMemDumpCallback); // atexit(CheckLeaksOnExit); } #endif // MEM_DEBUG /***************************************************************************** * * Module functions * ***/ //============================================================================ void MemSetLeakChecking (bool on) { if (on) SET_CRT_DEBUG_FIELD(_CRTDBG_LEAK_CHECK_DF); else CLEAR_CRT_DEBUG_FIELD(_CRTDBG_LEAK_CHECK_DF); } } using namespace ExeMalloc; /**************************************************************************** * * Exports * ***/ //============================================================================ void MemDumpAllocReport () { #ifdef MEM_DEBUG MemDumpParam param; param.file = kMemAllocs; param.showDialog = true; _CrtDoForAllClientObjects(MemDumpCallback, ¶m); #endif // MEM_DEBUG } //============================================================================ void MemDumpUsageReport () { #ifdef MEM_DEBUG #endif // MEM_DEBUG } //============================================================================ void MemValidateNow () { #ifdef MEM_DEBUG #endif // MEM_DEBUG } //============================================================================ void MemSetValidation (unsigned on) { REF(on); #ifdef MEM_DEBUG #endif // MEM_DEBUG } //============================================================================ void MemPushDisableTracking () { #ifdef MEM_DEBUG ++s_memCheckOff; #endif // MEM_DEBUG } //============================================================================ void MemPopDisableTracking () { #ifdef MEM_DEBUG ASSERT(s_memCheckOff); --s_memCheckOff; #endif // MEM_DEBUG } //============================================================================ void MemSetColor (unsigned short color) { REF(color); #ifdef MEM_DEBUG s_memColor = color & 0xFFFF; #endif // MEM_DEBUG } //=========================================================================== void * MemAlloc (unsigned bytes, unsigned flags, const char file[], int line) { REF(file); REF(line); #ifdef MEM_DEBUG unsigned block; if (flags & kMemIgnoreBlock || s_memCheckOff) block = _IGNORE_BLOCK; else block = _CLIENT_BLOCK | (s_memColor << 16); #endif // MEM_DEBUG #ifdef MEM_DEBUG if (s_critsect) s_critsect->Enter(); if (block == _IGNORE_BLOCK) CLEAR_CRT_DEBUG_FIELD(_CRTDBG_ALLOC_MEM_DF); #endif void * ptr = (flags & kMemZero) ? _calloc_dbg(bytes, 1, block, file, line) : _malloc_dbg(bytes, block, file, line); #ifdef MEM_DEBUG if (block == _IGNORE_BLOCK) SET_CRT_DEBUG_FIELD(_CRTDBG_ALLOC_MEM_DF); if (s_critsect) s_critsect->Leave(); #endif if (!ptr) ErrorFatal(__LINE__, __FILE__, "Out of memory"); // In debug mode ensure that memory is initialized to some freaky value #ifdef HS_DEBUGGING if (! (flags & kMemZero)) MemSet(ptr, (byte) ((unsigned_ptr)ptr >> 4), bytes); #endif #ifdef _MSC_VER // Compiler specific: // Adding this line causes MSVC to stop assuming that memory allocation // can fail thus producing more efficient assembler code. __assume(ptr); #endif // return the allocated buffer return ptr; } //============================================================================ void MemFree (void * ptr, unsigned flags) { REF(flags); if (!ptr) return; #ifdef MEM_DEBUG const _CrtMemBlockHeader * pHead = pHdr(ptr); unsigned block = pHead->nBlockUse; #endif // MEM_DEBUG _free_dbg(ptr, block); } //=========================================================================== void * MemRealloc (void * ptr, unsigned bytes, unsigned flags, const char file[], int line) { REF(file); REF(line); #ifdef HS_DEBUGGING unsigned oldBytes = ptr ? MemSize(ptr) : 0; #endif #ifdef MEM_DEBUG unsigned block; if (flags & kMemIgnoreBlock || s_memCheckOff) block = _IGNORE_BLOCK; else block = _CLIENT_BLOCK | (s_memColor << 16); #endif void * newPtr = nil; #ifdef MEM_DEBUG if (s_critsect) s_critsect->Enter(); if (block == _IGNORE_BLOCK) CLEAR_CRT_DEBUG_FIELD(_CRTDBG_ALLOC_MEM_DF); #endif for (;;) { if (flags & kMemReallocInPlaceOnly) { #ifndef MEM_DEBUG break; #else newPtr = _expand_dbg(ptr, bytes, block, file, line); // expand can succeed without making the block big enough -- check for this case! if (!newPtr || _msize_dbg(newPtr, block) < bytes) break; #endif // MEM_DEBUG } else if (!bytes) { newPtr = _malloc_dbg(0, block, file, line); _free_dbg(ptr, block); } else { newPtr = _realloc_dbg(ptr, bytes, block, file, line); } if (!newPtr) ErrorFatal(__LINE__, __FILE__, "Out of memory"); break; } #ifdef MEM_DEBUG if (block == _IGNORE_BLOCK) SET_CRT_DEBUG_FIELD(_CRTDBG_ALLOC_MEM_DF); if (s_critsect) s_critsect->Leave(); #endif /* This code doesn't work because the memory manager may have "rounded" the size * of a previous allocation upward to keep it aligned. Therefore, the tail of * the memory block may be initialized with garbage instead of zeroes, and the * realloc call actually copied that memory. if ((bytes > oldBytes) && (flags & kMemZero)) MemZero((byte *)newPtr + oldBytes, bytes - oldBytes); */ ASSERT(!(flags & kMemZero)); // In debug mode ensure that memory is initialized to some freaky value #ifdef HS_DEBUGGING if ((bytes > oldBytes) && !(flags & kMemZero)) MemSet((byte *)newPtr + oldBytes, (byte) ((unsigned_ptr) newPtr >> 4), bytes - oldBytes); #endif return newPtr; } //=========================================================================== unsigned MemSize (void * ptr) { ASSERT(ptr); unsigned result; #ifdef MEM_DEBUG const _CrtMemBlockHeader * pHead = pHdr(ptr); unsigned block = pHead->nBlockUse; #endif result = (unsigned)_msize_dbg(ptr, block); return result; } //=========================================================================== int MemCmp (const void * buf1, const void * buf2, unsigned bytes) { return memcmp(buf1, buf2, bytes); } //=========================================================================== void MemCopy (void * dest, const void * source, unsigned bytes) { memcpy(dest, source, bytes); } //=========================================================================== void MemMove (void * dest, const void * source, unsigned bytes) { memmove(dest, source, bytes); } //=========================================================================== void MemSet (void * dest, unsigned value, unsigned bytes) { memset(dest, value, bytes); } //=========================================================================== void MemZero (void * dest, unsigned bytes) { memset(dest, 0, bytes); } //=========================================================================== void * MemDup (const void * ptr, unsigned bytes, unsigned flags, const char file[], int line) { void * dst = MemAlloc(bytes, flags, file, line); MemCopy(dst, ptr, bytes); return dst; }