Consider the following library which can be preloaded before any program execution:
// g++ -std=c++11 -shared -fPIC preload.cpp -o preload.so
// LD_PRELOAD=.
If the program exits via _exit
(POSIX) or _Exit
(C99) or abnormal program termination (abort
, fatal signals, etc.) then there's no way destructors could be called. I don't see any way around this.
ls
has atexit (close_stdout);
as its initialisation code. When it finishes, it closes stdout (i.e. close(1)
), so your cout
, printf
or write(1, ...
operations will not print anything. It doesn't mean destructor isn't called. You can verify this by e.g. creating a new file in your destructor.
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c#n1285 here is the line in GNU coreutils ls.
It is not just ls
, most of coreutils do that. Unfortunately, I don't know exact reason why they prefer to close it.
Side note on how this could be found (or at least what I did) - may help next time or with program with no source code access:
Destructor message is printed with /bin/true
(simplest program I could think of), but isn't printed with ls
or df
. I started with strace /bin/true
and strace /bin/ls
and compared latest system calls. It shown close(1)
and close(2)
for ls
, but none for true
. After that things started to make sense and I just had to verify that destructor is called.
Like others said, a program might call via _exit()
, _Exit()
or abort()
and your destructors won't even notice. To solve these cases you could override these functions by just writing a wrapper like the following the example below:
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);
}
But this wouldn't solve all the possibilities of a program escaping without your library's knowledge, because those are not the only exit points an application could have. It could also trigger the exit
system call via the syscall()
function and to avoid it you would have to wrap it too.
Another way a program could exit is by receiving an unhandled signal, so you should also handle (or wrap?) all signals that could trigger the death of a program. Read the signal(2)
man page for more information but please be aware that signals like SIGKILL
(9) cannot be handled and an application could destroy itself by calling kill()
. With that being said and unless you don't expect to handle insane applications written by crazy monkeys you should wrap kill()
too.
Another system call you'd have to wrap is execve()
.
Anyway, a system call (like _exit
) could also be triggered directly via an assembly int 0x80 instruction or the obsolete _syscallX()
macro. How'd you wrap it if not from outside the application (like strace
or valgrind
)? Well, if you expect this kind of behaviour in you programs I suggest you drop the LD_PRELOAD
technique and start thinking about doing like strace
and valgrind
do (using ptrace()
from another process) or creating a Linux kernel module to trace it.