rtld: Add support for preinit, init, and fini arrays
authorJohn Marino <draco@marino.st>
Sat, 11 Feb 2012 19:17:51 +0000 (20:17 +0100)
committerJohn Marino <draco@marino.st>
Sat, 11 Feb 2012 20:04:12 +0000 (21:04 +0100)
As far as I can tell, all BSDs limit their ELF executable file
initialization and termination to the .init and .fini sections.  In
contrast, Linux has additionally supported the .preinit_array,
.init_array, and .fini_array sections for over a decade through glibc.
With this commit, DragonFly becomes the first BSD to support these
arrays of function pointers.  It was tested using test cases taken from
glibc, gnu ld linker, and the gold linker.

For the main executable file, the .init_array and .fini_array sections
are handled by crt1, just like .init and .fini are.  In the case of
a statically linked binary, the .preinit_array section is also handled
by crt1.  The real-time linker handles the .init_array and .fini_array
sections for dynamically shared objects (libraries) and .preinit_array
for dynamically-linked binaries.  There are no .preinit_array sections
in the DSOs per standard.

These sections are described by the System V Application Binary Interface
http://www.sco.com/developers/gabi/latest/ch4.sheader.html#special_sections

The .init_array and .fini_array handling by rtld was reviewed by
Konstantin Belousov.

lib/csu/i386/crt1_c.c
lib/csu/x86_64/crt1.c
libexec/rtld-elf/i386/rtld_machdep.h
libexec/rtld-elf/rtld.c
libexec/rtld-elf/rtld.h
libexec/rtld-elf/x86_64/rtld_machdep.h

index a78ad5c..127a012 100644 (file)
@@ -45,6 +45,13 @@ extern void _fini(void);
 extern void _init(void);
 extern int main(int, char **, char **);
 
+extern void (*__preinit_array_start []) (int, char **, char **) __dso_hidden;
+extern void (*__preinit_array_end   []) (int, char **, char **) __dso_hidden;
+extern void (*__init_array_start    []) (int, char **, char **) __dso_hidden;
+extern void (*__init_array_end      []) (int, char **, char **) __dso_hidden;
+extern void (*__fini_array_start    []) (void) __dso_hidden;
+extern void (*__fini_array_end      []) (void) __dso_hidden;
+
 #ifdef GCRT
 extern void _mcleanup(void);
 extern void monstartup(void *, void *);
@@ -63,6 +70,7 @@ _start1(fptr cleanup, int argc, char *argv[])
 {
     char **env;
     const char *s;
+    size_t n, array_size;
 
     env = argv + argc + 1;
     environ = env;
@@ -88,12 +96,34 @@ _start1(fptr cleanup, int argc, char *argv[])
 #ifdef GCRT
     atexit(_mcleanup);
 #endif
+    /*
+     * The fini_array needs to be executed in the opposite order of its
+     * definition.  However, atexit works like a LIFO stack, so by
+     * pushing functions in array order, they will be executed in the
+     * reverse order as required.
+     */
     atexit(_fini);
+    array_size = __fini_array_end - __fini_array_start;
+    for (n = 0; n < array_size; n++)
+       atexit(*__fini_array_start [n]);
 #ifdef GCRT
     monstartup(&eprol, &etext);
 __asm__("eprol:");
 #endif
+    if (&_DYNAMIC == NULL) {
+       /*
+        * For static executables preinit happens right before init.
+        * Dynamic executable preinit arrays are handled by rtld
+        * before any DSOs are initialized.
+        */
+       array_size = __preinit_array_end - __preinit_array_start;
+       for (n = 0; n < array_size; n++)
+               (*__preinit_array_start [n])(argc, argv, env);
+    }
     _init();
+    array_size = __init_array_end - __init_array_start;
+    for (n = 0; n < array_size; n++)
+       (*__init_array_start [n])(argc, argv, env);
     exit( main(argc, argv, env) );
 }
 
index 01173c5..35f23d7 100644 (file)
@@ -48,6 +48,13 @@ extern void _init(void);
 extern int main(int, char **, char **);
 extern void _start(char **, void (*)(void));
 
+extern void (*__preinit_array_start []) (int, char **, char **) __dso_hidden;
+extern void (*__preinit_array_end   []) (int, char **, char **) __dso_hidden;
+extern void (*__init_array_start    []) (int, char **, char **) __dso_hidden;
+extern void (*__init_array_end      []) (int, char **, char **) __dso_hidden;
+extern void (*__fini_array_start    []) (void) __dso_hidden;
+extern void (*__fini_array_end      []) (void) __dso_hidden;
+
 #ifdef GCRT
 extern void _mcleanup(void);
 extern void monstartup(void *, void *);
@@ -66,6 +73,7 @@ _start(char **ap, void (*cleanup)(void))
        char **argv;
        char **env;
        const char *s;
+       size_t n, array_size;
 
        argc = *(long *)(void *)ap;
        argv = ap + 1;
@@ -93,11 +101,33 @@ _start(char **ap, void (*cleanup)(void))
 #ifdef GCRT
        atexit(_mcleanup);
 #endif
+       /*
+        * The fini_array needs to be executed in the opposite order of its
+        * definition.  However, atexit works like a LIFO stack, so by
+        * pushing functions in array order, they will be executed in the
+        * reverse order as required.
+        */
        atexit(_fini);
+       array_size = __fini_array_end - __fini_array_start;
+       for (n = 0; n < array_size; n++)
+               atexit(*__fini_array_start [n]);
 #ifdef GCRT
        monstartup(&eprol, &etext);
 #endif
+       if (&_DYNAMIC == NULL) {
+               /*
+                * For static executables preinit happens right before init.
+                * Dynamic executable preinit arrays are handled by rtld
+                * before any DSOs are initialized.
+                */
+               array_size = __preinit_array_end - __preinit_array_start;
+               for (n = 0; n < array_size; n++)
+                       (*__preinit_array_start [n])(argc, argv, env);
+       }
        _init();
+       array_size = __init_array_end - __init_array_start;
+       for (n = 0; n < array_size; n++)
+               (*__init_array_start [n])(argc, argv, env);
        exit( main(argc, argv, env) );
 }
 
index 2e47438..987b110 100644 (file)
@@ -60,6 +60,9 @@ reloc_jmpslot(Elf_Addr *where, Elf_Addr target,
 #define call_initfini_pointer(obj, target) \
        (((InitFunc)(target))())
 
+#define call_array_pointer(target, argc, argv, env) \
+       (((InitArrayFunc)(target))(argc, argv, env))
+
 #define round(size, align) \
        (((size) + (align) - 1) & ~((align) - 1))
 #define calculate_first_tls_offset(size, align) \
index b0c570c..54b63f1 100644 (file)
@@ -109,6 +109,7 @@ static void map_stacks_exec(RtldLockState *);
 static Obj_Entry *obj_from_addr(const void *);
 static void objlist_call_fini(Objlist *, Obj_Entry *, RtldLockState *);
 static void objlist_call_init(Objlist *, RtldLockState *);
+static void preinitialize_main_object (void);
 static void objlist_clear(Objlist *);
 static Objlist_Entry *objlist_find(Objlist *, const Obj_Entry *);
 static void objlist_init(Objlist *);
@@ -249,6 +250,13 @@ char *__progname;
 char **environ;
 
 /*
+ * Globals passed as arguments to .init_array and .preinit_array functions
+ */
+
+int glac;
+char **glav;
+
+/*
  * Globals to control TLS allocation.
  */
 size_t tls_last_offset;                /* Static TLS offset of last module */
@@ -396,6 +404,8 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
     __progname = obj_rtld.path;
     argv0 = argv[0] != NULL ? argv[0] : "(null)";
     environ = env;
+    glac = argc;
+    glav = argv;
 
     trust = !issetugid();
 
@@ -553,6 +563,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
     obj_loads++;
     /* Make sure we don't call the main program's init and fini functions. */
     obj_main->init = obj_main->fini = (Elf_Addr)NULL;
+    obj_main->init_array = obj_main->fini_array = (Elf_Addr)NULL;
 
     /* Initialize a fake symbol for resolving undefined weak references. */
     sym_zero.st_info = ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE);
@@ -689,6 +700,7 @@ _rtld_call_init(void)
     RtldLockState lockstate;
     Obj_Entry *obj;
 
+    preinitialize_main_object();
     wlock_acquire(rtld_bind_lock, &lockstate);
     objlist_call_init(&initlist, &lockstate);
     objlist_clear(&initlist);
@@ -1088,6 +1100,30 @@ digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath,
            obj->fini = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr);
            break;
 
+       case DT_PREINIT_ARRAY:
+           obj->preinit_array = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr);
+           break;
+
+       case DT_INIT_ARRAY:
+           obj->init_array = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr);
+           break;
+
+       case DT_FINI_ARRAY:
+           obj->fini_array = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr);
+           break;
+
+       case DT_PREINIT_ARRAYSZ:
+           obj->preinit_array_num = dynp->d_un.d_val / sizeof(Elf_Addr);
+           break;
+
+       case DT_INIT_ARRAYSZ:
+           obj->init_array_num = dynp->d_un.d_val / sizeof(Elf_Addr);
+           break;
+
+       case DT_FINI_ARRAYSZ:
+           obj->fini_array_num = dynp->d_un.d_val / sizeof(Elf_Addr);
+           break;
+
        case DT_DEBUG:
            /* XXX - not implemented yet */
            if (!early)
@@ -1623,11 +1659,12 @@ initlist_add_objects(Obj_Entry *obj, Obj_Entry **tail, Objlist *list)
        initlist_add_neededs(obj->needed, list);
 
     /* Add the object to the init list. */
-    if (obj->init != (Elf_Addr)NULL)
+    if (obj->init != (Elf_Addr)NULL || obj->init_array != (Elf_Addr)NULL)
        objlist_push_tail(list, obj);
 
     /* Add the object to the global fini list in the reverse order. */
-    if (obj->fini != (Elf_Addr)NULL && !obj->on_fini_list) {
+    if ((obj->fini != (Elf_Addr)NULL || obj->fini_array != (Elf_Addr)NULL)
+      && !obj->on_fini_list) {
        objlist_push_head(&list_fini, obj);
        obj->on_fini_list = true;
     }
@@ -1923,6 +1960,8 @@ objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate)
 {
     Objlist_Entry *elm;
     char *saved_msg;
+    Elf_Addr *fini_addr;
+    int index;
 
     assert(root == NULL || root->refcount == 1);
 
@@ -1936,10 +1975,7 @@ objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate)
            if (root != NULL && (elm->obj->refcount != 1 ||
              objlist_find(&root->dagmembers, elm->obj) == NULL))
                continue;
-           dbg("calling fini function for %s at %p", elm->obj->path,
-               (void *)elm->obj->fini);
-           LD_UTRACE(UTRACE_FINI_CALL, elm->obj, (void *)elm->obj->fini, 0, 0,
-               elm->obj->path);
+
            /* Remove object from fini list to prevent recursive invocation. */
            STAILQ_REMOVE(list, elm, Struct_Objlist_Entry, link);
            /*
@@ -1950,7 +1986,32 @@ objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate)
             * called.
             */
            lock_release(rtld_bind_lock, lockstate);
-           call_initfini_pointer(elm->obj, elm->obj->fini);
+
+           /*
+            * It is legal to have both DT_FINI and DT_FINI_ARRAY defined.  When this
+            * happens, DT_FINI_ARRAY is processed first, and it is also processed
+            * backwards.  It is possible to encounter DT_FINI_ARRAY elements with
+            * values of 0 or 1, but they need to be ignored.
+            */
+           fini_addr = (Elf_Addr *)elm->obj->fini_array;
+           if (fini_addr != (Elf_Addr)NULL) {
+               for (index = elm->obj->fini_array_num - 1; index >= 0; index--) {
+                   if (fini_addr[index] != 0 && fini_addr[index] != 1) {
+                       dbg("DSO Array: calling fini function for %s at %p",
+                           elm->obj->path, (void *)fini_addr[index]);
+                       LD_UTRACE(UTRACE_FINI_CALL, elm->obj,
+                           (void *)fini_addr[index], 0, 0, elm->obj->path);
+                       call_initfini_pointer(elm->obj, fini_addr[index]);
+                   }
+               }
+           }
+           if (elm->obj->fini != (Elf_Addr)NULL) {
+               dbg("DSO: calling fini function for %s at %p", elm->obj->path,
+                   (void *)elm->obj->fini);
+               LD_UTRACE(UTRACE_FINI_CALL, elm->obj, (void *)elm->obj->fini,
+                   0, 0, elm->obj->path);
+               call_initfini_pointer(elm->obj, elm->obj->fini);
+           }
            wlock_acquire(rtld_bind_lock, lockstate);
            /* No need to free anything if process is going down. */
            if (root != NULL)
@@ -1967,6 +2028,31 @@ objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate)
 }
 
 /*
+ * If the main program is defined with a .preinit_array section, call
+ * each function in order.  This must occur before the initialization
+ * of any shared object or the main program.
+ */
+static void
+preinitialize_main_object (void)
+{
+    Elf_Addr *init_addr;
+    int index;
+
+    init_addr = (Elf_Addr *)obj_main->preinit_array;
+    if (init_addr == (Elf_Addr)NULL)
+       return;
+
+    for (index = 0; index < obj_main->preinit_array_num; index++)
+       if (init_addr[index] != 0 && init_addr[index] != 1) {
+           dbg("Calling preinit array function for %s at %p",
+               (void *) obj_main->path, (void *)init_addr[index]);
+           LD_UTRACE(UTRACE_INIT_CALL, obj_main, (void *)init_addr[index],
+               0, 0, obj_main->path);
+           call_array_pointer(init_addr[index], glac, glav, environ);
+       }
+}
+
+/*
  * Call the initialization functions for each of the objects in
  * "list".  All of the objects are expected to have non-NULL init
  * functions.
@@ -1977,6 +2063,8 @@ objlist_call_init(Objlist *list, RtldLockState *lockstate)
     Objlist_Entry *elm;
     Obj_Entry *obj;
     char *saved_msg;
+    Elf_Addr *init_addr;
+    int index;
 
     /*
      * Clean init_scanned flag so that objects can be rechecked and
@@ -1994,10 +2082,7 @@ objlist_call_init(Objlist *list, RtldLockState *lockstate)
     STAILQ_FOREACH(elm, list, link) {
        if (elm->obj->init_done) /* Initialized early. */
            continue;
-       dbg("calling init function for %s at %p", elm->obj->path,
-           (void *)elm->obj->init);
-       LD_UTRACE(UTRACE_INIT_CALL, elm->obj, (void *)elm->obj->init, 0, 0,
-           elm->obj->path);
+
        /*
         * Race: other thread might try to use this object before current
         * one completes the initilization. Not much can be done here
@@ -2005,7 +2090,31 @@ objlist_call_init(Objlist *list, RtldLockState *lockstate)
         */
        elm->obj->init_done = true;
        lock_release(rtld_bind_lock, lockstate);
-       call_initfini_pointer(elm->obj, elm->obj->init);
+
+        /*
+         * It is legal to have both DT_INIT and DT_INIT_ARRAY defined.  When
+         * this happens, DT_INIT is processed first.  It is possible to
+         * encounter DT_INIT_ARRAY elements with values of 0 or 1, but they
+         * need to be ignored.
+         */
+         if (elm->obj->init != (Elf_Addr)NULL) {
+           dbg("DSO: calling init function for %s at %p", elm->obj->path,
+               (void *)elm->obj->init);
+           LD_UTRACE(UTRACE_INIT_CALL, elm->obj, (void *)elm->obj->init,
+               0, 0, elm->obj->path);
+           call_initfini_pointer(elm->obj, elm->obj->init);
+       }
+       init_addr = (Elf_Addr *)elm->obj->init_array;
+       if (init_addr != (Elf_Addr)NULL) {
+           for (index = 0; index < elm->obj->init_array_num; index++)
+               if (init_addr[index] != 0 && init_addr[index] != 1) {
+                   dbg("DSO Array: calling init function for %s at %p",
+                       elm->obj->path, (void *)init_addr[index]);
+                   LD_UTRACE(UTRACE_INIT_CALL, elm->obj,
+                       (void *)init_addr[index], 0, 0, elm->obj->path);
+                   call_array_pointer(init_addr[index], glac, glav, environ);
+               }
+       }
        wlock_acquire(rtld_bind_lock, lockstate);
     }
     errmsg_restore(saved_msg);
index a80702d..7678518 100644 (file)
@@ -72,6 +72,7 @@ typedef STAILQ_HEAD(Struct_Objlist, Struct_Objlist_Entry) Objlist;
 
 /* Types of init and fini functions */
 typedef void (*InitFunc)(void);
+typedef void (*InitArrayFunc)(int, char **, char **);
 
 /* Lists of shared object dependencies */
 typedef struct Struct_Needed_Entry {
@@ -195,6 +196,12 @@ typedef struct Struct_Obj_Entry {
 
     Elf_Addr init;             /* Initialization function to call */
     Elf_Addr fini;             /* Termination function to call */
+    Elf_Addr preinit_array;    /* Pre-initialization array of functions */
+    Elf_Addr init_array;       /* Initialization array of functions */
+    Elf_Addr fini_array;       /* Termination array of functions */
+    int preinit_array_num;     /* Number of entries in preinit_array */
+    int init_array_num;        /* Number of entries in init_array */
+    int fini_array_num;        /* Number of entries in fini_array */
 
     bool mainprog : 1;         /* True if this is the main program */
     bool rtld : 1;             /* True if this is the dynamic linker */
index 8531a2c..d159884 100644 (file)
@@ -60,6 +60,9 @@ reloc_jmpslot(Elf_Addr *where, Elf_Addr target,
 #define call_initfini_pointer(obj, target) \
        (((InitFunc)(target))())
 
+#define call_array_pointer(target, argc, argv, env) \
+       (((InitArrayFunc)(target))(argc, argv, env))
+
 #define round(size, align) \
        (((size) + (align) - 1) & ~((align) - 1))
 #define calculate_first_tls_offset(size, align) \