mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
363 lines
9.6 KiB
C++
363 lines
9.6 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platformMemory.h"
|
|
#include "console/dynamicTypes.h"
|
|
#include "console/engineAPI.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/strings/stringFunctions.h"
|
|
#include "console/console.h"
|
|
#include "platform/profiler.h"
|
|
#include "platform/threads/mutex.h"
|
|
#include "core/module.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <dbghelp.h>
|
|
#pragma comment(lib, "Dbghelp.lib")
|
|
#else
|
|
#include <execinfo.h>
|
|
#endif
|
|
#include <ctime>
|
|
|
|
// If profile paths are enabled, disable profiling of the
|
|
// memory manager as that would cause a cyclic dependency
|
|
// through the string table's allocation stuff used by the
|
|
// profiler (talk about a long sentence...)
|
|
|
|
#ifdef TORQUE_ENABLE_PROFILE_PATH
|
|
# undef PROFILE_START
|
|
# undef PROFILE_END
|
|
# undef PROFILE_SCOPE
|
|
# define PROFILE_START( x )
|
|
# define PROFILE_END()
|
|
# define PROFILE_SCOPE( x )
|
|
#endif
|
|
|
|
#ifdef TORQUE_MULTITHREAD
|
|
void* gMemMutex = NULL;
|
|
#endif
|
|
|
|
//-------------------------------------- Make sure we don't have the define set
|
|
#ifdef new
|
|
#undef new
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
namespace Memory
|
|
{
|
|
#if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
|
|
|
|
|
|
static const U32 MaxAllocs = 10240;
|
|
static MemInfo allocList[MaxAllocs];
|
|
static U32 allocCount = 0;
|
|
static U32 currentAllocId = 0;
|
|
static bool initialized = false;
|
|
char gLogFilename[256] = { 0 };
|
|
bool gStackTrace = false;
|
|
|
|
void init()
|
|
{
|
|
if (initialized) return;
|
|
std::memset(allocList, 0, sizeof(allocList));
|
|
allocCount = 0;
|
|
currentAllocId = 0;
|
|
initialized = true;
|
|
|
|
// Generate timestamped log filename
|
|
std::time_t now = std::time(nullptr);
|
|
std::tm* localTime = std::localtime(&now);
|
|
std::strftime(gLogFilename, sizeof(gLogFilename), "memlog_%Y-%m-%d_%H-%M-%S.txt", localTime);
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
if (!initialized) return;
|
|
|
|
FILE* log = std::fopen(gLogFilename, "w");
|
|
if (!log)
|
|
return;
|
|
|
|
std::fprintf(log, "\n--- Memory Leak Report ---\n");
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
{
|
|
if (allocList[i].ptr != nullptr)
|
|
{
|
|
std::fprintf(log, "Leak: %p (%zu bytes) from %s:%u [id=%u]\n",
|
|
allocList[i].ptr, allocList[i].size,
|
|
allocList[i].file ? allocList[i].file : "(null)", allocList[i].line,
|
|
allocList[i].allocId);
|
|
if (gStackTrace)
|
|
{
|
|
#ifdef _WIN32
|
|
SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 256);
|
|
symbol->MaxNameLen = 255;
|
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
|
|
HANDLE process = GetCurrentProcess();
|
|
SymInitialize(process, NULL, TRUE);
|
|
|
|
for (int j = 0; j < allocList[i].backtraceSize; ++j)
|
|
{
|
|
DWORD64 addr = (DWORD64)(allocList[i].backtracePtrs[j]);
|
|
if (SymFromAddr(process, addr, 0, symbol))
|
|
{
|
|
std::fprintf(log, " [%d] %s - 0x%0llX\n", j, symbol->Name, symbol->Address);
|
|
}
|
|
else
|
|
{
|
|
std::fprintf(log, " [%d] ??? - 0x%0llX\n", j, addr);
|
|
}
|
|
}
|
|
|
|
std::free(symbol);
|
|
#else
|
|
char** symbols = backtrace_symbols(allocList[i].backtracePtrs, allocList[i].backtraceSize);
|
|
for (int j = 0; j < allocList[i].backtraceSize; ++j)
|
|
{
|
|
std::fprintf(log, " [%d] %s\n", j, symbols[j]);
|
|
}
|
|
std::free(symbols);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
std::fclose(log);
|
|
initialized = false;
|
|
}
|
|
|
|
void checkPtr(void* ptr)
|
|
{
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
if (allocList[i].ptr == ptr)
|
|
return;
|
|
|
|
Platform::debugBreak();
|
|
}
|
|
|
|
static void* alloc(dsize_t size, bool array, const char* fileName, U32 line)
|
|
{
|
|
if (size == 0)
|
|
return nullptr;
|
|
|
|
void* ptr = std::malloc(size);
|
|
if (!ptr)
|
|
return nullptr;
|
|
|
|
if (!initialized || allocCount >= MaxAllocs)
|
|
return ptr;
|
|
|
|
MemInfo& info = allocList[allocCount++];
|
|
info.ptr = ptr;
|
|
info.size = size;
|
|
info.file = fileName ? fileName : "unknown";
|
|
info.line = line;
|
|
info.allocId = currentAllocId++;
|
|
info.flagged = false;
|
|
|
|
if (gStackTrace)
|
|
{
|
|
#ifdef _WIN32
|
|
info.backtraceSize = CaptureStackBackTrace(0, 16, info.backtracePtrs, nullptr);
|
|
#else
|
|
info.backtraceSize = backtrace(info.backtracePtrs, MaxBacktraceDepth);
|
|
#endif
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static void free(void* ptr, bool array)
|
|
{
|
|
if (!ptr || !initialized)
|
|
return;
|
|
|
|
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
{
|
|
if (allocList[i].ptr == ptr)
|
|
{
|
|
std::free(ptr);
|
|
allocList[i] = allocList[allocCount - 1];
|
|
allocList[--allocCount] = {};
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Unknown pointer, still free it.
|
|
std::free(ptr);
|
|
}
|
|
|
|
void getMemoryInfo(void* ptr, MemInfo& info)
|
|
{
|
|
if (!ptr || !initialized)
|
|
return;
|
|
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
{
|
|
if (allocList[i].ptr == ptr)
|
|
{
|
|
info = allocList[i];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void* realloc(void* oldPtr, dsize_t newSize, const char* fileName, U32 line)
|
|
{
|
|
if (!initialized)
|
|
return std::realloc(oldPtr, newSize); // fallback if not tracking
|
|
|
|
if (newSize == 0)
|
|
{
|
|
free(oldPtr, false);
|
|
return nullptr;
|
|
}
|
|
|
|
if (oldPtr == nullptr)
|
|
return alloc(newSize, false, fileName, line);
|
|
|
|
|
|
void* newPtr = std::realloc(oldPtr, newSize);
|
|
if (!newPtr)
|
|
return nullptr;
|
|
|
|
|
|
// Update existing record
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
{
|
|
if (allocList[i].ptr == oldPtr)
|
|
{
|
|
allocList[i].ptr = newPtr;
|
|
allocList[i].size = newSize;
|
|
allocList[i].file = fileName;
|
|
allocList[i].line = line;
|
|
allocList[i].allocId = currentAllocId++;
|
|
return newPtr;
|
|
}
|
|
}
|
|
|
|
// Not found — see if newPtr is already being tracked
|
|
for (U32 i = 0; i < allocCount; ++i)
|
|
{
|
|
if (allocList[i].ptr == newPtr)
|
|
{
|
|
allocList[i].size = newSize;
|
|
allocList[i].file = fileName;
|
|
allocList[i].line = line;
|
|
allocList[i].allocId = currentAllocId++;
|
|
|
|
return newPtr;
|
|
}
|
|
}
|
|
|
|
// Still not found — treat as a new allocation
|
|
if (allocCount < MaxAllocs)
|
|
{
|
|
MemInfo& info = allocList[allocCount++];
|
|
info = {};
|
|
info.ptr = newPtr;
|
|
info.size = newSize;
|
|
info.file = fileName;
|
|
info.line = line;
|
|
info.allocId = currentAllocId++;
|
|
}
|
|
return newPtr;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
#if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
|
|
|
|
// Manage our own memory, add overloaded memory operators and functions
|
|
|
|
void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line)
|
|
{
|
|
return Memory::alloc(size, false, fileName, line);
|
|
}
|
|
|
|
void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line)
|
|
{
|
|
return Memory::alloc(size, true, fileName, line);
|
|
}
|
|
|
|
void* FN_CDECL operator new(dsize_t size)
|
|
{
|
|
return Memory::alloc(size, false, NULL, 0);
|
|
}
|
|
|
|
void* FN_CDECL operator new[](dsize_t size)
|
|
{
|
|
return Memory::alloc(size, true, NULL, 0);
|
|
}
|
|
|
|
void FN_CDECL operator delete(void* mem)
|
|
{
|
|
Memory::free(mem, false);
|
|
}
|
|
|
|
void FN_CDECL operator delete[](void* mem)
|
|
{
|
|
Memory::free(mem, true);
|
|
}
|
|
|
|
void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
|
|
{
|
|
return Memory::alloc(in_size, false, fileName, line);
|
|
}
|
|
|
|
void dFree(void* in_pFree)
|
|
{
|
|
Memory::free(in_pFree, false);
|
|
}
|
|
|
|
void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
|
|
{
|
|
return Memory::realloc(in_pResize, in_size, fileName, line);
|
|
}
|
|
|
|
#else
|
|
|
|
// Don't manage our own memory
|
|
void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
|
|
{
|
|
return malloc(in_size);
|
|
}
|
|
|
|
void dFree(void* in_pFree)
|
|
{
|
|
free(in_pFree);
|
|
}
|
|
|
|
void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
|
|
{
|
|
return realloc(in_pResize,in_size);
|
|
}
|
|
|
|
#endif
|