mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +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
|