Currently, unwinding the unix libraries (or full ELF builds) requires having libunwind available.
When starting up a wine environment, an exception of type `RPC_S_SERVER_UNAVAILABLE` gets thrown - and if libunwind isn't available, this breaks the startup. Thus, currently on ARM/ARM64, libunwind is essentially mandatory.
Additionally, at least on ARM, libunwind seems brittle (commits in latest git master breaks the unwinding use cases in Wine, see e.g. https://github.com/libunwind/libunwind/pull/203#issuecomment-984126066.
For ARM, libunwind has currently relied on DWARF debug info in the `.debug_frame` section, since Linux on ARM doesn't use DWARF for runtime unwinding. This MR adds ARM EHABI unwind opcodes where necessary, and adds a local reimplementation of an ARM EHABI/EXIDX/EXTBL unwinder, similar to the DWARF one.
With these changes, unwinding on ARM seems to work fine, even without libunwind.
See the individual commit messages for more commentary on the decisions taken so far (I've tried to write up all relevant considerations there - it's a fair amount of commentery).
A couple open questions: - I wrote the ARM EHABI unwind code mostly with `stdint.h` types like `uint32_t`(as it's in the ntdll/unix directory anyway), but I can rewrite it with `DWORD` or similar if that's preferred. - The ARM EHABI opcodes are enabled for `defined(__arm__) && defined(__ELF__) && defined(__GNUC__) && !defined(__SEH__) && !defined(__ARM_DWARF_EH__)` - there's no define to check for for knowing it's used and enabled, but it has to be implicitly assumed if no other other unwind mechanism is signaled. - The `dl_iterate_phdr` function, used for finding the EXIDX section for locating ARM EHABI unwind info, is used within `#ifdef linux`. It might be available on other OSes too (like FreeBSD etc). Or should I go ahead and add a configure check for it?
-- v2: ntdll: Implement ARM EHABI unwinding ntdll: Add ARM EHABI unwind instructions in assembly functions
From: Martin Storsjö martin@martin.st
On most ELF platforms on ARM, ARM EHABI is the unwind info format normally used, instead of DWARF like on most other platforms.
Currently, when unwinding through ELF objects with libunwind, the libraries don't have any .eh_frame section mapped at runtime (since DWARF isn't used for unwinding). Instead, what happens is that libunwind ends up loading .debug_frame from the libraries on disk instead.
Therefore, currently, ELF unwinding relies on the .so files not being stripped.
This patch adds the necessary EHABI unwinding instructions in the assembly functions that currently have DWARF CFI instructions.
EHABI isn't signaled via any specific preprocessor macro, but is signaled by the absence of other unwind mechanisms (such as __ARM_DWARF_EH__ and __SEH__, or maybe SjLj).
Mark the asm functions in the preloaders as .cantunwind, to avoid undefined references to __aeabi_unwind_cpp_pr* functions.
Also mark other assembly functions as .cantunwind; for signal_exit_thread this is essential if the function is marked with .fnstart/.fnend - otherwise exiting threads does hang. (pthread_exit internally calls _Unwind_ForcedUnwind, which would hang if signal_exit_thread had .fnstart without any matching unwind info).
This would, in principle, allow unwinding through these functions with libunwind, for versions of libunwind that can parse the EHABI unwind info - see e.g. https://github.com/libunwind/libunwind/commit/4d779f55c0d0a3814453517e54cd0f.... (This commit isn't yet in any current release AFAIK). Unwinding with EHABI via libunwind would require a few tweaks to the libunwind interface usage in unix/signal_arm.c though, since e.g. the unw_get_proc_info call fails if there's no .eh_frame or .debug_frame available.
Signed-off-by: Martin Storsjö martin@martin.st --- dlls/ntdll/signal_arm.c | 15 +++++++++++++++ dlls/ntdll/unix/signal_arm.c | 10 ++++++++++ include/wine/asm.h | 8 +++++++- loader/preloader.c | 3 +++ 4 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/dlls/ntdll/signal_arm.c b/dlls/ntdll/signal_arm.c index c3674487277..0952be6ba33 100644 --- a/dlls/ntdll/signal_arm.c +++ b/dlls/ntdll/signal_arm.c @@ -1129,6 +1129,18 @@ __ASM_GLOBAL_FUNC( call_consolidate_callback, __ASM_CFI(".cfi_escape 0x10,0x8e,0x02,0x03,0x7d,0xc0,0x01\n\t") /* DW_CFA_expression: D14 DW_OP_breg13 + 192 */ __ASM_CFI(".cfi_escape 0x10,0x8f,0x02,0x03,0x7d,0xc8,0x01\n\t") /* DW_CFA_expression: D15 DW_OP_breg13 + 200 */ #endif + /* These EHABI opcodes are to be read bottom up - they + * restore relevant registers from the CONTEXT. */ + __ASM_EHABI(".save {sp}\n\t") /* Restore Sp last */ + __ASM_EHABI(".pad #-(0x80 + 0x0c + 0x0c)\n\t") /* Move back across D0-D15, Cpsr, Fpscr, Padding, Pc, Lr and Sp */ + __ASM_EHABI(".vsave {d8-d15}\n\t") + __ASM_EHABI(".pad #0x40\n\t") /* Skip past D0-D7 */ + __ASM_EHABI(".pad #0x0c\n\t") /* Skip past Cpsr, Fpscr and Padding */ + __ASM_EHABI(".save {lr, pc}\n\t") + __ASM_EHABI(".pad #0x08\n\t") /* Skip past R12 and Sp - Sp is restored last */ + __ASM_EHABI(".save {r4-r11}\n\t") + __ASM_EHABI(".pad #0x14\n\t") /* Skip past ContextFlags and R0-R3 */ + "ldrd r1, r2, [sp, #0x1a4]\n\t" "mov r0, r2\n\t" "blx r1\n\t" @@ -1327,6 +1339,7 @@ extern LONG __C_ExecuteExceptionFilter(PEXCEPTION_POINTERS ptrs, PVOID frame, PUCHAR nonvolatile); __ASM_GLOBAL_FUNC( __C_ExecuteExceptionFilter, "push {r4-r11,lr}\n\t" + __ASM_EHABI(".save {r4-r11,lr}\n\t") __ASM_SEH(".seh_save_regs_w {r4-r11,lr}\n\t") __ASM_SEH(".seh_endprologue\n\t")
@@ -1439,8 +1452,10 @@ EXCEPTION_DISPOSITION WINAPI __C_specific_handler( EXCEPTION_RECORD *rec, */ __ASM_STDCALL_FUNC( RtlRaiseException, 4, "push {r0, lr}\n\t" + __ASM_EHABI(".save {r0, lr}\n\t") __ASM_SEH(".seh_save_regs {r0, lr}\n\t") "sub sp, sp, #0x1a0\n\t" /* sizeof(CONTEXT) */ + __ASM_EHABI(".pad #0x1a0\n\t") __ASM_SEH(".seh_stackalloc 0x1a0\n\t") __ASM_SEH(".seh_endprologue\n\t") __ASM_CFI(".cfi_adjust_cfa_offset 424\n\t") diff --git a/dlls/ntdll/unix/signal_arm.c b/dlls/ntdll/unix/signal_arm.c index 5d1478a1ff4..add01687c4b 100644 --- a/dlls/ntdll/unix/signal_arm.c +++ b/dlls/ntdll/unix/signal_arm.c @@ -593,6 +593,11 @@ __ASM_GLOBAL_FUNC( raise_func_trampoline, "push {r3}\n\t" /* Original Sp */ __ASM_CFI(".cfi_escape 0x0f,0x03,0x7D,0x04,0x06\n\t") /* CFA, DW_OP_breg13 + 0x04, DW_OP_deref */ __ASM_CFI(".cfi_escape 0x10,0x0e,0x02,0x7D,0x0c\n\t") /* LR, DW_OP_breg13 + 0x0c */ + __ASM_EHABI(".save {sp}\n\t") + __ASM_EHABI(".pad #-12\n\t") + __ASM_EHABI(".save {pc}\n\t") + __ASM_EHABI(".pad #8\n\t") + __ASM_EHABI(".save {lr}\n\t") /* We can't express restoring both Pc and Lr with CFI * directives, but we manually load Lr from the stack * in unwind_builtin_dll above. */ @@ -1171,6 +1176,7 @@ void DECLSPEC_HIDDEN call_init_thunk( LPTHREAD_START_ROUTINE entry, void *arg, B * signal_start_thread */ __ASM_GLOBAL_FUNC( signal_start_thread, + __ASM_EHABI(".cantunwind\n\t") "push {r4-r12,lr}\n\t" /* store exit frame */ "str sp, [r3, #0x1d4]\n\t" /* arm_thread_data()->exit_frame */ @@ -1187,6 +1193,7 @@ __ASM_GLOBAL_FUNC( signal_start_thread, * signal_exit_thread */ __ASM_GLOBAL_FUNC( signal_exit_thread, + __ASM_EHABI(".cantunwind\n\t") "ldr r3, [r2, #0x1d4]\n\t" /* arm_thread_data()->exit_frame */ "mov ip, #0\n\t" "str ip, [r2, #0x1d4]\n\t" @@ -1200,6 +1207,7 @@ __ASM_GLOBAL_FUNC( signal_exit_thread, * __wine_syscall_dispatcher */ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, + __ASM_EHABI(".cantunwind\n\t") "mrc p15, 0, r1, c13, c0, 2\n\t" /* NtCurrentTeb() */ "ldr r1, [r1, #0x1d8]\n\t" /* arm_thread_data()->syscall_frame */ "add r0, r1, #0x10\n\t" @@ -1276,6 +1284,7 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, * __wine_setjmpex */ __ASM_GLOBAL_FUNC( __wine_setjmpex, + __ASM_EHABI(".cantunwind\n\t") "stm r0, {r1,r4-r11}\n" /* jmp_buf->Frame,R4..R11 */ "str sp, [r0, #0x24]\n\t" /* jmp_buf->Sp */ "str lr, [r0, #0x28]\n\t" /* jmp_buf->Pc */ @@ -1293,6 +1302,7 @@ __ASM_GLOBAL_FUNC( __wine_setjmpex, * __wine_longjmp */ __ASM_GLOBAL_FUNC( __wine_longjmp, + __ASM_EHABI(".cantunwind\n\t") "ldm r0, {r3-r11}\n\t" /* jmp_buf->Frame,R4..R11 */ "ldr sp, [r0, #0x24]\n\t" /* jmp_buf->Sp */ "ldr r2, [r0, #0x28]\n\t" /* jmp_buf->Pc */ diff --git a/include/wine/asm.h b/include/wine/asm.h index 2c996ccd86b..2448f591189 100644 --- a/include/wine/asm.h +++ b/include/wine/asm.h @@ -41,6 +41,12 @@ # define __ASM_CFI(str) #endif
+#if defined(__arm__) && defined(__ELF__) && defined(__GNUC__) && !defined(__SEH__) && !defined(__ARM_DWARF_EH__) +# define __ASM_EHABI(str) str +#else +# define __ASM_EHABI(str) +#endif + #if defined(__SEH__) || (defined(_MSC_VER) && defined(__clang__) && (defined(__x86_64__) || defined(__aarch64__))) # if defined(__aarch64__) && defined(__clang_major__) && (__clang_major__ < 12 || defined(__apple_build_version__)) /* Clang got support for aarch64 SEH assembly directives in Clang 12, @@ -86,7 +92,7 @@ #define __ASM_DEFINE_FUNC(name,code) \ __ASM_BLOCK_BEGIN(__LINE__) \ asm(".text\n\t.align 4\n\t.globl " name "\n\t" __ASM_FUNC_TYPE(name) __ASM_SEH("\n\t.seh_proc " name) "\n" name ":\n\t" \ - __ASM_CFI(".cfi_startproc\n\t") code __ASM_CFI("\n\t.cfi_endproc") __ASM_SEH("\n\t.seh_endproc") __ASM_FUNC_SIZE(name)); \ + __ASM_CFI(".cfi_startproc\n\t") __ASM_EHABI(".fnstart\n\t") code __ASM_CFI("\n\t.cfi_endproc") __ASM_EHABI("\n\t.fnend") __ASM_SEH("\n\t.seh_endproc") __ASM_FUNC_SIZE(name)); \ __ASM_BLOCK_END
#define __ASM_GLOBAL_FUNC(name,code) __ASM_DEFINE_FUNC(__ASM_NAME(#name),code) diff --git a/loader/preloader.c b/loader/preloader.c index d88964e9c4b..72556f09720 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -542,6 +542,7 @@ void *thread_data[256]; void _start(void); extern char _end[]; __ASM_GLOBAL_FUNC(_start, + __ASM_EHABI(".cantunwind\n\t") "mov r0, sp\n\t" "sub sp, sp, #144\n\t" /* allocate some space for extra aux values */ "str r0, [sp]\n\t" /* orig stack pointer */ @@ -564,6 +565,7 @@ __ASM_GLOBAL_FUNC(_start,
#define SYSCALL_FUNC( name, nr ) \ __ASM_GLOBAL_FUNC( name, \ + __ASM_EHABI(".cantunwind\n\t") \ "push {r4-r5,r7,lr}\n\t" \ "ldr r4, [sp, #16]\n\t" \ "ldr r5, [sp, #20]\n\t" \ @@ -576,6 +578,7 @@ __ASM_GLOBAL_FUNC(_start,
#define SYSCALL_NOERR( name, nr ) \ __ASM_GLOBAL_FUNC( name, \ + __ASM_EHABI(".cantunwind\n\t") \ "push {r7,lr}\n\t" \ "mov r7, #" #nr "\n\t" \ "svc #0\n\t" \
From: Martin Storsjö martin@martin.st
This avoids relying on libunwind, which isn't always available, and which can be brittle (e.g. current git master of libunwind fails, see https://github.com/libunwind/libunwind/pull/203#issuecomment-984126066).
This allows unwinding with the EXIDX/EXTBL info which is used normally for C++ exception handling/unwinding. This avoids needing to keep the .so files unstripped and avoids needing libunwind to load .debug_frame from disk instead of the already mapped EXIDX/EXTBL.
This patch uses the dl_iterate_phdr function for finding the EXIDX section; keeping this call within #ifdef linux to avoid breaking someone's build, even though it probably is available on most unix (or ELF) platforms.
Alternatively, we could add configure checks for this function.
This passes all my unwinding tests, for full ELF builds of Wine, built with both GCC and Clang. (It also works for PE builds, where only very few ELF bits need to be unwound.)
Signed-off-by: Martin Storsjö martin@martin.st --- dlls/ntdll/unix/signal_arm.c | 448 ++++++++++++++++++++++++++++++++++- 1 file changed, 439 insertions(+), 9 deletions(-)
diff --git a/dlls/ntdll/unix/signal_arm.c b/dlls/ntdll/unix/signal_arm.c index add01687c4b..bdf685ae05a 100644 --- a/dlls/ntdll/unix/signal_arm.c +++ b/dlls/ntdll/unix/signal_arm.c @@ -55,6 +55,9 @@ # define UNW_LOCAL_ONLY # include <libunwind.h> #endif +#ifdef HAVE_LINK_H +# include <link.h> +#endif
#define NONAMELESSUNION #define NONAMELESSSTRUCT @@ -223,13 +226,421 @@ static BOOL is_inside_syscall( ucontext_t *sigcontext )
extern void raise_func_trampoline( EXCEPTION_RECORD *rec, CONTEXT *context, void *dispatcher );
+struct exidx_entry +{ + uint32_t addr; + uint32_t data; +}; + +static uint32_t prel31_to_abs(const uint32_t *ptr) +{ + uint32_t prel31 = *ptr; + uint32_t rel = prel31 | ((prel31 << 1) & 0x80000000); + return (uintptr_t)ptr + rel; +} + +static uint8_t get_byte(const uint32_t *ptr, int offset, int bytes) +{ + int word = offset >> 2; + int byte = offset & 0x3; + if (offset >= bytes) + return 0xb0; /* finish opcode */ + return (ptr[word] >> (24 - 8*byte)) & 0xff; +} + +static uint32_t get_uleb128(const uint32_t *ptr, int *offset, int bytes) +{ + int shift = 0; + uint32_t val = 0; + while (1) + { + uint8_t byte = get_byte(ptr, (*offset)++, bytes); + val |= (byte & 0x7f) << shift; + if ((byte & 0x80) == 0) + break; + shift += 7; + } + return val; +} + +static void pop_regs(CONTEXT *context, uint32_t regs) +{ + int i; + DWORD new_sp = 0; + for (i = 0; i < 16; i++) + { + if (regs & (1U << i)) + { + DWORD val = *(DWORD *)context->Sp; + if (i != 13) + (&context->R0)[i] = val; + else + new_sp = val; + context->Sp += 4; + } + } + if (regs & (1 << 13)) + context->Sp = new_sp; +} + +static void pop_vfp(CONTEXT *context, int first, int last) +{ + int i; + for (i = first; i <= last; i++) + { + context->u.D[i] = *(ULONGLONG *)context->Sp; + context->Sp += 8; + } +} + +static uint32_t regmask(int first_bit, int n_bits) +{ + return ((1U << (n_bits + 1)) - 1) << first_bit; +} + /*********************************************************************** - * unwind_builtin_dll + * ehabi_virtual_unwind */ -NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispatch, CONTEXT *context ) +static NTSTATUS ehabi_virtual_unwind( DWORD ip, DWORD *frame, CONTEXT *context, + const struct exidx_entry *entry, + PEXCEPTION_ROUTINE *handler, void **handler_data ) +{ + const uint32_t *ptr; + const void *lsda = NULL; + int compact_inline = 0; + int offset = 0; + int bytes = 0; + int personality; + int extra_words; + int finish = 0; + int set_pc = 0; + DWORD func_begin = prel31_to_abs(&entry->addr); + + *frame = context->Sp; + + TRACE( "ip %#x function %#lx\n", + ip, (unsigned long)func_begin ); + + if (entry->data == 1) + { + ERR("EXIDX_CANTUNWIND\n"); + return STATUS_UNSUCCESSFUL; + } + else if (entry->data & 0x80000000) + { + if ((entry->data & 0x7f000000) != 0) + { + ERR("compact inline EXIDX must have personality 0\n"); + return STATUS_UNSUCCESSFUL; + } + ptr = &entry->data; + compact_inline = 1; + } + else + { + ptr = (uint32_t *)prel31_to_abs(&entry->data); + } + + if ((*ptr & 0x80000000) == 0) + { + /* Generic */ + void *personality_func = (void *)prel31_to_abs(ptr); + int words = (ptr[1] >> 24) & 0xff; + lsda = ptr + 1 + words + 1; + + ERR("generic EHABI unwinding not supported\n"); + (void)personality_func; + return STATUS_UNSUCCESSFUL; + } + + /* Compact */ + + personality = (*ptr >> 24) & 0x0f; + switch (personality) + { + case 0: + if (!compact_inline) + lsda = ptr + 1; + extra_words = 0; + offset = 1; + break; + case 1: + extra_words = (*ptr >> 16) & 0xff; + lsda = ptr + extra_words + 1; + offset = 2; + break; + case 2: + extra_words = (*ptr >> 16) & 0xff; + lsda = ptr + extra_words + 1; + offset = 2; + break; + default: + ERR("unsupported compact EXIDX personality %d\n", personality); + return STATUS_UNSUCCESSFUL; + } + + /* Not inspecting the descriptors */ + (void)lsda; + + bytes = 4 + 4*extra_words; + while (offset < bytes && !finish) + { + uint8_t byte = get_byte(ptr, offset++, bytes); + if ((byte & 0xc0) == 0x00) + { + /* Increment Sp */ + context->Sp += (byte & 0x3f) * 4 + 4; + } + else if ((byte & 0xc0) == 0x40) + { + /* Decrement Sp */ + context->Sp -= (byte & 0x3f) * 4 + 4; + } + else if ((byte & 0xf0) == 0x80) + { + /* Pop {r4-r15} based on register mask */ + int regs = ((byte & 0x0f) << 8) | get_byte(ptr, offset++, bytes); + if (!regs) + { + ERR("refuse to unwind\n"); + return STATUS_UNSUCCESSFUL; + } + regs <<= 4; + pop_regs(context, regs); + if (regs & (1 << 15)) + set_pc = 1; + } + else if ((byte & 0xf0) == 0x90) + { + /* Restore Sp from other register */ + int reg = byte & 0x0f; + if (reg == 13 || reg == 15) + { + ERR("reserved opcode\n"); + return STATUS_UNSUCCESSFUL; + } + context->Sp = (&context->R0)[reg]; + } + else if ((byte & 0xf0) == 0xa0) + { + /* Pop r4-r(4+n) (+lr) */ + int n = byte & 0x07; + int regs = regmask(4, n); + if (byte & 0x08) + regs |= 1 << 14; + pop_regs(context, regs); + } + else if (byte == 0xb0) + { + finish = 1; + } + else if (byte == 0xb1) + { + /* Pop {r0-r3} based on register mask */ + int regs = get_byte(ptr, offset++, bytes); + if (regs == 0 || (regs & 0xf0) != 0) + { + ERR("spare opcode\n"); + return STATUS_UNSUCCESSFUL; + } + pop_regs(context, regs); + } + else if (byte == 0xb2) + { + /* Increment Sp by a larger amount */ + int imm = get_uleb128(ptr, &offset, bytes); + context->Sp += 0x204 + imm * 4; + } + else if (byte == 0xb3) + { + /* Pop VFP registers as if saved by FSTMFDX; this opcode + * is deprecated. */ + ERR("FSTMFDX unsupported\n"); + return STATUS_UNSUCCESSFUL; + } + else if ((byte & 0xfc) == 0xb4) + { + ERR("spare opcode\n"); + return STATUS_UNSUCCESSFUL; + } + else if ((byte & 0xf8) == 0xb8) + { + /* Pop VFP registers as if saved by FSTMFDX; this opcode + * is deprecated. */ + ERR("FSTMFDX unsupported\n"); + return STATUS_UNSUCCESSFUL; + } + else if ((byte & 0xf8) == 0xc0) + { + ERR("spare opcode / iWMMX\n"); + return STATUS_UNSUCCESSFUL; + } + else if ((byte & 0xfe) == 0xc8) + { + /* Pop VFP registers d(16+ssss)-d(16+ssss+cccc), or + * d(0+ssss)-d(0+ssss+cccc) as if saved by VPUSH */ + int first, last; + if ((byte & 0x01) == 0) + first = 16; + else + first = 0; + byte = get_byte(ptr, offset++, bytes); + first += (byte & 0xf0) >> 4; + last = first + (byte & 0x0f); + if (last >= 32) + { + ERR("reserved opcode\n"); + return STATUS_UNSUCCESSFUL; + } + pop_vfp(context, first, last); + } + else if ((byte & 0xf8) == 0xc8) + { + ERR("spare opcode\n"); + return STATUS_UNSUCCESSFUL; + } + else if ((byte & 0xf8) == 0xd0) + { + /* Pop VFP registers d8-d(8+n) as if saved by VPUSH */ + int n = byte & 0x07; + pop_vfp(context, 8, 8 + n); + } + else + { + ERR("spare opcode\n"); + return STATUS_UNSUCCESSFUL; + } + } + if (offset > bytes) + { + ERR("truncated opcodes\n"); + return STATUS_UNSUCCESSFUL; + } + + *handler = NULL; /* personality */ + *handler_data = NULL; /* lsda */ + + context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL; + if (!set_pc) + context->Pc = context->Lr; + + /* There's no need to check for raise_func_trampoline and manually restore + * Lr separately from Pc like with libunwind; the EHABI unwind info + * describes how both of them are restored separately, and as long as + * the unwind info restored Pc, it doesn't have to be set from Lr. */ + + TRACE( "next function pc=%08x\n", context->Pc ); + TRACE(" r0=%08x r1=%08x r2=%08x r3=%08x\n", + context->R0, context->R1, context->R2, context->R3 ); + TRACE(" r4=%08x r5=%08x r6=%08x r7=%08x\n", + context->R4, context->R5, context->R6, context->R7 ); + TRACE(" r8=%08x r9=%08x r10=%08x r11=%08x\n", + context->R8, context->R9, context->R10, context->R11 ); + TRACE(" r12=%08x sp=%08x lr=%08x pc=%08x\n", + context->R12, context->Sp, context->Lr, context->Pc ); + + return STATUS_SUCCESS; +} + +#ifdef linux +struct iterate_data +{ + ULONG_PTR ip; + int failed; + struct exidx_entry *entry; +}; + +static int contains_addr(struct dl_phdr_info *info, const ElfW(Phdr) *phdr, struct iterate_data *data) +{ + if (phdr->p_type != PT_LOAD) + return 0; + return data->ip >= info->dlpi_addr + phdr->p_vaddr && data->ip < info->dlpi_addr + phdr->p_vaddr + phdr->p_memsz; +} + +static int check_exidx(struct dl_phdr_info *info, size_t info_size, void *arg) { + struct iterate_data *data = arg; + int i; + int found_addr; + const ElfW(Phdr) *exidx = NULL; + struct exidx_entry *begin, *end; + + if (info->dlpi_phnum == 0 || data->ip < info->dlpi_addr || data->failed) + return 0; + + found_addr = 0; + for (i = 0; i < info->dlpi_phnum; i++) + { + const ElfW(Phdr) *phdr = &info->dlpi_phdr[i]; + if (contains_addr(info, phdr, data)) + found_addr = 1; + if (phdr->p_type == SHT_ARM_EXIDX) + exidx = phdr; + } + + if (!found_addr || !exidx) + { + if (found_addr) + { + TRACE("found matching address in %s, but no EXIDX\n", info->dlpi_name); + data->failed = 1; + } + return 0; + } + + begin = (struct exidx_entry *)(info->dlpi_addr + exidx->p_vaddr); + end = (struct exidx_entry *)(info->dlpi_addr + exidx->p_vaddr + exidx->p_memsz); + if (data->ip < prel31_to_abs(&begin->addr)) + { + TRACE("%lx before EXIDX start at %x\n", data->ip, prel31_to_abs(&begin->addr)); + data->failed = 1; + return 0; + } + + while (begin + 1 < end) + { + struct exidx_entry *mid = begin + (end - begin)/2; + uint32_t abs_addr = prel31_to_abs(&mid->addr); + if (abs_addr > data->ip) + { + end = mid; + } + else if (abs_addr < data->ip) + { + begin = mid; + } + else + { + begin = mid; + end = mid + 1; + } + } + + data->entry = begin; + TRACE("found %lx in %s, base %x, entry %p with addr %x (rel %x) data %x\n", + data->ip, info->dlpi_name, info->dlpi_addr, begin, + prel31_to_abs(&begin->addr), + prel31_to_abs(&begin->addr) - info->dlpi_addr, begin->data); + return 1; +} + +static const struct exidx_entry *find_exidx_entry( void *ip ) +{ + struct iterate_data data = {}; + + data.ip = (ULONG_PTR)ip; + data.failed = 0; + data.entry = NULL; + dl_iterate_phdr(check_exidx, &data); + + return data.entry; +} +#endif + #ifdef HAVE_LIBUNWIND - DWORD ip = context->Pc - (dispatch->ControlPcIsUnwound ? 2 : 0); +static NTSTATUS libunwind_virtual_unwind( DWORD ip, DWORD *frame, CONTEXT *context, + PEXCEPTION_ROUTINE *handler, void **handler_data ) +{ unw_context_t unw_context; unw_cursor_t cursor; unw_proc_info_t info; @@ -261,8 +672,8 @@ NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispa TRACE( "no info found for %x ip %x-%x, %s\n", ip, info.start_ip, info.end_ip, status == STATUS_SUCCESS ? "assuming leaf function" : "error, stuck" ); - dispatch->LanguageHandler = NULL; - dispatch->EstablisherFrame = context->Sp; + *handler = NULL; + *frame = context->Sp; context->Pc = context->Lr; context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL; return status; @@ -279,9 +690,9 @@ NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispa return STATUS_INVALID_DISPOSITION; }
- dispatch->LanguageHandler = (void *)info.handler; - dispatch->HandlerData = (void *)info.lsda; - dispatch->EstablisherFrame = context->Sp; + *handler = (void *)info.handler; + *handler_data = (void *)info.lsda; + *frame = context->Sp;
for (i = 0; i <= 12; i++) unw_get_reg( &cursor, UNW_ARM_R0 + i, (unw_word_t *)&(&context->R0)[i] ); @@ -297,7 +708,7 @@ NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispa * individual values, thus do that manually here. * (The function we unwind to might be a leaf function that hasn't * backed up its own original Lr value on the stack.) */ - const DWORD *orig_lr = (const DWORD *) dispatch->EstablisherFrame; + const DWORD *orig_lr = (const DWORD *) *frame; context->Lr = *orig_lr; }
@@ -311,6 +722,25 @@ NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispa TRACE(" r12=%08x sp=%08x lr=%08x pc=%08x\n", context->R12, context->Sp, context->Lr, context->Pc ); return STATUS_SUCCESS; +} +#endif + +/*********************************************************************** + * unwind_builtin_dll + */ +NTSTATUS CDECL unwind_builtin_dll( ULONG type, struct _DISPATCHER_CONTEXT *dispatch, CONTEXT *context ) +{ + DWORD ip = context->Pc - (dispatch->ControlPcIsUnwound ? 2 : 0); +#ifdef linux + const struct exidx_entry *entry = find_exidx_entry( (void *)ip ); + + if (entry) + return ehabi_virtual_unwind( ip, &dispatch->EstablisherFrame, context, entry, + &dispatch->LanguageHandler, &dispatch->HandlerData ); +#endif +#ifdef HAVE_LIBUNWIND + return libunwind_virtual_unwind( ip, &dispatch->EstablisherFrame, context, + &dispatch->LanguageHandler, &dispatch->HandlerData ); #else ERR("libunwind not available, unable to unwind\n"); return STATUS_INVALID_DISPOSITION;
On Fri Nov 4 20:35:01 2022 +0000, Alexandre Julliard wrote:
It looks reasonable, but would you mind using the same brace placement as the rest of the code?
Sure, fixed that now (and rebased it) - sorry about forgetting that detail originally.