platformMemory.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platformMemory.h"
  23. #include "console/dynamicTypes.h"
  24. #include "console/engineAPI.h"
  25. #include "core/stream/fileStream.h"
  26. #include "core/strings/stringFunctions.h"
  27. #include "console/console.h"
  28. #include "platform/profiler.h"
  29. #include "platform/threads/mutex.h"
  30. #include "core/module.h"
  31. #ifdef _WIN32
  32. #include <windows.h>
  33. #include <dbghelp.h>
  34. #pragma comment(lib, "Dbghelp.lib")
  35. #else
  36. #include <execinfo.h>
  37. #endif
  38. #include <ctime>
  39. #include <string>
  40. // If profile paths are enabled, disable profiling of the
  41. // memory manager as that would cause a cyclic dependency
  42. // through the string table's allocation stuff used by the
  43. // profiler (talk about a long sentence...)
  44. #ifdef TORQUE_ENABLE_PROFILE_PATH
  45. # undef PROFILE_START
  46. # undef PROFILE_END
  47. # undef PROFILE_SCOPE
  48. # define PROFILE_START( x )
  49. # define PROFILE_END()
  50. # define PROFILE_SCOPE( x )
  51. #endif
  52. #ifdef TORQUE_MULTITHREAD
  53. void* gMemMutex = NULL;
  54. #endif
  55. //-------------------------------------- Make sure we don't have the define set
  56. #ifdef new
  57. #undef new
  58. #endif
  59. //---------------------------------------------------------------------------
  60. namespace Memory
  61. {
  62. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  63. static const U32 MaxAllocs = 10240;
  64. static MemInfo allocList[MaxAllocs];
  65. static U32 allocCount = 0;
  66. static U32 currentAllocId = 0;
  67. static bool initialized = false;
  68. char gLogFilename[256] = { 0 };
  69. bool gStackTrace = true;
  70. bool gFromScript = false;
  71. struct memReport
  72. {
  73. std::string report;
  74. bool skip;
  75. U32 count = 1;
  76. U32 total = 0;
  77. } memLog[MaxAllocs];
  78. void init()
  79. {
  80. if (initialized) return;
  81. std::memset(allocList, 0, sizeof(allocList));
  82. allocCount = 0;
  83. currentAllocId = 0;
  84. initialized = true;
  85. // Generate timestamped log filename
  86. std::time_t now = std::time(nullptr);
  87. std::tm* localTime = std::localtime(&now);
  88. std::strftime(gLogFilename, sizeof(gLogFilename), "memlog_%Y-%m-%d_%H-%M-%S.txt", localTime);
  89. }
  90. void shutdown()
  91. {
  92. if (!initialized) return;
  93. FILE* log = std::fopen(gLogFilename, "w");
  94. if (!log)
  95. return;
  96. std::fprintf(log, "\n--- Memory Leak Report ---\n");
  97. U32 start = 0;
  98. U32 stop = allocCount;
  99. if (gFromScript) //filter out the bits from console
  100. {
  101. start = 6;
  102. stop = allocCount - 8;
  103. }
  104. for (U32 curRep = start; curRep < stop; ++curRep)
  105. {
  106. if (allocList[curRep].ptr != nullptr)
  107. {
  108. char entry[512] = "";
  109. std::sprintf(entry, "from %s:%u\n", allocList[curRep].file ? allocList[curRep].file : "(null)", allocList[curRep].line);
  110. memLog[curRep].skip = false;
  111. std::string report = entry;
  112. if (gStackTrace)
  113. {
  114. char stack[512] = "";
  115. #ifdef _WIN32
  116. SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 256);
  117. symbol->MaxNameLen = 255;
  118. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  119. HANDLE process = GetCurrentProcess();
  120. SymInitialize(process, NULL, TRUE);
  121. for (int curStack = 0; curStack < allocList[curRep].backtraceSize; ++curStack)
  122. {
  123. DWORD64 addr = (DWORD64)(allocList[curRep].backtracePtrs[curStack]);
  124. if (SymFromAddr(process, addr, 0, symbol))
  125. {
  126. std::sprintf(stack, " [%d] %s - 0x%0llX\n", curStack, symbol->Name, symbol->Address);
  127. }
  128. else
  129. {
  130. std::sprintf(stack, " [%d] ??? - 0x%0llX\n", curStack, addr);
  131. }
  132. report += stack;
  133. }
  134. std::free(symbol);
  135. #else
  136. char** symbols = backtrace_symbols(allocList[curRep].backtracePtrs, allocList[i].backtraceSize);
  137. for (int curStack = 0; curStack < allocList[curRep].backtraceSize; ++curStack)
  138. {
  139. std::sprintf(stack, " [%d] %s\n", curStack, symbols[curStack]);
  140. report += stack;
  141. }
  142. std::free(symbols);
  143. #endif
  144. }
  145. if (report.find("getDocsLink") != std::string::npos)
  146. {
  147. //known issue. one off allocation
  148. memLog[curRep].skip = true;
  149. }
  150. for (U32 oldRep = start; oldRep < curRep; ++oldRep)
  151. {
  152. if (!memLog[oldRep].skip && (memLog[oldRep].report.find(report) != std::string::npos))
  153. {
  154. //inc origional
  155. memLog[oldRep].count++;
  156. memLog[oldRep].total += allocList[curRep].size;
  157. //skip dupe report
  158. memLog[curRep].skip = true;
  159. }
  160. }
  161. if (!memLog[curRep].skip)
  162. {
  163. memLog[curRep].report = report;
  164. memLog[curRep].count = 1;
  165. memLog[curRep].total = allocList[curRep].size;
  166. }
  167. }
  168. }
  169. for (U32 ntry = start; ntry < stop; ++ntry)
  170. {
  171. if (!memLog[ntry].skip)
  172. {
  173. std::fprintf(log, "Leak-count[%i]total[%i]:%s", memLog[ntry].count, memLog[ntry].total, memLog[ntry].report.c_str());
  174. memLog[ntry].report.clear();
  175. }
  176. }
  177. std::fclose(log);
  178. initialized = false;
  179. }
  180. void checkPtr(void* ptr)
  181. {
  182. for (U32 i = 0; i < allocCount; ++i)
  183. if (allocList[i].ptr == ptr)
  184. return;
  185. Platform::debugBreak();
  186. }
  187. static void* alloc(dsize_t size, bool array, const char* fileName, U32 line)
  188. {
  189. if (size == 0)
  190. return nullptr;
  191. void* ptr = std::malloc(size);
  192. if (!ptr)
  193. return nullptr;
  194. if (!initialized || allocCount >= MaxAllocs)
  195. return ptr;
  196. MemInfo& info = allocList[allocCount++];
  197. info.ptr = ptr;
  198. info.size = size;
  199. info.file = fileName ? fileName : "unknown";
  200. info.line = line;
  201. info.allocId = currentAllocId++;
  202. info.flagged = false;
  203. if (gStackTrace)
  204. {
  205. #ifdef _WIN32
  206. info.backtraceSize = CaptureStackBackTrace(0, 16, info.backtracePtrs, nullptr);
  207. #else
  208. info.backtraceSize = backtrace(info.backtracePtrs, MaxBacktraceDepth);
  209. #endif
  210. }
  211. return ptr;
  212. }
  213. static void free(void* ptr, bool array)
  214. {
  215. if (!ptr || !initialized)
  216. return;
  217. for (U32 i = 0; i < allocCount; ++i)
  218. {
  219. if (allocList[i].ptr == ptr)
  220. {
  221. std::free(ptr);
  222. allocList[i] = allocList[allocCount - 1];
  223. allocList[--allocCount] = {};
  224. return;
  225. }
  226. }
  227. // Unknown pointer, still free it.
  228. std::free(ptr);
  229. }
  230. void getMemoryInfo(void* ptr, MemInfo& info)
  231. {
  232. if (!ptr || !initialized)
  233. return;
  234. for (U32 i = 0; i < allocCount; ++i)
  235. {
  236. if (allocList[i].ptr == ptr)
  237. {
  238. info = allocList[i];
  239. return;
  240. }
  241. }
  242. }
  243. static void* realloc(void* oldPtr, dsize_t newSize, const char* fileName, U32 line)
  244. {
  245. if (!initialized)
  246. return std::realloc(oldPtr, newSize); // fallback if not tracking
  247. if (newSize == 0)
  248. {
  249. free(oldPtr, false);
  250. return nullptr;
  251. }
  252. if (oldPtr == nullptr)
  253. return alloc(newSize, false, fileName, line);
  254. void* newPtr = std::realloc(oldPtr, newSize);
  255. if (!newPtr)
  256. return nullptr;
  257. // Update existing record
  258. for (U32 i = 0; i < allocCount; ++i)
  259. {
  260. if (allocList[i].ptr == oldPtr)
  261. {
  262. allocList[i].ptr = newPtr;
  263. allocList[i].size = newSize;
  264. allocList[i].file = fileName;
  265. allocList[i].line = line;
  266. allocList[i].allocId = currentAllocId++;
  267. return newPtr;
  268. }
  269. }
  270. // Not found — see if newPtr is already being tracked
  271. for (U32 i = 0; i < allocCount; ++i)
  272. {
  273. if (allocList[i].ptr == newPtr)
  274. {
  275. allocList[i].size = newSize;
  276. allocList[i].file = fileName;
  277. allocList[i].line = line;
  278. allocList[i].allocId = currentAllocId++;
  279. return newPtr;
  280. }
  281. }
  282. // Still not found — treat as a new allocation
  283. if (allocCount < MaxAllocs)
  284. {
  285. MemInfo& info = allocList[allocCount++];
  286. info = {};
  287. info.ptr = newPtr;
  288. info.size = newSize;
  289. info.file = fileName;
  290. info.line = line;
  291. info.allocId = currentAllocId++;
  292. }
  293. return newPtr;
  294. }
  295. #endif
  296. }
  297. //---------------------------------------------------------------------------
  298. //---------------------------------------------------------------------------
  299. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  300. // Manage our own memory, add overloaded memory operators and functions
  301. void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line)
  302. {
  303. return Memory::alloc(size, false, fileName, line);
  304. }
  305. void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line)
  306. {
  307. return Memory::alloc(size, true, fileName, line);
  308. }
  309. void* FN_CDECL operator new(dsize_t size)
  310. {
  311. return Memory::alloc(size, false, NULL, 0);
  312. }
  313. void* FN_CDECL operator new[](dsize_t size)
  314. {
  315. return Memory::alloc(size, true, NULL, 0);
  316. }
  317. void FN_CDECL operator delete(void* mem)
  318. {
  319. Memory::free(mem, false);
  320. }
  321. void FN_CDECL operator delete[](void* mem)
  322. {
  323. Memory::free(mem, true);
  324. }
  325. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  326. {
  327. return Memory::alloc(in_size, false, fileName, line);
  328. }
  329. void dFree(void* in_pFree)
  330. {
  331. Memory::free(in_pFree, false);
  332. }
  333. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  334. {
  335. return Memory::realloc(in_pResize, in_size, fileName, line);
  336. }
  337. DefineEngineFunction(LeakTrace, void, (bool start, bool stackTrace), (true, true), "start/stop tracing leaks")
  338. {
  339. if (Memory::initialized) Memory::shutdown();
  340. if (start) Memory::init();
  341. Memory::gFromScript = true;
  342. Memory::gStackTrace = stackTrace;
  343. }
  344. #else
  345. // Don't manage our own memory
  346. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  347. {
  348. return malloc(in_size);
  349. }
  350. void dFree(void* in_pFree)
  351. {
  352. free(in_pFree);
  353. }
  354. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  355. {
  356. return realloc(in_pResize,in_size);
  357. }
  358. #endif