1
0
Fork 0
mirror of synced 2025-03-07 03:53:26 +01:00

ntdll: Remove support for unwinding ELF dlls on ARM.

This commit is contained in:
Alexandre Julliard 2024-02-22 17:38:28 +01:00
parent 654c03d131
commit 2778e45421

View file

@ -256,444 +256,13 @@ static BOOL is_inside_syscall( ucontext_t *sigcontext )
(char *)SP_sig(sigcontext) <= (char *)arm_thread_data()->syscall_frame);
}
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->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;
}
/***********************************************************************
* ehabi_virtual_unwind
*/
static NTSTATUS ehabi_virtual_unwind( UINT 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;
UINT func_begin = prel31_to_abs(&entry->addr);
*frame = context->Sp;
TRACE( "ip %#x function %#x\n", ip, 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 */
if (!set_pc)
{
context->Pc = context->Lr;
context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
}
else context->ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL;
TRACE( "next function pc=%08lx\n", context->Pc );
TRACE(" r0=%08lx r1=%08lx r2=%08lx r3=%08lx\n",
context->R0, context->R1, context->R2, context->R3 );
TRACE(" r4=%08lx r5=%08lx r6=%08lx r7=%08lx\n",
context->R4, context->R5, context->R6, context->R7 );
TRACE(" r8=%08lx r9=%08lx r10=%08lx r11=%08lx\n",
context->R8, context->R9, context->R10, context->R11 );
TRACE(" r12=%08lx sp=%08lx lr=%08lx pc=%08lx\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
/***********************************************************************
* unwind_builtin_dll
*/
NTSTATUS unwind_builtin_dll( void *args )
{
struct unwind_builtin_dll_params *params = args;
DISPATCHER_CONTEXT *dispatch = params->dispatch;
CONTEXT *context = params->context;
DWORD ip = context->Pc - (dispatch->ControlPcIsUnwound ? 2 : 0);
#ifdef linux
const struct exidx_entry *entry = find_exidx_entry( (void *)ip );
NTSTATUS status;
if (entry)
return ehabi_virtual_unwind( ip, &dispatch->EstablisherFrame, context, entry,
&dispatch->LanguageHandler, &dispatch->HandlerData );
status = context->Pc != context->Lr ?
STATUS_SUCCESS : STATUS_INVALID_DISPOSITION;
TRACE( "no info found for %lx, %s\n", ip, status == STATUS_SUCCESS ?
"assuming leaf function" : "error, stuck" );
dispatch->LanguageHandler = NULL;
dispatch->EstablisherFrame = context->Sp;
context->Pc = context->Lr;
context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
return status;
#else
ERR("ARM EHABI unwinding available, unable to unwind\n");
return STATUS_INVALID_DISPOSITION;
#endif
return STATUS_UNSUCCESSFUL;
}