executing init and fini

16,418

Solution 1

Don't do that; let your compiler and linker fill in the sections as they see fit.

Instead, mark your functions with the appropriate function attributes, so that the compiler and linker will put them in the correct sections.

For example,

static void before_main(void) __attribute__((constructor));
static void after_main(void) __attribute__((destructor));

static void before_main(void)
{
    /* This is run before main() */
}

static void after_main(void)
{
    /* This is run after main() returns (or exit() is called) */
}

You can also assign a priority (say, __attribute__((constructor (300)))), an integer between 101 and 65535, inclusive, with functions having a smaller priority number run first.

Note that for illustration, I marked the functions static. That is, the functions won't be visible outside the file scope. The functions do not need to be exported symbols to be automatically called.


For testing, I suggest saving the following in a separate file, say tructor.c:

#include <unistd.h>
#include <string.h>
#include <errno.h>

static int outfd = -1;

static void wrout(const char *const string)
{
    if (string && *string && outfd != -1) {
        const char       *p = string;
        const char *const q = string + strlen(string);

        while (p < q) {
            ssize_t n = write(outfd, p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1 || errno != EINTR)
                break;
        }
    }
}

void before_main(void) __attribute__((constructor (101)));
void before_main(void)
{
    int saved_errno = errno;

    /* This is run before main() */
    outfd = dup(STDERR_FILENO);
    wrout("Before main()\n");

    errno = saved_errno;
}

static void after_main(void) __attribute__((destructor (65535)));
static void after_main(void)
{
    int saved_errno = errno;

    /* This is run after main() returns (or exit() is called) */
    wrout("After main()\n");

    errno = saved_errno;
}

so you can compile and link it as part of any program or library. To compile it as a shared library, use e.g.

gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so

and you can interpose it into any dynamically linked command or binary using

LD_PRELOAD=./libtructor.so some-command-or-binary

The functions keep errno unchanged, although it should not matter in practice, and use the low-level write() syscall to output the messages to standard error. The initial standard error is duplicated to a new descriptor, because in many instances, the standard error itself gets closed before the last global destructor -- our destructor here -- gets run.

(Some paranoid binaries, typically security sensitive ones, close all descriptors they don't know about, so you might not see the After main() message in all cases.)

Solution 2

It is not a bug in ld but in the glibc startup code for the main executable. For shared objects the function set by the -init option is called.


This is the commit to ld adding the options -init and -fini.
The _init function of the program isn't called from file glibc-2.21/elf/dl-init.c:58 by the DT_INIT entry by the dynamic linker, but called from __libc_csu_init in file glibc-2.21/csu/elf-init.c:83 by the main executable.

That is, the function pointer in DT_INIT of the program is ignored by the startup.

If you compile with -static, fini isn't called, too.

DT_INIT and DT_FINI should definitely not be used, because they are old-style, see line 255.

The following works:

#include <stdio.h>

static void preinit(int argc, char **argv, char **envp) {
    puts(__FUNCTION__);
}

static void init(int argc, char **argv, char **envp) {
    puts(__FUNCTION__);
}

static void fini(void) {
    puts(__FUNCTION__);
}


__attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit;
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
__attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini;

int main(void) {
    puts(__FUNCTION__);
    return 0;
}

$ gcc -Wall a.c
$ ./a.out
preinit
init
main
fini
$ 
Share:
16,418

Related videos on Youtube

michas
Author by

michas

Updated on July 22, 2022

Comments

  • michas
    michas almost 2 years

    I just read about init and fini sections in ELF files and gave it a try:

    #include <stdio.h>
    int main(){
      puts("main");
      return 0;
    }
    
    void init(){
      puts("init");
    }
    void fini(){
      puts("fini");
    }
    

    If I do gcc -Wl,-init,init -Wl,-fini,fini foo.c and run the result the "init" part is not printed:

    $ ./a.out
    main
    fini
    

    Did the init part not run, or was it not able to print somehow?

    Is there a any "official" documentation about the init/fini stuff?

    man ld says:

     -init=name
         When creating an ELF executable or shared object, call
         NAME when the executable or shared object is loaded, by
         setting DT_INIT to the address of the function.  By
         default, the linker uses "_init" as the function to call.
    

    Shouldn't that mean, that it would be enough to name the init function _init? (If I do gcc complains about multiple definition.)

    • MByD
      MByD almost 9 years
      This is weird... regarding your first question - my GDB shows that the init function does not run at all.
    • nobody
      nobody almost 9 years
      It's quite possible that the unit section runs before the standard library is initialized enough for puts to work. Why don't you try something with no dependencies, like setting a global variable, to see if the unit function actually runs.
    • Hibou57
      Hibou57 about 4 years
      The way you did, works for share library, not for program which gets a default _init and a default _fini, already.
  • michas
    michas almost 9 years
    How exactly do those funktion attributes work? Does the compiler generate individual code for the init/fini sections? But as far as I understood the code is linked from a different file (crti.o).
  • michas
    michas almost 9 years
    In this case it would be a bug in either the man page or ld. Or is there a good reason to ignore it in this case?
  • Iwillnotexist Idonotexist
    Iwillnotexist Idonotexist almost 9 years
    @michas Fundamentally, those attributes expose within the C language exactly the same mechanism that allows C++ global objects to be constructed at initialization time. Both C++ constructors and __attribute__((constructor)) -annotated C functions are simply code that is called "before main", so that (in the former case) the objects are ready to use by the time main begins.
  • Nominal Animal
    Nominal Animal almost 9 years
    @michas: Yes, crti.o provides the code the GNU C library needs in the ELF _init and _fini sections. It depends on the specific ABI used (x86, x86-64, ARM variants, etc.) exactly how the marked functions get called. On x86-64, for example, I believe the function addresses are listed in the .init_array and .fini_array sections instead (__frame_dummy_init_array_entry and ` __do_global_dtors_aux_fini_array_entry` symbols, respectively).
  • MByD
    MByD almost 9 years
    @NominalAnimal - I agree that constructor/destructor attributes should be used, but can you explain why -init did not work? (just to satisfy my curiosity... :) )
  • MByD
    MByD almost 9 years
    @4566976 - any documentation for this?
  • Nominal Animal
    Nominal Animal almost 9 years
    @MByD: As 4566976 wrote, ld ignores the -init option completely. I just verified it with GCC 4.8.4 and binutils 2.24.
  • Nominal Animal
    Nominal Animal almost 9 years
    @MByD: Use readelf -d binary to list the dynamic symbols in a binary. If linker option -init did anything, you'd see the address for the INIT symbol in the above list differ (point to the named symbol, instead of the C library _init function). It does not, on the aforementioned versions, on x86-64. ld option -fini changes the address of the dynamic symbol FINI. This means that the _fini() from crti.o gets replaced by your own function. (Which is okay on x86-64, because _fini() doesn't really do anything on that architecture.)
  • michas
    michas almost 9 years
    ld does not complain about an unknown fini name, but fini nevertheless works.
  • michas
    michas almost 9 years
    The init section is run! You can verify by adding a bit of assembler.
  • 4566976
    4566976 almost 9 years
    @michas Yes, but the function pointer in DT_INIT of the elf executable is ignored by the dynamic linker and the clib initialization.
  • D3Hunter
    D3Hunter over 8 years
    if you have .so constructor with name init, it's not run, but when change to name other init, such as test_init, it works. Tested with gcc 4.1.2
  • Hibou57
    Hibou57 about 4 years
    -init and -fini ld options works, but this prevent the methods from being static.