mariadb/mysys/safemalloc.c
2023-12-17 11:20:43 +01:00

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