• 一文搞懂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博客
    简书博客


  • 相关阅读:
    标题栏外区域拖动窗体
    搜索引擎技术核心揭密
    用C#实现木马程序(转载)
    DotNet里的控件数组
    RECORDNUMBER应用之控制每页显示行数及隔行换色
    搜索引擎技术学习
    VB里面操作Excel(居然比C#强)
    第三代搜索引擎技术与P2P
    C# 用API播放声音
    如何判断ExecuteScalar()得到的结果是否有记录
  • 原文地址:https://www.cnblogs.com/leon1124/p/14039694.html
Copyright © 2020-2023  润新知