ntdll: Don't restore PC from LR after unwinding through frame switches.

Fixes unwinds from within a consolidation callback on ARM64EC, as the
context frame could be x64 with UNWOUND_TO_CALL set, in which case LR
contains garbage.

See: 71abc26cf8/src/coreclr/unwinder/arm64/unwinder.cpp (L2334)
This commit is contained in:
Billy Laws 2024-05-06 14:29:25 +00:00 committed by Alexandre Julliard
parent 646729f3f2
commit a2b4924db5
2 changed files with 39 additions and 29 deletions

View file

@ -1566,6 +1566,8 @@ struct unwind_test_arm64
unsigned int nb_results;
int unwound_clear;
int last_set_reg_ptr;
int stack_value_index;
ULONG64 stack_value;
};
enum regs_arm64
@ -1630,6 +1632,7 @@ static void call_virtual_unwind_arm64( void *code_mem, int testnum, const struct
memset( &context, 0x55, sizeof(context) );
memset( &unset_reg, 0x55, sizeof(unset_reg) );
for (j = 0; j < 256; j++) fake_stack[j] = j * 8;
if (test->stack_value_index != -1) fake_stack[test->stack_value_index] = test->stack_value;
context.Sp = (ULONG_PTR)fake_stack;
context.Lr = (ULONG_PTR)ORIG_LR;
@ -2598,27 +2601,27 @@ static void test_virtual_unwind_arm64(void)
static const struct unwind_test_arm64 tests[] =
{
#define TEST(func, unwind, size, results, unwound_clear, last_ptr) \
{ func, sizeof(func), unwind, size, results, ARRAY_SIZE(results), unwound_clear, last_ptr }
TEST(function_0, unwind_info_0, sizeof(unwind_info_0), results_0, 0, 0),
TEST(function_1, unwind_info_1, 0, results_1, 0, 0),
TEST(function_2, unwind_info_2, sizeof(unwind_info_2), results_2, 1, 0),
TEST(function_3, unwind_info_3, sizeof(unwind_info_3), results_3, 1, x28),
TEST(function_4, unwind_info_4, sizeof(unwind_info_4), results_4, 0, 0),
TEST(function_5, unwind_info_5, sizeof(unwind_info_5), results_5, 0, 0),
TEST(function_6, unwind_info_6, 0, results_6, 0, 0),
TEST(function_7, unwind_info_7, 0, results_7, 0, 0),
TEST(function_8, unwind_info_8, 0, results_8, 0, 0),
TEST(function_9, unwind_info_9, 0, results_9, 0, 0),
TEST(function_10, unwind_info_10, 0, results_10, 0, 0),
TEST(function_11, unwind_info_11, 0, results_11, 0, 0),
TEST(function_12, unwind_info_12, 0, results_12, 0, 0),
TEST(function_13, unwind_info_13, 0, results_13, 0, 0),
TEST(function_14, unwind_info_14, sizeof(unwind_info_14), results_14, 0, 0),
TEST(function_15, unwind_info_15, sizeof(unwind_info_15), results_15, 0, 0),
TEST(function_16, unwind_info_16, sizeof(unwind_info_16), results_16, 1, x18),
TEST(function_17, unwind_info_17, sizeof(unwind_info_17), results_17, 2, 0),
TEST(function_18, NULL, 0, results_18, 0, 0),
#define TEST(func, unwind, size, results, unwound_clear, last_ptr, stack_value_index, stack_value) \
{ func, sizeof(func), unwind, size, results, ARRAY_SIZE(results), unwound_clear, last_ptr, stack_value_index, stack_value }
TEST(function_0, unwind_info_0, sizeof(unwind_info_0), results_0, 0, 0, -1, 0),
TEST(function_1, unwind_info_1, 0, results_1, 0, 0, -1, 0),
TEST(function_2, unwind_info_2, sizeof(unwind_info_2), results_2, 1, 0, -1, 0),
TEST(function_3, unwind_info_3, sizeof(unwind_info_3), results_3, 2, x28, 0, CONTEXT_ARM64_UNWOUND_TO_CALL),
TEST(function_4, unwind_info_4, sizeof(unwind_info_4), results_4, 0, 0, -1, 0),
TEST(function_5, unwind_info_5, sizeof(unwind_info_5), results_5, 0, 0, -1, 0),
TEST(function_6, unwind_info_6, 0, results_6, 0, 0, -1, 0),
TEST(function_7, unwind_info_7, 0, results_7, 0, 0, -1, 0),
TEST(function_8, unwind_info_8, 0, results_8, 0, 0, -1, 0),
TEST(function_9, unwind_info_9, 0, results_9, 0, 0, -1, 0),
TEST(function_10, unwind_info_10, 0, results_10, 0, 0, -1, 0),
TEST(function_11, unwind_info_11, 0, results_11, 0, 0, -1, 0),
TEST(function_12, unwind_info_12, 0, results_12, 0, 0, -1, 0),
TEST(function_13, unwind_info_13, 0, results_13, 0, 0, -1, 0),
TEST(function_14, unwind_info_14, sizeof(unwind_info_14), results_14, 0, 0, -1, 0),
TEST(function_15, unwind_info_15, sizeof(unwind_info_15), results_15, 0, 0, -1, 0),
TEST(function_16, unwind_info_16, sizeof(unwind_info_16), results_16, 2, x18, 6, CONTEXT_ARM64_UNWOUND_TO_CALL),
TEST(function_17, unwind_info_17, sizeof(unwind_info_17), results_17, 2, 0, -1, 0),
TEST(function_18, NULL, 0, results_18, 0, 0, -1, 0),
#undef TEST
};
unsigned int i;

View file

@ -402,7 +402,8 @@ static void do_pac_auth( ARM64_NT_CONTEXT *context )
}
static void process_unwind_codes( BYTE *ptr, BYTE *end, ARM64_NT_CONTEXT *context,
KNONVOLATILE_CONTEXT_POINTERS_ARM64 *ptrs, int skip )
KNONVOLATILE_CONTEXT_POINTERS_ARM64 *ptrs, int skip,
BOOLEAN *final_pc_from_lr )
{
unsigned int i, val, len, save_next = 2;
@ -482,6 +483,7 @@ static void process_unwind_codes( BYTE *ptr, BYTE *end, ARM64_NT_CONTEXT *contex
context->Pc = ((DWORD64 *)context->Sp)[1];
context->Sp = ((DWORD64 *)context->Sp)[0];
context->ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL;
*final_pc_from_lr = FALSE;
}
else if (*ptr == 0xea) /* MSFT_OP_CONTEXT */
{
@ -494,6 +496,7 @@ static void process_unwind_codes( BYTE *ptr, BYTE *end, ARM64_NT_CONTEXT *contex
for (i = 19; i < 29; i++) (&ptrs->X19)[i - 19] = &src_ctx->X[i];
for (i = 8; i < 16; i++) (&ptrs->D8)[i - 8] = &src_ctx->V[i].Low;
}
*final_pc_from_lr = FALSE;
}
else if (*ptr == 0xeb) /* MSFT_OP_EC_CONTEXT */
{
@ -502,11 +505,13 @@ static void process_unwind_codes( BYTE *ptr, BYTE *end, ARM64_NT_CONTEXT *contex
context_x64_to_arm( context, src_ctx );
context->ContextFlags = flags | (src_ctx->ContextFlags & CONTEXT_UNWOUND_TO_CALL);
if (ptrs) for (i = 8; i < 16; i++) (&ptrs->D8)[i - 8] = &src_ctx->V[i].Low;
*final_pc_from_lr = FALSE;
}
else if (*ptr == 0xec) /* MSFT_OP_CLEAR_UNWOUND_TO_CALL */
{
context->Pc = context->Lr;
context->ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL;
*final_pc_from_lr = FALSE;
}
else if (*ptr == 0xfc) /* pac_sign_lr */
{
@ -675,7 +680,8 @@ static void *unwind_packed_data( ULONG_PTR base, ULONG_PTR pc, ARM64_RUNTIME_FUN
static void *unwind_full_data( ULONG_PTR base, ULONG_PTR pc, ARM64_RUNTIME_FUNCTION *func,
ARM64_NT_CONTEXT *context, PVOID *handler_data,
KNONVOLATILE_CONTEXT_POINTERS_ARM64 *ptrs )
KNONVOLATILE_CONTEXT_POINTERS_ARM64 *ptrs,
BOOLEAN *final_pc_from_lr )
{
IMAGE_ARM64_RUNTIME_FUNCTION_ENTRY_XDATA *info;
struct unwind_info_epilog *info_epilog;
@ -711,7 +717,7 @@ static void *unwind_full_data( ULONG_PTR base, ULONG_PTR pc, ARM64_RUNTIME_FUNCT
len = get_sequence_len( data, end );
if (offset < len)
{
process_unwind_codes( data, end, context, ptrs, len - offset );
process_unwind_codes( data, end, context, ptrs, len - offset, final_pc_from_lr );
return NULL;
}
}
@ -728,7 +734,7 @@ static void *unwind_full_data( ULONG_PTR base, ULONG_PTR pc, ARM64_RUNTIME_FUNCT
len = get_sequence_len( ptr, end );
if (offset <= info_epilog[i].offset + len)
{
process_unwind_codes( ptr, end, context, ptrs, offset - info_epilog[i].offset );
process_unwind_codes( ptr, end, context, ptrs, offset - info_epilog[i].offset, final_pc_from_lr );
return NULL;
}
}
@ -740,12 +746,12 @@ static void *unwind_full_data( ULONG_PTR base, ULONG_PTR pc, ARM64_RUNTIME_FUNCT
len = get_sequence_len( ptr, end ) + 1;
if (offset >= info->FunctionLength - len)
{
process_unwind_codes( ptr, end, context, ptrs, offset - (info->FunctionLength - len) );
process_unwind_codes( ptr, end, context, ptrs, offset - (info->FunctionLength - len), final_pc_from_lr );
return NULL;
}
}
process_unwind_codes( data, end, context, ptrs, 0 );
process_unwind_codes( data, end, context, ptrs, 0, final_pc_from_lr );
/* get handler since we are inside the main code */
if (info->ExceptionDataPresent)
@ -797,6 +803,7 @@ NTSTATUS WINAPI RtlVirtualUnwind2( ULONG type, ULONG_PTR base, ULONG_PTR pc,
ULONG_PTR *limit_low, ULONG_PTR *limit_high,
PEXCEPTION_ROUTINE *handler_ret, ULONG flags )
{
BOOLEAN final_pc_from_lr = TRUE;
TRACE( "type %lx base %I64x pc %I64x rva %I64x sp %I64x\n", type, base, pc, pc - base, context->Sp );
if (limit_low || limit_high) FIXME( "limits not supported\n" );
@ -810,9 +817,9 @@ NTSTATUS WINAPI RtlVirtualUnwind2( ULONG type, ULONG_PTR base, ULONG_PTR pc,
else if (func->Flag)
*handler_ret = unwind_packed_data( base, pc, func, context, ctx_ptr );
else
*handler_ret = unwind_full_data( base, pc, func, context, handler_data, ctx_ptr );
*handler_ret = unwind_full_data( base, pc, func, context, handler_data, ctx_ptr, &final_pc_from_lr );
if (context->ContextFlags & CONTEXT_UNWOUND_TO_CALL) context->Pc = context->Lr;
if (final_pc_from_lr) context->Pc = context->Lr;
TRACE( "ret: pc=%I64x lr=%I64x sp=%I64x handler=%p\n", context->Pc, context->Lr, context->Sp, *handler_ret );
*frame_ret = context->Sp;