Home > Net >  what can be called from -fini function of shared library?
what can be called from -fini function of shared library?

Time:11-04

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.

  • Related