以前在meego性能优化团队实习的时候,为了提高APP启动速度,我曾计算APP调用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 () { ... }