mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			425 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			425 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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_stacktrace.h>      /* my_addr_resolve */
 | |
| 
 | |
| #if HAVE_EXECINFO_H
 | |
| #include <execinfo.h>
 | |
| #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 uint   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;
 | |
|   uint 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;
 | |
|   uint first= 0, chunks= 0;
 | |
| 
 | |
|   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;
 | |
|       if (!first++)
 | |
|         fprintf(stderr, "Memory report from safemalloc\n");
 | |
|       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;
 | |
|       chunks++;
 | |
|     }
 | |
|   }
 | |
|   if (total)
 | |
|     fprintf(stderr, "Memory lost: %lu bytes in %u chunks of %u total chunks\n",
 | |
|             (ulong) total, chunks, sf_malloc_count);
 | |
|   return;
 | |
| }
 | |
| 
 | |
| static void sf_terminate()
 | |
| {
 | |
|   if (!sf_leaking_memory)
 | |
|     sf_report_leaked_memory(0);
 | |
| 
 | |
|   pthread_mutex_destroy(&sf_mutex);
 | |
| }
 | |
| 
 | |
| #endif
 | 
