I am using a shared library with LD_PRELOAD
, and it seems that I can't call some functions from the function set with -fini=
ld
option. I am running Linux Ubuntu 20.04 on a 64-bit machine.
Here is the SSCCE:
shared.sh
:
#!/bin/bash
gcc -shared -fPIC -Wl,-init=init -Wl,-fini=fini shared.c -o shared.so
LD_PRELOAD=$PWD/shared.so whoami
shared.c
:
#include <stdio.h>
#include <unistd.h>
void init() {
printf("%s\n", __func__);
fflush(stdout);
}
void fini() {
int printed;
printed = printf("%s\n", __func__);
if (printed < 0)
sleep(2);
fflush(stdout);
}
When I call ./shared.sh
, I get
init
mark
and 2 second pause.
So it seems printf()
fails in fini()
but sleep()
works (errno
values are not specified for printf
, so I don't check it) Why and what kind of functions can I call from fini
? ld
manpage does not say anything about any restrictions.
CodePudding user response:
The initialization functions of each dynamically linked component are executed in the order in which the components are loaded. In particular, if A depends on B but B does not depend on A, then B's initialization functions run before A's. The termination functions of each dynamically linked component are executed in the order in which the components are unloaded. In particular, if A depends on B but B does not depend on A, then B's initialization functions run after A's. Generally, termination functions run in reverse order from initialization functions, but I don't know if that's true in all cases (for example when there are circular dependencies). You can find the rules in the System V ABI specification which Linux and many other Unix variants follow. Note that the rules leave some cases unspecified; they might depend on the compiler and on the standard library (possibly on the kernel, but I think for this particular topic it doesn't matter).
A shared library loaded with LD_PRELOAD
is loaded before the main executable, so its initialization functions run before the ones from libc and its termination functions run after the ones from libc. In particular, libc flushes standard streams and closes the file descriptors for the output streams. You can see this happening by tracing system calls:
$ strace env LD_PRELOAD=$PWD/shared.so whoami
…
write(1, "gilles\n", 6gilles
) = 6
close(1) = 0
close(2) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=2, tv_nsec=0}, 0x7ffc12bd2df0) = 0
exit_group(0) = ?
exited with 0
The call to clock_nanosleep
is sleep(2)
. The calls to printf
and fflush
happen just before; since stdout has been closed, they do nothing and return -1. Check the return value or use a debugger to confirm this.
Contrast with what happens if shared.so
is linked normally, rather than preloaded.
$ cat main.c
#include <stdio.h>
int main(void) {
puts("main");
return 0;
}
$ gcc -o main main.c -Wl,-rpath,. -Wl,--no-as-needed -L. -l:shared.so
$ ./main
init
main
fini
Here, since main
loads shared.so
, the shared library is initialized last and terminated first. So by the time the fini
function in shared.so
runs, libc hasn't run its termination functions and the standard streams are still available.