diff --git a/source/lib/debug.cpp b/source/lib/debug.cpp index dda7518a93..383df2b7d2 100644 --- a/source/lib/debug.cpp +++ b/source/lib/debug.cpp @@ -22,6 +22,10 @@ #include "lib/sysdep/cpu.h" // cpu_CAS #include "lib/sysdep/sysdep.h" +#if OS_WIN +#include "lib/sysdep/win/wdbg_heap.h" +#endif + ERROR_ASSOCIATE(ERR::SYM_NO_STACK_FRAMES_FOUND, "No stack frames found", -1); ERROR_ASSOCIATE(ERR::SYM_UNRETRIEVABLE_STATIC, "Value unretrievable (stored in external module)", -1); @@ -396,7 +400,13 @@ static ErrorReaction carry_out_ErrorReaction(ErrorReaction er, uint flags, u8* s case ER_EXIT: exit_requested = true; // see declaration - abort(); +#if OS_WIN + // prevent (slow) heap reporting since we're exiting abnormally and + // thus probably leaking like a sieve. + wdbg_heap_Enable(false); +#endif + + exit(EXIT_FAILURE); } return er; diff --git a/source/lib/debug.h b/source/lib/debug.h index 7a7cb30acf..b92d6d34e8 100644 --- a/source/lib/debug.h +++ b/source/lib/debug.h @@ -15,7 +15,6 @@ // diagnosing and reporting program errors. // - a symbol engine provides access to compiler-generated debug information and // can also give a stack trace including local variables; -// - hooks into the memory allocator improve its leak detection; // - our more powerful assert() replacement gives a stack trace so // that the underlying problem becomes apparent; // - the output routines make for platform-independent logging and @@ -35,17 +34,6 @@ extern void debug_break(); #endif -//----------------------------------------------------------------------------- -// debug memory allocator -//----------------------------------------------------------------------------- - -/** - * check heap integrity. - * errors are reported by the CRT or via debug_display_error. - **/ -LIB_API void debug_heap_check(void); - - //----------------------------------------------------------------------------- // output //----------------------------------------------------------------------------- diff --git a/source/lib/file/archive/archive_zip.cpp b/source/lib/file/archive/archive_zip.cpp index 22e71cb9ff..f18c97aa8a 100644 --- a/source/lib/file/archive/archive_zip.cpp +++ b/source/lib/file/archive/archive_zip.cpp @@ -71,11 +71,11 @@ public: size_t Size() const { debug_assert(m_magic == lfh_magic); - const size_t fn_len = read_le16(&m_fn_len); - const size_t e_len = read_le16(&m_e_len); + size_t size = sizeof(LFH); + size += read_le16(&m_fn_len); + size += read_le16(&m_e_len); // note: LFH doesn't have a comment field! - - return sizeof(LFH) + fn_len + e_len; + return size; } private: @@ -386,17 +386,12 @@ public: std::string zipPathname; cdfh->GetPathname(zipPathname); - const size_t lastSlash = zipPathname.find_last_of('/'); - if(lastSlash != zipPathname.length()-1) // we only want files + const size_t lastSlashOfs = zipPathname.find_last_of('/'); + const size_t nameOfs = (lastSlashOfs == std::string::npos)? 0 : lastSlashOfs+1; + if(nameOfs != zipPathname.length()) // ignore paths ending in slash (i.e. representing a directory) { - std::string name; - std::string* pname = &zipPathname; // assume zipPathname only has a name component - if(lastSlash != std::string::npos) - { - name = zipPathname.substr(lastSlash, zipPathname.length()-lastSlash); - pname = &name; - } - FileInfo fileInfo(*pname, cdfh->USize(), cdfh->MTime()); + const std::string name = zipPathname.substr(nameOfs, zipPathname.length()-nameOfs); + FileInfo fileInfo(name, cdfh->USize(), cdfh->MTime()); shared_ptr archiveFile(new ArchiveFile_Zip(m_file, cdfh->HeaderOffset(), cdfh->CSize(), cdfh->Checksum(), cdfh->Method())); cb(zipPathname, fileInfo, archiveFile, cbData); } diff --git a/source/lib/file/io/io.cpp b/source/lib/file/io/io.cpp index c2dbaee6df..87ebcb8adc 100644 --- a/source/lib/file/io/io.cpp +++ b/source/lib/file/io/io.cpp @@ -119,7 +119,7 @@ public: { m_file = file; m_blockId = BlockId(file->Pathname(), alignedOfs); - if(file->Mode() == 'r' && s_blockCache.Retrieve(m_blockId, m_cachedBlock)) + if(false && file->Mode() == 'r' && s_blockCache.Retrieve(m_blockId, m_cachedBlock)) { stats_block_cache(CR_HIT); diff --git a/source/lib/file/vfs/vfs.cpp b/source/lib/file/vfs/vfs.cpp index 3fa8032e38..45672c5e39 100644 --- a/source/lib/file/vfs/vfs.cpp +++ b/source/lib/file/vfs/vfs.cpp @@ -70,7 +70,6 @@ public: CHECK_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE)); PRealDirectory realDirectory = directory->AssociatedDirectory(); -if(!realDirectory) return ERR::FAIL; // WORKAROUND: vfs doesn't create real dirs by itself const std::string& name = pathname.leaf(); RETURN_ERR(realDirectory->Store(name, fileContents, size)); diff --git a/source/lib/file/vfs/vfs_lookup.cpp b/source/lib/file/vfs/vfs_lookup.cpp index 2ba964dd6d..679370b757 100644 --- a/source/lib/file/vfs/vfs_lookup.cpp +++ b/source/lib/file/vfs/vfs_lookup.cpp @@ -139,18 +139,25 @@ LibError vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDi { TIMER_ACCRUE(tc_lookup); - directory = startDirectory; + // extract and validate flags (ensure no unknown bits are set) + const bool addMissingDirectories = (flags & VFS_LOOKUP_ADD) != 0; + const bool createMissingDirectories = (flags & VFS_LOOKUP_CREATE) != 0; + debug_assert((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE)) == 0); + if(pfile) *pfile = 0; + + directory = startDirectory; RETURN_ERR(Populate(directory)); - if(pathname.empty()) // early out for root directory - return INFO::OK; // (prevents iterator error below) + // early-out for pathname == "" when mounting into VFS root + if(pathname.empty()) // (prevent iterator error in loop end condition) + return INFO::OK; - VfsPath currentPath; // only used if flags & VFS_LOOKUP_CREATE - VfsPath::iterator it; + Path currentPath; // (.. thus far; used when createMissingDirectories) // for each directory component: + VfsPath::iterator it; // (used outside of loop to get filename) for(it = pathname.begin(); it != --pathname.end(); ++it) { const std::string& subdirectoryName = *it; @@ -158,26 +165,29 @@ TIMER_ACCRUE(tc_lookup); VfsDirectory* subdirectory = directory->GetSubdirectory(subdirectoryName); if(!subdirectory) { - if(!(flags & VFS_LOOKUP_ADD)) + if(addMissingDirectories) + subdirectory = directory->AddSubdirectory(subdirectoryName); + else return ERR::VFS_DIR_NOT_FOUND; // NOWARN + } - subdirectory = directory->AddSubdirectory(subdirectoryName); - - if(flags & VFS_LOOKUP_CREATE) + if(createMissingDirectories) + { + if(subdirectory->AssociatedDirectory()) + currentPath /= subdirectory->AssociatedDirectory()->GetPath().leaf(); + else { currentPath /= subdirectoryName; - -#if 0 - (void)mkdir(currentPath.external_directory_string().c_str(), S_IRWXO|S_IRWXU|S_IRWXG); - - PRealDirectory realDirectory(new RealDirectory(currentPath.string(), 0, 0)); - subdirectory->Attach(realDirectory); -#endif + if(mkdir(currentPath.external_directory_string().c_str(), S_IRWXO|S_IRWXU|S_IRWXG) == 0) + { + PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0)); + subdirectory->Attach(realDirectory); + } } } + RETURN_ERR(Populate(subdirectory)); directory = subdirectory; - RETURN_ERR(Populate(directory)); } if(pfile) diff --git a/source/lib/mmgr.cpp b/source/lib/mmgr.cpp deleted file mode 100644 index 9ee1ab1d42..0000000000 --- a/source/lib/mmgr.cpp +++ /dev/null @@ -1,1423 +0,0 @@ -/** - * ========================================================================= - * File : mmgr.cpp - * Project : 0 A.D. - * Description : memory manager and tracker. - * ========================================================================= - */ - -// license: GPL; see lib/license.txt - -#include "precompiled.h" - -#if 0 - -ERROR_ASSOCIATE(ERR::MEM_ALLOC_NOT_FOUND, "Not a valid allocated address", -1); -ERROR_ASSOCIATE(ERR::MEM_OVERWRITTEN, "Wrote to memory outside valid allocation", -1); - - -// for easy removal in release builds, so that we don't cause any overhead. -// note that any application calls to our functions must be removed also, -// but this is preferable to stubbing them out here ("least surprise"). -#if CONFIG_USE_MMGR - -#include "mmgr.h" - -#include -#include -#include -#include -#include -#include - -#include "posix/posix.h" -#include "debug.h" - -// remove macro hooks (we need to use the actual malloc/new etc. routines) -#include "nommgr.h" - - -////////////////////////////////////////////////////////////////////////////// -// locking for thread safety -////////////////////////////////////////////////////////////////////////////// - -static pthread_mutex_t mutex; - -// prevents using uninitialized lock before init (due to undefined -// NLSO ctor call order) or after shutdown -static bool lock_initialized; - -static void lock_init() throw() -{ - if(pthread_mutex_init(&mutex, 0) == 0) - lock_initialized = true; -} - -static void lock_shutdown() throw() -{ - WARN_ERR(pthread_mutex_destroy(&mutex)); - lock_initialized = false; -} - -static void lock() throw() -{ - if(lock_initialized) - WARN_ERR(pthread_mutex_lock(&mutex)); -} - -static void unlock() throw() -{ - if(lock_initialized) - WARN_ERR(pthread_mutex_unlock(&mutex)); -} - - -////////////////////////////////////////////////////////////////////////////// -// options (enable/disable additional checks) -////////////////////////////////////////////////////////////////////////////// - -// we can induce allocations to fail, for testing the application's -// error handling. uncomment and set to percentage that should fail. -// note: we use #define to make absolutely sure no -// failures are induced unless desired. -//#define RANDOM_FAILURE 10.0 - -// note: padding size is in bytes, and is added before and after the -// user's buffer. pattern_set assumes it's an integral number of ulongs. - -// enable all checks (slow!) -#if CONFIG_PARANOIA -static uint options = MMGR_ALL; -static const size_t padding_size = 256 * sizeof(ulong); -// normal settings -#else -static uint options = 0; -static const size_t padding_size = 1 * sizeof(ulong); -#endif - - -uint mmgr_set_options(uint new_options) -{ - lock(); - - if(new_options != MMGR_QUERY) - { - debug_assert(!(new_options & ~MMGR_ALL) && "unrecognized options set"); - options = new_options; - } - uint ret = options; - - unlock(); - return ret; -} - - -////////////////////////////////////////////////////////////////////////////// -// string formatting routines for log and reports -////////////////////////////////////////////////////////////////////////////// - -const size_t NUM_SIZE = 32; - // enough to cover even 64 bit numbers - -static const char* insert_commas(char* out, size_t value) -{ - char num[NUM_SIZE]; - sprintf(num, "%u", value); - const size_t num_len = strlen(num); - debug_assert(num_len != 0); // messes up #comma calc below - - const size_t out_len = num_len + (num_len-1)/3; - char* pos = out+out_len; - *pos-- = '\0'; - - uint digits = 0; - for(int i = (int)num_len-1; i >= 0; i--) - { - *pos-- = num[i]; - if(++digits == 3 && i != 0) - { - *pos-- = ','; - digits = 0; - } - } - - return out; -} - - -static const char* format_size_string(char* str, size_t value) -{ - char num[NUM_SIZE]; - (void)insert_commas(num, value); - if(value > GiB) - sprintf(str, "%10s (%7.2fGi)", num, value / (float)GiB); - else if(value > MiB) - sprintf(str, "%10s (%7.2fMi)", num, value / (float)MiB); - else if(value > KiB) - sprintf(str, "%10s (%7.2fKi)", num, value / (float)KiB); - else - sprintf(str, "%10s bytes ", num); - return str; -} - - -////////////////////////////////////////////////////////////////////////////// -// allocator for Alloc objects -////////////////////////////////////////////////////////////////////////////// - -enum AllocType -{ - AT_UNKNOWN = 0, - - AT_MALLOC = 1, - AT_CALLOC = 2, - AT_REALLOC = 3, - AT_FREE = 4, - - AT_NEW = 5, - AT_NEW_ARRAY = 6, - AT_DELETE = 7, - AT_DELETE_ARRAY = 8, - - AT_INVALID = 9 -}; - -// must match enum AllocType! -static const char* types[] = -{ - "unknown", - "malloc", - "calloc", - "realloc", - "free", - "new", - "new[]", - "delete", - "delete[]", -}; - -struct Alloc -{ - void* p; - size_t size; - uint num; - Alloc* next; - Alloc* prev; - const char* owner; - - uint type : 4; - uint break_on_free : 1; - uint break_on_realloc : 1; - - void* user_p() const - { - return (char*)p + padding_size; - } - size_t user_size() const - { - return size - padding_size*2; - } -}; - - -static Alloc* freelist; -static Alloc** reservoirs = 0; // array of pointers to runs of 256 Allocs -static size_t num_reservoirs = 0; // # entries - -static Alloc* alloc_new() -{ - // If necessary, grow the freelist of unused Allocs - if(!freelist) - { - freelist = (Alloc*)calloc(256, sizeof(Alloc)); - if(!freelist) - { - DEBUG_WARN_ERR(ERR::NO_MEM); - return 0; - } - - for(uint i = 0; i < 256 - 1; i++) - freelist[i].next = &freelist[i+1]; - - const size_t bytes = (num_reservoirs + 1) * sizeof(Alloc*); - Alloc* *temp = (Alloc* *) realloc(reservoirs, bytes); - debug_assert(temp); - if(temp) - { - reservoirs = temp; - reservoirs[num_reservoirs++] = freelist; - } - } - - // Grab a new Alloc from the front of the freelist - Alloc* a = freelist; - freelist = a->next; - - return a; -} - -static void alloc_delete(Alloc* a) -{ - memset(a, 0, sizeof(Alloc)); - - // add to the front of our freelist of unused Allocs - a->next = freelist; - freelist = a; -} - -static void alloc_shutdown() -{ - if(reservoirs) - { - for(uint i = 0; i < num_reservoirs; i++) - free(reservoirs[i]); - free(reservoirs); - reservoirs = 0; - num_reservoirs = 0; - freelist = 0; - } -} - - -////////////////////////////////////////////////////////////////////////////// -// user allocation pointer -> Alloc lookup data structure -////////////////////////////////////////////////////////////////////////////// - -// rationale: -// - split into separate routines (as opposed to exposing details to -// user code) for easier modification. -// - may as well have used STL hash_map, but it's non-standard -// thus far, and we have a hand-rolled container already. - -static const size_t hash_entries = (1u << 11)+1; - // ~8kb memory used; prime for better distribution -static Alloc* hash_table[hash_entries]; - -// return pointer to list of all Allocs with the same pointer hash. -static Alloc*& hash_chain(const void* pointer) -{ - uintptr_t address = reinterpret_cast(pointer); - size_t index = ((size_t)address >> 4) % hash_entries; - // many allocations are 16-byte aligned, so shift off lower 4 bits - // (=> better hash distribution) - return hash_table[index]; -} - -static void allocs_remove(const Alloc* a) -{ - Alloc*& chain = hash_chain(a->user_p()); - // it was at head of chain - if(chain == a) - chain = a->next; - // in middle of chain - else - { - if(a->prev) - a->prev->next = a->next; - if(a->next) - a->next->prev = a->prev; - } -} - -static void allocs_add(Alloc* a) -{ - Alloc*& chain = hash_chain(a->user_p()); - if(chain) - chain->prev = a; - a->next = chain; - a->prev = 0; - chain = a; -} - -static Alloc* allocs_find(const void* user_p) -{ - if(!user_p) - debug_assert(user_p); - - Alloc* a = hash_chain(user_p); - while(a) - { - if(a->user_p() == user_p) - break; - a = a->next; - } - return a; -} - -static void allocs_foreach(void (*cb)(const Alloc*, void*), void* arg) -{ - for(uint i = 0; i < hash_entries; i++) - { - const Alloc* a = hash_table[i]; - while(a) - { - cb(a, arg); - a = a->next; - } - } -} - - -////////////////////////////////////////////////////////////////////////////// -// padding: make sure the user hasn't over/underrun their buffer. -////////////////////////////////////////////////////////////////////////////// - -static const ulong pattern_before = 0xbaadf00d; -static const ulong pattern_after = 0xdeadc0de; -static const ulong pattern_unused = 0xfeedface; -static const ulong pattern_freed = 0xdeadbeef; - -static void pattern_set(const Alloc* a, ulong pattern) -{ - // fill user's data (optional) - // note: don't use memset, because we want multi-byte patterns - if(options & MMGR_FILL && a->user_size() > 0) - { - u8* p = (u8*)a->user_p(); - const size_t size = a->user_size(); - - // write whole ulongs - for(size_t i = 0; i < size / sizeof(ulong); i++) - { - *(ulong*)p = pattern; - p += sizeof(ulong); - } - - // remainder - for(size_t shift = 0; shift < (size % sizeof(ulong)) * 8; shift += 8) - *p++ = (u8)((pattern >> shift) & 0xff); - } - - // fill prefix/postfix bytes (note: integral number of ulongs) - ulong* pre = (ulong*)a->p; - ulong* post = (ulong*)( (char*)a->p + a->size - padding_size ); - for(uint i = 0; i < padding_size / sizeof(ulong); i++) - { - *pre++ = pattern_before; - *post++ = pattern_after; - // note: doesn't need to be split into 2 loops; cache is A2 - } -} - - -static bool padding_is_intact(const Alloc* a, ulong **corrupt_address) -{ - *corrupt_address=NULL; - - ulong* pre = (ulong*)a->p; - ulong* post = (ulong*)( (char*)a->p + a->size - padding_size ); - - for(uint i = 0; i < padding_size / sizeof(ulong); i++) - { - if(pre[i] != pattern_before) - { - *corrupt_address=&pre[i]; - return false; - } - else if (post[i] != pattern_after) - { - *corrupt_address=&post[i]; - return false; - } - } - return true; -} - - -static inline size_t calc_actual_size(const size_t user_size) -{ - return user_size + padding_size*2u; -} - -static inline void* calc_user_p(const void* actual_p) -{ - if(!actual_p) - return 0; - return (char*)actual_p + padding_size; -} - - -////////////////////////////////////////////////////////////////////////////// -// unused memory reporting -////////////////////////////////////////////////////////////////////////////// - -// return byte size of all the ulongs in the allocation whose contents -// haven't changed from the "unused" pattern since allocation. -// called from calc_all_unused_cb and log_this_alloc. -static size_t calc_unused(const Alloc* a) -{ - size_t total = 0; - const ulong* p = (const ulong*)a->user_p(); - for(uint i = 0; i < a->user_size(); i += sizeof(ulong)) - if(*p++ == pattern_unused) - total += sizeof(long); - - return total; -} - - -static void calc_all_unused_cb(const Alloc* a, void* arg) -{ - size_t* ptotal = (size_t*)arg; - *ptotal += calc_unused(a); -} - -// return total unused size in all allocations. -static size_t calc_all_unused() -{ - size_t total = 0; - allocs_foreach(calc_all_unused_cb, &total); - return total; -} - - -////////////////////////////////////////////////////////////////////////////// -// statistics: track current, cumulative, and peak allocations -////////////////////////////////////////////////////////////////////////////// - -static struct Stats -{ - size_t cur_user_mem; - size_t peak_user_mem; - size_t total_user_mem; - - size_t cur_mem; - size_t peak_mem; - size_t total_mem; - - size_t cur_allocs; - size_t peak_allocs; - size_t total_allocs; -} stats; - -// note: also called when reallocating. cumulative # allocations -// will increase, but that makes sense. -static void stats_add(const Alloc* a) -{ - const size_t size = a->size; - const size_t user_size = a->user_size(); - - stats.cur_user_mem += user_size; - stats.cur_mem += size; - stats.cur_allocs++; - - stats.total_user_mem += user_size; - stats.total_mem += size; - stats.total_allocs++; - - stats.peak_user_mem = std::max(stats.peak_user_mem, stats.cur_user_mem); - stats.peak_mem = std::max(stats.peak_mem, stats.cur_mem); - stats.peak_allocs = std::max(stats.peak_allocs, stats.cur_allocs); -} - -static void stats_remove(const Alloc* a) -{ - const size_t size = a->size; - const size_t user_size = a->user_size(); - - stats.cur_user_mem -= user_size; - stats.cur_mem -= size; - stats.cur_allocs--; -} - - -////////////////////////////////////////////////////////////////////////////// -// logging to file -////////////////////////////////////////////////////////////////////////////// - -static void log_init(); -static const char* const log_filename = "mem_log.txt"; - -static FILE* log_fp; - -// open/append/close every call to make sure nothing gets lost when crashing. -// split out of log() to allow locked_log without code duplication. -static void vlog(const char* fmt, va_list args) -{ - log_init(); - - (void)vfprintf(log_fp, fmt, args); - - // user requested each log line go directly to disk. - if(options & MMGR_FLUSH_LOG) - fflush(log_fp); -} - -static void log(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - vlog(fmt, args); - va_end(args); -} - -// convenience function for overloaded new/delete that generate warnings -static void locked_log(const char* fmt, ...) -{ - lock(); - - va_list args; - va_start(args, fmt); - vlog(fmt, args); - va_end(args); - - unlock(); -} - - - -static void log_init() -{ - // open => we're already initialized. - if(log_fp) - return; - - log_fp = fopen(log_filename, "w"); - if(!log_fp) - { - debug_assert(0 && "log file open failed"); - return; - } - - // - // write header - // - - // get current time as string. - // doesn't need to be reentrant, but we need to use strftime - // elsewhere, so stick with it for consistency. - char time_str[100]; - time_t t = time(0); - const struct tm* tm_ = localtime(&t); - (void)strftime(time_str, sizeof(time_str), "%#c", tm_); - - log("Memory manager log, started on %s.\n", time_str); - log("\n"); - log("Logs errors, operations, and mmgr calls, depending on settings.\n"); - log("Entry types:\n"); - log("\n"); - log(" [!] - Error\n"); - log(" [?] - Warning\n"); - log(" [+] - Allocation\n"); - log(" [~] - Reallocation\n"); - log(" [-] - Deallocation\n"); - log(" [I] - Information\n"); - log(" [F] - Induced random failure to test the app's error handling\n"); - log(" [D] - Debug information for debugging mmgr\n"); - log("\n"); - log("Problematic allocations can be tracked by address+owner or number.\n"); - log("\n"); -} - - -static void log_shutdown() -{ - if(log_fp) - { - fclose(log_fp); - log_fp = 0; - } -} - - -static void log_this_alloc(const Alloc* a) -{ - // duplicated in write_alloc_cb(); factoring out isn't worth it - log("%06d 0x%08p 0x%08X 0x%08p 0x%08X 0x%08X %-8s %c %c %s\n", - a->num, - a->user_p(), a->user_size(), - a->p, a->size, - calc_unused(a), - types[a->type], - a->break_on_free ? 'Y':'N', - a->break_on_realloc ? 'Y':'N', - a->owner - ); -} - - -////////////////////////////////////////////////////////////////////////////// -// write report text files (leaks, statistics) -////////////////////////////////////////////////////////////////////////////// - -static void write_alloc_cb(const Alloc* a, void* arg) -{ - FILE* f = (FILE*)arg; - - // duplicated in log_this_alloc(Alloc*); factoring out isn't worth it - fprintf(f, "%06d 0x%08p 0x%08X 0x%08p 0x%08X 0x%08X %-8s %c %c %s\n", - a->num, - a->user_p(), a->user_size(), - a->p, a->size, - calc_unused(a), - types[a->type], - a->break_on_free ? 'Y':'N', - a->break_on_realloc ? 'Y':'N', - a->owner - ); -} - -static void write_all_allocs(FILE* f) -{ - fprintf(f, "Alloc. Addr Size Addr Size BreakOn BreakOn \n"); - fprintf(f, "Number Reported Reported Actual Actual Unused Method Dealloc Realloc Allocated by \n"); - fprintf(f, "------ ---------- ---------- ---------- ---------- ---------- -------- ------- ------- --------------------------------------------------- \n"); - - allocs_foreach(write_alloc_cb, f); -} - - -void mmgr_write_report(void) -{ - FILE* f = fopen("mem_report.txt", "w"); - if(!f) - { - debug_assert(0 && "open of memory report file failed"); - return; - } - - // get current time as string - // (needs to be reentrant, so don't use asctime et al) - char time_str[100]; - time_t t = time(0); - const struct tm* tm_ = localtime(&t); - (void)strftime(time_str, sizeof(time_str), "%#c", tm_); - - fprintf(f, "Detailed memory report:\n"); - fprintf(f, "Built %s %s; test run ended on %s\n", __DATE__, __TIME__, time_str); - fprintf(f, "\n"); - - char num[NUM_SIZE]; - char size[99]; // more than enough - - fprintf(f, "Unused:\n"); - fprintf(f, "-------\n"); - fprintf(f, "Memory allocated but not in use: %s\n", format_size_string(size, calc_all_unused())); - fprintf(f, "\n"); - - fprintf(f, "Peaks:\n"); - fprintf(f, "------\n"); - fprintf(f, " Allocation unit count: %10s\n", insert_commas(num, stats.peak_allocs)); - fprintf(f, " Reported to application: %s\n", format_size_string(size, stats.peak_user_mem)); - fprintf(f, " Actual: %s\n", format_size_string(size, stats.peak_mem)); - fprintf(f, " Memory tracking overhead: %s\n", format_size_string(size, stats.peak_mem - stats.peak_user_mem)); - fprintf(f, "\n"); - - fprintf(f, "Totals:\n"); - fprintf(f, "-------\n"); - fprintf(f, " Allocation unit count: %10s\n", insert_commas(num, stats.total_allocs)); - fprintf(f, " Reported to application: %s\n", format_size_string(size, stats.total_user_mem)); - fprintf(f, " Actual: %s\n", format_size_string(size, stats.total_mem)); - fprintf(f, " Memory tracking overhead: %s\n", format_size_string(size, stats.total_mem - stats.total_user_mem)); - fprintf(f, "\n"); - - fprintf(f, "Current:\n"); - fprintf(f, "--------\n"); - fprintf(f, " Allocation count: %10s\n", insert_commas(num, stats.cur_allocs)); - fprintf(f, " Reported to application: %s\n", format_size_string(size, stats.cur_user_mem)); - fprintf(f, " Actual: %s\n", format_size_string(size, stats.cur_mem)); - fprintf(f, " Memory tracking overhead: %s\n", format_size_string(size, stats.cur_mem - stats.cur_user_mem)); - fprintf(f, "\n"); - - write_all_allocs(f); - - fclose(f); -} - -// only generate leak report at exit if the app has called -// mmgr_write_leak_report, since it's slow. -static bool app_wants_leak_report = false; - -// separate from the main report - this is updated on every deallocate -// call when exiting, because each call may be the last -// (we can't say "everything after app shutdown is a leak", because -// it's common, albeit bad practice, for NLSOs to allocate memory). -void mmgr_write_leak_report(void) -{ - app_wants_leak_report = true; - - FILE* f = fopen("mem_leaks.txt", "w"); - if(!f) - { - debug_assert(0 && "open of memory leak report file failed"); - return; - } - - // get current time as string - // (needs to be reentrant, so don't use asctime et al) - char time_str[100]; - time_t t = time(0); - const struct tm* tm_ = localtime(&t); - (void)strftime(time_str, sizeof(time_str), "%#c", tm_); - - fprintf(f, "Memory leak report:\n"); - fprintf(f, "Built %s %s; test run ended on %s\n", __DATE__, __TIME__, time_str); - fprintf(f, "\n"); - - const size_t num_leaks = stats.cur_allocs; - if(num_leaks) - { - fprintf(f, "%d memory leak%s found:\n", num_leaks, num_leaks == 1 ? "":"s"); - fprintf(f, "\n"); - write_all_allocs(f); - } - else - fprintf(f, "No leaks found! Congratulations!\n"); - - fclose(f); -} - - -////////////////////////////////////////////////////////////////////////////// -// user-callable integrity checks -////////////////////////////////////////////////////////////////////////////// - -static bool alloc_is_valid(const Alloc *a); - -bool mmgr_is_valid_ptr(const void* p) -{ - // null pointer - won't even try ;-) - if (p == NULL) - return false; - lock(); - Alloc *a=allocs_find(p); - bool found = a != NULL && alloc_is_valid(a); - unlock(); - return found; -} - - -static bool alloc_is_valid(const Alloc* a) -{ - // catch(...) is really evil in that it stops serious and unexpected - // exceptions (e.g. "hardware is on fire") from getting through, but - // there's really no alternative, since padding_is_intact is very - // likely to crash if the Alloc is corrupted. - // TODO Linux doesn't treat e.g. SIGSEGV as an exception (crashes instead) - // Research linux/unix/gcc alternative for catching those (signal handler - // and set/longjmp?) - bool intact; - ulong *p=NULL; // The corrupted address - try - { - intact = padding_is_intact(a, &p); - } - catch(...) - { - intact = false; - } - - // this allocation has been over/underrun (i.e. modified outside the - // allocation's memory range) or is otherwise corrupt. - if(!intact) - { - // if p is between a->p and a->p+padding_size, accuse a and the alloc - // before a, if p is between a->p+a->size-padding_size and a->p+a->size, - // accuse a and the alloc after a - // TODO This doesn't really find out the next/previous alloc area, just - // prints the address, which end was currupted, and the data found - if (p) - { - u8 *user_p = (u8 *)a->user_p(); - u8 *user_p_end = ((u8*)a->user_p()) + a->user_size(); - ulong expected=0; - if ((u8*)p < user_p) - { - log("[!] Memory underrun: padding currupt at offset %d (bytes)\n", - ((u8 *)p)-user_p); - expected=pattern_before; - } - else - { - log("[!] Memory overrun: padding currupt at %d bytes past end of buffer\n", - ((u8 *)p)-user_p_end); - expected=pattern_after; - } - log("[!] Memory over/underrun: expected padding %08x, found garbage %08x\n", expected, *p); - } - else - log("[!] alloc_is_valid encountered an exception - something may be very wrong!\n"); - log_this_alloc(a); - DEBUG_WARN_ERR(ERR::MEM_OVERWRITTEN); - } - return intact; -} - -struct ValidateAllParams -{ - uint num_invalid; - uint num_allocs; -}; - -static void validate_all_cb(const Alloc* a, void* arg) -{ - ValidateAllParams* p = (ValidateAllParams*)arg; - p->num_allocs++; - if(!alloc_is_valid(a)) - p->num_invalid++; -} - -// provide separate lock-is-held version to prevent recursive locks. -// called from mmgr_alloc/realloc/free; return true if all are valid. -static bool validate_all() -{ - ValidateAllParams params = { 0, 0 }; - allocs_foreach(validate_all_cb, ¶ms); - - // Test for hash-table correctness - if(params.num_allocs != stats.cur_allocs) - { - // our internal pointer->Alloc lookup data structure is inconsistent! - // enable MMGR_VALIDATE_ALL, trigger this condition again, - // and check the log for the last successful operation. the problem - // will have occurred between then and now. - DEBUG_WARN_ERR(ERR::CORRUPTED); - log("[!] Memory tracking hash table corrupt!\n"); - } - - if(params.num_invalid) - { - DEBUG_WARN_ERR(ERR::MEM_OVERWRITTEN); - log("[!] %d allocations are corrupt\n", params.num_invalid); - return false; - } - - return true; -} - -bool mmgr_are_all_valid() -{ - lock(); - // do our check first, because it fails more cleanly - // (=> better chance to see where it happened in the debugger) - bool all_valid = validate_all(); - debug_heap_check(); - unlock(); - return all_valid; -} - - -////////////////////////////////////////////////////////////////////////////// -// init/shutdown hook - notifies us when ctor/dtor are called -////////////////////////////////////////////////////////////////////////////// - -static bool static_dtor_called = false; - -static struct NonLocalStaticObject -{ - NonLocalStaticObject() - { - // by calling now instead of on first lock() call, - // we ensure no threads have been spawned yet. - lock_init(); - - // note: don't init log here - current directory hasn't yet been set. - // the log file may otherwise be split between 2 files. - } - ~NonLocalStaticObject() - { - // if the app requested a leak report before now, the deallocator - // will update its leak report on every call (since dtors are now - // being called, each may be the last). note that there is no - // portable way to make sure a mmgr_shutdown() would be called - // as the very last thing before exit. - static_dtor_called = true; - - // don't shutdown the lock - some threads may still be active. - // do so in shutdown() - see call site. - } -} nlso; - - -static void shutdown(void) -{ - alloc_shutdown(); - log_shutdown(); - lock_shutdown(); -} - - -////////////////////////////////////////////////////////////////////////////// -// trigger breakpoint when accessing specified allocations -////////////////////////////////////////////////////////////////////////////// - -static uint cur_alloc_count = 0; -static uint break_on_count = 0; - - -void mmgr_break_on_alloc(uint count) -{ - lock(); - - break_on_count = count; - - unlock(); -} - - -void mmgr_break_on_realloc(const void* p) -{ - lock(); - - Alloc* a = allocs_find(p); - if(!a) - { - debug_assert(0 && "setting realloc breakpoint on invalid pointer"); - return; - } - - // setting realloc breakpoint on an allocation that - // doesn't support realloc. - debug_assert(a->type == AT_MALLOC || a->type == AT_CALLOC || - a->type == AT_REALLOC); - - a->break_on_realloc = true; - - unlock(); -} - - -void mmgr_break_on_free(const void* p) -{ - lock(); - - Alloc* a = allocs_find(p); - if(!a) - { - debug_assert(0 && "setting free breakpoint on invalid pointer"); - return; - } - - a->break_on_free = true; - - unlock(); -} - - -////////////////////////////////////////////////////////////////////////////// -// actual allocator, making use of all of the above :) -////////////////////////////////////////////////////////////////////////////// - -void* alloc_dbg(size_t user_size, AllocType type, const char* file, int line, const char* func, uint stack_frames) -{ - void* ret = 0; - - lock(); - - if(options & MMGR_TRACE) - log("[D] ENTER: alloc_dbg\n"); - - void* caller = debug_get_nth_caller(1+stack_frames, 0); - const char* caller_string = debug_get_symbol_string(caller, func, file, line); - - if(options & MMGR_LOG_ALL) - log("[+] %05d %8s of size 0x%08X(%08d) by %s\n", cur_alloc_count, types[type], user_size, user_size, caller_string); - - // caller's source file didn't include "mmgr.h" - debug_assert(type != AT_UNKNOWN); - - // you requested a breakpoint on this allocation number - ++cur_alloc_count; - debug_assert(cur_alloc_count != break_on_count); - - // simulate random failures -#ifdef RANDOM_FAILURE - { - double a = rand(); - double b = RAND_MAX / 100.0 * RANDOM_FAILURE; - if(a < b) - { - log("[F] Random induced failure\n"); - goto fail; - } - } -#endif - - const size_t size = calc_actual_size(user_size); - void* p = malloc(size); - if(!p) - { - DEBUG_WARN_ERR(ERR::NO_MEM); - log("[!] Allocation failed (out of memory)\n"); - goto fail; - } - - { - Alloc* a = alloc_new(); - if(!a) - goto fail; - a->p = p; - a->size = size; - a->num = cur_alloc_count; - a->owner = caller_string; - a->type = type; - a->break_on_free = a->break_on_realloc = 0; - - allocs_add(a); - stats_add(a); - - pattern_set(a, pattern_unused); - - // calloc() must zero the memory - if(type == AT_CALLOC) - memset(a->user_p(), 0, a->user_size()); - - if(options & MMGR_LOG_ALL) - log("[+] ----> addr 0x%08p\n", a->user_p()); - - ret = a->user_p(); - } -fail: - if(options & MMGR_VALIDATE_ALL) - (void)validate_all(); - - if(options & MMGR_TRACE) - log("[D] EXIT : alloc_dbg\n"); - - unlock(); - return ret; -} - - -void free_dbg(const void* user_p, AllocType type, const char* file, int line, const char* func, uint stack_frames) -{ - lock(); - - if(options & MMGR_TRACE) - log("[D] ENTER: free_dbg\n"); - - void* caller = debug_get_nth_caller(1+stack_frames, 0); - const char* caller_string = debug_get_symbol_string(caller, func, file, line); - - if(options & MMGR_LOG_ALL) - log("[-] ----- %8s of addr 0x%08p by %s\n", types[type], user_p, caller_string); - - // freeing a zero pointer is allowed by C and C++, and a no-op. - if(!user_p) - goto done; - - - // - // security checks - // - { - Alloc* a = allocs_find(user_p); - if(!a) - { - log("[!] mmgr_free: not allocated by this memory manager\n"); - DEBUG_WARN_ERR(ERR::MEM_ALLOC_NOT_FOUND); - goto fail; - } - // .. overrun? (note: alloc_is_valid already asserts if invalid) - alloc_is_valid(a); - // .. the owner wasn't compiled with mmgr.h - debug_assert(type != AT_UNKNOWN); - // .. allocator / deallocator type mismatch - debug_assert( - (type == AT_DELETE && a->type == AT_NEW ) || - (type == AT_DELETE_ARRAY && a->type == AT_NEW_ARRAY) || - (type == AT_FREE && a->type == AT_MALLOC ) || - (type == AT_FREE && a->type == AT_CALLOC ) || - (type == AT_FREE && a->type == AT_REALLOC ) - ); - // .. you requested a breakpoint when freeing this allocation - debug_assert(!a->break_on_free); - - - // "poison" the allocation's memory, to catch use-after-free bugs. - // the VC7 debug heap does this also (in free), so we're wasting time - // in that case. oh well, better to be safe/consistent. - pattern_set(a, pattern_freed); - - free(a->p); - - allocs_remove(a); - alloc_delete(a); - stats_remove(a); - } - - // we're being called from destructors. each call may be the last. - if(static_dtor_called) - { - // update leak report (write a new one) - if(app_wants_leak_report) - mmgr_write_leak_report(); - - // all allocations have been freed. there's no better time to shut - // down, so do so now. (there's no portable way to call this as - // the very last thing before exit) - if(stats.cur_allocs == 0) - shutdown(); - } - -done: -fail: - if(options & MMGR_VALIDATE_ALL) - (void)validate_all(); - - if(options & MMGR_TRACE) - log("[D] EXIT : free_dbg\n"); - - unlock(); -} - - -void* realloc_dbg(const void* user_p, size_t user_size, AllocType type, const char* file, int line, const char* func, uint stack_frames) -{ - void* ret = 0; - size_t old_size = 0; - - debug_assert(type == AT_REALLOC); - - lock(); - - if(options & MMGR_TRACE) - log("[D] ENTER: realloc_dbg\n"); - - - // - // security checks - // - - if(user_p) - { - Alloc* a = allocs_find(user_p); - if(!a) - { - // you called realloc for a pointer mmgr didn't allocate - log("[!] realloc: wasn't previously allocated\n"); - DEBUG_WARN_ERR(ERR::MEM_ALLOC_NOT_FOUND); - goto fail; - } - // .. the owner wasn't compiled with mmgr.h - debug_assert(a->type != AT_UNKNOWN); - // .. realloc for an allocation type that doesn't support it. - debug_assert(a->type == AT_MALLOC || a->type == AT_CALLOC || - a->type == AT_REALLOC); - // .. you requested a breakpoint when reallocating this allocation - // (it will continue to be triggered unless you clear a->break_on_realloc) - debug_assert(!a->break_on_realloc); - - old_size = a->size; - } - // else: skip security checks; realloc(0, size) is equivalent to malloc - - unlock(); // avoid recursive lock - - if(user_size) - ret = alloc_dbg(user_size, type, file,line,func, stack_frames+1); - - // old_size should only be non-zero if the Alloc security checks all passed - // If the old buffer was actually zero bytes large, do nothing :P - if (old_size && ret) - cpu_memcpy(ret, user_p, std::min(old_size, user_size)); - - if(user_p) - free_dbg(user_p, AT_FREE, file,line,func, stack_frames+1); - - lock(); - - if(options & MMGR_LOG_ALL) - { - log("[~] ----> from 0x%08X(%08d)\n", user_size, user_size); - log("[~] ----> addr 0x%08p\n", ret); - } - -fail: - if(options & MMGR_VALIDATE_ALL) - (void)validate_all(); - - if(options & MMGR_TRACE) - log("[D] EXIT : realloc_dbg\n"); - - unlock(); - return ret; -} - - -////////////////////////////////////////////////////////////////////////////// -// wrappers -////////////////////////////////////////////////////////////////////////////// - -void* mmgr_malloc_dbg(size_t size, const char* file, int line, const char* func) -{ - return alloc_dbg(size, AT_MALLOC, file,line,func, 1); -} -void* mmgr_calloc_dbg(size_t num, size_t size, const char* file, int line, const char* func) -{ - return alloc_dbg(num*size, AT_CALLOC, file,line,func, 1); -} -void* mmgr_realloc_dbg(void* p, size_t size, const char* file, int line, const char* func) -{ - return realloc_dbg(p, size, AT_REALLOC, file,line,func, 1); -} -void mmgr_free_dbg(void* p, const char* file, int line, const char* func) -{ - return free_dbg(p, AT_FREE, file,line,func, 1); -} - - -// -// note: we can call mmgr_malloc_dbg because the macro hook has set -// file+line+func, so the stack trace info won't be used and frames-to-skip -// is irrelevant. -// - -char* mmgr_strdup_dbg(const char* s, const char* file, int line, const char* func) -{ - const size_t size = strlen(s)+1; - char* copy = (char*)mmgr_malloc_dbg(size, file,line,func); - if(!copy) - return 0; - strcpy(copy, s); // safe - return copy; -} - -wchar_t* mmgr_wcsdup_dbg(const wchar_t* s, const char* file, int line, const char* func) -{ - const size_t size = (wcslen(s) + 1) * sizeof(wchar_t); - wchar_t* copy = (wchar_t*)mmgr_malloc_dbg(size, file,line,func); - if(!copy) - return 0; - wcscpy(copy, s); - return copy; -} - - -// -// wrappers for more complicated functions that allocate memory. -// instead of reimplementing them entirely, we just make sure the memory -// returned here was allocated from our functions, so the user can call -// the hooked free(). -// - -char* mmgr_getcwd_dbg(char* buf, size_t buf_size, const char* file, int line, const char* func) -{ - char* ret = getcwd(buf, buf_size); - // user already had a buffer or CRT version failed - pass on return value. - if(buf || !ret) - return ret; - char* copy = mmgr_strdup_dbg(ret, file,line,func); - free(ret); - return copy; -} - - -// -// note: separate versions for new/new[], and the VC debug new(file, line). -// - -static void* new_common(size_t size, AllocType type, - const char* file, int line, const char* func) -{ - const char* allocator = types[type]; - - if(options & MMGR_TRACE) - log("[D] ENTER: %s\n", allocator); - - // C++ requires size==0 to return a unique address, so allocate 1 byte. - if(size == 0) - size = 1; - - // loop because C++ says error handler may free up some memory. - for(;;) - { - void* p = alloc_dbg(size, type, file,line,func, 2); - if(p) - { - if(options & MMGR_TRACE) - log("[D] EXIT : %s\n", allocator); - - return p; - } - - // is a handler set? - std::new_handler nh = std::set_new_handler(0); - (void)std::set_new_handler(nh); - // .. yes: call, and loop again (hoping it freed up memory) - if(nh) - (*nh)(); - // .. no: throw - else - { - if(options & MMGR_TRACE) - log("[D] EXIT : %s\n", allocator); - - throw std::bad_alloc(); - } - } -} - - -void* operator new(size_t size) throw(std::bad_alloc) -{ - return new_common(size, AT_NEW, 0,0,0); -} - -void* operator new[](size_t size) throw(std::bad_alloc) -{ - return new_common(size, AT_NEW_ARRAY, 0,0,0); -} - -void* operator new(size_t size, const char* file, int line) -{ - locked_log("[?] Overloaded(file,line) global operator new called - check call site"); - return new_common(size, AT_NEW, file,line,0); -} - -void* operator new[](size_t size, const char* file, int line) -{ - locked_log("[?] Overloaded(file,line) global operator new[] called - check call site"); - return new_common(size, AT_NEW_ARRAY, file,line,0); -} - -void* operator new(size_t size, const char* file, int line, const char* func) -{ - return new_common(size, AT_NEW, file,line,func); -} - -void* operator new[](size_t size, const char* file, int line, const char* func) -{ - return new_common(size, AT_NEW_ARRAY, file,line,func); -} - - -void operator delete(void* p) throw() -{ - free_dbg(p, AT_DELETE, 0,0,0, 1); -} -void operator delete[](void* p) throw() -{ - free_dbg(p, AT_DELETE_ARRAY, 0,0,0, 1); -} - -// -// called by compiler after a ctor (during the counterpart overloaded global -// operator new) raises an exception. not accessible from user code. -// - -void operator delete(void* p, const char* file, int line) throw() -{ - locked_log("[?] Overloaded(file,line) global operator delete called, i.e. exception raised in a ctor"); - free_dbg(p, AT_DELETE, file,line,0, 1); -} -void operator delete[](void* p, const char* file, int line) throw() -{ - locked_log("[?] Overloaded(file,line) global operator delete[] called, i.e. exception raised in a ctor"); - free_dbg(p, AT_DELETE_ARRAY, file,line,0, 1); -} - -void operator delete(void* p, const char* file, int line, const char* func) throw() -{ - locked_log("[?] Overloaded(file,line,func) global operator delete called, i.e. exception raised in a ctor"); - free_dbg(p, AT_DELETE, file,line,func, 1); -} -void operator delete[](void* p, const char* file, int line, const char* func) throw() -{ - locked_log("[?] Overloaded(file,line,func) global operator delete[] called, i.e. exception raised in a ctor"); - free_dbg(p, AT_DELETE_ARRAY, file,line,func, 1); -} - -#endif // #if CONFIG_USE_MMGR - -#endif \ No newline at end of file diff --git a/source/lib/mmgr.h b/source/lib/mmgr.h deleted file mode 100644 index d299f7f57c..0000000000 --- a/source/lib/mmgr.h +++ /dev/null @@ -1,296 +0,0 @@ -#if 0 - -/** - * ========================================================================= - * File : mmgr.h - * Project : 0 A.D. - * Description : memory manager and tracker. - * ========================================================================= - */ - -// license: GPL; see lib/license.txt - -/* - -purpose and history -------------------- - -our goal is to expose any memory handling bugs in the application as -early as possible. various checks are performed upon each memory API call; -if all options are on, we can spot the following: - memory leaks, double-free, allocation over/underruns, - unused memory, and use-after-free. - -this code started life as Paul Nettle's memory manager (available -at http:www.fluidstudios.com), and has been completely overhauled. -in particular, it is now thread-safe and modularized; -duplicated code has been eliminated. - - -instructions for integrating into your project ----------------------------------------------- - -1) #include this from all project source files [that will allocate memory]. - doing so from the precompiled header is recommended, since the - compiler will make sure it has actually been included. -2) all system headers must be #include-d before this header, so that - we don't mess with any of their local operator new/delete. -3) if project source/headers also use local operator new/delete, #include - "nommgr.h" before that spot, and re-#include "mmgr.h" afterwards. - -4) if using MFC: - - set linker option /FORCE - works around conflict between our global - operator new and that of MFC. be sure to check for other errors. - - remove any #define new DEBUG_NEW from all source files. - - -effects -------- - -many bugs are caught and announced with no further changes -required, due to integrity checks inside the allocator. - -at exit, three report files are generated: a listing of leaks, -various statistics (e.g. total unused memory), and the log. -this lists (depending on settings) all allocations, enter/exit -indications for our functions, and failure notifications. - - -digging deeper --------------- - -when tracking down hard-to-find bugs, more stringent checks can be -activated via mmgr_set_option, or by changing the initial value of -options in mmgr.cpp. however, they slow down the app considerably -and need not always be enabled. see option declarations above. - -you can also change padding_size in mmgr.cpp at compile-time to provide -more safety vs. overruns, at the cost of wasting lots of memory per -allocation (which must also be cleared). this is only done in -CONFIG_PARANOIA builds, because overruns seldom 'skip' padding. - -finally, you can induce memory allocations to fail a certain percentage -of the time - this tests your application's error handling. -adjust the RANDOM_FAILURE #define in mmgr.cpp. - - -fixing your bugs ----------------- - -if this code crashes or fails an debug_assert, it is most likely due to a bug -in your application. consult the current Alloc for information; -search the log for its address to determine what operation failed, -and what piece of code owns the allocation. - -if the cause isn't visible (i.e. the error is reported after the fact), -you can try activating the more stringent checks to catch the problem -earlier. you may also call the validation routines at checkpoints -in your code to narrow the cause down. if all else fails, break on -the allocation number to see what's happening. - -good luck! - -*/ - - -// -// memory headers -// - -// these are all system headers that contain "new", "malloc" etc.; they must -// come before the memory tracker headers to avoid conflicts with their -// macros. therefore, they are always included, even if !CONFIG_PCH. - -#if OS_WIN -# include -# include -# include -#endif - -#include -#include -#include // operator new -#include // free() member function - - -// VC debug memory allocator / leak detector -// notes: -// - PCH is required because it makes sure system headers are included -// before redefining new (otherwise, tons of errors result); -// - disabled on ICC9 because the ICC 9.0.006 beta appears to generate -// incorrect code when we redefine new. -// TODO: remove when no longer necessary -#if MSC_VERSION && \ - (!defined(NDEBUG) || defined(TESTING)) && \ - HAVE_PCH && \ - ICC_VERSION != 900 -# define HAVE_VC_DEBUG_ALLOC 1 -#else -# define HAVE_VC_DEBUG_ALLOC 0 -#endif - - - - - -#ifndef INCLUDED_MMGR -#define INCLUDED_MMGR - -namespace ERR -{ - const LibError MEM_ALLOC_NOT_FOUND = -100200; - const LibError MEM_OVERWRITTEN = -100201; -} - - -// provide for completely disabling the memory manager -// (e.g. when using other debug packages) -// -// note: this must go around the include-guarded part (constants+externs) -// as well as the macros. we don't want to mess up compiler include-guard -// optimizations, so duplicate this #if. -#if CONFIG_USE_MMGR - -// -// optional additional checks, enabled via mmgr_set_options. -// these slow down the application; see 'digging deeper' in documentation. -// - -// log all allocation/deallocation operations undertaken. -const uint MMGR_LOG_ALL = 0x001; - -// validate all allocations on every memory API call. slow! -const uint MMGR_VALIDATE_ALL = 0x002; - -// fill the user-visible part of each allocation with a certain pattern -// on alloc and free. this is required for unused memory tracking. -const uint MMGR_FILL = 0x004; - -// log all enter/exit into our API. if there's an -// unmatched pair in the log, we know where a crash occurred. -const uint MMGR_TRACE = 0x008; - -// use debug information to resolve owner address to file/line/function. -// note: passing owner information to global operator delete via macro -// isn't reliable, so a stack backtrace (list of function addresses) is all -// we have there. this costs ~500us per unique call site on Windows. -const uint MMGR_RESOLVE_OWNER = 0x010; - -// force each log line to be written directly to disk. slow! -// use only when the application is crashing, to make sure all -// available information is written out. -const uint MMGR_FLUSH_LOG = 0x020; - -// an alias that includes all of the above. (more convenient) -const uint MMGR_ALL = 0xfff; - -// return the current options unchanged. -const uint MMGR_QUERY = ~0; - -extern uint mmgr_set_options(uint); - - -// break when a certain allocation is created/reallocated/freed: -extern void mmgr_break_on_alloc(uint count); -extern void mmgr_break_on_realloc(const void*); -extern void mmgr_break_on_free(const void*); - -// "proactive" validation: (see 'digging deeper') -extern bool mmgr_is_valid_ptr(const void*); -extern bool mmgr_are_all_valid(void); - -// write a report file -extern void mmgr_write_report(void); -extern void mmgr_write_leak_report(void); - - -// -// our wrappers for C++ memory handling functions -// - -// note that all line numbers are int, for compatibility with any external -// overloaded operator new (in case someone forget to include "mmgr.h"). - -extern void* mmgr_malloc_dbg (size_t size, const char* file, int line, const char* func); -extern void* mmgr_calloc_dbg (size_t num, size_t size, const char* file, int line, const char* func); -extern void* mmgr_realloc_dbg(void* p, size_t size, const char* file, int line, const char* func); -extern void mmgr_free_dbg (void* p, const char* file, int line, const char* func); - -extern char* mmgr_strdup_dbg(const char*, const char* file, int line, const char* func); -extern wchar_t* mmgr_wcsdup_dbg(const wchar_t*, const char* file, int line, const char* func); -extern char* mmgr_getcwd_dbg(char*, size_t, const char* file, int line, const char* func); - - -// .. global operator new (to catch allocs from STL/external libs) -extern void* operator new (size_t size) throw(std::bad_alloc); -extern void* operator new[](size_t size) throw(std::bad_alloc); -// .. override commonly used global operator new overload (done e.g. by MFC), -// in case someone hasn't included this file -extern void* operator new (size_t size, const char* file, int line); -extern void* operator new[](size_t size, const char* file, int line); -// .. called by our global operator new hook macro -extern void* operator new (size_t size, const char* file, int line, const char* func); -extern void* operator new[](size_t size, const char* file, int line, const char* func); -// .. global operator delete -extern void operator delete (void* p) throw(); -extern void operator delete[](void* p) throw(); -// .. corresponding delete for first overloaded new, -// only called if exception raised during ctor -extern void operator delete (void* p, const char* file, int line) throw(); -extern void operator delete[](void* p, const char* file, int line) throw(); -// .. corresponding delete for our overloaded new, -// only called if exception raised during ctor -extern void operator delete (void* p, const char* file, int line, const char* func) throw(); -extern void operator delete[](void* p, const char* file, int line, const char* func) throw(); - -#endif // #if CONFIG_USE_MMGR - -#endif // #ifdef INCLUDED_MMGR - - -// -// hook macros -// - -#include "nommgr.h" - -#if CONFIG_USE_MMGR || HAVE_VC_DEBUG_ALLOC -// notify user code that they must #include "nommgr.h" in places -// where our macro would cause breakage (e.g. placement new) -# define REDEFINED_NEW -#endif - -// mmgr version: -// (to simplify code that may either use mmgr or the VC debug heap, -// we support enabling/disabling both in this header) -#if CONFIG_USE_MMGR - -// get rid of __FUNCTION__ unless we know the compiler supports it. -// (note: don't define if built-in - compiler will raise a warning) -#if !MSC_VERSION && !GCC_VERSION -#define __FUNCTION__ 0 -#endif - -#define new new(__FILE__, __LINE__, __FUNCTION__) -// hooking delete and setting global owner variables/pushing them on a stack -// isn't thread-safe and can be fooled with destructor chains. -// we instead rely on the call stack (works with VC and GCC) -#define malloc(size) mmgr_malloc_dbg (size, __FILE__,__LINE__,__FUNCTION__) -#define calloc(num, size) mmgr_calloc_dbg (num,size,__FILE__,__LINE__,__FUNCTION__) -#define realloc(p,size) mmgr_realloc_dbg(p,size, __FILE__,__LINE__,__FUNCTION__) -#define free(p) mmgr_free_dbg (p, __FILE__,__LINE__,__FUNCTION__) - -#define strdup(p) mmgr_strdup_dbg(p, __FILE__,__LINE__,__FUNCTION__) -#define wcsdup(p) mmgr_wcsdup_dbg(p, __FILE__,__LINE__,__FUNCTION__) -#define getcwd(p,size) mmgr_getcwd_dbg(p, size, __FILE__,__LINE__,__FUNCTION__) - -#elif HAVE_VC_DEBUG_ALLOC - -#define _CRTDBG_MAP_ALLOC -#include -// crtdbg.h didn't define "new" (probably for compatibility); do so now. -#define new new(_NORMAL_BLOCK, __FILE__, __LINE__) - -#endif // #if CONFIG_USE_MMGR - -#endif \ No newline at end of file diff --git a/source/lib/nommgr.h b/source/lib/nommgr.h deleted file mode 100644 index 04461cc9f3..0000000000 --- a/source/lib/nommgr.h +++ /dev/null @@ -1,18 +0,0 @@ -// remove all of mmgr.h's memory allocation "hooks" - -// but only if we actually defined them! -#if CONFIG_USE_MMGR || HAVE_VC_DEBUG_ALLOC -# undef new -# undef delete -# undef malloc -# undef calloc -# undef realloc -# undef free -# undef strdup -# undef wcsdup -# undef getcwd -#endif - -// sanity check -#ifdef new -#error "nommgr.h - something is wrong, new is still defined" -#endif diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp index b2cdeeeae6..a79dde79b9 100644 --- a/source/lib/res/h_mgr.cpp +++ b/source/lib/res/h_mgr.cpp @@ -174,11 +174,6 @@ static i32 last_in_use = -1; // don't search unused entries // also used by h_data, and alloc_idx to find a free entry. static HDATA* h_data_from_idx(const i32 idx) { - // makes things *crawl*! -#if CONFIG_PARANOIA - debug_heap_check(); -#endif - // don't compare against last_in_use - this is called before allocating // new entries, and to check if the next (but possibly not yet valid) // entry is free. tag check protects against using unallocated entries. diff --git a/source/lib/sysdep/win/wdbg.cpp b/source/lib/sysdep/win/wdbg.cpp index 2c2d0da66a..032ab461de 100644 --- a/source/lib/sysdep/win/wdbg.cpp +++ b/source/lib/sysdep/win/wdbg.cpp @@ -14,64 +14,7 @@ #include "lib/bits.h" #include "win.h" #include "wutil.h" -#include "winit.h" -WINIT_REGISTER_CRITICAL_INIT(wdbg_Init); - - -static NT_TIB* get_tib() -{ -#if ARCH_IA32 - NT_TIB* tib; - // ICC 10 doesn't support the NT_TIB.Self syntax, so we have to use - // a constant (asm code isn't 64-bit safe anyway). - __asm - { - mov eax, fs:[NT_TIB.Self] - mov [tib], eax - } - return tib; -#endif -} - - -//----------------------------------------------------------------------------- -// debug heap -//----------------------------------------------------------------------------- - -static void debug_heap_init() -{ - uint flags = 0; - flags |= _CRTDBG_ALLOC_MEM_DF; // enable checks at deallocation time - flags |= _CRTDBG_LEAK_CHECK_DF; // report leaks at exit -#if CONFIG_PARANOIA - flags |= _CRTDBG_CHECK_ALWAYS_DF; // check during every heap operation (slow!) - flags |= _CRTDBG_DELAY_FREE_MEM_DF; // blocks cannot be reused -#endif - _CrtSetDbgFlag(flags); -} - - -void debug_heap_check() -{ - int ret; - __try - { - // NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF. - // we could call _heapchk but that would catch fewer errors. - ret = _CrtCheckMemory(); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - ret = _HEAPBADNODE; - } - - if(ret != _HEAPOK) - DEBUG_DISPLAY_ERROR(L"debug_heap_check: heap is corrupt"); -} - - -//----------------------------------------------------------------------------- // return 1 if the pointer appears to be totally bogus, otherwise 0. // this check is not authoritative (the pointer may be "valid" but incorrect) @@ -112,6 +55,21 @@ bool debug_is_code_ptr(void* p) } +static NT_TIB* get_tib() +{ +#if ARCH_IA32 + NT_TIB* tib; + // ICC 10 doesn't support the NT_TIB.Self syntax, so we have to use + // a constant (asm code isn't 64-bit safe anyway). + __asm + { + mov eax, fs:[NT_TIB.Self] + mov [tib], eax + } + return tib; +#endif +} + bool debug_is_stack_ptr(void* p) { uintptr_t addr = (uintptr_t)p; @@ -167,10 +125,3 @@ void debug_set_thread_name(const char* name) debug_assert(0); // thread name hack doesn't work under this debugger } } - - -static LibError wdbg_Init() -{ - debug_heap_init(); - return INFO::OK; -} diff --git a/source/lib/sysdep/win/wdbg_heap.cpp b/source/lib/sysdep/win/wdbg_heap.cpp new file mode 100644 index 0000000000..db56927529 --- /dev/null +++ b/source/lib/sysdep/win/wdbg_heap.cpp @@ -0,0 +1,94 @@ +#include "precompiled.h" +#include "wdbg_heap.h" + +#include +#include +#include "winit.h" + +WINIT_REGISTER_CRITICAL_INIT(wdbg_heap_Init); + +// CAUTION: called from critical init +void wdbg_heap_Enable(bool enable) +{ + uint flags = 0; + if(enable) + { + flags |= _CRTDBG_ALLOC_MEM_DF; // enable checks at deallocation time + flags |= _CRTDBG_LEAK_CHECK_DF; // report leaks at exit +#if CONFIG_PARANOIA + flags |= _CRTDBG_CHECK_ALWAYS_DF; // check during every heap operation (slow!) + flags |= _CRTDBG_DELAY_FREE_MEM_DF; // blocks cannot be reused +#endif + } + _CrtSetDbgFlag(flags); + + // Send output to stdout as well as the debug window, so it works during + // the normal build process as well as when debugging the test .exe + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT); +} + + +void wdbg_heap_Validate() +{ + int ret; + __try + { + // NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF. + // we could call _heapchk but that would catch fewer errors. + ret = _CrtCheckMemory(); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + ret = _HEAPBADNODE; + } + + if(ret != _HEAPOK) + DEBUG_DISPLAY_ERROR(L"Heap is corrupted!"); +} + + +//----------------------------------------------------------------------------- + +static int __cdecl ReportHook(int reportType, char* message, int* shouldTriggerBreakpoint) +{ + if(message[0] == '{') + { + debug_printf("leak\n"); + } + + *shouldTriggerBreakpoint = 0; + return 1; // CRT is to display the message as normal +} + +static _CRT_ALLOC_HOOK previousAllocHook; + +/** + * @param userData is only valid (nonzero) for allocType == _HOOK_FREE because + * we are called BEFORE the actual heap operation. + **/ +static int __cdecl AllocHook(int allocType, void* userData, size_t size, int blockType, long requestNumber, const unsigned char* file, int line) +{ + if(previousAllocHook) + return previousAllocHook(allocType, userData, size, blockType, requestNumber, file, line); + return 1; // continue as if the hook had never been called +} + + + +// NB: called from critical init => can't use debug.h services +static void InstallHooks() +{ + int ret = _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, ReportHook); + if(ret == -1) + abort(); + + previousAllocHook = _CrtSetAllocHook(AllocHook); +} + +static LibError wdbg_heap_Init() +{ + InstallHooks(); + wdbg_heap_Enable(true); + return INFO::OK; +} diff --git a/source/lib/sysdep/win/wdbg_heap.h b/source/lib/sysdep/win/wdbg_heap.h new file mode 100644 index 0000000000..b97fc64f2c --- /dev/null +++ b/source/lib/sysdep/win/wdbg_heap.h @@ -0,0 +1,33 @@ +/** + * ========================================================================= + * File : wdbg_heap.h + * Project : 0 A.D. + * Description : improved debug heap using MS CRT + * ========================================================================= + */ + +// license: GPL; see lib/license.txt + +#ifndef INCLUDED_WDBG_HEAP +#define INCLUDED_WDBG_HEAP + +// this module provides a more convenient interface to the MS CRT's +// debug heap checks. it also hooks into allocations to record the +// caller/owner information without requiring macros (which break code +// using placement new or member functions called free). + +/** + * enable or disable manual and automatic heap validity checking. + * (enabled by default during critical_init.) + **/ +LIB_API void wdbg_heap_Enable(bool); + +/** + * check heap integrity. + * errors are reported by the CRT or via debug_display_error. + * no effect if called between wdbg_heap_Enable(false) and the next + * wdbg_heap_Enable(true). + **/ +LIB_API void wdbg_heap_Validate(void); + +#endif // #ifndef INCLUDED_WDBG_HEAP diff --git a/source/ps/Profile.cpp b/source/ps/Profile.cpp index 865ecf62a1..c6220f3f8c 100644 --- a/source/ps/Profile.cpp +++ b/source/ps/Profile.cpp @@ -373,7 +373,7 @@ void CProfileNode::Frame() } // TODO: these should probably only count allocations that occur in the thread being profiled -#if HAVE_VC_DEBUG_ALLOC +#if MSC_VERSION static intptr_t memory_alloc_count = 0; static int (*old_alloc_hook) (int, void*, size_t, int, long, const unsigned char*, int); static int alloc_hook(int allocType, void* userData, size_t size, int blockType, diff --git a/source/test_setup.cpp b/source/test_setup.cpp index 0af95650d1..acade9bf2a 100644 --- a/source/test_setup.cpp +++ b/source/test_setup.cpp @@ -9,6 +9,10 @@ #include +#if OS_WIN +#include "lib/sysdep/win/wdbg_heap.h" +#endif + class LeakReporter : public CxxTest::GlobalFixture { virtual bool tearDownWorld() @@ -16,19 +20,9 @@ class LeakReporter : public CxxTest::GlobalFixture // Enable leak reporting on exit. // (This is done in tearDownWorld so that it doesn't report 'leaks' // if the program is aborted before finishing cleanly.) - -#if HAVE_VC_DEBUG_ALLOC - int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flags |= _CRTDBG_LEAK_CHECK_DF; // check for memory leaks - flags |= _CRTDBG_ALLOC_MEM_DF; // also check allocs using the non-debug version of new - _CrtSetDbgFlag(flags); - - // Send output to stdout as well as the debug window, so it works during - // the normal build process as well as when debugging the test .exe - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT); +#if OS_WIN + wdbg_heap_Enable(true); #endif - return true; }