![](/img/trans.png)
[英]mmap() with LD_PRELOAD and boost::interprocess does not work
[英]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() 退出。
然而:
有没有办法在预加载的程序退出时强制调用给定的函数?
ls
有atexit (close_stdout);
作为它的初始化代码。 完成后,它会关闭 stdout(即close(1)
),因此您的cout
、 printf
或write(1, ...
操作不会打印任何内容。这并不意味着不会调用析构函数。您可以通过以下方式验证这一点例如在析构函数中创建一个新文件。
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c#n1285这里是 GNU coreutils ls 中的行。
不仅仅是ls
,大多数 coreutils 都这样做。 不幸的是,我不知道确切的原因,他们更愿意将其关闭。
关于如何找到它(或至少我做了什么)的旁注 - 可能有助于下次或没有源代码访问的程序:
析构函数消息用/bin/true
打印(我能想到的最简单的程序),但不是用ls
或df
打印的。 我从strace /bin/true
和strace /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()
宏直接触发。 如果不是从应用程序外部(如strace
或valgrind
),你如何包装它? 好吧,如果你希望在你的程序中出现这种行为,我建议你放弃LD_PRELOAD
技术并开始考虑像strace
和valgrind
那样做(使用另一个进程的ptrace()
)或创建一个 Linux 内核模块来跟踪它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.