侧边栏壁纸
  • 累计撰写 25 篇文章
  • 累计创建 11 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Linux 库打桩机制

LinJHS
2024-03-20 / 0 评论 / 0 点赞 / 31 阅读 / 5492 字 / 正在检测是否收录...

Linux 库打桩机制

前言

库打桩(library interpositioning),它允许你截获对共享库函数的调用,取而代之执行自己的代码。使用打桩机制,你可以追踪对某个特殊库函数的调用次数,验证和追踪它的输人和输出值,或者甚至把它替换成一个完全不同的实现。

下面是它的基本思想:

  • 给定一个需要打桩的目标函数,创建一个包装函数,它的原型与目标函数完全一样。
  • 使用某种特殊的打桩机制,你就可以欺骗系统调用包装函数而不是目标函数了。
  • 包装函数通常会执行它自己的逻辑,然后调用目标函数,再将目标函数的返回值传递给调用者。

发生时机:

  • 可以发生在编译时链接时或当程序被加载和执行的运行时

01 编译时打桩

举个栗子,对 mallocfree 追踪:

// int.c
#include <stdio.h>
#include <malloc.h>

int main()
{
    int *p = malloc(32);
    free(p)
    return 0;
}
// malloc.h
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)

void *mymalloc(size_t size);
void myfree(void *ptr);
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>

void *mymalloc(size_t size)
{
    void *ptr = malloc(size);
    printf("malloc(%d)=%p\n", (int)size, ptr);
    return ptr;
}

void myfree(void *ptr)
{
    free(ptr);
    printf("free(%p)\n", ptr);
}
#endif
linux> gcc -DCOMPILETIME -c mymalloc.c
linux> gcc -I. -o intc int.c mymalloc.o

其中

  • -D 代表给源文件传递一个宏 COMPILETIME
  • -c 在当前目录下生成同名目标文件
  • -I 用于指定头文件的目录,这里是为了让 C 预处理器在搜索通常的系统目录前,现在当前目录查找 malloc.h

注:因为只有 int.c 指定头文件搜索当前目录,所以 mymalloc.c 仍然使用的是库文件的 malloc

02 链接时打桩

Linux 支持用 --wrap f 标志进行链接时打桩。这个标志告诉链接器,把对符号 f 的引用解析成 __wrap_f ,还要把对符号 __real_f 的引用解析为 f

举个栗子:

// mymalloc.c
#ifdef LINKTIME
#include <stdio.h>

void *__real_malloc(size_t size); // 这个是库函数的 malloc
void __real_free(void *ptr);

void *__wrap_malloc(size_t size) // 这个是包装的 malloc
{
    void *ptr = __real_malloc(size);
    printf("malloc(%d)=%p\n", (int)size, ptr);
    return ptr;
}

void __wrap_free(void *ptr)
{
    __real_free(ptr);
    printf("free(%p)\n", ptr);
}
linux> gcc -DLINKTIME -c mymalloc.c
linux> gcc -c int.c
linux> gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o

其中

  • -Wl 后面的东西是作为参数传递给链接器 ld 的。比如:gcc -Wl,arg1,arg2

03 运行时打桩

编译时打桩需要能够访问程序的源代码,链接时打桩需要能够访问程序的可重定位对象文件。不过,有一种机制能够在运行时打桩,它只需要能够访问可执行目标文件。

这个机制基于动态链接器的 LD_PRELOAD 环境变量。如果 LD_PRELOAD 环境变量被设置为一个共享库路径名的列表(以空格或分号分隔),那么当你加载和执行一个程序,需要解析未定义的引用时,动态链接器 (LD-LINUX.SO) 会先搜索 LD_PRELOAD 库,然后才搜索任何其他的库。

有了这个机制,当你加载和执行任意可执行文件时,可以对任何共享库中的任何函数打桩,包括 libc.so

// mymalloc.c
#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void *malloc(size_t size)
{
    void *(*mallocp)(size_t size);
    char *error;

    // 获取 libc 中 malloc 的地址
    malloc = dlsym(RTLD_NEXT, "malloc");
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(1);
    }
    char *ptr = mallocp(size);
    printf("malloc(%d)=%p\n", (int)size, ptr);
    return ptr;
}

void free(void *ptr)
{
    void (*freep)(void *);
    char *error;

    // 获取 libc 中 free 的地址
    freep = dlsym(RTLD_NEXT, "free");
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(1);
    }
    freep(ptr);
    printf("free(%p)\n", ptr);
}
# 构建包含这些包装函数的共享库
linux> gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
# 编译主程序
linux> gcc -o intr int.c
# 运行(shell)
linux> LD_PRELOAD="./mymalloc.so" ./intr
# 运行(csh/tcsh)
linux> (setenv LD_PRELOAD "./mymalloc.so"; ./intr; unsetenv LD_PRELOAD)

其中

  • -shared 生成动态链接库(共享库)
  • -fpic 生成位置无关代码,这是共享库所要求的
  • -ldl 程序中使用了 dlsym()dlerror() 等函数,需要加入此编译选项

注意:也可以用 LD_PRELOAD 对任何可执行程序的库函数调用打桩


参考资料:

  1. 《深入理解计算机系统》P526
0

评论区