考虑以下库,可以在任何程序执行之前预先加载:
// 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()来退出。
然而:
当预加载的程序退出时,有没有办法强制调用给定的函数?
答案 0 :(得分:31)
ls
有atexit (close_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
开始,并比较了最新的系统调用。它为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()
宏直接触发。如果不是从应用程序外部(如strace
或valgrind
),你如何包装它?好吧,如果你期望在你的程序中出现这种行为,我建议你放弃LD_PRELOAD
技术并开始考虑像strace
和valgrind
那样做(使用另一个ptrace()
过程)或creating a Linux kernel module to trace it。