Linux下的链接器支持一个强大的库打桩(library interpositioning),允许你拦截对系统标准库中某个目标函数的调用,取而代之执行自己的包装函数。它可以给我们带来两个好处,一是通过添加某些语句,可以追踪自己的程序对某些库函数的调用情况;二是可以在你自己的程序中,对某些库函数偷天换日,替换成一个完全不同的实现。
打桩可以发生在编译,链接和运行的任意一个阶段,相应的代码编写和编译也有一些区别,下文将分别做一阐述:
**需求:**假设需要在主程序myprog.c中跟踪对库函数malloc和free的使用情况。
建立mymalloc.c文件,定义需要的包装函数mymalloc和myfree.
#ifdef COMPILETIME
#include
#include
//定义malloc 包装函数
void *mymalloc(size_t size)
{
void *ptr = malloc(size);
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
//定义free 包装函数
void *myfree(void *ptr)
{
free(ptr);
printf("free(%p) = %p\n", ptr);
}
#endif
该文件向预处理器指明用mymalloc.c中的包装函数替换库里的目标函数
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(size_t size);
void *myfree(void *ptr);
建立文件myprog.c,并在其中正常调用malloc函数.
#include
#include
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
gcc -DCOMPILETIME -c mymalloc.c
gcc -I. -o myprog myprog.c mymalloc.c
-I.:指示C预处理器在搜索通常的系统目录前,先在当前目录中查找malloc.h
./myprog
malloc(32) = 0x9ee010
free(0x9ee010)
linux利用静态链接器完成库打桩。先看它的一个命令行参数:
–wrap f:指示链接器把对符号f的引用解析成**__wrap_f**,把对**__real_f的引用解析成符号f**。
此处f代表任意的库函数名或变量名
-Wl,option:将option传递给链接器, option中的每个逗号都要用空格来替换。
创建mymalloc.c文件,定义需要的包装函数.
#ifdef LIKETIME
#include
void *__real_malloc(size_t size);
void __real_free(void *ptr);
//定义malloc 包装函数
void *__wrap_malloc(size_t size)
{
void *ptr = *__real_malloc(size); //调用标准库里的malloc
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
//定义free 包装函数
void *__wrap_free(void *ptr)
{
__real_free(ptr); //调用标准库里的free
printf("free(%p) = %p\n", ptr);
}
#endif
建立文件myprog.c,并在其中正常调用malloc函数.
#include
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
gcc -DLINKTIME -c mymalloc.c
gcc -c myprog.c
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o myprog myprog.o mymalloc.o
-Wl,--wrap,malloc
意味着把--wrap malloc
传递给链接器。./myprog
malloc(32) = 0x18cf010
free(0x18cf010)
编译时打桩需要访问程序的源代码,而链接时需要访问可重定位目标文件。那有没有一种办法让仅仅访问可执行目标就能达到同样的目的呢?我们可以利用基于动态链接器的LD_PRELOAD环境变量来实现。
当你将LD_PRELOAD环境变量设置为一个共享路径名的列表(以空格或分号分开),那么在运行一个程序时,动态链接器(LD-LINUX.SO)会先搜索列表里的库,然后才搜素系统其它库。
利用这个原理,你可以对任何共享库中的任何函数打桩,包括libc.so。
下面建立mymalloc.c文件,其中定义了malloc和free的包装函数。每个包装函数中,利用dlsym调用libc中的标准函数。
#ifdef RUNTIME
#define _GNU_SOURCE
#include
#include
#include
//定义malloc 包装函数
void *malloc(size_t size)
{
void *(*mallocp)(size_t size);
//获得libc中malloc函数的地址
if( !(mallocp = dlsym(RTLD_NEXT, "malloc")) ){
fputs(dlerror());
exit(1);
}
char *ptr = *mallocp(size); //利用函数指针间接调用libc中的malloc函数
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
//定义free 包装函数
void *free(void *ptr)
{
void (*free)(void *) = NULL;
if(!ptr)
return;
//获得libc中free函数的地址
if( !(freep = dlsym(RTLD_NEXT, "free")) ){
fputs(dlerror());
exit(1);
}
*freep(ptr); //利用函数指针间接调用libc中的free函数
printf("free(%p)\n", ptr);
}
#endif
建立文件myprog.c,并在其中正常调用malloc函数.
#include
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
ifdef RUNTIME
呼应。 gcc -o myprog myprog.c //编译
在bash中运行,及其结果:
LD_PRELOAD="./mymalloc.so" ./myprog
结果如下
malloc(32) = 0x1bf7010
free(0x1bf7010)
在csh或tcsh中运行方法:
(setenv LD_PRELOAD "./mymalloc.so"; ./myprog; unsetenv LD_PRELOAD)
结果如下
malloc(32) = 0x2157010
free(0x2157010)
GNU binutils包有许多实用的工具特别有帮助,而且可以运行在每一个Linux平台上。
获取更多知识,请点击关注:
嵌入式Linux&ARM
CSDN博客
简书博客