[PATCH] preloader: Forward PT_DYNAMIC for debugger support

Tatsuyuki Ishi ishitatsuyuki at gmail.com
Mon Mar 21 09:38:28 CDT 2022


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
-- 
2.35.1




More information about the wine-devel mailing list