You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
17 KiB
620 lines
17 KiB
/*==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 <http://www.gnu.org/licenses/>. |
|
|
|
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) { |
|
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 () { |
|
if (!ErrorGetOption(kErrOptDisableMemLeakChecking)) { |
|
MemDumpParam param; |
|
param.file = kMemLeaks; |
|
param.showDialog = true; |
|
_CrtDoForAllClientObjects(MemDumpCallback, ¶m); |
|
} |
|
} |
|
#endif // MEM_DEBUG |
|
|
|
//============================================================================ |
|
#ifdef MEM_DEBUG |
|
static int __cdecl CrtAllocHook ( |
|
int method, |
|
void * pUserData, |
|
size_t nSize, |
|
int nBlockUse, |
|
long lRequest, |
|
const unsigned char * szFileName, |
|
int nLine |
|
) { |
|
if (nBlockUse == _NORMAL_BLOCK) { |
|
int xx = 0; |
|
} |
|
|
|
return 1; |
|
} |
|
#endif // MEM_DEBUG |
|
|
|
//=========================================================================== |
|
#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) { |
|
#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) { |
|
#ifdef MEM_DEBUG |
|
s_memColor = color & 0xFFFF; |
|
#endif // MEM_DEBUG |
|
} |
|
|
|
//=========================================================================== |
|
void * MemAlloc (unsigned bytes, unsigned flags, const char file[], int 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) { |
|
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) { |
|
#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; |
|
}
|
|
|