如何截获linux的库函数--库打桩机制

linux中的库打桩机制

库打桩(library interpositioning):简单的说就是要自己的函数替换库中相应的函数。比如我们想知道程序总共调用了多少次malloc().在程序已经写好的情况下,库打桩测试是一个很好的方式。使用打桩机制,可以追踪某个特殊库函数的调用次数、验证并追踪其输入输出,甚至把它替换成一个完全不同的实现。在程序编译、链接、运行等过程都可使用库打桩机制。下面将一一介绍。

编译时打桩

//建立三个文件 main.c mymalloc.c malloc.h,其中mymalloc.c是我们的打桩程序.malloc.h用于替换系统路径中的malloc.h,使编译器用mymalloc()代替main()中的malloc。free()同理。程序如下:
//-------------------main.c文件内容
#include <stdio.h>
#include <malloc.h>

int main(void)
{
    int *p = malloc(32);
    free(p);
    return 0;
}

//-------------------mymalloc.c文件内容
#ifdef COMPILETIME

#include <stdio.h>
#include <malloc.h>
#include <stdint.h>

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 <stdint.h>

#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)

void *mymalloc(uint16_t size);
void  myfree(void *ptr);

编译时打桩步骤:

  1. 使用命令gcc -DCOMPILETIME -c mymalloc.c生成可重定位文件mymalloc.o。用于后面的链接。
    1. -DCOMPILETIME:定义COMPILETIME宏,在mymalloc.c程序使用此宏作为开关。
  2. 使用命令gcc -I. -o main main.c mymalloc.o编译、并连接生成可执行文件main.
    1. -I.:I选项的本身含义是指定头文件的路径,gcc在预处理时首先在此目录下查找。-I.表示添加当前目录为第一个头文件搜索路径。那么gcc在预处理main.c时就首先找到了我们上边编写的malloc.h(而不是系统提供的malloc.h),然后gcc根据我们malloc.h中的宏定义将main.c中的malloc()函数替换成了mymalloc()函数。这样我们就达到了偷梁换柱的目的。这也是编译时打桩的核心。
  3. 使用命令./main运行程序。控制台打印相应信息。说明我们成功用mymalloc()函数替换了系统的malloc()函数。在mymalloc()函数中我们可以实现我们所要的任何逻辑。

总结:编译时打桩就是首先编写我们自己的打桩函数,然后编写和系统.h文件名称一样的.h文件。在此.h文件中使用宏定义将系统函数替换为我们自己编写的函数。最后在链接时使用-I指定我们编写的头文件路径。

从上面的打桩步骤我们可以看到,编译时打桩的行为发生在预处理的过程中,所以必须要有源代码(main.c)才行。

链接时打桩

链接时打桩程序如下,其中main.c的内容和上面一致,用不到上面的malloc.h,mymalloc.c文件内容如下:

//-------------------mymalloc.c文件内容
#ifdef LINKTIME

#include <stdio.h>
#include <stdint.h>

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_两个前缀。它是链接时打桩的核心。在进行链接时,

  1. gcc会将选项__wrap_ f中的f替换成__wrap_f.如指定选项为__wrap_ malloc,则函数malloc()被替换为了__wrap_malloc().
  2. 同时gcc会将选项__real_ f中的f替换成f。如指定选项为__real_ malloc,则函数__real_malloc()被替换为了malloc().

使用编译器上面的两个属性我们就很容易先将系统的malloc()通过选项__wrap_ malloc替换成__wrap_malloc()函数。而将我们定义的__wrap_malloc()函数替换成malloc()函数。这样就成功达到偷梁换柱的目的。

下面是链接时打桩步骤:

  1. gcc -DLINKTIME -c mymalloc.c使用此命令生成mymalloc.o。
    1. -DLINKTIME:定义LINKTIME宏,在mymalloc.c程序使用此宏作为开关。
  2. gcc -c main.c使用此命令生成main.o。
  3. gcc -Wl,--wrap,malloc -Wl,--wrap,free -o main main.o mymalloc.o使用此命令生成可执行文件。
    1. -WL:此选项告诉编译器将后面的参数传递给链接器,其中,换成空格。如-Wl,--wrap,malloc表示将--wrap malloc传给编译器。

从上面的步骤可以看出,链接时打桩的行为发生在链接的过程中,所以必须要有可重定位文件(main.o)才行。

运行时打桩

链接时打桩程序如下,其中main.c的内容和上面一致,用不到上面的malloc.h,mymalloc.c文件内容如下:

//-------------------mymalloc.c文件内容
#ifdef RUNTIME

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

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。

运行时打桩步骤如下:

  1. gcc -DRUNTIME -shared -fpic -o libmymalloc.so mymalloc.c -ldl使用此命令生成动态库libmymalloc.so。
  2. LD_PRELOAD="./libmymalloc.so" ./main。使用此命令运行程序。
    1. LD_PRELOAD:是个环境变量,用于动态库的加载,动态库加载的优先级最高。它是运行时打桩的核心。通过此环境变量让其先加载自定义的库。那么main.c中malloc()函数也就指向了我们库中的malloc()。也达到了偷梁换柱的目的。

注意:上面的程序中不能使用printf()。原因是printf()中也调用了malloc(),这样就陷入了无线循环。具体原因可以参看此博客http://www.voidcn.com/article/p-mbajvzdw-bmt.html

从上面的步骤可以看出,运行时打桩的行为发生在程序运行的过程中,所以只需要有可执行文件(main)就行。

关于技术交流

此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。二维码

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theboynoName

感谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值