简单的LD_PRELOAD指南

标签:


原文 https://catonmat.net/simple-ld-preload-tutorial

在运行任何程序时,可以使用自己的函数覆盖掉C标准库函数, 比如printf以及fopen。这篇简短的文章将教你通过环境变量LD_PRELOAD来实现这一点。

我们从一个简单的C程序(prog.c)开始:

#include <stdio.h>

int main(void) {
    printf("Calling the fopen() function...\n");

    FILE *fd = fopen("test.txt","r");
    if (!fd) {
        printf("fopen() returned NULL\n");
        return 1;
    }

    printf("fopen() succeeded\n");

    return 0;
}

上面的代码简单地调用了标准库函数fopen,然后检查其返回值。现在,让我们编译并执行它:

$ ls
prog.c  test.txt

$ gcc prog.c -o prog

$ ls
prog  prog.c  test.txt

$ ./prog
Calling the fopen() function...
fopen() succeeded

如你所见,对fopen的调用成功了。

现在,我们来编写自己的fopen函数,它总是失败。代码保存到文件myfopen.c中:

#include <stdio.h>

FILE *fopen(const char *path, const char *mode) {
    printf("Always failing fopen\n");
    return NULL;
}

我们将它编译为共享库myfopen.so

gcc -Wall -fPIC -shared -o myfopen.so myfopen.c

现在,如果我们在运行之前创建的程序之前将环境变量LD_PREADD设置为共享库myfopen.so,我们将得到以下输出:

$ LD_PRELOAD=./myfopen.so ./prog
Calling the fopen() function...
Always failing fopen
fopen() returned NULL

如你所见,fopen函数被我们自己的版本取代了,但是该版本的fopen总是调用失败。

当需要调试、替换程序或库的某些部分时,这种办法很方便。

接下来我将向你展示如何从重写的函数中调用原始函数。

首先,我们来回顾一下前面使用的代码示例。我们有一个名为prog.c的程序,它调用了fopen.

现在,我们来写一个叫做myfopen.c的共享库,它重写了在prog.c中调用的fopen函数,并且会调用C标准库中原始的fopen函数:

#define _GNU_SOURCE

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

FILE *fopen(const char *path, const char *mode) {
    printf("In our own fopen, opening %s\n", path);

    FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    return (*original_fopen)(path, mode);
}

该共享库导出了fopen函数,该函数会打印文件路径,然后使用dlsym函数以及伪句柄RTLD_NEXT来查找原始的fopen函数。

我们必须定义_GNU_SOURCE宏才能从<dlfcn.h>中获得RTLD_NEXT的定义。RTLD_NEXT的作用是从搜索路径中位于当前库之后的下一个库中查找指定的函数。

我们可以如下所示产编译这个库:

gcc -Wall -fPIC -shared -o myfopen.so myfopen.c -ldl

我们预加载它并运行prog,会得到以下输出:

$ LD_PRELOAD=./myfopen.so ./prog
Calling the fopen() function...
In our own fopen, opening test.txt
fopen() succeeded

它会打印正在打开的文件,然后成功地打开它。

当你需要修改程序的某一部分的工作方式或者执行一些高级调试时,这非常有用。