使用LD_PRELOAD机制覆盖'malloc'

时间:2011-05-21 16:03:15

标签: c malloc dynamic-linking

我正在尝试编写一个简单的共享库,它可以将malloc调用记录到stderr(如果你愿意,可以使用某种'mtrace')。

但是,这不起作用。 这是我的所作所为:

/* 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;
    }
}

我用以下代码编译:

gcc -shared -fPIC -o mtrace.so mtrace.c

然后当我尝试执行ls时:

$ LD_PRELOAD=./mtrace.so ls
malloc(352) = Segmentation fault

现在,我怀疑dlopen需要malloc,当我在共享库中重新定义它时,它会使用仍然未分配real_malloc的版本。

问题是......如何让它发挥作用?

P.S。对不起标签中的缺乏,我找不到合适的标签,而且我仍然没有足够的声誉来创建新标签。

5 个答案:

答案 0 :(得分:35)

我总是这样做:

#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;
}

不要使用构造函数,只需在第一次调用malloc时初始化。使用RTLD_NEXT来避免dlopen。您也可以尝试malloc hooks。请注意,所有这些都是GNU扩展,并且可能无法在其他地方工作。

答案 1 :(得分:27)

如果您真的想将LD_PRELOAD与malloc一起使用,并且发现接受的答案中的代码仍然是段错误,那么我有一个似乎有效的解决方案。

段错误是由dlsym调用calloc 32个字节引起的,导致递归到栈的末尾。

我的解决方案是创建一个超级简单的静态分配器,在dlsym返回malloc函数指针之前处理分配。

#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;
}

希望这有助于某人。

答案 2 :(得分:7)

如果你正在使用glibc,你应该使用它的built in malloc hooking mechanism - 这个例子中的例子有一个如何查找原始malloc的例子。如果您要为分配添加其他跟踪信息,这一点尤其重要,以确保返回malloc缓冲区的库函数与您的free()实现一致。

答案 3 :(得分:0)

这是malloc和自由挂钩的最简单示例。

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>

static void* (*real_malloc)(size_t size);
static void  (*real_free)(void *ptr);

__attribute__((constructor))
static void init()
{
        real_malloc = dlsym(RTLD_NEXT, "malloc");
        real_free   = dlsym(RTLD_NEXT, "free");
        fprintf(stderr, "init\n");
}

void *malloc(size_t size)
{
        void *ptr = real_malloc(size);
        fprintf(stderr, "malloc(%zd) = %p\n", size, ptr);
        return ptr;
}

void free(void *ptr)
{
        real_free(ptr);
        fprintf(stderr, "free(%p)\n", ptr);
}

答案 4 :(得分:0)

这里是上述示例的扩展,它通过使用mmap直到完成初始化来避免dlsym中的段错误:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <sys/mman.h>

static void* (*real_malloc)(size_t)         = NULL;
static void* (*real_realloc)(void*, size_t) = NULL;
static void* (*real_calloc)(size_t, size_t) = NULL;
static void  (*real_free)(void*)            = NULL;

static int alloc_init_pending = 0;

/* Load original allocation routines at first use */
static void alloc_init(void)
{
  alloc_init_pending = 1;
  real_malloc  = dlsym(RTLD_NEXT, "malloc");
  real_realloc = dlsym(RTLD_NEXT, "realloc");
  real_calloc  = dlsym(RTLD_NEXT, "calloc");
  real_free    = dlsym(RTLD_NEXT, "free");
  if (!real_malloc || !real_realloc || !real_calloc || !real_free) {
    fputs("alloc.so: Unable to hook allocation!\n", stderr);
    fputs(dlerror(), stderr);
    exit(1);
  } else {
    fputs("alloc.so: Successfully hooked\n", stderr);
  }
  alloc_init_pending = 0;
}

#define ZALLOC_MAX 1024
static void* zalloc_list[ZALLOC_MAX];
static size_t zalloc_cnt = 0;

/* dlsym needs dynamic memory before we can resolve the real memory 
 * allocator routines. To support this, we offer simple mmap-based 
 * allocation during alloc_init_pending. 
 * We support a max. of ZALLOC_MAX allocations.
 * 
 * On the tested Ubuntu 16.04 with glibc-2.23, this happens only once.
 */
void* zalloc_internal(size_t size)
{
  fputs("alloc.so: zalloc_internal called", stderr);
  if (zalloc_cnt >= ZALLOC_MAX-1) {
    fputs("alloc.so: Out of internal memory\n", stderr);
    return NULL;
  }
  /* Anonymous mapping ensures that pages are zero'd */
  void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
  if (MAP_FAILED == ptr) {
    perror("alloc.so: zalloc_internal mmap failed");
    return NULL;
  }
  zalloc_list[zalloc_cnt++] = ptr; /* keep track for later calls to free */
  return ptr;
}

void free(void* ptr)
{
  if (alloc_init_pending) {
    fputs("alloc.so: free internal\n", stderr);
    /* Ignore 'free' during initialization and ignore potential mem leaks 
     * On the tested system, this did not happen
     */
    return;
  }
  if(!real_malloc) {
    alloc_init();
  }
  for (size_t i = 0; i < zalloc_cnt; i++) {
    if (zalloc_list[i] == ptr) {
      /* If dlsym cleans up its dynamic memory allocated with zalloc_internal,
       * we intercept and ignore it, as well as the resulting mem leaks.
       * On the tested system, this did not happen
       */
      return;
    }
  }
  real_free(ptr);
}

void *malloc(size_t size)
{
  if (alloc_init_pending) {
    fputs("alloc.so: malloc internal\n", stderr);
    return zalloc_internal(size);
  }
  if(!real_malloc) {
    alloc_init();
  }
  void* result = real_malloc(size);
  //fprintf(stderr, "alloc.so: malloc(0x%zx) = %p\n", size, result);
  return result;
}

void *realloc(void* ptr, size_t size)
{
  if (alloc_init_pending) {
    fputs("alloc.so: realloc internal\n", stderr);
    if (ptr) {
      fputs("alloc.so: realloc resizing not supported\n", stderr);
      exit(1);
    }
    return zalloc_internal(size);
  }
  if(!real_malloc) {
    alloc_init();
  }
  return real_realloc(ptr, size);
}

void *calloc(size_t nmemb, size_t size)
{
  if (alloc_init_pending) {
    fputs("alloc.so: calloc internal\n", stderr);
    /* Be aware of integer overflow in nmemb*size.
     * Can only be triggered by dlsym */
    return zalloc_internal(nmemb * size);
  }
  if(!real_malloc) {
    alloc_init();
  }
  return real_calloc(nmemb, size);
}