Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 557
Dany Zatuchna Avatar asked May 21 '11 16:05

Dany Zatuchna


3 Answers

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.

like image 165
Piotr Praszmo Avatar answered Oct 22 '22 02:10

Piotr Praszmo


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.

like image 41
FatalFlaw Avatar answered Oct 22 '22 01:10

FatalFlaw


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.

like image 8
bdonlan Avatar answered Oct 22 '22 01:10

bdonlan