zoukankan      html  css  js  c++  java
  • 一文搞懂linux的库打桩

    Linux下的链接器支持一个强大的库打桩(library interpositioning),允许你拦截对系统标准库中某个目标函数的调用,取而代之执行自己的包装函数。它可以给我们带来两个好处,一是通过添加某些语句,可以追踪自己的程序对某些库函数的调用情况;二是可以在你自己的程序中,对某些库函数偷天换日,替换成一个完全不同的实现。

    打桩可以发生在编译,链接和运行的任意一个阶段,相应的代码编写和编译也有一些区别,下文将分别做一阐述:

    **需求:**假设需要在主程序myprog.c中跟踪对库函数malloc和free的使用情况。

    1.编译时打桩

    1.1 建立包装函数

    建立mymalloc.c文件,定义需要的包装函数mymalloc和myfree.

    #ifdef COMPILETIME
    #include <stdio.h>
    #include <malloc.h>
    
    //定义malloc 包装函数
    void *mymalloc(size_t size)
    {
      void *ptr = malloc(size);
      printf("malloc(%d) = %p
    ", (int)size, ptr);
      return ptr;
    }
    
    //定义free 包装函数
    void *myfree(void *ptr)
    {
      free(ptr);
      printf("free(%p) = %p
    ",  ptr);
    }
    
    #endif

    1.2 建立头文件malloc.h

    该文件向预处理器指明用mymalloc.c中的包装函数替换库里的目标函数

    #define malloc(size)  mymalloc(size)
    #define free(ptr)        myfree(ptr)
    
    void *mymalloc(size_t size);
    void *myfree(void *ptr);

    1.3 建立自己的程序文件

    建立文件myprog.c,并在其中正常调用malloc函数.

    #include <stdio.h>
    #include <malloc.h>
    
    int main(void)
    {
     int *p = malloc(32);
     free(p);
     return 0;
    }
    

    1.4 编译链接

    gcc -DCOMPILETIME -c mymalloc.c
    gcc -I. -o myprog myprog.c mymalloc.c

    -I.:指示C预处理器在搜索通常的系统目录前,先在当前目录中查找malloc.h

    1.5 运行结果

    ./myprog
    malloc(32) = 0x9ee010
    free(0x9ee010)

    2.链接时打桩

    linux利用静态链接器完成库打桩。先看它的一个命令行参数:

    • –wrap f:指示链接器把对符号f的引用解析成**__wrap_f**,把对**__real_f的引用解析成符号f**。
      此处f代表任意的库函数名或变量名

    • -Wl,option:将option传递给链接器, option中的每个逗号都要用空格来替换。

    2.1 建立包装函数

    创建mymalloc.c文件,定义需要的包装函数.

    #ifdef LIKETIME
    #include <stdio.h>
    
    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
    ", (int)size, ptr);
      return ptr;
    }
    
    //定义free 包装函数
    void *__wrap_free(void *ptr)
    {
      __real_free(ptr);     //调用标准库里的free
      printf("free(%p) = %p
    ",  ptr);
    }
    
    #endif

    2.2 建立自己的程序文件

    建立文件myprog.c,并在其中正常调用malloc函数.

    #include <stdio.h>
    
    int main(void)
    {
     int *p = malloc(32);
     free(p);
     return 0;
    }
    

    2.3 编译链接

    gcc -DLINKTIME -c mymalloc.c
    gcc  -c myprog.c
    gcc -Wl,--wrap,malloc  -Wl,--wrap,free -o myprog myprog.o mymalloc.o
    
    • -Wl,option:将option传递给链接器。 option中的每个逗号都要用空格来替换, 所以-Wl,--wrap,malloc意味着把--wrap malloc传递给链接器。

    2.4 运行结果

    ./myprog
    malloc(32) = 0x18cf010
    free(0x18cf010)
    

    3 运行时打桩

    编译时打桩需要访问程序的源代码,而链接时需要访问可重定位目标文件。那有没有一种办法让仅仅访问可执行目标就能达到同样的目的呢?我们可以利用基于动态链接器的LD_PRELOAD环境变量来实现。

    当你将LD_PRELOAD环境变量设置为一个共享路径名的列表(以空格或分号分开),那么在运行一个程序时,动态链接器(LD-LINUX.SO)会先搜索列表里的库,然后才搜素系统其它库。

    利用这个原理,你可以对任何共享库中的任何函数打桩,包括libc.so。

    3.1 建立包装函数文件

    下面建立mymalloc.c文件,其中定义了malloc和free的包装函数。每个包装函数中,利用dlsym调用libc中的标准函数。

    #ifdef RUNTIME
    #define  _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <dlfcn.h>
    
    //定义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
    ", (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)
    ",  ptr);
     }
    
    #endif

    3.2 建立自己的程序文件

    建立文件myprog.c,并在其中正常调用malloc函数.

    #include <stdio.h>
    
    int main(void)
    {
     int *p = malloc(32);
     free(p);
     return 0;
    }
    

    3.3 编译包含包装函数的共享库

      gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
    • -fpic:(position independent code)指示生成位置无关的目标文件;编译共享库时该参数为必选!
    • -DRUNTIME:在命令行中定义宏定义RUNTIME,为了与文件头部ifdef RUNTIME呼应。
    • -shared:指示编译器生成一个共享目标文件。
    • -ldl:指示链接器链接到libdl.so 共享库。

    3.4 编译与运行主程序

      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)
    

    4. 拓展

    GNU binutils包有许多实用的工具特别有帮助,而且可以运行在每一个Linux平台上。

    • AR: 创建静态库,插入/删除/列出和提取成员函数。
    • STRINGS:列出目标文件中所有可打印字符串;
    • STRIP:从目标文件中删除符号表信息;
    • NM: 列出目标文件中符号表中定义的符号;
    • SIZE:列出目标文件中各段的大小;
    • READELF:显示目标文件的完整结构,包括ELF头中编码的所有信息。包含了NM和SIZE的作用;
    • OBJDUMP:显示目标文件中所有的信息,最大的左右是反汇编.text段中的二进制指令成汇编指令。
    • LDD:列出可执行文件运行时需要的所有共享库。

    获取更多知识,请点击关注:
    嵌入式Linux&ARM
    CSDN博客
    简书博客


  • 相关阅读:
    Time Zone 【模拟时区转换】(HDU暑假2018多校第一场)
    HDU 1281 棋盘游戏 【二分图最大匹配】
    Codeforces Round #527 (Div. 3) F. Tree with Maximum Cost 【DFS换根 || 树形dp】
    Codeforces Round #527 (Div. 3) D2. Great Vova Wall (Version 2) 【思维】
    Codeforces Round #527 (Div. 3) D1. Great Vova Wall (Version 1) 【思维】
    Codeforces Round #528 (Div. 2, based on Technocup 2019 Elimination Round 4) C. Connect Three 【模拟】
    Avito Cool Challenge 2018 E. Missing Numbers 【枚举】
    Avito Cool Challenge 2018 C. Colorful Bricks 【排列组合】
    005 如何分析问题框架
    004 如何定义和澄清问题
  • 原文地址:https://www.cnblogs.com/leon1124/p/14039694.html
Copyright © 2011-2022 走看看