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: https://github.com/dotnet/runtime/blob/71abc26cf8f14f5d15665d7f580365ddfa296...
-- v3: ntdll: Don't restore PC from LR after unwinding through frame switches.
From: Billy Laws blaws05@gmail.com
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: https://github.com/dotnet/runtime/blob/71abc26cf8f14f5d15665d7f580365ddfa296... --- dlls/ntdll/tests/unwind.c | 45 +++++++++++++++++++++------------------ dlls/ntdll/unwind.c | 23 +++++++++++++------- 2 files changed, 39 insertions(+), 29 deletions(-)
diff --git a/dlls/ntdll/tests/unwind.c b/dlls/ntdll/tests/unwind.c index 61fbed1e017..daa90c19a11 100644 --- a/dlls/ntdll/tests/unwind.c +++ b/dlls/ntdll/tests/unwind.c @@ -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; diff --git a/dlls/ntdll/unwind.c b/dlls/ntdll/unwind.c index 5d830536688..04c843baefd 100644 --- a/dlls/ntdll/unwind.c +++ b/dlls/ntdll/unwind.c @@ -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;