Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Is it possible to determine exit code in library destructor?

2 views
Skip to first unread message

Arch Stanton

unread,
Oct 1, 2008, 11:02:16 AM10/1/08
to
I have an interesting problem. I'm writing a shared library which is
inserted into an alien process via LD_PRELOAD. I have almost everything
working beautifully but the library needs to get access to the exit code
before quitting. Its destructor function is definitely called after
exit() so the value must be around someplace but I do not know a way to
get it. Any ideas?

This is for Linux and Solaris. A simple test case using gcc is below.
Currently it reports "0" for the exit code but it needs to give the real
value.

Thanks,
Arch Stanton

% cat ldlab.c
---------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

static void ldlab_init(void) __attribute__ ((constructor));
static void ldlab_fini(void) __attribute__ ((destructor));

static void
ldlab_init(void)
{
write(STDERR_FILENO, "= Starting\n", 11);
}

static void
ldlab_fini(void)
{
fprintf(stderr, "= Ending with exit code %d\n", 0); // PROBLEM!
}
---------------------------------------

% gcc -g -W -Wall -fpic -shared -o ldlab.so ldl>

% LD_PRELOAD=./ldlab.so date
= Starting
Wed Oct 1 10:50:54 EDT 2008
= Ending with exit code 0

Maxim Yegorushkin

unread,
Oct 1, 2008, 11:57:12 AM10/1/08
to
On Oct 1, 4:02 pm, Arch Stanton <unkn...@sad.hill> wrote:
> I have an interesting problem. I'm writing a shared library which is
> inserted into an alien process via LD_PRELOAD. I have almost everything
> working beautifully but the library needs to get access to the exit code
> before quitting. Its destructor function is definitely called after
> exit() so the value must be around someplace but I do not know a way to
> get it. Any ideas?

You could interpose _exit() and _Exit() to take a peek at the return
code and then delegate to real _exit() and _Exit() respectively.

--
Max

Arch Stanton

unread,
Oct 1, 2008, 12:47:21 PM10/1/08
to
Maxim Yegorushkin wrote:
> You could interpose _exit() and _Exit() to take a peek at the return
> code and then delegate to real _exit() and _Exit() respectively.

Thanks. I should have mentioned that I actually tried that first. The
problem is the case where the program never makes an explicit exit call
but just returns from main(), e.g.:

int main(void) { return 0; )

In this case the call to _exit() seems to be made within the C library
and I don't know how to interpose on it. So I guess an alternate version
of my question would be: does anybody know how to interpose on an
intra-library call from another library?

Thanks,
Arch Stanton

Bernd Strieder

unread,
Oct 1, 2008, 1:33:15 PM10/1/08
to
Hello,

Arch Stanton wrote:

I'd try interposing _exit and main? A intra-library call might even be
inlined, so no chance to interpose.

Bernd Strieder

Huibert Bol

unread,
Oct 1, 2008, 1:58:31 PM10/1/08
to
Arch Stanton wrote:

> I have an interesting problem. I'm writing a shared library which is
> inserted into an alien process via LD_PRELOAD. I have almost everything
> working beautifully but the library needs to get access to the exit code
> before quitting. Its destructor function is definitely called after
> exit() so the value must be around someplace but I do not know a way to
> get it. Any ideas?

This works under Linux. I had to capture stdout, because date(1) from
the coreutils closes stdout before returning from main.

$ cat ldlab.c
#include <stdlib.h>
#include <stdio.h>

static void ldlab_init(void) __attribute__((constructor));
static FILE *stdout_capture;

static void
exit_handler(int exit_code, void *ignore)
{
fprintf(stdout_capture, "exit code = %d\n", exit_code);
}

static void
ldlab_init(void)
{
stdout_capture = fopen("/dev/stdout", "w");
on_exit(exit_handler, 0);
}

$ gcc -shared -o ldlab.so ldlab.c
huib@leela:~/tttt> LD_PRELOAD=./ldlab.so date
wo okt 1 19:56:07 CEST 2008
exit code = 0
$ LD_PRELOAD=./t3.so date -onzin
date: ongeldige optie -- o
Probeer `date --help' voor meer informatie.
exit code = 1

--
Huibert
"Hey! HEY! Curious cat, here!" -- Krosp I (GG)

William Pursell

unread,
Oct 1, 2008, 2:06:59 PM10/1/08
to
On 1 Oct, 16:02, Arch Stanton <unkn...@sad.hill> wrote:
> I have an interesting problem. I'm writing a shared library which is
> inserted into an alien process via LD_PRELOAD. I have almost everything
> working beautifully but the library needs to get access to the exit code
> before quitting. Its destructor function is definitely called after
> exit() so the value must be around someplace but I do not know a way to
> get it. Any ideas?
>

I haven't had a chance to try it yet, but what about calling main()
directly from your constructor, storing the return
value, and then calling exit()? (I suspect there are
some issues with doing that, but it might be an
interesting thing to experiment with.) Is there a better
way to avoid main being called after the constructor?
Will calling exit() accomplish that cleanly?

Nate Eldredge

unread,
Oct 1, 2008, 5:40:42 PM10/1/08
to
Arch Stanton <unk...@sad.hill> writes:

> I have an interesting problem. I'm writing a shared library which is
> inserted into an alien process via LD_PRELOAD. I have almost everything
> working beautifully but the library needs to get access to the exit code
> before quitting. Its destructor function is definitely called after
> exit() so the value must be around someplace but I do not know a way to
> get it. Any ideas?

It seems like any approach is likely to be a hack. One idea might be
to have your destructor function fork(). The child returns, and the
parent wait()s in order to see what the exit code is.

Arch Stanton

unread,
Oct 1, 2008, 6:58:35 PM10/1/08
to

Wow! You might call that a hack. I'd call it the most twisted^Wbrilliant
idea I've seen in a month. I just tried it and it works perfectly. Best
of all, no kernel hackery, no assembly code to look in some hardwired
register, no elevated privileges, just standard, elegant, portable Unix
semantics. Thanks!

Arch

Arch Stanton

unread,
Oct 1, 2008, 7:00:49 PM10/1/08
to

You know, that's an very cute idea and it might be made to work, though
I think you'll agree Nate Eldredge's idea is less intrusive. But this is
another one I never thought of. Thanks.

Arch

Arch Stanton

unread,
Oct 1, 2008, 7:03:02 PM10/1/08
to
Huibert Bol wrote:
> Arch Stanton wrote:
>
>> I have an interesting problem. I'm writing a shared library which is
>> inserted into an alien process via LD_PRELOAD. I have almost everything
>> working beautifully but the library needs to get access to the exit code
>> before quitting. Its destructor function is definitely called after
>> exit() so the value must be around someplace but I do not know a way to
>> get it. Any ideas?
>
> This works under Linux. I had to capture stdout, because date(1) from
> the coreutils closes stdout before returning from main.

Very nice, though I'm going to try Nate's fork/wait idea first as it
seems somewhat simpler.

Thanks,
Arch

Arch Stanton

unread,
Oct 1, 2008, 7:07:35 PM10/1/08
to

As I think further there are two, potentially solvable, problems with
this one:

1. The library constructor has no direct access to argv/argc. These can
often be found via the traditional hack of going to the environ pointer
and working back but it might not be too robust to depend on that.

2. The runtime linker ensures that main() is not called until all shared
libraries are loaded and initialized. If I call it myself I have to find
some way to make sure all that setup work is done, i.e. to be sure my
library is loaded last. And there may be other initializations between
library loading and main(), I haven't looked.

Arch

David Schwartz

unread,
Oct 2, 2008, 2:28:51 AM10/2/08
to
On Oct 1, 2:40 pm, Nate Eldredge <n...@vulcan.lan> wrote:

> It seems like any approach is likely to be a hack.  One idea might be
> to have your destructor function fork().  The child returns, and the
> parent wait()s in order to see what the exit code is.

This is a particularly dangerous hack, changing the program semantics
so that it terminates twice instead of once. What happens if the next
destructor is not idempotent?

DS

David Schwartz

unread,
Oct 2, 2008, 2:31:11 AM10/2/08
to
On Oct 1, 3:58 pm, Arch Stanton <unkn...@sad.hill> wrote:

> Wow! You might call that a hack. I'd call it the most twisted^Wbrilliant
> idea I've seen in a month. I just tried it and it works perfectly. Best
> of all, no kernel hackery, no assembly code to look in some hardwired
> register, no elevated privileges, just standard, elegant, portable Unix
> semantics. Thanks!

Suppose the next destructor does the following:
1) Delete foo.out
2) Rename foo.out.tmp to foo.out

Since you make the program end twice instead of once, step '2' above
will fail the second time, and the program's output will be lost.

This is an artificial example, of course, but the point is that you
can't assume that all subsequent destructors will be idempotent.
(Suppose the next destructor appends something to a file or makes a
log entry.)

DS

Maxim Yegorushkin

unread,
Oct 2, 2008, 5:20:02 AM10/2/08
to
On Oct 1, 6:58 pm, Huibert Bol <huibert....@quicknet.nl> wrote:
> Arch Stanton wrote:
> > I have an interesting problem. I'm writing a shared library which is
> > inserted into an alien process via LD_PRELOAD. I have almost everything
> > working beautifully but the library needs to get access to the exit code
> > before quitting. Its destructor function is definitely called after
> > exit() so the value must be around someplace but I do not know a way to
> > get it. Any ideas?
>
> This works under Linux.  I had to capture stdout, because date(1) from
> the coreutils closes stdout before returning from main.

[]

>   on_exit(exit_handler, 0);

[]

The man page says "The on_exit() function registers the given function
to be called *at normal process termination*, whether via exit(3) or
via return from the program's main()." Which means if the process
terminates due to a signal this handler is not called.

--
Max

Nate Eldredge

unread,
Oct 2, 2008, 1:32:38 PM10/2/08
to
David Schwartz <dav...@webmaster.com> writes:

I envisioned that the parent would call _exit afterwards, so that
further destructors would run only in the child. Obviously there are
situations where this could go wrong as well.

But I certainly don't claim that this hack is in any way reliable,
only that it might have the desired effect sometimes. :)


David Schwartz

unread,
Oct 2, 2008, 2:25:42 PM10/2/08
to
On Oct 2, 10:32 am, Nate Eldredge <n...@vulcan.lan> wrote:

> > This is a particularly dangerous hack, changing the program semantics
> > so that it terminates twice instead of once. What happens if the next
> > destructor is not idempotent?

> I envisioned that the parent would call _exit afterwards, so that
> further destructors would run only in the child.  Obviously there are
> situations where this could go wrong as well.

Then you have the problem of the pid changing, locks not being
inherited, and god knows what else. It's too bad the child can't call
'_exit', but then, of course, you'd have to tell it what code to
return, defeating the entire purpose.

> But I certainly don't claim that this hack is in any way reliable,
> only that it might have the desired effect sometimes. :)

Yeah. It's dangerous to use in a library that's intended to be a
general-purpose preloaded library to affect the behavior (predictably)
or arbitrary applications.

I believe that 'on_exit' handlers *are* passed the argument to 'exit'.
Sadly, they're not well-standardized, but they are available on at
least Linux and some versions of Solaris.

DS

0 new messages