Question

I'm writing a shared-object which is supposed to be LD_PRELOADed into processes.
In that shared object I have some initialization like

__attribute__((constructor)) void initFunc();

That I'd like to be called before any other code in the process.
With processes that are just an executable this works fine but if the process depends on some other share-objects on its own, these get initialized before my LD_PRELOAD shared object.

I tried giving the linker the option -Wl,-z,initfirst but that doesn't seem to have any effect at all. When I'm running the process with LD_DEBUG=files I still see the application so inited before mine.

I'm running CentOS 5.5

Was it helpful?

Solution

The problem is that the loader only supports one shared library with -z initfirst, and libpthread.so (which is used by almost everything) already has this set. Even if you use LD_PRELOAD to load a library, libpthread's constructors will be called first.

You can get around this by patching the loader to support multiple shared libraries with -z initfirst. Here is a patch for ld.so version 2.21 which preserves the binary ABI but makes a linked list out of initfirst libraries and calls them with LD_PRELOAD constructors first.

diff --git a/elf/dl-load.c b/elf/dl-load.c
index 6dd8550..ac3b079 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1387,7 +1387,27 @@ cannot enable executable stack as shared object requires");

   /* Remember whether this object must be initialized first.  */
   if (l->l_flags_1 & DF_1_INITFIRST)
-    GL(dl_initfirst) = l;
+    {
+#if 0
+      struct initfirst_list *first = malloc(sizeof(*first));
+      first->which = l;
+      first->next = GL(dl_initfirst);
+      GL(dl_initfirst) = first;
+#else
+      struct initfirst_list *node = malloc(sizeof(*node));
+      node->which = l;
+      node->next = NULL;
+      struct initfirst_list *it = GL(dl_initfirst);
+      if (!it)
+        GL(dl_initfirst) = node;
+      else
+        {
+          while (it->next)
+            it = it->next;
+          it->next = node;
+        }
+#endif
+    }

   /* Finally the file information.  */
   l->l_dev = st.st_dev;
diff --git a/elf/dl-map-segments.h b/elf/dl-map-segments.h
index baaa813..bca961c 100644
--- a/elf/dl-map-segments.h
+++ b/elf/dl-map-segments.h
@@ -55,7 +55,11 @@ _dl_map_segments (struct link_map *l, int fd,
       /* Remember which part of the address space this object uses.  */
       l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
                                             c->prot,
+#if 0
                                             MAP_COPY|MAP_FILE,
+#else
+                                            MAP_COPY|MAP_FILE|MAP_32BIT,
+#endif
                                             fd, c->mapoff);
       if (__glibc_unlikely ((void *) l->l_map_start == MAP_FAILED))
         return DL_MAP_SEGMENTS_ERROR_MAP_SEGMENT;
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 835dcb3..9ea0c05 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -148,7 +148,7 @@ struct r_search_path_elem *_dl_all_dirs;
 struct r_search_path_elem *_dl_init_all_dirs;

 /* The object to be initialized first.  */
-struct link_map *_dl_initfirst;
+struct initfirst_list *_dl_initfirst;

 /* Descriptor to write debug messages to.  */
 int _dl_debug_fd = STDERR_FILENO;
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index b421931..7bb7a69 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -318,7 +318,11 @@ struct rtld_global
   EXTERN unsigned long long _dl_load_adds;

   /* The object to be initialized first.  */
-  EXTERN struct link_map *_dl_initfirst;
+  /*EXTERN struct link_map *_dl_initfirst;*/
+  EXTERN struct initfirst_list {
+    struct link_map *which;
+    struct initfirst_list *next;
+  } *_dl_initfirst;

 #if HP_SMALL_TIMING_AVAIL || defined HP_TIMING_PAD
   /* Start time on CPU clock.  */

I suppose you can try hacking libpthread to not use -z initfirst, but this seems like the simplest option. I have used it successfully to get a constructor called before anything else. You just have to make sure your LD_PRELOADed library does not use libc, because then libc's constructors will be called first, and in a multithreaded program libc depends on libpthread, so pthread's constructors will be called before that.

Here's an example. I compile a hello, world program with -pthread (otherwise there are no problems). I write a small library which is meant to be LD_PRELOADed and which does not depend on libc. With the default loader, you can't get your init function called first:

$ cat hello.c 
#include <stdio.h>

int main() {
    puts("Hello, world!");
    return 0;
}
$ gcc hello.c -o hello -pthread
$ cat superearly.c
#include <unistd.h> 
#include <sys/syscall.h>

long write(int fd, const void *buffer, size_t len) {
    unsigned long ret;
    __asm__ __volatile__("syscall" : "=a"(ret) : "a"(__NR_write), "D"(fd),
        "S"(buffer), "d"(len));
    return ret;
}

void hello(void) __attribute__((constructor));
void hello(void) {
    write(STDOUT_FILENO, "Got in first!\n", 14);
}
$ gcc superearly.c -fPIC -shared -nostdlib -o libsuperearly.so -Wl,-z,initfirst
$ LD_DEBUG=libs LD_PRELOAD=./libsuperearly.so ./hello
    19997:     find library=libpthread.so.0 [0]; searching
    19997:      search cache=/etc/ld.so.cache
    19997:       trying file=/lib/x86_64-linux-gnu/libpthread.so.0
    19997:
    19997:     find library=libc.so.6 [0]; searching
    19997:      search cache=/etc/ld.so.cache
    19997:       trying file=/lib/x86_64-linux-gnu/libc.so.6
    19997:
    19997:
    19997:     calling init: /lib/x86_64-linux-gnu/libpthread.so.0
    19997:
    19997:
    19997:     calling init: /lib/x86_64-linux-gnu/libc.so.6
    19997:
    19997:
    19997:     calling init: ./libsuperearly.so
    19997:
Got in first!
    19997:
    19997:     initialize program: ./hello
    19997:
    19997:
    19997:     transferring control: ./hello
    19997:
Hello, world!
    19997:
    19997:     calling fini: ./hello [0]
    19997:
    19997:
    19997:     calling fini: /lib/x86_64-linux-gnu/libpthread.so.0 [0]
    19997:
$

However, with an ld.so which has been patched with my patch above:

$ LD_DEBUG=libs LD_PRELOAD=./libsuperearly.so ~/libc/lib/ld-2.21.so ./hello
    19986:     find library=libpthread.so.0 [0]; searching
    19986:      search cache=/home/user/libc/etc/ld.so.cache
    19986:       trying file=/home/user/libc/lib/libpthread.so.0
    19986:
    19986:     find library=libc.so.6 [0]; searching
    19986:      search cache=/home/user/libc/etc/ld.so.cache
    19986:       trying file=/home/user/libc/lib/libc.so.6
    19986:
    19986:
    19986:     calling init: ./libsuperearly.so
    19986:
Got in first!
    19986:
    19986:     calling init: /home/user/libc/lib/libpthread.so.0
    19986:
    19986:
    19986:     calling init: /home/user/libc/lib/libc.so.6
    19986:
    19986:
    19986:     initialize program: ./hello
    19986:
Hello, world!
    19986:
    19986:     calling fini: ./hello [0]
    19986:
    19986:
    19986:     calling fini: /home/user/libc/lib/libpthread.so.0 [0]
    19986:
$ 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top