27

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=./preload.so <command>
#include <iostream>

struct Goodbye {
    Goodbye() {std::cout << "Hello\n";}
    ~Goodbye() {std::cout << "Goodbye!\n";}
} goodbye;

The problem is that, while the constructor of the global variable goodbye is always called, the destructor is not called for some programs, like ls:

$ LD_PRELOAD=./preload.so ls
Hello

For some other programs, the destructor is called as expected:

$ LD_PRELOAD=./preload.so man
Hello
What manual page do you want?
Goodbye!

Can you explain why the destructor is not called in the first case? EDIT: the above question has been already answered, that is a program might well use _exit(), abort() to exit.

However:

Is there a way to force a given function being called when a preloaded program exits?

BЈовић
  • 62,405
  • 41
  • 173
  • 273
Martin
  • 9,089
  • 11
  • 52
  • 87
  • [This question](http://stackoverflow.com/questions/2204608) seems to indicate that the destructor should *always* be called. Can you do some more research to narrow down what types of programs end up calling the destructor and which ones do not? – Jonathon Reinhart May 24 '14 at 23:34
  • What do you mean by "type"? I cannot seem to find a way to distinguish "good" programs from "evil" programs. Note that the problem happens also when the return code is 0 (no error, no abort()) – Martin May 24 '14 at 23:36
  • Try writing a preload module in C, using GCC's `__attribute__((constructor))` to run a function on startup; have that function use `atexit` to register a function to run on teardown. Does that behave differently? (It *shouldn't*, but it might.) – zwol May 27 '14 at 15:12
  • Also, since we are in "potential implementation bug" territory here, it would be very helpful to know the OS, and the exact version of GCC and of the C library. – zwol May 27 '14 at 15:14
  • 1
    R.. has answered your second question but *not* your first question. It is *not* possible to force a function to be called no matter what, because of `_exit` and `abort` and various other "abnormal program termination" mechanisms whose contract includes that they *don't* execute any destructors, `atexit` functions, etc. However, I find it implausible that `/bin/ls` would normally exit that way, so there is still a mystery as to why your preload module's destructor isn't getting run. – zwol May 27 '14 at 15:19
  • @Martin OK, in that case edit the output of `g++ --version` and of `/lib/ld-linux.so.2 $(find /lib -name libc.so.6 -print | head -1)` into the question. DO NOT attempt to remove any apparently irrelevant information from the output of these commands. – zwol May 27 '14 at 15:21
  • 4
    Note that you are using fairly high-level functionality here: `std::cout` is a buffering stream that is synchronized with the stdio streams. It may well be that `ls` does something at exit that disrupts this mechanism. What happens if you use `::write(2, "Goodbye\n", 8);` instead? – Simon Richter May 27 '14 at 15:27

3 Answers3

33

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.

keltar
  • 17,711
  • 2
  • 37
  • 42
  • 1
    Oho! Would you mind adding a link to the source repository so we can all see this for ourselves? – zwol May 27 '14 at 20:24
  • 7
    I did a little more digging based on your results: it appears that this is done in order to detect write failures on `stdout` before it's too late to exit unsuccessfully. Unfortunately, the definition of `close_stdout` is hiding over in gnulib, but the comments there are pretty clear: http://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/closeout.c#n83 – zwol May 28 '14 at 20:31
9

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.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 2
    Why would `/bin/ls` do that? – zwol May 27 '14 at 15:15
  • 2
    That's a good guess, but http://lingrok.org/xref/coreutils/src/ls.c ends in `exit (exit_status);` (OP specified right now he's on linux) – loreb May 27 '14 at 15:20
  • 1
    @Zack, why would `/bin/ls` *not* do that? Unless there are external side-effects required for graceful shutdown (like on-disk data structures need updating or network messages sent to say "bye") why not just `_exit`? There is no need to `free()` the last few blocks if the whole heap is about to go away. – Ben May 27 '14 at 15:51
  • 1
    @Ben I know it looks like just one character of difference, but as a matter of idiom, `_exit` is *unusual*. It's a thing you only use if you have a specific reason you need it. `exit` / return from `main` are the normal way to end a program in C and it would be surprising for a basic utility like `ls` to be abnormal in this way. Also, if `ls` used `_exit` it would need to manually flush `stdout`. – zwol May 27 '14 at 16:19
  • 1
    @Ben (Note that `exit` does *not* deallocate the `malloc` heap piece by piece.) – zwol May 27 '14 at 16:20
  • Note to editors: If you're editing an answer to add links to some of the interfaces mentioned, link to authoritative sources like the C standard or at least some sort of C documentation rather than a C++ site. – R.. GitHub STOP HELPING ICE May 31 '14 at 01:54
2

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.

Community
  • 1
  • 1
  • This is clever, but when programs call `_exit` or `abort` they do it because there's some concrete and compelling reason why destructors, `atexit` functions, and so on *should not* be run. For instance, on the child side of a `fork`, if `execve` fails, it's critical to use `_exit` so that I/O is not flushed twice. A library should not be second-guessing the application about that sort of thing. – zwol May 31 '14 at 03:39
  • ... **no**, the lesson I wanted you to draw from my comment is *not* "you need to wrap `execve` as well", it was "this is the *bad* kind of clever." – zwol Jun 03 '14 at 03:21
  • ... **no**? What do you mean by that? Do you think I don't know this is a horrible hack? It's pretty obvious it is ugly. I added `execve` to the list just because I should, not because I didn't understood what you commented. – Fernando Silveira Jun 03 '14 at 12:19
  • Downvoter: care to explain? I'm answering the last question of the OP **AND** giving a minimal and working example. – Fernando Silveira Jun 03 '14 at 12:21