Overriding 'malloc' using the LD_PRELOAD mechanism
I'm trying to write a simple shared library that would log malloc calls to stderr (a sort of 'mtrace' if you will).
However, this is not working. Here's what I do:
/* mtrace.c */
#include <dlfcn.h>
#include <stdio.h>
static void* (*real_malloc)(size_t);
void *malloc(size_t size)
{
void *p = NULL;
fprintf(stderr, "malloc(%d) = ", size);
p = real_malloc(size);
fprintf(stderr, "%p\n", p);
return p;
}
static void __mtrace_init(void) __attribute__((constructor));
static void __mtrace_init(void)
{
void *handle = NULL;
handle = dlopen("libc.so.6", RTLD_LAZY);
if (NULL == handle) {
fprintf(stderr, "Error in `dlopen`: %s\n", dlerror());
return;
}
real_malloc = dlsym(handle, "malloc");
if (NULL == real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
return;
}
}
I compile this with:
gcc -shared -fPIC -o mtrace.so mtrace.c
And then when I try to execute ls
:
$ LD_PRELOAD=./mtrace.so ls
malloc(352) = Segmentation fault
Now, I suspect that dlopen needs malloc, and as I am redefining it within the shared library, it uses that version with the still unassigned real_malloc
.
The question is...how do I make it work?
P.S. sorry for the paucity in tags, I couldn't find appropriate tags, and I still don't have enough reputation to create new ones.
Solution 1:
I always do it this way:
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
static void* (*real_malloc)(size_t)=NULL;
static void mtrace_init(void)
{
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (NULL == real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
}
}
void *malloc(size_t size)
{
if(real_malloc==NULL) {
mtrace_init();
}
void *p = NULL;
fprintf(stderr, "malloc(%d) = ", size);
p = real_malloc(size);
fprintf(stderr, "%p\n", p);
return p;
}
Don't use constructors, just initialize at first call to malloc
. Use RTLD_NEXT
to avoid dlopen
. You can also try malloc hooks. Be aware that all those are GNU extensions, and probably wont work elsewhere.
Solution 2:
If you really want to use LD_PRELOAD with malloc and found that the code in the accepted answer still segfaults, I have a solution that seems to work.
The segfault was caused by dlsym calling calloc for 32 bytes, causing a recursion to the end of the stack.
My solution was to create a super-simple static allocator that takes care of allocations before dlsym returns the malloc function pointer.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
char tmpbuff[1024];
unsigned long tmppos = 0;
unsigned long tmpallocs = 0;
void *memset(void*,int,size_t);
void *memmove(void *to, const void *from, size_t size);
/*=========================================================
* interception points
*/
static void * (*myfn_calloc)(size_t nmemb, size_t size);
static void * (*myfn_malloc)(size_t size);
static void (*myfn_free)(void *ptr);
static void * (*myfn_realloc)(void *ptr, size_t size);
static void * (*myfn_memalign)(size_t blocksize, size_t bytes);
static void init()
{
myfn_malloc = dlsym(RTLD_NEXT, "malloc");
myfn_free = dlsym(RTLD_NEXT, "free");
myfn_calloc = dlsym(RTLD_NEXT, "calloc");
myfn_realloc = dlsym(RTLD_NEXT, "realloc");
myfn_memalign = dlsym(RTLD_NEXT, "memalign");
if (!myfn_malloc || !myfn_free || !myfn_calloc || !myfn_realloc || !myfn_memalign)
{
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
exit(1);
}
}
void *malloc(size_t size)
{
static int initializing = 0;
if (myfn_malloc == NULL)
{
if (!initializing)
{
initializing = 1;
init();
initializing = 0;
fprintf(stdout, "jcheck: allocated %lu bytes of temp memory in %lu chunks during initialization\n", tmppos, tmpallocs);
}
else
{
if (tmppos + size < sizeof(tmpbuff))
{
void *retptr = tmpbuff + tmppos;
tmppos += size;
++tmpallocs;
return retptr;
}
else
{
fprintf(stdout, "jcheck: too much memory requested during initialisation - increase tmpbuff size\n");
exit(1);
}
}
}
void *ptr = myfn_malloc(size);
return ptr;
}
void free(void *ptr)
{
// something wrong if we call free before one of the allocators!
// if (myfn_malloc == NULL)
// init();
if (ptr >= (void*) tmpbuff && ptr <= (void*)(tmpbuff + tmppos))
fprintf(stdout, "freeing temp memory\n");
else
myfn_free(ptr);
}
void *realloc(void *ptr, size_t size)
{
if (myfn_malloc == NULL)
{
void *nptr = malloc(size);
if (nptr && ptr)
{
memmove(nptr, ptr, size);
free(ptr);
}
return nptr;
}
void *nptr = myfn_realloc(ptr, size);
return nptr;
}
void *calloc(size_t nmemb, size_t size)
{
if (myfn_malloc == NULL)
{
void *ptr = malloc(nmemb*size);
if (ptr)
memset(ptr, 0, nmemb*size);
return ptr;
}
void *ptr = myfn_calloc(nmemb, size);
return ptr;
}
void *memalign(size_t blocksize, size_t bytes)
{
void *ptr = myfn_memalign(blocksize, bytes);
return ptr;
}
Hope this helps someone.
Solution 3:
If you are using glibc, you should use its built in malloc hooking mechanism - the example in this page has an example of how to look up the original malloc. This is particularly important if you're adding additional tracking information to allocations, to ensure library functions which return malloc'd buffers are consistent with your free()
implementation.