[v2] preloader: Allow a debugger to find ld.so's rendezvous-struct

Keno Fischer keno at juliacomputing.com
Wed Aug 24 23:16:14 CDT 2016

This is accomplished by creating a fake .dynamic section with the require
DT_DEBUG entry and filling it manually once we know where the rendezvous
structure will be in memory. As a result, a debugger like GDB is able to
find the dynamic linker's runtime structures and can introspect all
dynamic libraries that get loaded (including .dll.so's).

With GDB in particular, there is a little quirk to this story, since it
tries to eagerly (before the process is even started) resolve the breakpoint
by which the dynamic loader will notify the debugger of changes to the link
map. However, in our case that is of course not possible, since the debugger
has no way of knowing that the dynamic linker will later be loaded. It is
possible to create a _dl_debug_state symbol in the preloader and trigger
it once after ld.so has finished but before the executable has started.
This gives the debugger enough information to load at least the symbols for
libc/pthread/ld.so, but it still doesn't switch over to using the
dynamic linker's r_debug->r_brk symbol to determine the shared library event
breakpoint. Arguably, it should, but that would be a major change to gdb.
Other debuggers do no necessarily have this problem.

It should also be noted that this only applies if the preloader is started
as a gdb inferior directly, rather than attaching later. If the debugger
attaches after ld.so has run, there is no problem and symbols will automatically
be loaded.

The failure mode if symbols are not automatically loaded is that
1) When a library is loaded, symbols in that library won't be resolved (e.g.
   won't show up in backtraces) until the `sharedlibrary` command is executed.
   This command will resolve symbols for all libraries that have been loaded
   up until that point (but will still not activate auto-loading).
2) For the same reason, setting breakpoints on future symbols does not work.

As mentioned, these problems could be fixed in GDB, but there is also a manual
workaround, to simply execute the sharedlibrary command when required. The
following gdb script will do this automatically:

  tbreak about_to_finish
    set $main_entry = main_binary_map.l_entry
    tbreak *$main_entry
      b _dl_debug_state

Signed-off-by: Keno Fischer <keno at juliacomputing.com>
Changes since v1:
- add a `.previous` directive to make sure that we switch
  back to the section the compiler expected

 loader/preloader.c | 42 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 34 insertions(+), 8 deletions(-)

diff --git a/loader/preloader.c b/loader/preloader.c
index 7cf2946..f9e5586 100644
--- a/loader/preloader.c
+++ b/loader/preloader.c
@@ -123,6 +123,18 @@ static struct wine_preload_info preload_info[] =
     { 0, 0 }                             /* end of list */
+/* A fake .dynamic section through which we can inform the debugger of the
+   address of the chain-loaded dynamic linker's rendezvous struct */
+".section .dynamic,\"aw\"\n"
+".global _DYNAMIC\n"
+"_DYNAMIC: .quad 21\n" // DT_DEBUG
+".quad 0\n"
+".quad 0\n"
+".quad 0\n"
 /* debugging */
@@ -937,7 +949,7 @@ static unsigned int gnu_hash( const char *name )
  * Find a symbol in the symbol table of the executable loaded
-static void *find_symbol( const ElfW(Phdr) *phdr, int num, const char *var, int type )
+static void *find_symbol(struct wld_link_map *link_map, const ElfW(Phdr) *phdr, int num, const char *var, int type )
     const ElfW(Dyn) *dyn = NULL;
     const ElfW(Phdr) *ph;
@@ -962,7 +974,7 @@ static void *find_symbol( const ElfW(Phdr) *phdr, int num, const char *var, int
         if( PT_DYNAMIC == ph->p_type )
-            dyn = (void *) ph->p_vaddr;
+            dyn = (void *)(link_map->l_addr + ph->p_vaddr);
             num = ph->p_memsz / sizeof (*dyn);
@@ -972,13 +984,13 @@ static void *find_symbol( const ElfW(Phdr) *phdr, int num, const char *var, int
     while( dyn->d_tag )
         if( dyn->d_tag == DT_STRTAB )
-            strings = (const char*) dyn->d_un.d_ptr;
+            strings = (const char*)(link_map->l_addr + dyn->d_un.d_ptr);
         if( dyn->d_tag == DT_SYMTAB )
-            symtab = (const ElfW(Sym) *)dyn->d_un.d_ptr;
+            symtab = (const ElfW(Sym) *)(link_map->l_addr + dyn->d_un.d_ptr);
         if( dyn->d_tag == DT_HASH )
-            hashtab = (const Elf32_Word *)dyn->d_un.d_ptr;
+            hashtab = (const Elf32_Word *)(link_map->l_addr + dyn->d_un.d_ptr);
         if( dyn->d_tag == DT_GNU_HASH )
-            gnu_hashtab = (const Elf32_Word *)dyn->d_un.d_ptr;
+            gnu_hashtab = (const Elf32_Word *)(link_map->l_addr + dyn->d_un.d_ptr);
 #ifdef DUMP_SYMS
         wld_printf("%lx %p\n", (unsigned long)dyn->d_tag, (void *)dyn->d_un.d_ptr );
@@ -1028,7 +1040,7 @@ found:
 #ifdef DUMP_SYMS
     wld_printf("Found %s -> %p\n", strings + symtab[idx].st_name, (void *)symtab[idx].st_value );
-    return (void *)symtab[idx].st_value;
+    return (void *)(link_map->l_addr + symtab[idx].st_value);
@@ -1152,6 +1164,10 @@ static void set_process_name( int argc, char *argv[] )
     for (i = 1; i < argc; i++) argv[i] -= off;
+__attribute__ ((noinline))  static void about_to_finish(uint64_t addr)
+  asm volatile("":::"memory");
  *  wld_start
@@ -1232,11 +1248,19 @@ void* wld_start( void **stack )
     map_so_lib( interp, &ld_so_map );
     /* store pointer to the preload info into the appropriate main binary variable */
-    wine_main_preload_info = find_symbol( main_binary_map.l_phdr, main_binary_map.l_phnum,
+    wine_main_preload_info = find_symbol(&main_binary_map, main_binary_map.l_phdr, main_binary_map.l_phnum,
                                           "wine_main_preload_info", STT_OBJECT );
     if (wine_main_preload_info) *wine_main_preload_info = preload_info;
     else wld_printf( "wine_main_preload_info not found\n" );
+    /* Figure out the address of the rendezvous structure, and set put it into
+       DT_DEBUG for the debugger to find */
+    ElfW(Dyn) dt_debug;
+    dt_debug.d_tag = 21;
+    dt_debug.d_un.d_ptr = (ElfW(Addr)) find_symbol(&ld_so_map, ld_so_map.l_phdr,
+                          ld_so_map.l_phnum, "_r_debug", STT_OBJECT );
+    _DYNAMIC[0] = dt_debug;
 #define SET_NEW_AV(n,type,val) new_av[n].a_type = (type); new_av[n].a_un.a_val = (val);
     SET_NEW_AV( 0, AT_PHDR, (unsigned long)main_binary_map.l_phdr );
     SET_NEW_AV( 1, AT_PHENT, sizeof(ElfW(Phdr)) );
@@ -1273,5 +1297,7 @@ void* wld_start( void **stack )
     wld_printf("jumping to %p\n", (void *)ld_so_map.l_entry);
+    about_to_finish(main_binary_map.l_entry);
     return (void *)ld_so_map.l_entry;

More information about the wine-patches mailing list