platformMemory.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  32. #ifdef _WIN32
  33. #include <windows.h>
  34. #include <dbghelp.h>
  35. #pragma comment(lib, "Dbghelp.lib")
  36. #else
  37. #include <execinfo.h>
  38. #endif
  39. #include <ctime>
  40. #include <string>
  41. #endif
  42. // If profile paths are enabled, disable profiling of the
  43. // memory manager as that would cause a cyclic dependency
  44. // through the string table's allocation stuff used by the
  45. // profiler (talk about a long sentence...)
  46. #ifdef TORQUE_ENABLE_PROFILE_PATH
  47. # undef PROFILE_START
  48. # undef PROFILE_END
  49. # undef PROFILE_SCOPE
  50. # define PROFILE_START( x )
  51. # define PROFILE_END()
  52. # define PROFILE_SCOPE( x )
  53. #endif
  54. #ifdef TORQUE_MULTITHREAD
  55. void* gMemMutex = NULL;
  56. #endif
  57. //-------------------------------------- Make sure we don't have the define set
  58. #ifdef new
  59. #undef new
  60. #endif
  61. //---------------------------------------------------------------------------
  62. namespace Memory
  63. {
  64. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  65. static const U32 MaxAllocs = 10240;
  66. static MemInfo allocList[MaxAllocs];
  67. static U32 allocCount = 0;
  68. static U32 currentAllocId = 0;
  69. static bool initialized = false;
  70. char gLogFilename[256] = { 0 };
  71. bool gStackTrace = true;
  72. bool gFromScript = false;
  73. struct memReport
  74. {
  75. std::string report;
  76. bool skip;
  77. U32 count = 1;
  78. U32 total = 0;
  79. } memLog[MaxAllocs];
  80. void init()
  81. {
  82. if (initialized) return;
  83. std::memset(allocList, 0, sizeof(allocList));
  84. allocCount = 0;
  85. currentAllocId = 0;
  86. initialized = true;
  87. // Generate timestamped log filename
  88. std::time_t now = std::time(nullptr);
  89. std::tm* localTime = std::localtime(&now);
  90. std::strftime(gLogFilename, sizeof(gLogFilename), "memlog_%Y-%m-%d_%H-%M-%S.txt", localTime);
  91. //std::atexit(shutdown);
  92. }
  93. void shutdown()
  94. {
  95. if (!initialized) return;
  96. FILE* log = std::fopen(gLogFilename, "w");
  97. if (!log)
  98. return;
  99. std::fprintf(log, "\n--- Memory Leak Report ---\n");
  100. U32 start = 0;
  101. U32 stop = allocCount;
  102. if (gFromScript) //filter out the bits from console
  103. {
  104. start = 6;
  105. stop = allocCount - 8;
  106. }
  107. for (U32 curRep = start; curRep < stop; ++curRep)
  108. {
  109. if (allocList[curRep].ptr != nullptr)
  110. {
  111. char entry[512] = "";
  112. std::sprintf(entry, "from %s:%u\n", allocList[curRep].file ? allocList[curRep].file : "(null)", allocList[curRep].line);
  113. memLog[curRep].skip = false;
  114. std::string report = entry;
  115. if (gStackTrace)
  116. {
  117. char stack[512] = "";
  118. #ifdef _WIN32
  119. SYMBOL_INFO* symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 256);
  120. symbol->MaxNameLen = 255;
  121. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  122. HANDLE process = GetCurrentProcess();
  123. SymInitialize(process, NULL, TRUE);
  124. for (int curStack = 0; curStack < allocList[curRep].backtraceSize; ++curStack)
  125. {
  126. DWORD64 addr = (DWORD64)(allocList[curRep].backtracePtrs[curStack]);
  127. DWORD displacement = 0;
  128. IMAGEHLP_LINE64 line;
  129. std::memset(&line, 0, sizeof(IMAGEHLP_LINE64));
  130. line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
  131. if (SymFromAddr(process, addr, 0, symbol)) {
  132. if (SymGetLineFromAddr64(process, addr, &displacement, &line)) {
  133. std::sprintf(stack, " [%d] %s - %s:%lu (0x%0llX)\n",
  134. curStack, symbol->Name, line.FileName, line.LineNumber, symbol->Address);
  135. }
  136. else {
  137. std::sprintf(stack, " [%d] %s - ???:??? (0x%0llX)\n",
  138. curStack, symbol->Name, symbol->Address);
  139. }
  140. }
  141. else {
  142. std::sprintf(stack, " [%d] ??? - 0x%0llX\n", curStack, addr);
  143. }
  144. report += stack;
  145. }
  146. std::free(symbol);
  147. #else
  148. char** symbols = backtrace_symbols(allocList[curRep].backtracePtrs, allocList[i].backtraceSize);
  149. for (int curStack = 0; curStack < allocList[curRep].backtraceSize; ++curStack)
  150. {
  151. std::sprintf(stack, " [%d] %s\n", curStack, symbols[curStack]);
  152. report += stack;
  153. }
  154. std::free(symbols);
  155. #endif
  156. }
  157. if (report.find("getDocsLink") != std::string::npos)
  158. {
  159. //known issue. one off allocation
  160. memLog[curRep].skip = true;
  161. }
  162. for (U32 oldRep = start; oldRep < curRep; ++oldRep)
  163. {
  164. if (!memLog[oldRep].skip && (memLog[oldRep].report.find(report) != std::string::npos))
  165. {
  166. //inc origional
  167. memLog[oldRep].count++;
  168. memLog[oldRep].total += allocList[curRep].size;
  169. //skip dupe report
  170. memLog[curRep].skip = true;
  171. }
  172. }
  173. if (!memLog[curRep].skip)
  174. {
  175. memLog[curRep].report = report;
  176. memLog[curRep].count = 1;
  177. memLog[curRep].total = allocList[curRep].size;
  178. }
  179. }
  180. }
  181. for (U32 ntry = start; ntry < stop; ++ntry)
  182. {
  183. if (!memLog[ntry].skip)
  184. {
  185. std::fprintf(log, "Leak-count[%i]total[%i]:%s", memLog[ntry].count, memLog[ntry].total, memLog[ntry].report.c_str());
  186. memLog[ntry].report.clear();
  187. }
  188. }
  189. std::fclose(log);
  190. std::memset(allocList, 0, sizeof(allocList));
  191. allocCount = 0;
  192. currentAllocId = 0;
  193. initialized = false;
  194. }
  195. void checkPtr(void* ptr)
  196. {
  197. for (U32 i = 0; i < allocCount; ++i)
  198. if (allocList[i].ptr == ptr)
  199. return;
  200. Platform::debugBreak();
  201. }
  202. static void* alloc(dsize_t size, bool array, const char* fileName, U32 line)
  203. {
  204. if (size == 0)
  205. return nullptr;
  206. void* ptr = std::malloc(size);
  207. if (!ptr)
  208. return nullptr;
  209. if (!initialized || allocCount >= MaxAllocs)
  210. return ptr;
  211. MemInfo& info = allocList[allocCount++];
  212. info.ptr = ptr;
  213. info.size = size;
  214. info.file = fileName ? fileName : "unknown";
  215. info.line = line;
  216. info.allocId = currentAllocId++;
  217. info.flagged = false;
  218. if (gStackTrace)
  219. {
  220. #ifdef _WIN32
  221. info.backtraceSize = CaptureStackBackTrace(0, 16, info.backtracePtrs, nullptr);
  222. #else
  223. info.backtraceSize = backtrace(info.backtracePtrs, MaxBacktraceDepth);
  224. #endif
  225. }
  226. return ptr;
  227. }
  228. static void free(void* ptr, bool array)
  229. {
  230. if (!ptr)
  231. return;
  232. if (!initialized)
  233. {
  234. std::free(ptr);
  235. return;
  236. }
  237. for (U32 i = 0; i < allocCount; ++i)
  238. {
  239. if (allocList[i].ptr == ptr)
  240. {
  241. std::free(ptr);
  242. allocList[i] = allocList[allocCount - 1];
  243. allocList[--allocCount] = {};
  244. return;
  245. }
  246. }
  247. // Unknown pointer, still free it.
  248. std::free(ptr);
  249. }
  250. void getMemoryInfo(void* ptr, MemInfo& info)
  251. {
  252. if (!ptr || !initialized)
  253. return;
  254. for (U32 i = 0; i < allocCount; ++i)
  255. {
  256. if (allocList[i].ptr == ptr)
  257. {
  258. info = allocList[i];
  259. return;
  260. }
  261. }
  262. }
  263. static void* realloc(void* oldPtr, dsize_t newSize, const char* fileName, U32 line)
  264. {
  265. if (!initialized)
  266. return std::realloc(oldPtr, newSize); // fallback if not tracking
  267. if (newSize == 0)
  268. {
  269. free(oldPtr, false);
  270. return nullptr;
  271. }
  272. if (oldPtr == nullptr)
  273. return alloc(newSize, false, fileName, line);
  274. void* newPtr = std::realloc(oldPtr, newSize);
  275. if (!newPtr)
  276. return nullptr;
  277. // Update existing record
  278. for (U32 i = 0; i < allocCount; ++i)
  279. {
  280. if (allocList[i].ptr == oldPtr)
  281. {
  282. allocList[i].ptr = newPtr;
  283. allocList[i].size = newSize;
  284. allocList[i].file = fileName;
  285. allocList[i].line = line;
  286. allocList[i].allocId = currentAllocId++;
  287. return newPtr;
  288. }
  289. }
  290. // Not found — see if newPtr is already being tracked
  291. for (U32 i = 0; i < allocCount; ++i)
  292. {
  293. if (allocList[i].ptr == newPtr)
  294. {
  295. allocList[i].size = newSize;
  296. allocList[i].file = fileName;
  297. allocList[i].line = line;
  298. allocList[i].allocId = currentAllocId++;
  299. return newPtr;
  300. }
  301. }
  302. // Still not found — treat as a new allocation
  303. if (allocCount < MaxAllocs)
  304. {
  305. MemInfo& info = allocList[allocCount++];
  306. info = {};
  307. info.ptr = newPtr;
  308. info.size = newSize;
  309. info.file = fileName;
  310. info.line = line;
  311. info.allocId = currentAllocId++;
  312. }
  313. return newPtr;
  314. }
  315. #endif
  316. }
  317. //---------------------------------------------------------------------------
  318. //---------------------------------------------------------------------------
  319. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  320. // Manage our own memory, add overloaded memory operators and functions
  321. void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line)
  322. {
  323. return Memory::alloc(size, false, fileName, line);
  324. }
  325. void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line)
  326. {
  327. return Memory::alloc(size, true, fileName, line);
  328. }
  329. void* FN_CDECL operator new(dsize_t size)
  330. {
  331. return Memory::alloc(size, false, NULL, 0);
  332. }
  333. void* FN_CDECL operator new[](dsize_t size)
  334. {
  335. return Memory::alloc(size, true, NULL, 0);
  336. }
  337. void FN_CDECL operator delete(void* mem)
  338. {
  339. Memory::free(mem, false);
  340. }
  341. void FN_CDECL operator delete[](void* mem)
  342. {
  343. Memory::free(mem, true);
  344. }
  345. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  346. {
  347. return Memory::alloc(in_size, false, fileName, line);
  348. }
  349. void dFree(void* in_pFree)
  350. {
  351. Memory::free(in_pFree, false);
  352. }
  353. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  354. {
  355. return Memory::realloc(in_pResize, in_size, fileName, line);
  356. }
  357. DefineEngineFunction(LeakTrace, void, (bool start, bool stackTrace), (true, true), "start/stop tracing leaks")
  358. {
  359. if (Memory::initialized) Memory::shutdown();
  360. if (start) Memory::init();
  361. Memory::gFromScript = true;
  362. Memory::gStackTrace = stackTrace;
  363. }
  364. #else
  365. // Don't manage our own memory
  366. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  367. {
  368. return malloc(in_size);
  369. }
  370. void dFree(void* in_pFree)
  371. {
  372. free(in_pFree);
  373. }
  374. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  375. {
  376. return realloc(in_pResize,in_size);
  377. }
  378. #endif