// This may look like C code, but it is really -*- C++ -*- // ------------------------------------------------------------------ // The Goldware Library // Copyright (C) 1990-1999 Odinn Sorensen // Copyright (C) 2000 Alexander S. Aganichev // ------------------------------------------------------------------ // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this program; if not, write to the Free // Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, // MA 02111-1307, USA // ------------------------------------------------------------------ // $Id$ // ------------------------------------------------------------------ // Memory management routines with debugging features. // Based on free code from SNIPPETS 9404 by Walter Bright. // ------------------------------------------------------------------ #include <climits> #include <gdbgerr.h> #include <gmemdbg.h> #include <glog.h> #include <gmemall.h> // ------------------------------------------------------------------ // Global vars int throw_alloc_extra = 0; #if defined(GTHROW_LOG) glog* throw_log = NULL; #define TLOG if(throw_log) throw_log #endif // ------------------------------------------------------------------ #define gmem_strdup strdup #define gmem_malloc malloc #define gmem_calloc calloc #define gmem_realloc realloc #define gmem_free free // ------------------------------------------------------------------ #if defined(GTHROW_DEBUG) // ------------------------------------------------------------------ // Various values const dword BEFOREVAL = 0x12345678L; const dword AFTERVAL = 0x87654321L; const byte BADVAL = 0xFF; const byte MALLOCVAL = 0xEE; const size_t BAD_SIZE = 0xFFFFFFFF; const size_t MALLOC_SIZE = 0xEEEEEEEE; // ------------------------------------------------------------------ // Struct #if defined(GOLD_CANPACK) #pragma pack(1) #endif struct Throw { Throw* next; Throw* prev; const char* file; int line; int index; size_t nbytes; dword beforeval; char data[1]; }; #if defined(GOLD_CANPACK) #pragma pack() #endif // ------------------------------------------------------------------ struct ThrowIndex { Throw* pointer; }; // ------------------------------------------------------------------ // Internal vars int throw_inited = 0; int throw_count; uint32_t throw_allocations = 0; Throw throw_alloclist; int throw_max_count = 0; Throw** throw_index = NULL; int throw_index_size = 0; int throw_index_free = 0; int throw_last_free = -1; uint32_t throw_index_cache_hits = 0; int throw_overhead = sizeof(Throw) - 1; #define throw_index_init_size 1000 #define throw_index_increment 100 // ------------------------------------------------------------------ // Inlined functions inline Throw* throw_ptrtodl(const void* ptr) { return (Throw*)((const char*)ptr-sizeof(Throw)+1); } inline void* throw_dltoptr(Throw* dl) { return (void*)dl->data; } // ------------------------------------------------------------------ int throw_index_find_free() { if(throw_last_free != -1) { throw_index_cache_hits++; return throw_last_free; } else if(throw_index_free) { Throw** i = throw_index; for(int n=0; n<throw_index_size; n++,i++) { if(*i == NULL) { throw_last_free = n; return throw_last_free; } } } if(throw_index_size) { throw_index = (Throw**)realloc(throw_index, (throw_index_size+throw_index_increment)*sizeof(Throw*)); throw_new(throw_index); memset(throw_index+throw_index_size, 0, throw_index_increment*sizeof(Throw*)); throw_last_free = throw_index_size; throw_index_size += throw_index_increment; throw_index_free = throw_index_increment; } else { throw_index = (Throw**)calloc(throw_index_init_size, sizeof(Throw*)); throw_new(throw_index); throw_index_size = throw_index_init_size; throw_index_free = throw_index_init_size; throw_last_free = 0; } return throw_last_free; } // ------------------------------------------------------------------ int throw_index_add(Throw* pointer) { int index = throw_index_find_free(); throw_index[index] = pointer; throw_last_free = -1; throw_index_free--; return index; } // ------------------------------------------------------------------ void throw_index_remove(int index) { throw_index[index] = NULL; throw_last_free = index; throw_index_free++; } // ------------------------------------------------------------------ Throw* throw_find_overrun(Throw* pointer) { Throw* last_candidate = NULL; long last_diff = LONG_MAX; Throw** i = throw_index; for(int n=0; n<throw_index_size; n++,i++) { long diff = (unsigned long)*i - (unsigned long)pointer; if((diff > 0) and (diff < last_diff)) { last_candidate = *i; last_diff = diff; } } return last_candidate; } // ------------------------------------------------------------------ Throw* throw_find_underrun(Throw* pointer) { Throw* last_candidate = NULL; long last_diff = LONG_MAX; Throw** i = throw_index; for(int n=0; n<throw_index_size; n++,i++) { long diff = (unsigned long)pointer - (unsigned long)*i; if((diff > 0) and (diff < last_diff)) { last_candidate = *i; last_diff = diff; } } return last_candidate; } // ------------------------------------------------------------------ // Dump pointer information to the log #if defined(GTHROW_LOG) void throw_printdl(Throw* dl) { char buf[100]; char* ptr = (char*)throw_dltoptr(dl); TLOG->printf(": Ptr (%p,%u) at [%s,%u].", ptr, (uint)dl->nbytes, CleanFilename(dl->file), (uint)dl->line); TLOG->printf(": %s", HexDump16(buf, ptr, dl->nbytes, HEX_DUMP2)); } #else inline void throw_printdl(Throw* dl) {} #endif // ------------------------------------------------------------------ // Debugging new void throw_new_debug(const void* __ptr, const char* __file, int __line) { if(__ptr == NULL) { #if defined(GTHROW_LOG) TLOG->errmemory(__file, __line); TLOG->printf("! A memory allocation failed (out of memory)."); TLOG->printf("+ Advice: Restart."); #endif MemoryErrorExit(); } } // ------------------------------------------------------------------ // Debugging strdup() char* throw_strdup_debug(const char* __str, const char* __file, int __line) { char* _ptr = (char*)(__str ? throw_calloc_debug(1, strlen(__str)+1, __file, __line) : NULL); return _ptr ? strcpy(_ptr, __str) : _ptr; } // ------------------------------------------------------------------ // Debugging malloc() void* throw_malloc_debug(size_t __size, const char* __file, int __line) { void* _ptr = throw_calloc_debug(1, __size, __file, __line); if(_ptr) memset(_ptr, MALLOCVAL, __size); return _ptr; } // ------------------------------------------------------------------ // Debugging calloc() void* throw_calloc_debug(size_t __items, size_t __size, const char* __file, int __line) { __size *= __items; __size += throw_alloc_extra; if(__size == 0) { #if defined(GTHROW_LOG) TLOG->errmemory(__file, __line); TLOG->printf("! Attempted to allocate zero bytes of memory."); TLOG->printf("+ Advice: This is a bug. Please report to the Author."); #endif MemoryErrorExit(); } size_t _siz = sizeof(Throw) + __size + sizeof(AFTERVAL) - 1; Throw* dl = (Throw*)calloc(_siz, 1); if(dl == NULL) { #if defined(GTHROW_LOG) TLOG->errmemory(__file, __line); TLOG->printf("! A memory allocation failed (out of memory)."); TLOG->printf(": Needed %u (%Xh) bytes.", (uint)__size, (uint)__size); if(__size == BAD_SIZE) { TLOG->printf("+ Info: Value could be from free'd data."); TLOG->printf("+ Info: This indicates a serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); } else if(__size == MALLOC_SIZE) { TLOG->printf("+ Info: Value could be from uninitialized data"); TLOG->printf("+ Info: This indicates a serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); } else { TLOG->printf("+ Advice: Restart."); } #endif MemoryErrorExit(); } dl->index = throw_index_add(dl); dl->file = __file; dl->line = __line; dl->nbytes = __size; dl->beforeval = BEFOREVAL; *(dword*)&(dl->data[__size]) = AFTERVAL; dl->next = throw_alloclist.next; dl->prev = &throw_alloclist; throw_alloclist.next = dl; if(dl->next != NULL) dl->next->prev = dl; throw_count++; throw_max_count = maximum_of_two(throw_count, throw_max_count); throw_allocations++; return throw_dltoptr(dl); } // ------------------------------------------------------------------ // Debugging realloc() void* throw_realloc_debug(void* __oldptr, size_t __size, const char* __file, int __line) { void* _ptr; Throw* dl = throw_ptrtodl(__oldptr); if(__size == 0) { throw_free_debug(__oldptr,__file,__line); _ptr = NULL; } else if(__oldptr == NULL) { _ptr = throw_malloc_debug(__size,__file,__line); } else { _ptr = throw_malloc_debug(__size,__file,__line); if(dl->nbytes < __size) __size = dl->nbytes; memcpy(_ptr,__oldptr,__size); throw_free_debug(__oldptr,__file,__line); } return _ptr; } // ------------------------------------------------------------------ // Debugging free() void throw_free_debug(void* __ptr, const char* __file, int __line) { Throw* dl; Throw* overrun_dl = NULL; Throw* underrun_dl = NULL; if(__ptr == NULL) return; int inerr = false; if(throw_count <= 0) { #if defined(GTHROW_LOG) TLOG->errmemory(__file, __line); TLOG->printf("! More free's than allocs."); TLOG->printf("+ Info: This indicates a potentially serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); #endif goto err; } dl = throw_ptrtodl(__ptr); if(dl->nbytes == BAD_SIZE) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! A memory allocation was already free'd."); #endif goto err2; } if(dl->beforeval != BEFOREVAL) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! An allocated memory region was underrun."); #endif dl->prev = dl->next = NULL; dl->file = NULL; underrun_dl = throw_find_underrun(dl); goto err2; } if(*(dword*)&dl->data[dl->nbytes] != AFTERVAL) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! An allocated memory region was overrun."); #endif overrun_dl = throw_find_overrun(dl); inerr = true; } if(inerr) goto err2; if(dl->prev) dl->prev->next = dl->next; if(dl->next) dl->next->prev = dl->prev; throw_index_remove(dl->index); memset(dl,BADVAL,sizeof(*dl)+dl->nbytes); throw_count--; free(dl); return; err2: throw_printdl(dl); if(underrun_dl) { #if defined(GTHROW_LOG) TLOG->printf("! Possibly caused by overrun in this allocation:"); throw_printdl(underrun_dl); if(*(dword*)&underrun_dl->data[underrun_dl->nbytes] != AFTERVAL) TLOG->printf("! Overrun of previous allocation confirmed."); #endif } if(overrun_dl) { #if defined(GTHROW_LOG) TLOG->printf("! Possibly cause - Underrun in this allocation:"); throw_printdl(overrun_dl); if(overrun_dl->beforeval != BEFOREVAL) TLOG->printf("! Underrun of previous allocation confirmed."); #endif } #if defined(GTHROW_LOG) TLOG->printf(": Detected while freeing the allocation."); TLOG->printf("+ Info: This indicates a serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); #endif err: //PointerErrorExit(); ; } // ------------------------------------------------------------------ // Check all allocations void throw_check_debug(const char* __file, int __line) { Throw* dl = throw_alloclist.next; while(dl != NULL) { throw_checkptr_debug(throw_dltoptr(dl), __file, __line); dl = dl->next; } } // ------------------------------------------------------------------ // Check individual allocation void throw_checkptr_debug(const void* __ptr, const char* __file, int __line) { int inerr = false; Throw* dl; Throw* overrun_dl = NULL; Throw* underrun_dl = NULL; if(__ptr == NULL) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! Found NULL pointer instead of allocated memory."); #endif goto err3; } dl = throw_ptrtodl(__ptr); if(dl->beforeval != BEFOREVAL) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! An allocated memory region was underrun."); #endif dl->prev = dl->next = NULL; dl->file = NULL; underrun_dl = throw_find_underrun(dl); goto err2; } if(*(dword*)&dl->data[dl->nbytes] != AFTERVAL) { #if defined(GTHROW_LOG) TLOG->errpointer(__file, __line); TLOG->printf("! An allocated memory region was overrun."); #endif overrun_dl = throw_find_overrun(dl); inerr = true; } if(inerr) goto err2; return; err2: throw_printdl(dl); if(underrun_dl) { #if defined(GTHROW_LOG) TLOG->printf("! Possibly caused by overrun in this allocation:"); throw_printdl(underrun_dl); if(*(dword*)&underrun_dl->data[underrun_dl->nbytes] != AFTERVAL) TLOG->printf("! Overrun of previous allocation confirmed."); #endif } if(overrun_dl) { #if defined(GTHROW_LOG) TLOG->printf("! Possibly cause - Underrun in this allocation:"); throw_printdl(overrun_dl); if(overrun_dl->beforeval != BEFOREVAL) TLOG->printf("! Underrun of previous allocation confirmed."); #endif } err3: #if defined(GTHROW_LOG) TLOG->printf(": Detected while checking the allocation."); TLOG->printf("+ Info: This indicates a serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); #endif PointerErrorExit(); } // ------------------------------------------------------------------ // Termination function static void throw_term(void) { if(throw_inited) { #if defined(GTHROW_LOG) if(throw_count) TLOG->printf("! Detected %i unfree'd memory allocation%s.", throw_count, throw_count==1?"":"s"); if(not error_exit) { Throw* dl = throw_alloclist.next; int count = throw_count; for(; dl; ) { Throw* dl_next = dl->next; throw_printdl(dl); throw_free_debug(throw_dltoptr(dl), __FILE__, __LINE__); dl = dl_next; } throw_count = count; if(throw_count) { TLOG->printf("+ Info: The memory should have been free'd before exit."); TLOG->printf("+ Info: This indicates a potentially serious bug."); TLOG->printf("+ Advice: Report to the Author immediately."); } } #endif free(throw_index); throw_inited = 0; } } // ------------------------------------------------------------------ // Init function void throw_init() { if(throw_inited == 0) { throw_count = 0; throw_alloclist.next = NULL; throw_alloclist.prev = NULL; throw_alloclist.file = __FILE__; throw_alloclist.line = __LINE__; throw_alloclist.nbytes = 0; throw_alloclist.beforeval = BEFOREVAL; throw_alloclist.data[0] = 0xFF; throw_inited++; atexit(throw_term); } } // ------------------------------------------------------------------ #ifdef gmem_strdup #undef gmem_strdup #define gmem_strdup(a) throw_strdup_debug(a, file, line) #undef gmem_malloc #define gmem_malloc(a) throw_malloc_debug(a, file, line) #undef gmem_calloc #define gmem_calloc(a,b) throw_calloc_debug(a, b, file, line) #undef gmem_realloc #define gmem_realloc(a,b) throw_realloc_debug(a, b, file, line) #undef gmem_free #define gmem_free(a) throw_free_debug(a, file, line) #endif #endif // ------------------------------------------------------------------ void* throw_outofmem_report(const char* file, int line, uint size) { #if defined(GTHROW_LOG) TLOG->errmemory(file, line); if(size == 0) { TLOG->printf("! Attempted to allocate zero bytes of memory."); TLOG->printf("+ Advice: This is a bug. Please report to the Author."); } else { TLOG->printf("! A memory allocation failed (out of memory)."); TLOG->printf(": Needed %u (%Xh) bytes.", size, size); TLOG->printf("+ Advice: Restart."); } #else NW(file); NW(line); NW(size); #endif MemoryErrorExit(); return NULL; } // ------------------------------------------------------------------ void throw_xnew_debug(void* ptr, const char* file, int line) { if(ptr == NULL) throw_outofmem_report(file, line, 0); } // ------------------------------------------------------------------ char* throw_xstrdup_debug(const char* str, const char* file, int line) { char* s = gmem_strdup(str); return s ? s : (char*)throw_outofmem_report(file, line, strlen(str)); } // ------------------------------------------------------------------ void* throw_xmalloc_debug(size_t size, const char* file, int line) { void* p = gmem_malloc(size+throw_alloc_extra); return p ? p : throw_outofmem_report(file, line, size); } // ------------------------------------------------------------------ void* throw_xcalloc_debug(size_t items, size_t size, const char* file, int line) { void* p = gmem_calloc(items, size+throw_alloc_extra); return p ? p : throw_outofmem_report(file, line, items*size); } // ------------------------------------------------------------------ void* throw_xrealloc_debug(void* ptr, size_t size, const char* file, int line) { void* p = gmem_realloc(ptr, size+throw_alloc_extra); return p ? p : throw_outofmem_report(file, line, size); } // ------------------------------------------------------------------ void throw_xfree_debug(void* ptr, const char* file, int line) { if(ptr) gmem_free(ptr); } // ------------------------------------------------------------------