From: Keno Fischer Subject: [v2] preloader: Allow a debugger to find ld.so's rendezvous-struct Message-Id: <20160825041614.GA16214@juliacomputing.com> Date: Thu, 25 Aug 2016 00:16:14 -0400 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 commands silent up set $main_entry = main_binary_map.l_entry tbreak *$main_entry commands silent sharedlibrary b _dl_debug_state commands silent sharedlibrary c end c end c end ``` Signed-off-by: Keno Fischer --- 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 */ +__asm__( +".section .dynamic,\"aw\"\n" +".global _DYNAMIC\n" +"_DYNAMIC: .quad 21\n" // DT_DEBUG +".quad 0\n" +".quad 0\n" +".quad 0\n" +".previous\n" +); + /* debugging */ #undef DUMP_SEGMENTS #undef DUMP_AUX_INFO @@ -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); break; } @@ -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 ); #endif @@ -1028,7 +1040,7 @@ found: #ifdef DUMP_SYMS wld_printf("Found %s -> %p\n", strings + symtab[idx].st_name, (void *)symtab[idx].st_value ); #endif - 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); #endif + about_to_finish(main_binary_map.l_entry); + return (void *)ld_so_map.l_entry; } -- 2.7.4