当前位置: 首页 > 科技观察

Linux中追踪[库函数]调用的三种[插桩]技巧

时间:2023-03-18 23:11:44 科技观察

什么是插桩?在稍微大一点的代码(C语言)中,调用第三方动态库中的函数来完成一些功能,是很常见的工作场景。假设现在有一个任务:你需要在调用某个动态库中的某个函数前后做一些额外的处理工作。这样的需求一般称为:instrumentation,即针对一个指定的目标函数,新建一个wrapper函数来完成一些额外的功能。在包装函数中调用真正的目标函数,但在调用之前或之后,你可以做一些额外的事情。比如:统计函数调用次数,验证函数的入参是否合法等等。programinstrumentation的官方定义可以看【百度百科】中的描述:Programinstrumentation最早是由J.C.Huang教授提出的。就是在保证被测程序原有逻辑完整性的基础上,在程序中插入一些探针(也称“探针”),本质上是一段用于信息收集的代码段,可以是赋值语句,也可以是收集覆盖信息。函数调用)。通过探针的执行,抛出程序运行的特征数据,通过对这些数据的分析,可以得到程序的控制流和数据流信息,进而得到逻辑覆盖率等动态信息,从而达到测试的目的。根据插入探针的时间,可分为目标代码插桩和源代码插桩。在这篇文章中,我们一起来探讨一下:在Linux环境下的C语言开发中,可以使用哪些方法来实现存根功能。插入示例代码分析示例代码很简单:├──app.c└──lib├──rd3.h└──librd3.so假设动态库librd3.so是第三方提供的,有一个其中的函数:intrd3_func(int,int);。//lib/rd3.h#ifndef_RD3_H_#define_RD3_H_externintrd3_func(int,int);#endif在app.c中调用动态库中的这个函数:app.c代码如下:#include#include#include"rd3.h"intmain(intargc,char*argv[]){intresult=rd3_func(1,1);printf("result=%d\n",result);return0;}编译:$gcc-oappapp.c-I./lib-L./lib-lrd3-Wl,--rpath=./lib-L./lib:指定编译时,在lib目录下搜索库文件。-Wl,--rpath=./lib:指定执行时在lib目录下搜索库文件。生成可执行程序:app,执行:$./appresult=3示例代码足够简单,堪称helloworld的兄弟版!编译阶段对检测函数的基本要求是:对原始文件(app.c)进行附加修改。因为在app.c文件中,已经包含了“rd3.h”,调用了里面的rd3_func(int,int)函数。所以我们需要新建一个伪造的“rd3.h”提供给app.c,并将函数rd3_func(int,int)“重定向”到一个包装函数,然后在包装函数中调用真正的目标函数,如下图所示:“重定向”功能:可以使用宏来实现。包装函数:新建一个C文件,在这个文件中,需要#include"lib/rd3.h",然后调用真正的目标文件。完整的文件结构如下:├──app.c├──lib│├──librd3.so│└──rd3.h├──rd3.h└──rd3_wrap.c最后两个文件是新建的创建:rd3.h,rd3_wrap.c,它们的内容如下://rd3.h#ifndef_LIB_WRAP_H_#define_LIB_WRAP_H_//函数“redirects”,使得wrap_rd3_func可以在app.c中调用#definerd3_func(a,b)wrap_rd3_func(a,b)//函数声明externintwrap_rd3_func(int,int);#endif//rd3_wrap.c#include#include//真正的目标函数#include"lib/rd3.h"//包装函数,由app.c调用intwrap_rd3_func(inta,intb){//在调用目标函数之前,做一些处理printf("beforecallrd3_func.dosomething...\n");//调用目标函数intc=rd3_func(a,b);//调用目标函数后,做一些处理I./-L./lib-Wl,--rpath=./lib-oappapp.crd3_wrap.clrd3头文件的搜索路径不能错:必须在th中搜索rd3.he当前目录。在这种情况下,#在app.c中的include"rd3.h"找到了新添加的头文件rd3.h。所以在编译命令中,第一个选项是-I./,意思是在当前目录下搜索头文件。另外,由于#include"lib/rd3.h"用于在rd3_wrap.c文件中包含库中的头文件,所以在编译命令中不需要指定lib目录来查找头文件。编译可执行程序app,执行:$./appbeforecallrd3_func.dosomething...aftercallrd3_func.dosomething...result=3完美!链接阶段检测Linux系统中的链接器功能非常强大,它提供了一个选项:--wrapf,可以在链接阶段进行检测。这个选项的作用是告诉链接器,遇到f符号就解析成__wrap_f,遇到__real_f符号就解析成f,刚好是一对!我们可以利用这个属性新建一个文件rd3_wrap.c,并定义一个函数__wrap_rd3_func(int,int),在这个函数中调用__real_rd3_func函数。只要在编译选项中加上-Wl,--wrap,rd3_func,编译器就会:将app.c中的rd3_func符号解析成__wrap_rd3_func来调用wrapper函数;转换rd3_wrap.c中的__real_rd3_func符号,解析成rd3_func调用真正的函数。这些符号的转换是由链接器自动完成的!按照这个思路,我们一起来测试一下。文件目录结构如下:.├──app.c├──lib│├──librd3.so│└──rd3.h├──rd3_wrap.c└──rd3_wrap.hrd3_wrap.h被引用app.c内容如下:#ifndef_RD3_WRAP_H_#define_RD3_WRAP_H_externint__wrap_rd3_func(int,int);#endifrd3_wrap.c的内容如下:#include#include#include"rd3_wrap.h"//这里不能直接喝lib/rd3.h中的函数都没有了,并且链接器必须完成分析。externint__real_rd3_func(int,int);//包装函数int__wrap_rd3_func(inta,intb){//在调用目标函数前做一些处理printf("beforecallrd3_func.dosomething...\n");//调用目标函数,链接将被解析为rd3_func。intc=__real_rd3_func(a,b);//调用目标函数后做一些处理,因为lib/rd3.h中的函数声明是intrd3_func(int,int);没有__real前缀。编译:$gcc-I./lib-L./lib-Wl,--rpath=./lib-Wl,--wrap,rd3_func-oappapp.crd3_wrap.c-lrd3注意:这里的头文件搜索路径还是设置为-I./lib,因为这个头文件包含在app.c中。得到可执行程序app,执行:$./appbeforecallrd3_func.dosomething...beforecallrd3_func.dosomething...result=3完美!执行阶段插入到编译阶段,新创建的文件rd3_wrap.c是和app.c一起编译的,wrapper函数名是wrap_rd3_func。函数的“重定向”是通过app.c中的一个宏定义实现的:rd3_func-->wrap_rd3_func。我们也可以直接“霸王推弓”:在新建的文件rd3_wrap.c中,直接定义rd3_func函数。然后使用dlopen、dlsym系列函数在这个函数中动态打开真正的动态库,在里面找到目标文件,然后调用真正的目标函数。当然,这样的话,在编译app.c的时候,是无法连接lib/librd3.so文件的。按照这个思路继续修行!文件目录结构如下:├──app.c├──lib│├──librd3.so│└──rd3.h└──rd3_wrap.crd3_wrap.c文件内容如下(部分errors检查暂时忽略):#include#include#include//库的头文件#include"rd3.h"//函数与与目标函数相同的签名Typedefint(*pFunc)(int,int);intrd3_func(inta,intb){printf("beforecallrd3_func.dosomething...\n");//打开动态链接库void*handle=dlopen("./lib/librd3.so",RTLD_NOW);//在库中查找目标函数pFuncpf=dlsym(handle,"rd3_func");//调用目标函数intc=pf(a,b);//关闭动态库句柄dlclose(handle);printf("aftercallrd3_func.dosomething...\n");returnc;}编译打包动态库:$gcc-shared-fPIC-I./lib-olibrd3_wrap.sord3_wrap.c得到打包后的动态库:librd3_wrap。所以。编译可执行程序需要链接包装库librd3_wrap.so:$gcc-I./lib-L./-oappapp.c-lrd3_wrap-ldl得到可执行程序app,执行:$./appbeforecallrd3_func。dosomething...aftercallrd3_func.dosomething...result=3完美!本文转载自微信公众号「IOT物联网小镇」