我正在尝试为C语言中的free
和malloc
创建包装函数,以帮助我检测内存泄漏。有人知道如何声明这些函数,以便在调用malloc()
和free()
时调用我的自定义函数而不是标准库函数吗?
我正在尝试为C语言中的free
和malloc
创建包装函数,以帮助我检测内存泄漏。有人知道如何声明这些函数,以便在调用malloc()
和free()
时调用我的自定义函数而不是标准库函数吗?
有几种选择:
针对GLIBC (大多数Linux) 的解决方案。 如果您的编译环境是使用 gcc
的 glibc
,则首选方法是使用malloc hooks(malloc 钩子)。它不仅可以让您指定自定义的 malloc
和 free
,而且还会通过堆栈上的返回地址标识调用者。
针对POSIX的解决方案。 将 malloc
和 free
定义为可执行文件中原始分配例程的包装器,这将“覆盖”来自 libc 的版本。在包装器中,您可以调用原始的 malloc
实现,您可以使用 dlsym
和 RTLD_NEXT
句柄查找。定义包装器函数的应用程序或库需要链接到 -ldl
。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
void* malloc(size_t sz)
{
void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc");
printf("malloc\n");
return libc_malloc(sz);
}
void free(void *p)
{
void (*libc_free)(void*) = dlsym(RTLD_NEXT, "free");
printf("free\n");
libc_free(p);
}
int main()
{
free(malloc(10));
return 0;
}
仅适用于Linux系统。 您可以通过在LD_PRELOAD
环境变量中指定函数来非侵入式地覆盖动态库中的函数。
LD_PRELOAD=mymalloc.so ./exe
仅适用于Mac OSX。
与Linux相同,不同之处在于您将使用DYLD_INSERT_LIBRARIES
环境变量。
LD_PRELOAD=./malloc.so ls
会导致段错误。这不是创建无限递归吗?如何告诉我们自定义的malloc内部的函数使用libc-malloc? - phip1611*.so
共享对象:https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html。 - Gabriel Staplesmalloc()
和 free()
,而不是仅仅包装它们,则必须考虑一些特殊情况,例如,如果您从 malloc()
中调用 printf()
,可能会创建无限递归,因为 printf()
可能会调用malloc()
,从而又调用printf()
...直到堆栈溢出。请参见我在这里的回答:“Segmentation fault (core dumped)” for: “No such file or directory” for libioP.h, printf-parse.h, vfprintf-internal.c, etc。 - Gabriel StaplesLD_PRELOAD=/path.../lib_fake_malloc.so ./app
但我建议以“稍微”聪明的方式来做,我的意思是只调用一次dlsym。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
void* malloc(size_t size)
{
static void* (*real_malloc)(size_t) = NULL;
if (!real_malloc)
real_malloc = dlsym(RTLD_NEXT, "malloc");
void *p = real_malloc(size);
fprintf(stderr, "malloc(%d) = %p\n", size, p);
return p;
}
我在这里找到的示例:http://www.jayconrod.com/cgi/view_post.py?23,是 Jay Conrod 的帖子。
但是我在这个页面上发现真正酷的东西是:GNU 链接器提供了一个有用的选项,--wrap。当我查看“man ld”时,出现以下示例:
void *
__wrap_malloc (size_t c)
{
printf ("malloc called with %zu\n", c);
return __real_malloc (c);
}
我同意他们所说的“平凡示例” :). 即使没有dlsym,也不需要它。
让我引用我“man ld”页面的另一部分:
--wrap=symbol
Use a wrapper function for symbol.
Any undefined reference to symbol will be resolved to "__wrap_symbol".
Any undefined reference to "__real_symbol" will be resolved to symbol.
我希望说明已经完整,并展示了如何使用这些东西。strdup
内部的 malloc 重命名为包装器? - Dan M.在我的情况下,我需要在malloc下封装memalign/aligned_malloc。在尝试其他解决方案后,最终实现了下面列出的方法。它似乎可以正常工作。
/*
* Link-time interposition of malloc and free using the static
* linker's (ld) "--wrap symbol" flag.
*
* Compile the executable using "-Wl,--wrap,malloc -Wl,--wrap,free".
* This tells the linker to resolve references to malloc as
* __wrap_malloc, free as __wrap_free, __real_malloc as malloc, and
* __real_free as free.
*/
#include <stdio.h>
void *__real_malloc(size_t size);
void __real_free(void *ptr);
/*
* __wrap_malloc - malloc wrapper function
*/
void *__wrap_malloc(size_t size)
{
void *ptr = __real_malloc(size);
printf("malloc(%d) = %p\n", size, ptr);
return ptr;
}
/*
* __wrap_free - free wrapper function
*/
void __wrap_free(void *ptr)
{
__real_free(ptr);
printf("free(%p)\n", ptr);
}
#define malloc(x) _my_malloc(x, __FILE__, __LINE__)
#define free(x) _my_free(x)
__
和下划线加大写字母开头的名称是由标准保留的。以单个下划线开头的名称没有被保留,但它们应该包含在文件内,即链接器不应该看到它们。因此,只要_my_malloc
和_my_free
是静态函数,就可以使用它们。另一方面,重新定义库函数是未定义行为。 - Shahbaz_my_malloc()
作为静态函数违反了第二个限制。 - Jonathan Lefflerstatic void *_my_malloc(size_t)
在文件作用域中定义,属于普通名称空间”是否与标准说法“所有以下划线开头的标识符都保留为普通名称空间中具有文件作用域的标识符”相冲突?我错过了什么吗? - Jonathan Lefflermalloc
和free
的唯一客户端(即您不尝试为某个其他库中的代码打补丁),则可以使用依赖注入。#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include <stddef.h>
struct Allocator;
typedef struct {
void *(*allocate)(struct Allocator *allocator, size_t size);
void (*free)(struct Allocator *allocator, void *object);
} AllocatorVTable;
typedef struct Allocator {
const AllocatorVTable *vptr;
} Allocator;
typedef struct {
Allocator super;
char *buffer;
size_t offset;
size_t capacity;
} BufferedAllocator;
void BufferedAllocator_init(BufferedAllocator *allocator, char *buffer, size_t capacity);
typedef Allocator MallocAllocator;
void MallocAllocator_init(MallocAllocator *allocator);
void *Allocator_allocate(Allocator *allocator, size_t size);
void Allocator_free(Allocator *allocator, void *object);
#endif
#include "allocator.h"
#include "malloc.h"
void *Allocator_allocate(Allocator *allocator, size_t size) {
return allocator->vptr->allocate(allocator, size);
}
void Allocator_free(Allocator *allocator, void *object) {
allocator->vptr->free(allocator, object);
}
void *BufferedAllocator_allocate(Allocator *allocator, size_t size) {
BufferedAllocator *bufferedAllocator = (BufferedAllocator *) allocator;
if (bufferedAllocator->offset + size > bufferedAllocator->capacity) {
fprintf(stderr, "buffer overflow: %ld + %ld > %ld\n",
bufferedAllocator->offset, size, bufferedAllocator->capacity);
return NULL;
}
bufferedAllocator->offset += size;
return bufferedAllocator->buffer + bufferedAllocator->offset - size;
}
void BufferedAllocator_free(Allocator *allocator, void *object) {
}
const AllocatorVTable bufferedAllocatorVTable = {
.allocate = BufferedAllocator_allocate,
.free = BufferedAllocator_free,
};
void BufferedAllocator_init(BufferedAllocator *allocator, char *buffer,
size_t capacity) {
allocator->super.vptr = &bufferedAllocatorVTable;
allocator->buffer = buffer;
allocator->offset = 0;
allocator->capacity = capacity;
}
void *MallocAllocator_allocate(Allocator *allocator, size_t size) {
return malloc(size);
}
void MallocAllocator_free(Allocator *allocator, void *object) {
free(object);
}
const AllocatorVTable mallocAllocatorVTable = {
.allocate = MallocAllocator_allocate,
.free = MallocAllocator_free,
};
void MallocAllocator_init(MallocAllocator *allocator) {
allocator->vptr = &mallocAllocatorVTable;
}
#include <assert.h>
#include "allocator_test.h"
#include "allocator.h"
void testAllocator() {
{
BufferedAllocator bufferedAllocator;
char buffer[4];
size_t capacity = sizeof(buffer);
BufferedAllocator_init(&bufferedAllocator, buffer, capacity);
Allocator *allocator = &bufferedAllocator.super;
void *chill = Allocator_allocate(allocator, capacity);
assert(chill == buffer);
void *oops = Allocator_allocate(allocator, 1);
assert(oops == NULL);
}
{
MallocAllocator allocator;
MallocAllocator_init(&allocator);
void *chill = Allocator_allocate(&allocator, 100);
assert(chill != NULL);
void *alsoChill = Allocator_allocate(&allocator, 100);
assert(alsoChill != NULL);
}
}
Allocator *
(除了在栈上使用char buf[n]
之类的东西)。 您可以使用 MallocAllocator
来仅使用系统的 malloc
/free
,或者您可以在程序的顶部使用 BufferedAllocator
。 BufferedAllocator
只是一个非常简单的 malloc/free 的示例。 在我的使用案例中,它运行良好,因为我基本上知道我的程序将预先使用多少内存,并且在整个程序完成之前不删除任何对象。 使用此接口,您可以编写更复杂的算法,例如this lecture 中描述的算法之一。 有许多不同的防止碎片化的策略和许多权衡,因此自己编写 malloc/free 可能非常有用。如果你只是谈论你自己控制的内存,即你自己分配和释放的内存,你可以看一下rmdebug。可能这正是你要写的,所以你可以节省时间。它有一个非常自由的许可证,如果这对你很重要的话。
我个人在一个项目中使用它来查找内存泄漏,好处是它比valgrind快得多,但它不是那么强大,所以你不能得到完整的调用堆栈。