diff --git a/dlls/ntdll/unix/signal_arm.c b/dlls/ntdll/unix/signal_arm.c index add01687c4ba524ce8503f80adb66f3fcc8770c1..9300ea2adc3dfc7f3f79da3c49088afa4ec2409b 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 == PT_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;