/* Copyright (C) 2000 MySQL AB, 2011 Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ /******************************************************************** memory debugger based on safemalloc, memory sub-system, written by Bjorn Benson ********************************************************************/ #include "mysys_priv.h" #include /* my_addr_resolve */ #if HAVE_EXECINFO_H #include #endif /* this can be set to 1 if we leak memory and know it (to disable memory leak tests on exit) */ int sf_leaking_memory= 0; #ifdef SAFEMALLOC /* this mutex protects all sf_* variables, and nothing else*/ static pthread_mutex_t sf_mutex; static int init_done= 0; #ifndef SF_REMEMBER_FRAMES #ifdef USE_MYSYS_NEW #define SF_REMEMBER_FRAMES 14 #else #define SF_REMEMBER_FRAMES 8 #endif /* USE_MYSYS_NEW */ #endif /* SF_REMEMBER_FRAMES */ /* ignore the first two frames (sf_malloc itself, and my_malloc) */ #define SF_FRAMES_SKIP 2 /* Structure that stores information of an allocated memory block The data is at &struct_adr+sizeof(struct irem) Note that sizeof(struct st_irem) % sizeof(double) == 0 */ struct st_irem { struct st_irem *next; /* Linked list of structures */ struct st_irem *prev; /* Other link */ size_t datasize; /* Size requested */ #if SIZEOF_SIZE_T == 4 size_t pad; /* Compensate 32bit datasize */ #endif #ifdef HAVE_BACKTRACE void *frame[SF_REMEMBER_FRAMES]; /* call stack */ #endif uint32 flags; /* Flags passed to malloc */ my_thread_id thread_id; /* Which thread did the allocation */ uint32 marker; /* Underrun marker value */ }; static int sf_malloc_count= 0; /* Number of allocated chunks */ static void *sf_min_adress= (void*) (intptr)~0ULL, *sf_max_adress= 0; static struct st_irem *sf_malloc_root = 0; #define MAGICSTART 0x14235296 /* A magic value for underrun key */ #define MAGICEND 0x12345678 /* Value for freed block */ #define MAGICEND0 0x68 /* Magic values for overrun keys */ #define MAGICEND1 0x34 /* " */ #define MAGICEND2 0x7A /* " */ #define MAGICEND3 0x15 /* " */ static int bad_ptr(const char *where, void *ptr); static void free_memory(void *ptr); static void sf_terminate(); /* Setup default call to get a thread id for the memory */ my_thread_id default_sf_malloc_dbug_id(void) { return my_thread_dbug_id(); } my_thread_id (*sf_malloc_dbug_id)(void)= default_sf_malloc_dbug_id; /** allocates memory */ void *sf_malloc(size_t size, myf my_flags) { struct st_irem *irem; uchar *data; /* this style of initialization looks like race conditon prone, but it is safe under the assumption that a program does at least one malloc() while still being single threaded. */ if (!init_done) { pthread_mutex_init(&sf_mutex, NULL); atexit(sf_terminate); init_done= 1; } if (size > SIZE_T_MAX - 1024L*1024L*16L) /* Wrong call */ return 0; if (!(irem= (struct st_irem *) malloc (sizeof(struct st_irem) + size + 4))) return 0; /* we guarantee the alignment */ compile_time_assert(sizeof(struct st_irem) % sizeof(double) == 0); /* Fill up the structure */ data= (uchar*) (irem + 1); irem->datasize= size; irem->prev= 0; irem->flags= my_flags; irem->marker= MAGICSTART; irem->thread_id= sf_malloc_dbug_id(); data[size + 0]= MAGICEND0; data[size + 1]= MAGICEND1; data[size + 2]= MAGICEND2; data[size + 3]= MAGICEND3; #ifdef HAVE_BACKTRACE { void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; int frames= backtrace(frame, array_elements(frame)); if (frames < SF_FRAMES_SKIP) frames= 0; else { frames-= SF_FRAMES_SKIP; memcpy(irem->frame, frame + SF_FRAMES_SKIP, sizeof(void*)*frames); } if (frames < SF_REMEMBER_FRAMES) irem->frame[frames]= 0; } #endif pthread_mutex_lock(&sf_mutex); /* Add this structure to the linked list */ if ((irem->next= sf_malloc_root)) sf_malloc_root->prev= irem; sf_malloc_root= irem; /* Keep the statistics */ sf_malloc_count++; set_if_smaller(sf_min_adress, (void*)data); set_if_bigger(sf_max_adress, (void*)data); pthread_mutex_unlock(&sf_mutex); TRASH_ALLOC(data, size); return data; } void *sf_realloc(void *ptr, size_t size, myf my_flags) { char *data; if (!ptr) return sf_malloc(size, my_flags); if (bad_ptr("Reallocating", ptr)) return 0; if ((data= sf_malloc(size, my_flags))) { struct st_irem *irem= (struct st_irem *)ptr - 1; set_if_smaller(size, irem->datasize); memcpy(data, ptr, size); free_memory(ptr); } return data; } void sf_free(void *ptr) { if (!ptr || bad_ptr("Freeing", ptr)) return; free_memory(ptr); } /** Return size of memory block and if block is thread specific sf_malloc_usable_size() @param ptr Pointer to malloced block @param flags We will store 1 here if block is marked as MY_THREAD_SPECIFIC otherwise 0 @return Size of block */ size_t sf_malloc_usable_size(void *ptr, my_bool *is_thread_specific) { struct st_irem *irem= (struct st_irem *)ptr - 1; DBUG_ENTER("sf_malloc_usable_size"); *is_thread_specific= MY_TEST(irem->flags & MY_THREAD_SPECIFIC); DBUG_PRINT("exit", ("size: %lu flags: %lu", (ulong) irem->datasize, (ulong)irem->flags)); DBUG_RETURN(irem->datasize); } #ifdef HAVE_BACKTRACE static void print_stack(void **frame) { const char *err; int i; if ((err= my_addr_resolve_init())) { fprintf(stderr, "(my_addr_resolve failure: %s)\n", err); return; } for (i=0; i < SF_REMEMBER_FRAMES && frame[i]; i++) { my_addr_loc loc; if (i) fprintf(stderr, ", "); if (my_addr_resolve(frame[i], &loc)) fprintf(stderr, "%p", frame[i]); else fprintf(stderr, "%s:%u", loc.file, loc.line); } fprintf(stderr, "\n"); } #else #define print_stack(X) fprintf(stderr, "???\n") #endif static void free_memory(void *ptr) { struct st_irem *irem= (struct st_irem *)ptr - 1; size_t end_offset; if ((irem->flags & MY_THREAD_SPECIFIC) && irem->thread_id && irem->thread_id != sf_malloc_dbug_id()) { fprintf(stderr, "Warning: %4lu bytes freed by T@%lu, allocated by T@%lu at ", (ulong) irem->datasize, (ulong) sf_malloc_dbug_id(), (ulong) irem->thread_id); print_stack(irem->frame); } pthread_mutex_lock(&sf_mutex); /* Protect against double free at same time */ if (irem->marker != MAGICSTART) { pthread_mutex_unlock(&sf_mutex); /* Allow stack trace alloc mem */ DBUG_ASSERT(irem->marker == MAGICSTART); /* Crash */ pthread_mutex_lock(&sf_mutex); /* Impossible, but safer */ } /* Remove this structure from the linked list */ if (irem->prev) irem->prev->next= irem->next; else sf_malloc_root= irem->next; if (irem->next) irem->next->prev= irem->prev; /* Handle the statistics */ sf_malloc_count--; irem->marker= MAGICEND; /* Double free detection */ pthread_mutex_unlock(&sf_mutex); /* Trash the data and magic values, but keep the stack trace */ end_offset= sizeof(*irem) - ((char*) &irem->marker - (char*) irem); TRASH_FREE((uchar*)(irem + 1) - end_offset, irem->datasize + 4 + end_offset); free(irem); return; } static void warn(const char *format,...) { va_list args; DBUG_PRINT("error", ("%s", format)); va_start(args,format); vfprintf(stderr, format, args); fflush(stderr); va_end(args); #ifdef HAVE_BACKTRACE { void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; int frames= backtrace(frame, array_elements(frame)); fprintf(stderr, " at "); if (frames < SF_REMEMBER_FRAMES + SF_FRAMES_SKIP) frame[frames]= 0; print_stack(frame + SF_FRAMES_SKIP); } #endif } static int bad_ptr(const char *where, void *ptr) { struct st_irem *irem= (struct st_irem *)ptr - 1; const uchar *magicend; if (((intptr) ptr) % sizeof(double)) { warn("Error: %s wrong aligned pointer", where); return 1; } if (ptr < sf_min_adress || ptr > sf_max_adress) { warn("Error: %s pointer out of range", where); return 1; } if (irem->marker != MAGICSTART) { DBUG_PRINT("error",("Unallocated data or underrun buffer %p", ptr)); warn("Error: %s unallocated data or underrun buffer %p", where, ptr); return 1; } magicend= (uchar*)ptr + irem->datasize; if (magicend[0] != MAGICEND0 || magicend[1] != MAGICEND1 || magicend[2] != MAGICEND2 || magicend[3] != MAGICEND3) { DBUG_PRINT("error",("Overrun buffer %p", ptr)); warn("Error: %s overrun buffer %p", where, ptr); fprintf(stderr, "Allocated at "); print_stack(irem->frame); return 1; } return 0; } /* check all allocated memory list for consistency */ int sf_sanity() { struct st_irem *irem; int flag= 0; int count= 0; pthread_mutex_lock(&sf_mutex); count= sf_malloc_count; for (irem= sf_malloc_root; irem && count > 0; count--, irem= irem->next) flag+= bad_ptr("Safemalloc", irem + 1); pthread_mutex_unlock(&sf_mutex); if (count || irem) { warn("Error: Safemalloc link list destroyed"); flag= 1; } return flag; } /** report on all the memory pieces that have not been free'd @param id Id of thread to report. 0 if all */ void sf_report_leaked_memory(my_thread_id id) { size_t total= 0; struct st_irem *irem; sf_sanity(); /* Report on all the memory that was allocated but not free'd */ for (irem= sf_malloc_root; irem; irem= irem->next) { if (!id || (irem->thread_id == id && irem->flags & MY_THREAD_SPECIFIC)) { my_thread_id tid = irem->thread_id && irem->flags & MY_THREAD_SPECIFIC ? irem->thread_id : 0; fprintf(stderr, "Warning: %4lu bytes lost at %p, allocated by T@%llu at ", (ulong) irem->datasize, (char*) (irem + 1), tid); print_stack(irem->frame); total+= irem->datasize; } } if (total) fprintf(stderr, "Memory lost: %lu bytes in %d chunks\n", (ulong) total, sf_malloc_count); return; } static void sf_terminate() { if (!sf_leaking_memory) sf_report_leaked_memory(0); pthread_mutex_destroy(&sf_mutex); } #endif