我正在尝试编写一个简单的共享库,它可以将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。对不起标签中的缺乏,我找不到合适的标签,而且我仍然没有足够的声誉来创建新标签。
答案 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);
}