From: Damjan Jovanovic Subject: [PATCH] ntdll: fix ELF initializer address calculations on GNU, NetBSD and FreeBSD Message-Id: Date: Sun, 17 May 2020 12:49:08 +0200 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 --- dlls/ntdll/loader.c | 51 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) 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 ),