executing init and fini

后端 未结 2 537
星月不相逢
星月不相逢 2021-02-01 04:45

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

#include 
int main(){
  puts(\"main\");
  return 0;
}

void init(){
  put         


        
相关标签:
2条回答
  • 2021-02-01 05:24

    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
    $ 
    
    0 讨论(0)
  • 2021-02-01 05:37

    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.)

    0 讨论(0)
提交回复
热议问题