繁体   English   中英

LD_PRELOAD 未按预期工作

[英]LD_PRELOAD does not work as expected

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

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

然而:

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

lsatexit (close_stdout); 作为它的初始化代码。 完成后,它会关闭 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并比较了最新的系统调用。 它显示了ls close(1)close(2) ,但没有显示true 在那之后事情开始变得有意义了,我只需要验证析构函数是否被调用。

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

就像其他人说的那样,程序可能会通过_exit()_Exit()abort() _Exit()而您的析构函数甚至不会注意到。 要解决这些情况,您可以通过编写如下示例的包装器来覆盖这些函数:

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);
}

但这并不能解决程序在您的库不知情的情况下逃逸的所有可能性,因为这些并不是应用程序可能拥有的唯一退出点。 它还可以通过syscall()函数触发exit系统调用,为了避免它,您也必须包装它。

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

您必须包装的另一个系统调用是execve()

无论如何,系统调用(如_exit )也可以通过assembly int 0x80指令或过时的_syscallX()直接触发。 如果不是从应用程序外部(如stracevalgrind ),你如何包装它? 好吧,如果你希望在你的程序中出现这种行为,我建议你放弃LD_PRELOAD技术并开始考虑像stracevalgrind那样做(使用另一个进程的ptrace() )或创建一个 Linux 内核模块来跟踪它

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM