26

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

// 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() 来退出。

然而:

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

4

3 回答 3

32

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

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

不仅如此ls,大多数 coreutils 都这样做。不幸的是,我不知道他们为什么喜欢关闭它的确切原因。

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

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

于 2014-05-27T16:35:26.180 回答
9

如果程序通过_exit(POSIX) 或_Exit(C99) 或异常程序终止(abort、致命信号等)退出,则无法调用析构函数。我看不出有什么办法。

于 2014-05-25T00:05:22.830 回答
2

就像其他人说的那样,一个程序可能会调用 via _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)也可以通过汇编int 0x80指令或过时的_syscallX() 直接触发。如果不是来自应用程序外部(如straceor valgrind),您将如何包装它?好吧,如果您希望在您的程序中出现这种行为,我建议您放弃该LD_PRELOAD技术并开始考虑做喜欢stracevalgrind做(使用ptrace()来自另一个进程)或创建一个 Linux 内核模块来跟踪它

于 2014-05-30T19:01:39.873 回答