库打桩机制
什么是库打桩机制
库打桩机制(library interpositioning),允许调用者截获对共享库函数的调用,用调用者所写的代码替换执行。
作用:追踪某个库函数的调用次数,甚至是替换成一个全新功能的函数。
基本思想为:为一个需要打桩的目标函数,创建一个包装函数。包装函数的原型与目标函数需要完全一致。
包装函数通常会执行自己的逻辑,然后调用目标函数,最后将目标函数的返回值传递给调用者。
打桩可以发生在编译时,链接时或者运行时。
以malloc与free函数为例,示范打桩机制过程。
编译时打桩
声明与定义包装的malloc与free函数
头文件mymalloc.h
#ifndef MALLOC_H_
#define MALLOC_H_
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(size_t size);
void myfree(void* ptr);
#endif
.c文件mymalloc.cc
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>
void *mymalloc(size_t size){
void* ptr = malloc(size);
printf("malloc(%d)=%p\n",(int)size,ptr);
return ptr;
}
void myfree(void* ptr){
free(ptr);
printf("free(%p) \n",ptr);
}
#endif
测试程序main.c
#include <stdio.h>
#include "mymalloc.h"
int main(){
int *p = malloc(32);
free(p);
return 0;
}
利用gcc进行编译:
gcc -DCOMPILETIME -c mymalloc.c
gcc -I. -o test main.c mymalloc.o
结果:
malloc(32)=0x5638216d6670
free(0x5638216d6670)
从结果看出,确实执行了我们定义的malloc与free函数。
链接时打桩
mymalloc.c
#ifdef LINKTIME
#include <stdio.h>
void *__real_malloc(size_t size);
void __real_free(void* ptr);
void *__wrap_malloc(size_t size){
void* ptr = __real_malloc(size);
printf("malloc(%d) = %p\n",(int)size,ptr);
return ptr;
}
void __wrap_free(void *ptr){
__real_free(ptr);
printf("free(%p)\n",ptr);
}
#endif
测试程序跟编译时打桩类似。执行编译与链接指令:
gcc -DLINKTIME -c mymalloc.c
gcc -c main.c
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o test main.o mymalloc.o
这里的-Wl,option标志会把option传递给编译器,所以-Wl,–wrap,malloc就把–wrap与malloc传递给编译器。
运行时打桩
编译时打桩需要访问程序的源代码,链接时打桩需要访问程序的可重定位对象文件(.o),运行时打桩只需要访问动态库即可。
这种机制的实现依赖于动态链接器的LD_PRELOAD环境变量。
mymalloc.c代码`
#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
void *malloc(size_t size){
void *(*mallocp)(size_t size);
char* error;
mallocp=dlsym(RTLD_NEXT,"malloc");
if((error=dlerror()) != NULL){
fputs(error,stderr);
exit(1);
}
char* ptr = mallocp(size);
//对一些linux版本,使用printf的话由于printf本身需要使用malloc,来开辟缓存(因为printf使用stdout方式),会导致段错误,因此这里打印通过stderr的方式
fprintf(stderr,"malloc(%d)=%p\n",(int)size,ptr);
// fputs("malloc(%d)=%p \n",stderr);
// printf("malloc(%d)=%p \n",(int)size,ptr);
return ptr;
}
void free(void* ptr){
void (*freep)(void*) = NULL;
char* error;
if(!ptr){
return;
}
freep=dlsym(RTLD_NEXT,"free");
if((error=dlerror()) != NULL){
fputs(error,stderr);
exit(1);
}
freep(ptr);
fprintf(stderr,"free(%p)\n",ptr);
}
#endif
编译与执行:
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
LD_PRELOAD="./mymalloc.so" ./test
使用运行时打桩LD_PRELOAD方式,可以对任何可执行文件进行函数打桩。