#include #include #include #include #include #include "KString.h" #include "KMemory.h" // last include #pragma warning(disable:4996) /////////////////////////////////////////////////////////////////////////////// // // KANJI_DUMP_LEAKED_MEM will print out the memory block that was leaked. // This is helpful when leaked objects have string identifiers. // /////////////////////////////////////////////////////////////////////////////// //#define KANJI_DUMP_LEAKED_MEM /////////////////////////////////////////////////////////////////////////////// // // KANJI_TRACK_MEM_USAGE will print out all memory allocations. // /////////////////////////////////////////////////////////////////////////////// //#define KANJI_TRACK_MEM_USAGE /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Our memory system is thread-safe, but instead of linking massive libraries, // we attempt to use C++11 std::mutex. #ifdef USE_CPP11_MUTEX_DISABLED #include typedef std::recursive_mutex KSysLock; // rentrant struct KAutoLock { KAutoLock(KSysLock& lock) :mLock(lock) { mLock.lock(); } // acquire ~KAutoLock() { mLock.unlock(); } // release KSysLock& mLock; }; #else // Fallback to unsafe. don't spawn threads typedef int KSysLock; struct KAutoLock { KAutoLock(KSysLock) {} // acquire ~KAutoLock() {} // release }; #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// struct KANJI_ALLOC_INFO { size_t size; std::string file; int line; }; static bool showLeaks = false; class KAllocMap : public std::map { public: KSysLock crit; static bool allocMapValid; public: KAllocMap() { allocMapValid = true; } ~KAllocMap() { if (showLeaks) KMemoryDumpUnfreed(); allocMapValid = false; } }; bool KAllocMap::allocMapValid = false; static KAllocMap allocMap; // once this static object destructs, it dumps unfreed memory /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #ifdef KANJI_TRACK_MEM_USAGE void KMemoryDumpUsage(); // forward declaration class KAllocStat { public: typedef std::map allocCount; // [size] = count typedef std::map, allocCount> allocInfo; // [file, line] = allocCount allocInfo memInfo; static bool allocMapValid; public: KAllocStat() { allocMapValid = true; } ~KAllocStat() { if (showLeaks) KMemoryDumpUsage(); allocMapValid = false; } void addTrack(const char* fname, int lnum, int asize) { allocCount& info = memInfo[std::pair(fname, lnum)]; info[asize]++; } }; bool KAllocStat::allocMapValid = false; static KAllocStat allocStat; #endif // KANJI_TRACK_MEM_USAGE /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// extern void KMemoryAddTrack( void* addr, size_t asize, const char* fname, int lnum ) { if (!KAllocMap::allocMapValid || asize == 0) return; KAutoLock aCrit(allocMap.crit); showLeaks = true; KANJI_ALLOC_INFO& info = allocMap[addr]; info.file = fname; info.line = lnum; info.size = asize; #ifdef KANJI_TRACK_MEM_USAGE if (KAllocStat::allocMapValid) allocStat.addTrack(fname, lnum, asize); #endif }; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void KMemoryRemoveTrack(void* addr) { if (!KAllocMap::allocMapValid) return; KAutoLock aCrit(allocMap.crit); KAllocMap::iterator anItr = allocMap.find(addr); if (anItr != allocMap.end()) allocMap.erase(anItr); }; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void KMemoryDumpUnfreed() { if (!KAllocMap::allocMapValid) return; KAutoLock aCrit(allocMap.crit); // prevent modification of the map while iterating size_t totalSize = 0; char buf[8192]; FILE* f = fopen("mem_leaks.txt", "wt"); if (!f) return; time_t aTime = time(NULL); sprintf(buf, "Memory Leak Report for %s\n", asctime(localtime(&aTime))); fprintf(f, "%s", buf); KOutputDebug(DEBUGLVL, "\n"); KOutputDebug(INFOLVL, buf); for(KAllocMap::iterator i = allocMap.begin(); i != allocMap.end(); ++i) { sprintf(buf, "%s(%d) : Leak %u byte%s @0x%08X\n", i->second.file.c_str(), i->second.line, i->second.size, i->second.size > 1 ? "s" : "", (size_t)i->first); KOutputDebug(ERRORLVL, buf); fprintf(f, "%s", buf); #ifdef KANJI_DUMP_LEAKED_MEM unsigned char* data = (unsigned char*)i->first; int count = 0; char hex_dump[1024]; char ascii_dump[1024]; for (int index = 0; index < i->second.size; index++) { unsigned char _c = *data; if (count == 0) sprintf(hex_dump, "\t%02X ", _c); else sprintf(hex_dump, "%s%02X ", hex_dump, _c); // technically, this is undefined behavior if ((_c < 32) || (_c > 126)) _c = '.'; if (count == 7) sprintf(ascii_dump, "%s%c ", ascii_dump, _c); else sprintf(ascii_dump, "%s%c", count == 0 ? "\t" : ascii_dump, _c); // technically, this is undefined behavior if (++count == 16) { count = 0; sprintf(buf, "%s\t%s\n", hex_dump, ascii_dump); fprintf(f, buf); memset((void*)hex_dump, 0, 1024); memset((void*)ascii_dump, 0, 1024); } data++; } if (count != 0) { fprintf(f, hex_dump); for (int index = 0; index < 16 - count; index++) fprintf(f, "\t"); fprintf(f, ascii_dump); for (int index = 0; index < 16 - count; index++) fprintf(f, "."); } count = 0; fprintf(f, "\n\n"); memset((void*)hex_dump, 0, 1024); memset((void*)ascii_dump, 0, 1024); #endif // KANJI_DUMP_LEAKED_MEM totalSize += i->second.size; } ErrorLevel lvl = (totalSize > 0) ? ERRORLVL : INFOLVL; sprintf(buf, "-----------------------------------------------------------\n"); fprintf(f, "%s", buf); KOutputDebug(lvl, buf); sprintf(buf, "Total Unfreed: %u bytes (%luKB)\n\n", totalSize, totalSize / 1024); KOutputDebug(lvl, buf); fprintf(f, "%s", buf); fclose(f); } #ifdef KANJI_TRACK_MEM_USAGE void KMemoryDumpUsage() { if (!KAllocStat::allocMapValid) return; char buf[8192]; FILE* f = fopen("mem_usage.txt", "wt"); time_t aTime = time(NULL); sprintf(buf, "Memory Usage Report for %s\n", asctime(localtime(&aTime))); if (f) fprintf(f, "%s", buf); KOutputDebug("\n"); KOutputDebug(buf); for(KAllocStat::allocInfo::iterator i = allocStat.memInfo.begin(); i != allocStat.memInfo.end(); ++i) { int aBytesTotal = 0; int aCallsTotal = 0; for (KAllocStat::allocCount::iterator index = i->second.begin(); index != i->second.end(); ++index) { aBytesTotal += index->first; aCallsTotal += index->second; sprintf(buf, "%s(%d) : %d bytes (%d %s)\n", i->first.first.c_str(), i->first.second, index->first, index->second, index->second == 1 ? "call" : "calls"); if (f) fprintf(f, "%s", buf); KOutputDebug(buf); } if (i->second.size() > 1) { sprintf(buf, " %s(%d) : %d KB total (%d calls)\n", i->first.first.c_str(), i->first.second, aBytesTotal / 1024, aCallsTotal); if (f) fprintf(f, "%s", buf); KOutputDebug(buf); } } if (f) fclose(f); } #endif // KANJI_TRACK_MEM_USAGE size_t KMemoryAllocated() { if (!KAllocMap::allocMapValid) return 0; KAutoLock aCrit(allocMap.crit); size_t size = 0; for(auto i = allocMap.begin(); i != allocMap.end(); ++i) { KANJI_ALLOC_INFO& info = i->second; size += info.size; } return size; }