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
You could interpose _exit() and _Exit() to take a peek at the return
code and then delegate to real _exit() and _Exit() respectively.
--
Max
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
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
> 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)
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?
> 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.
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
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
Very nice, though I'm going to try Nate's fork/wait idea first as it
seems somewhat simpler.
Thanks,
Arch
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
> 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
> 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
[]
> 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
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. :)
> > 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