LD_PRELOAD无法按预期工作

时间:2014-05-24 23:30:06

标签: c++ linux gcc ld ld-preload

考虑以下库,可以在任何程序执行之前预先加载:

// g++ -std=c++11 -shared -fPIC preload.cpp -o preload.so
// LD_PRELOAD=./preload.so <command>
#include <iostream>

struct Goodbye {
    Goodbye() {std::cout << "Hello\n";}
    ~Goodbye() {std::cout << "Goodbye!\n";}
} goodbye;

问题是,虽然总是调用全局变量goodbye的构造函数,但是没有为某些程序调用析构函数,例如ls

$ LD_PRELOAD=./preload.so ls
Hello

对于其他一些程序,析构函数按预期调用:

$ LD_PRELOAD=./preload.so man
Hello
What manual page do you want?
Goodbye!

你能解释为什么在第一种情况下没有调用析构函数吗? 编辑:上面的问题已经得到解答,那就是程序可能会使用_exit(),abort()来退出。

然而:

当预加载的程序退出时,有没有办法强制调用给定的函数?

3 个答案:

答案 0 :(得分:31)

lsatexit (close_stdout);作为初始化代码。完成后,它会关闭标准输出(即close(1)),因此您的coutprintfwrite(1, ...操作不会打印任何内容。它并不意味着析构函数不被调用。您可以通过例如验证在析构函数中创建一个新文件。

http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c#n1285这里是GNU coreutils ls。

中的一行

不只是ls,大多数coreutils都这样做。不幸的是,我不知道确切的理由为什么他们更喜欢关闭它。

关于如何找到它(或者至少我做了什么)的附注 - 可能有助于下次或没有源代码访问的程序:

析构函数消息打印有/bin/true(我能想到的最简单的程序),但不会打印lsdf。我从strace /bin/truestrace /bin/ls开始,并比较了最新的系统调用。它为close(1)显示close(2)ls,但true显示为{{1}}。之后事情开始变得有意义了,我只需要验证析构函数是否被调用。

答案 1 :(得分:9)

如果程序通过_exit(POSIX)或_Exit(C99)或异常程序终止(abort,致命信号等)退出,那么就没有办法了可以调用析构函数。我没有看到任何解决方法。

答案 2 :(得分:2)

与其他人说的一样,某个程序可能会通过_exit()_Exit()abort()拨打电话,您的析构人员甚至都不会注意到。要解决这些情况,您可以通过编写如下面示例的包装器来覆盖这些函数:

void
_exit(int status)
{
    void (*real__exit)(int) __attribute__((noreturn));
    const char *errmsg;

    /* Here you should call your "destructor" function. */
    destruct();

    (void)dlerror();
    real__exit = (void(*)(int))dlsym(RTLD_NEXT, "_exit");
    errmsg = dlerror();
    if (errmsg) {
        fprintf(stderr, "dlsym: _exit: %s\n", errmsg);
        abort();
    }

    real__exit(status);
}

但如果没有图书馆的知识,这并不能解决程序逃逸的所有可能性,因为这些不是应用程序可能拥有的唯一出口点。它还可以通过exit函数触发syscall()系统调用,并且为了避免它,你也必须将它包装起来。

程序可以退出的另一种方式是接收未处理的信号,因此您还应该处理(或包装?)可能触发程序死亡的所有信号。请阅读signal(2)手册页以获取更多信息,但请注意,SIGKILL(9)之类的信号无法处理,应用程序可能会通过调用kill()来破坏自身。说到这一点,除非你不希望处理由疯狂的猴子写的疯狂的应用程序,你也应该包裹kill()

您需要打包的另一个系统调用是execve()

无论如何,系统调用(例如_exit)也可以通过assembly int 0x80 instruction或过时的_syscallX() 直接触发。如果不是从应用程序外部(如stracevalgrind),你如何包装它?好吧,如果你期望在你的程序中出现这种行为,我建议你放弃LD_PRELOAD技术并开始考虑像stracevalgrind那样做(使用另一个ptrace()过程)或creating a Linux kernel module to trace it