In order for a debugger to properly resolve loaded modules and detect loading of them, it's necessary that the correct DT_DEBUG entry is visible to the debugger. We had two problems with this before: 1. Since the preloader is the main executable, its ELF metadata is used instead of the executable that we are trying to load. 2. The ELF header was actually getting overwritten by the reserved region, since we only adjusted the placement for the .text section.
While 1. cannot be solved unless we run the preloader in another way, e.g. as an interpreter, we can use some dirty hacks to solve 2. This patch simply creates a stub PT_DYNAMIC section in the preloader, then during execution patch that to point to the main executable's respective section.
This allows GDB to have full shared library support when attaching. There are numerous limitation due to the nature of the hack: It relies on the BFD linker to create a PT_DYNAMIC entry, it doesn't work when the executable was launched within the debugger, and it doesn't work with LLDB. GDB doesn't seem to resolve the symbols of the main executable, but that should not be much of an issue since the loader itself is very short.
Some alternative ways to fix this are also possible: 1. Run the preloader as an ELF interpreter. This would get rid of the need of messing with the auxiliary vector and various ELF metadata, but the interpreter can be only specified by absolute path, which is a packaging nuisance. 2. Get rid of the preloader entirely, make the loader static and let it perform the reservation. This should work on glibc, but it's in general not a very well supported use case and feels like a compatibility disaster. --- configure | 17 ++++++---- configure.ac | 17 ++++++---- loader/Makefile.in | 5 +-- loader/preloader.c | 68 ++++++++++++++++++++++++++++++++------ loader/preloader_dynamic.s | 11 ++++++ 5 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 loader/preloader_dynamic.s
diff --git a/configure b/configure index 6a3a43a9e97..098c67f250a 100755 --- a/configure +++ b/configure @@ -10372,7 +10372,7 @@ if test "x$ac_cv_cflags__Wl___export_dynamic" = xyes then : WINELOADER_LDFLAGS="-Wl,--export-dynamic" fi - WINEPRELOADER_LDFLAGS="-static -nostartfiles -nodefaultlibs -Wl,-Ttext=0x7d400000" + WINEPRELOADER_LDFLAGS="-static -nostartfiles -nodefaultlibs"
case $host_cpu in *i[3456789]86* | x86_64 | *aarch64* | arm*) @@ -10402,10 +10402,13 @@ fi printf "%s\n" "$ac_cv_cflags__Wl__Ttext_segment_0x7bc00000" >&6; } if test "x$ac_cv_cflags__Wl__Ttext_segment_0x7bc00000" = xyes then : - case $host_os in - freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x60000000" ;; - *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d000000" ;; - esac + + case $host_os in + freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x60000000" ;; + *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d000000" ;; + esac + WINEPRELOADER_LDFLAGS="$WINEPRELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d400000" + else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports -Wl,--section-start,.interp=0x7d000400" >&5 printf %s "checking whether the compiler supports -Wl,--section-start,.interp=0x7d000400... " >&6; } @@ -10433,11 +10436,13 @@ fi printf "%s\n" "$ac_cv_cflags__Wl___section_start__interp_0x7d000400" >&6; } if test "x$ac_cv_cflags__Wl___section_start__interp_0x7d000400" = xyes then : - case $host_os in + + case $host_os in freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,--section-start,.interp=0x60000400" ;; *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,--section-start,.interp=0x7d000400" ;; esac fi + WINEPRELOADER_LDFLAGS="$WINEPRELOADER_LDFLAGS -Wl,-Ttext=0x7d400000" # Extract the first word of "prelink", so it can be a program name with args. set dummy prelink; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 diff --git a/configure.ac b/configure.ac index 5a85fce12a4..9b0e167448f 100644 --- a/configure.ac +++ b/configure.ac @@ -826,20 +826,25 @@ case $host_os in
WINE_TRY_CFLAGS([-Wl,-z,defs],[UNIXLDFLAGS="$UNIXLDFLAGS -Wl,-z,defs"]) WINE_TRY_CFLAGS([-Wl,--export-dynamic],[WINELOADER_LDFLAGS="-Wl,--export-dynamic"]) - WINEPRELOADER_LDFLAGS="-static -nostartfiles -nodefaultlibs -Wl,-Ttext=0x7d400000" + WINEPRELOADER_LDFLAGS="-static -nostartfiles -nodefaultlibs"
case $host_cpu in *i[[3456789]]86* | x86_64 | *aarch64* | arm*) WINE_TRY_CFLAGS([-Wl,-Ttext-segment=0x7bc00000], - [case $host_os in - freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x60000000" ;; - *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d000000" ;; - esac], + [ + case $host_os in + freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x60000000" ;; + *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d000000" ;; + esac + WINEPRELOADER_LDFLAGS="$WINEPRELOADER_LDFLAGS -Wl,-Ttext-segment=0x7d400000" + ], [WINE_TRY_CFLAGS([-Wl,--section-start,.interp=0x7d000400], - [case $host_os in + [ + case $host_os in freebsd* | kfreebsd*-gnu) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,--section-start,.interp=0x60000400" ;; *) WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,--section-start,.interp=0x7d000400" ;; esac]) + WINEPRELOADER_LDFLAGS="$WINEPRELOADER_LDFLAGS -Wl,-Ttext=0x7d400000" AC_PATH_PROG(PRELINK, prelink, false, [/sbin /usr/sbin $PATH]) if test "x$PRELINK" = xfalse then diff --git a/loader/Makefile.in b/loader/Makefile.in index 7302c231915..b5bdf6e1439 100644 --- a/loader/Makefile.in +++ b/loader/Makefile.in @@ -2,6 +2,7 @@ SOURCES = \ main.c \ preloader.c \ preloader_mac.c \ + preloader_dynamic.s \ wine.de.UTF-8.man.in \ wine.desktop \ wine.fr.UTF-8.man.in \ @@ -25,10 +26,10 @@ wine64_OBJS = main.o wine64_DEPS = $(WINELOADER_DEPENDS) wine64_LDFLAGS = $(WINELOADER_LDFLAGS) $(LDEXECFLAGS) $(PTHREAD_LIBS)
-wine_preloader_OBJS = preloader.o preloader_mac.o +wine_preloader_OBJS = preloader.o preloader_mac.o preloader_dynamic.o wine_preloader_DEPS = $(WINELOADER_DEPENDS) wine_preloader_LDFLAGS = $(WINEPRELOADER_LDFLAGS)
-wine64_preloader_OBJS = preloader.o preloader_mac.o +wine64_preloader_OBJS = preloader.o preloader_mac.o preloader_dynamic.o wine64_preloader_DEPS = $(WINELOADER_DEPENDS) wine64_preloader_LDFLAGS = $(WINEPRELOADER_LDFLAGS) diff --git a/loader/preloader.c b/loader/preloader.c index 585be50624f..6af37cfca12 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -868,7 +868,7 @@ static void set_auxiliary_values( struct wld_auxv *av, const struct wld_auxv *ne * * Get a field of the auxiliary structure */ -static int get_auxiliary( struct wld_auxv *av, int type, int def_val ) +static unsigned long get_auxiliary(struct wld_auxv *av, unsigned long type, unsigned long def_val) { for ( ; av->a_type != AT_NULL; av++) if( av->a_type == type ) return av->a_un.a_val; @@ -1148,13 +1148,30 @@ static unsigned int gnu_hash( const char *name ) return h; }
+static void *find_segment(const struct wld_link_map *map, int type, ElfW(Half) * size) +{ + void *ret = NULL; + const ElfW(Phdr) *ph; + /* parse the (already loaded) ELF executable's header */ + for (ph = map->l_phdr; ph < &map->l_phdr[map->l_phnum]; ++ph) + { + if (type == ph->p_type) + { + ret = (void *)(ph->p_vaddr + map->l_addr); + if (size) + *size = ph->p_memsz; + break; + } + } + return ret; +} + /* * Find a symbol in the symbol table of the executable loaded */ static void *find_symbol( const struct wld_link_map *map, const char *var, int type ) { const ElfW(Dyn) *dyn = NULL; - const ElfW(Phdr) *ph; const ElfW(Sym) *symtab = NULL; const Elf32_Word *hashtab = NULL; const Elf32_Word *gnu_hashtab = NULL; @@ -1165,15 +1182,7 @@ static void *find_symbol( const struct wld_link_map *map, const char *var, int t #ifdef DUMP_SYMS wld_printf("%p %x\n", map->l_phdr, map->l_phnum ); #endif - /* parse the (already loaded) ELF executable's header */ - for (ph = map->l_phdr; ph < &map->l_phdr[map->l_phnum]; ++ph) - { - if( PT_DYNAMIC == ph->p_type ) - { - dyn = (void *)(ph->p_vaddr + map->l_addr); - break; - } - } + dyn = find_segment( map, PT_DYNAMIC, NULL ); if( !dyn ) return NULL;
while( dyn->d_tag ) @@ -1375,6 +1384,12 @@ void* wld_start( void **stack ) struct wld_auxv new_av[8], delete_av[3], *av; struct wld_link_map main_binary_map, ld_so_map; struct wine_preload_info **wine_main_preload_info; + void *main_binary_dynamic = NULL; + ElfW(Half) main_binary_dynamic_size = 0; + ElfW(Phdr) *self_phdr; + unsigned long phdr_offset = 0; + ElfW(Half) self_phnum; + ElfW(Phdr) *ph;
pargc = *stack; argv = (char **)pargc + 1; @@ -1394,6 +1409,8 @@ void* wld_start( void **stack ) av = (struct wld_auxv *)(p+1); page_size = get_auxiliary( av, AT_PAGESZ, 4096 ); page_mask = page_size - 1; + self_phdr = (void *)get_auxiliary(av, AT_PHDR, (unsigned long)NULL); + self_phnum = get_auxiliary(av, AT_PHNUM, 0);
preloader_start = (char *)_start - ((unsigned long)_start & page_mask); preloader_end = (char *)((unsigned long)(_end + page_mask) & ~page_mask); @@ -1442,6 +1459,35 @@ void* wld_start( void **stack ) interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp; map_so_lib( interp, &ld_so_map );
+ main_binary_dynamic = find_segment(&main_binary_map, PT_DYNAMIC, &main_binary_dynamic_size); + for (ph = self_phdr; ph < &self_phdr[self_phnum]; ++ph) + { + if (is_addr_reserved(ph)) + { + wld_printf("preloader: Warning: preloader PHDR is in reserved range, symbols in gdb " + "will be broken! (Make sure the linker used to build Wine supports " + "-Ttext-segment)\n"); + break; + } + + if (PT_PHDR == ph->p_type) + { + /* Best effort PIE support */ + phdr_offset = (unsigned long)self_phdr - (unsigned long)ph->p_vaddr; + } + else if (PT_DYNAMIC == ph->p_type) + { + /* + * Monkey patch our PT_DYNAMIC entry to point it to the real executable's PT_DYNAMIC section. + * This is required by debuggers to resolve loaded shared libraries and more. + */ + wld_mprotect((void *)((unsigned long)ph & ~page_mask), page_size, PROT_READ | PROT_WRITE); + ph->p_vaddr = (unsigned long)main_binary_dynamic - phdr_offset; + ph->p_memsz = main_binary_dynamic_size; + break; + } + } + /* store pointer to the preload info into the appropriate main binary variable */ wine_main_preload_info = find_symbol( &main_binary_map, "wine_main_preload_info", STT_OBJECT ); if (wine_main_preload_info) *wine_main_preload_info = preload_info; diff --git a/loader/preloader_dynamic.s b/loader/preloader_dynamic.s new file mode 100644 index 00000000000..cd658c758d8 --- /dev/null +++ b/loader/preloader_dynamic.s @@ -0,0 +1,11 @@ +DT_NULL=0 +# This section is a stub PT_DYNAMIC entry that will be patched over to the real exectuable's +# respective section. +# This only works with ld.bfd. gold and lld (with --image-base) will create their own +# PT_DYNAMIC entry unless instructed otherwise with a linker script. +.section ".dynamic" + _DYNAMIC: .globl _DYNAMIC + # Don't put any "real" entries here: GDB will look at the on-disk image first, and we + # need to make sure that the stub entries doesn't get preferred over the real entries + # that we forward to. + .long DT_NULL,0