库打桩
(library interpositioning):简单的说就是要自己的函数替换库中相应的函数。比如我们想知道程序总共调用了多少次malloc()
.在程序已经写好的情况下,库打桩测试是一个很好的方式。使用打桩机制,可以追踪某个特殊库函数的调用次数、验证并追踪其输入输出,甚至把它替换成一个完全不同的实现。在程序编译、链接、运行等过程都可使用库打桩机制。下面将一一介绍。
//建立三个文件 main.c mymalloc.c malloc.h,其中mymalloc.c是我们的打桩程序.malloc.h用于替换系统路径中的malloc.h,使编译器用mymalloc()代替main()中的malloc。free()同理。程序如下:
//-------------------main.c文件内容
#include
#include
int main(void)
{
int *p = malloc(32);
free(p);
return 0;
}
//-------------------mymalloc.c文件内容
#ifdef COMPILETIME
#include
#include
#include
void *mymalloc(uint16_t size)
{
void *ptr = malloc(size);
printf("malloc addr:%p,size:%d\n",ptr,size);
return ptr;
}
void myfree(void *ptr)
{
free(ptr);
printf("free addr:%p\n",ptr);
}
#endif
//-------------------malloc.h文件内容
#include
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(uint16_t size);
void myfree(void *ptr);
编译时打桩步骤:
gcc -DCOMPILETIME -c mymalloc.c
生成可重定位文件mymalloc.o。用于后面的链接。
COMPILETIME
宏,在mymalloc.c程序使用此宏作为开关。gcc -I. -o main main.c mymalloc.o
编译、并连接生成可执行文件main.
-I.
:I
选项的本身含义是指定头文件的路径,gcc在预处理时首先在此目录下查找。-I.
表示添加当前目录为第一个头文件搜索路径。那么gcc在预处理main.c时就首先找到了我们上边编写的malloc.h
(而不是系统提供的malloc.h),然后gcc根据我们malloc.h中的宏定义将main.c中的malloc()函数替换成了mymalloc()函数。这样我们就达到了偷梁换柱的目的。这也是编译时打桩的核心。./main
运行程序。控制台打印相应信息。说明我们成功用mymalloc()函数替换了系统的malloc()函数。在mymalloc()函数中我们可以实现我们所要的任何逻辑。总结:编译时打桩就是首先编写我们自己的打桩函数,然后编写和系统.h文件名称一样的.h文件。在此.h文件中使用宏定义将系统函数替换为我们自己编写的函数。最后在链接时使用-I
指定我们编写的头文件路径。
从上面的打桩步骤我们可以看到,编译时打桩
的行为发生在预处理的过程中,所以必须要有源代码(main.c)才行。
链接时打桩程序如下,其中main.c的内容和上面一致,用不到上面的malloc.h,mymalloc.c文件内容如下:
//-------------------mymalloc.c文件内容
#ifdef LINKTIME
#include
#include
void *__wrap_malloc(uint16_t size);
void __wrap_free(void *ptr);
void *__wrap_malloc(uint16_t size)
{
void *ptr = __real_malloc(size);
printf("malloc addr:%p,size:%d\n",ptr,size);
return ptr;
}
void __wrap_free(void *ptr)
{
__real_free(ptr);
printf("free addr:%p\n",ptr);
}
#endif
从程序中我们可以看出多了__wrap_
和__real_
两个前缀。它是链接时打桩的核心。在进行链接时,
__wrap_ f
中的f
替换成__wrap_f
.如指定选项为__wrap_ malloc
,则函数malloc()被替换为了__wrap_malloc().__real_ f
中的f
替换成f
。如指定选项为__real_ malloc
,则函数__real_malloc()被替换为了malloc().使用编译器上面的两个属性我们就很容易先将系统的malloc()通过选项__wrap_ malloc
替换成__wrap_malloc()函数。而将我们定义的__wrap_malloc()函数替换成malloc()函数。这样就成功达到偷梁换柱的目的。
下面是链接时打桩步骤:
gcc -DLINKTIME -c mymalloc.c
使用此命令生成mymalloc.o。
LINKTIME
宏,在mymalloc.c程序使用此宏作为开关。gcc -c main.c
使用此命令生成main.o。gcc -Wl,--wrap,malloc -Wl,--wrap,free -o main main.o mymalloc.o
使用此命令生成可执行文件。
-WL
:此选项告诉编译器将后面的参数传递给链接器,其中,
换成空格。如-Wl,--wrap,malloc
表示将--wrap malloc
传给编译器。从上面的步骤可以看出,链接时打桩
的行为发生在链接的过程中,所以必须要有可重定位文件(main.o)才行。
链接时打桩程序如下,其中main.c的内容和上面一致,用不到上面的malloc.h,mymalloc.c文件内容如下:
//-------------------mymalloc.c文件内容
#ifdef RUNTIME
#define _GNU_SOURCE
#include
#include
#include
void *malloc(size_t size)
{
void *(*pmalloc)(size_t size);
char *error;
pmalloc = dlsym(RTLD_NEXT,"malloc");
if( (error = dlerror()) != NULL){
fputs(error,stderr);
exit(1);
}
char *ptr = pmalloc(size);
fputs("malloc\n",stderr);
return ptr;
}
void free(void *ptr)
{
void (*pfree)(void *) =NULL;
char *error;
pfree = dlsym(RTLD_NEXT,"free");
if( (error = dlerror()) != NULL){
fputs(error,stderr);
exit(1);
}
pfree(ptr);
fputs("free\n",stderr);
}
#endif
运行时打桩需要用到共享库中的运行时的动态链接库
。如果对其不了解的可以参考linux中的静态库_动态库详解.md。
运行时打桩步骤如下:
gcc -DRUNTIME -shared -fpic -o libmymalloc.so mymalloc.c -ldl
使用此命令生成动态库libmymalloc.so。LD_PRELOAD="./libmymalloc.so" ./main
。使用此命令运行程序。
LD_PRELOAD
:是个环境变量,用于动态库的加载,动态库加载的优先级最高。它是运行时打桩的核心。通过此环境变量让其先加载自定义的库。那么main.c中malloc()函数也就指向了我们库中的malloc()。也达到了偷梁换柱的目的。注意:上面的程序中不能使用printf()。原因是printf()中也调用了malloc(),这样就陷入了无线循环。具体原因可以参看此博客http://www.voidcn.com/article/p-mbajvzdw-bmt.html
从上面的步骤可以看出,运行时打桩
的行为发生在程序运行的过程中,所以只需要有可执行文件(main)就行。
此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。