platformMemory.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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. bool sortMemReports(memReport const& lhs, memReport const& rhs)
  79. {
  80. if (lhs.total != rhs.total)
  81. return lhs.total > rhs.total;
  82. return lhs.count > rhs.count;
  83. }
  84. void init()
  85. {
  86. if (initialized) return;
  87. std::memset(allocList, 0, sizeof(allocList));
  88. allocCount = 0;
  89. currentAllocId = 0;
  90. initialized = true;
  91. // Generate timestamped log filename
  92. std::time_t now = std::time(nullptr);
  93. std::tm* localTime = std::localtime(&now);
  94. std::strftime(gLogFilename, sizeof(gLogFilename), "memlog_%Y-%m-%d_%H-%M-%S.txt", localTime);
  95. std::atexit(shutdown);
  96. }
  97. void shutdown()
  98. {
  99. if (!initialized) return;
  100. FILE* log = std::fopen(gLogFilename, "w");
  101. if (!log)
  102. return;
  103. std::fprintf(log, "\n--- Memory Leak Report ---\n");
  104. for (U32 curRep = 0; curRep < allocCount; ++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. DWORD displacement = 0;
  125. IMAGEHLP_LINE64 line;
  126. std::memset(&line, 0, sizeof(IMAGEHLP_LINE64));
  127. line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
  128. if (SymFromAddr(process, addr, 0, symbol)) {
  129. if (SymGetLineFromAddr64(process, addr, &displacement, &line)) {
  130. std::sprintf(stack, " [%d] %s - %s:%lu\n",
  131. curStack, symbol->Name, line.FileName, line.LineNumber);
  132. }
  133. else {
  134. std::sprintf(stack, " [%d] %s - ???:???\n",
  135. curStack, symbol->Name);
  136. }
  137. }
  138. else {
  139. std::sprintf(stack, " [%d] ???\n", curStack);
  140. }
  141. report += stack;
  142. }
  143. std::free(symbol);
  144. #else
  145. char** symbols = backtrace_symbols(allocList[curRep].backtracePtrs, allocList[i].backtraceSize);
  146. for (int curStack = 0; curStack < allocList[curRep].backtraceSize; ++curStack)
  147. {
  148. std::sprintf(stack, " [%d] %s\n", curStack, symbols[curStack]);
  149. report += stack;
  150. }
  151. std::free(symbols);
  152. #endif
  153. }
  154. for (U32 oldRep = 0; oldRep < curRep; ++oldRep)
  155. {
  156. if (!memLog[oldRep].skip && (memLog[oldRep].report.find(report) != std::string::npos))
  157. {
  158. //inc origional
  159. memLog[oldRep].count++;
  160. memLog[oldRep].total += allocList[curRep].size;
  161. //skip dupe report
  162. memLog[curRep].skip = true;
  163. }
  164. }
  165. if (!memLog[curRep].skip)
  166. {
  167. memLog[curRep].report = report;
  168. memLog[curRep].count = 1;
  169. memLog[curRep].total = allocList[curRep].size;
  170. }
  171. }
  172. }
  173. std::sort(memLog, memLog + allocCount, &sortMemReports);
  174. for (U32 ntry = 0; ntry < allocCount; ++ntry)
  175. {
  176. if (!memLog[ntry].skip /* && (memLog[ntry].count>9 || memLog[ntry].total >1023)*/) //unrem to focus on large leaks only -BJR
  177. {
  178. std::fprintf(log, "Leak-count[%i]total[%i]:%s", memLog[ntry].count, memLog[ntry].total, memLog[ntry].report.c_str());
  179. memLog[ntry].report.clear();
  180. }
  181. }
  182. std::fclose(log);
  183. std::memset(allocList, 0, sizeof(allocList));
  184. allocCount = 0;
  185. currentAllocId = 0;
  186. initialized = false;
  187. }
  188. void checkPtr(void* ptr)
  189. {
  190. for (U32 i = 0; i < allocCount; ++i)
  191. if (allocList[i].ptr == ptr)
  192. return;
  193. Platform::debugBreak();
  194. }
  195. static void* alloc(dsize_t size, bool array, const char* fileName, U32 line)
  196. {
  197. if (size == 0)
  198. return nullptr;
  199. void* ptr = std::malloc(size);
  200. if (!ptr)
  201. return nullptr;
  202. if (!initialized || allocCount >= MaxAllocs)
  203. return ptr;
  204. MemInfo& info = allocList[allocCount++];
  205. info.ptr = ptr;
  206. info.size = size;
  207. info.file = fileName ? fileName : "unknown";
  208. info.line = line;
  209. info.allocId = currentAllocId++;
  210. info.flagged = false;
  211. if (gStackTrace)
  212. {
  213. #ifdef _WIN32
  214. info.backtraceSize = CaptureStackBackTrace(0, 16, info.backtracePtrs, nullptr);
  215. #else
  216. info.backtraceSize = backtrace(info.backtracePtrs, MaxBacktraceDepth);
  217. #endif
  218. }
  219. return ptr;
  220. }
  221. static void free(void* ptr, bool array)
  222. {
  223. if (!ptr)
  224. return;
  225. if (!initialized)
  226. {
  227. std::free(ptr);
  228. return;
  229. }
  230. for (U32 i = 0; i < allocCount; ++i)
  231. {
  232. if (allocList[i].ptr == ptr)
  233. {
  234. std::free(ptr);
  235. allocList[i] = allocList[allocCount - 1];
  236. allocList[--allocCount] = {};
  237. return;
  238. }
  239. }
  240. // Unknown pointer, still free it.
  241. std::free(ptr);
  242. }
  243. void getMemoryInfo(void* ptr, MemInfo& info)
  244. {
  245. if (!ptr || !initialized)
  246. return;
  247. for (U32 i = 0; i < allocCount; ++i)
  248. {
  249. if (allocList[i].ptr == ptr)
  250. {
  251. info = allocList[i];
  252. return;
  253. }
  254. }
  255. }
  256. static void* realloc(void* oldPtr, dsize_t newSize, const char* fileName, U32 line)
  257. {
  258. if (!initialized)
  259. return std::realloc(oldPtr, newSize); // fallback if not tracking
  260. if (newSize == 0)
  261. {
  262. free(oldPtr, false);
  263. return nullptr;
  264. }
  265. if (oldPtr == nullptr)
  266. return alloc(newSize, false, fileName, line);
  267. void* newPtr = std::realloc(oldPtr, newSize);
  268. if (!newPtr)
  269. return nullptr;
  270. // Update existing record
  271. for (U32 i = 0; i < allocCount; ++i)
  272. {
  273. if (allocList[i].ptr == oldPtr)
  274. {
  275. allocList[i].ptr = newPtr;
  276. allocList[i].size = newSize;
  277. allocList[i].file = fileName;
  278. allocList[i].line = line;
  279. allocList[i].allocId = currentAllocId++;
  280. return newPtr;
  281. }
  282. }
  283. // Not found — see if newPtr is already being tracked
  284. for (U32 i = 0; i < allocCount; ++i)
  285. {
  286. if (allocList[i].ptr == newPtr)
  287. {
  288. allocList[i].size = newSize;
  289. allocList[i].file = fileName;
  290. allocList[i].line = line;
  291. allocList[i].allocId = currentAllocId++;
  292. return newPtr;
  293. }
  294. }
  295. // Still not found — treat as a new allocation
  296. if (allocCount < MaxAllocs)
  297. {
  298. MemInfo& info = allocList[allocCount++];
  299. info = {};
  300. info.ptr = newPtr;
  301. info.size = newSize;
  302. info.file = fileName;
  303. info.line = line;
  304. info.allocId = currentAllocId++;
  305. }
  306. return newPtr;
  307. }
  308. #endif
  309. }
  310. //---------------------------------------------------------------------------
  311. //---------------------------------------------------------------------------
  312. #if !defined(TORQUE_DISABLE_MEMORY_MANAGER)
  313. // Manage our own memory, add overloaded memory operators and functions
  314. void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line)
  315. {
  316. return Memory::alloc(size, false, fileName, line);
  317. }
  318. void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line)
  319. {
  320. return Memory::alloc(size, true, fileName, line);
  321. }
  322. void* FN_CDECL operator new(dsize_t size)
  323. {
  324. return Memory::alloc(size, false, NULL, 0);
  325. }
  326. void* FN_CDECL operator new[](dsize_t size)
  327. {
  328. return Memory::alloc(size, true, NULL, 0);
  329. }
  330. void FN_CDECL operator delete(void* mem)
  331. {
  332. Memory::free(mem, false);
  333. }
  334. void FN_CDECL operator delete[](void* mem)
  335. {
  336. Memory::free(mem, true);
  337. }
  338. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  339. {
  340. return Memory::alloc(in_size, false, fileName, line);
  341. }
  342. void dFree(void* in_pFree)
  343. {
  344. Memory::free(in_pFree, false);
  345. }
  346. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  347. {
  348. return Memory::realloc(in_pResize, in_size, fileName, line);
  349. }
  350. DefineEngineFunction(LeakTrace, void, (bool start, bool stackTrace), (true, true), "start/stop tracing leaks")
  351. {
  352. if (Memory::initialized) Memory::shutdown();
  353. if (start) Memory::init();
  354. Memory::gFromScript = true;
  355. Memory::gStackTrace = stackTrace;
  356. }
  357. #else
  358. // Don't manage our own memory
  359. void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line)
  360. {
  361. return malloc(in_size);
  362. }
  363. void dFree(void* in_pFree)
  364. {
  365. free(in_pFree);
  366. }
  367. void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line)
  368. {
  369. return realloc(in_pResize,in_size);
  370. }
  371. #endif