linux C 包装函数使用
以前在meego性能优化团队实习的时候,我曾经被要求计算一个软件调用malloc的次数和耗时,并探讨如果使用内存池对软件可能产生的影响。通用的讲,我需要将某些函数的调用重定向到我们自己定义的替代函数中来,这样的替代函数就叫做包装函数(wrapper function)。对malloc这个具体的场景来说我们不仅需要hack函数的调用,还要获取原本glibc定义的“real malloc”的句柄(函数指针)以使程序正常运行下去,当然如果这一切可以在不需要重新编译软件的前提下完成就更好了。
我总结了Linux环境下包装函数的几种实现方法:
1.LD_PRELOAD+dlsym
这对组合中,LD_PRELOAD完成了hack,而dlsym可以帮组我们获取到real malloc。LD_PRELOAD是一个环境变量,它可以影响程序的运行时的链接,让我们指定优先加载的动态链接库。虽然这个方法无法hack静态链接的符号,不过还好gcc编译的标准C函数大多包含在动态链接库libc.so.6中,所以这个方法还是可行的。
my_malloc.c
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> void* malloc(size_t sz) { void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc"); printf("malloc\n"); return libc_malloc(sz); }
main.c
#include<stdio.h> #include <malloc.h> int main(int argc, char** argv) { int* ptr = (int *)malloc(sizeof(int)); free(ptr); return 0; }
gcc -o main.o -Wall -O2 main.c
gcc -o my_malloc.so -shared my_malloc.c -Wall -ldl -fPIC
export LD_PRELOAD=/path/.../my_malloc.so
./main.o
注意:
1.my_malloc.c中如果不加"#define _GNU_SOURCE",编译时报错“RTLD_NEXT undeclared”,因为glibc不会自动使用某些GNU extensions,所以需要定义这个宏启用这些extensions。
2.LD_PRELOAD后面的so文件一定要使用绝对路径或者把so文件放入LD_LIBRARY_PATH指定的目录下,否则会找不到so文件。
3.LD_PRELOAD的作用效果是全局的,使用完后要及时恢复。
2.ld --wrap
适用:Linux
ld有一个wrap选项,linux man的解释是: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.
void * __wrap_malloc (size_t size) { printf ("malloc\\n"); return __real_malloc (size); }
所有对malloc的调用被解析为调用__wrap_malloc,而__real_malloc则会被解析为原始malloc函数。这种方法在gDEBugger上得到了实际应用,gDEBugger是一个OpenGL Profiler,它可以给出每个OpenGL函数的调用次数使用时间等数据,它的实现方法就是为所有OpenGL函数定义wrapper函数,在wrapper函数中添加统计代码。
3.GCC Malloc Hook
gcc only malloc only
参考文献:http://www.gnu.org/s/hello/manual/libc/Hooks-for-Malloc.html 这种方法其实有很强的局限性,只针对使用gcc编译的malloc,realloc,free等内存管理函数。gcc定义了一批函数指针变量,例如__malloc_hook指向“malloc”实际调用的函数,初始化时先保存真正的malloc函数指针然后将自己定义的malloc函数指针赋值给__malloc_hook。
/* Prototypes for __malloc_hook, __free_hook */ #include <malloc.h> /* Prototypes for our hooks. */ static void my_init_hook (void); static void *my_malloc_hook (size_t, const void *); static void my_free_hook (void*, const void *); /* Override initializing hook from the C library. */ void (*__malloc_initialize_hook) (void) = my_init_hook; static void my_init_hook (void) { old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; } static void * my_malloc_hook (size_t size, const void *caller) { void *result; /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ result = malloc (size); /* Save underlying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* printf might call malloc, so protect it too. */ printf ("malloc (%u) returns %p\\n", (unsigned int) size, result); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; return result; } static void my_free_hook (void *ptr, const void *caller) { /* Restore all old hooks */ __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; /* Call recursively */ free (ptr); /* Save underlying hooks */ old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; /* printf might call free, so protect it too. */ printf ("freed pointer %p\\n", ptr); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; } int main () { ... }