[PATCH] ntdll: fix ELF initializer address calculations on GNU, NetBSD and FreeBSD

Damjan Jovanovic damjan.jov at gmail.com
Sun May 17 05:49:08 CDT 2020


dlinfo() doesn't conform to a standard, each platform implements in differently,
if at all. On GNU and NetBSD its l_addr field returned is the "relocbase",
the relative offset between addresses wanted by the file and addresses obtained
in memory (==0 when no relocation occurred), which we add to d_un.d_ptr to
obtain the memory address where the initializer is. On FreeBSD (and possibly
Solaris) this won't work, as l_addr is the "mapbase" instead, the absolute
starting memory address where the binary was loaded, resulting in wrong
calculations and crashes on startup as we call into wrong addresses.

However PT_LOAD segments are in increasing order of their start address,
and the first segment starts at the beginning of the ELF file. Thus we
can convert mapbase into relocbase by finding that segment and working out
the difference between mapbase and the address it wanted, then use that to
work out the correct initializer address. This gets Wine working on
FreeBSD again.

Note how commit 6a12d93b88145cb1f31e89c6faca39184fb819ca broke both Linux
and FreeBSD for relocated binaries. Linux broke because GNU's l_addr is
the relocbase, which has already taken relocation into account, thus adding
l_addr to d_un.d_ptr should always be performed, no matter what, but that
commit sometimes won't. On FreeBSD l_addr is mapbase, and we can never
add it to d_un.d_ptr as both are absolute addresses; the reason the commit
appeared to fix FreeBSD is because relocbase is 0 in the common case
when no relocation happens, so adding nothing to d_un.d_ptr is usually the
right thing to do, but when relocation does happen, we have to add relocbase,
which is neither detected nor handled correctly, as it isn't l_addr nor
does it have anything to do with l_addr's value alone.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49139
Signed-off-by: Damjan Jovanovic <damjan.jov at gmail.com>
---
 dlls/ntdll/loader.c | 51 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 47 insertions(+), 4 deletions(-)
-------------- next part --------------
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c
index 35dc7e1eaa..5bfd4e2a43 100644
--- a/dlls/ntdll/loader.c
+++ b/dlls/ntdll/loader.c
@@ -1317,6 +1317,36 @@ static void call_tls_callbacks( HMODULE module, UINT reason )
     }
 }
 
+#if defined(__FreeBSD__)
+/* The PT_LOAD segments are sorted in increasing order, and the first
+ * starts at the beginning of the ELF file. By parsing the file, we can
+ * find that first PT_LOAD segment, from which we can find the base
+ * address it wanted, and knowing mapbase where the binary was actually
+ * loaded, use them to work out the relocbase offset. */
+static BOOL mapbase_to_relocbase(const caddr_t mapbase, caddr_t *relocbase)
+{
+    const Elf_Phdr *prog_header;
+    Elf_Half i;
+#ifdef _WIN64
+    const Elf64_Ehdr *elf_header = (Elf64_Ehdr*) mapbase;
+#else
+    const Elf32_Ehdr *elf_header = (Elf32_Ehdr*) mapbase;
+#endif
+
+    prog_header = (const Elf_Phdr*) ((const caddr_t)elf_header + elf_header->e_phoff);
+    for (i = 0; i < elf_header->e_phnum; i++)
+    {
+         if (prog_header->p_type == PT_LOAD)
+         {
+             const caddr_t desired_base = (caddr_t) ((prog_header->p_vaddr / prog_header->p_align) * prog_header->p_align);
+             *relocbase = (caddr_t) (mapbase - desired_base);
+             return TRUE;
+         }
+         prog_header++;
+    }
+    return FALSE;
+}
+#endif
 
 /*************************************************************************
  *              call_constructors
@@ -1338,14 +1368,27 @@ static void call_constructors( WINE_MODREF *wm )
     if (dlinfo( wm->so_handle, RTLD_DI_LINKMAP, &map ) == -1) return;
     for (dyn = map->l_ld; dyn->d_tag; dyn++)
     {
-#define GET_PTR(base,ptr)  ((ptr) > (base) ? (ptr) : (base) + (ptr))
+        /* The offset between addresses wanted by the binary ELF file on disk,
+         * and the addresses they actually got in memory. 0 when no relocation occurred.
+         * How it's worked out depends on the system's dlinfo() implementation. */
+        caddr_t relocbase;
+
+#if defined(__FreeBSD__)
+        if (!mapbase_to_relocbase(map->l_addr, &relocbase))
+        {
+            ERR("conversion of mapbase to relocbase failed, skipping initializers!!!\n");
+            return;
+        }
+#else
+        /* GNU, NetBSD */
+        relocbase = (caddr_t)map->l_addr;
+#endif
         switch (dyn->d_tag)
         {
-        case 0x60009990: init_array = (void *)GET_PTR( map->l_addr, dyn->d_un.d_ptr ); break;
+        case 0x60009990: init_array = (void *)(relocbase + dyn->d_un.d_val); break;
         case 0x60009991: init_arraysz = dyn->d_un.d_val; break;
-        case 0x60009992: init_func = (void *)GET_PTR( map->l_addr, dyn->d_un.d_ptr ); break;
+        case 0x60009992: init_func = (void *)(relocbase + dyn->d_un.d_val); break;
         }
-#undef GET_PTR
     }
 
     TRACE( "%s: got init_func %p init_array %p %lu\n", debugstr_us( &wm->ldr.BaseDllName ),


More information about the wine-devel mailing list