diff --git a/dlls/ntdll/signal_arm64.c b/dlls/ntdll/signal_arm64.c index 23c01789293..094041e93fd 100644 --- a/dlls/ntdll/signal_arm64.c +++ b/dlls/ntdll/signal_arm64.c @@ -378,81 +378,41 @@ __ASM_GLOBAL_FUNC( KiUserCallbackDispatcher, /********************************************************************** - * call_consolidate_callback + * consolidate_callback * * Wrapper function to call a consolidate callback from a fake frame. * If the callback executes RtlUnwindEx (like for example done in C++ handlers), * we have to skip all frames which were already processed. To do that we * trick the unwinding functions into thinking the call came from somewhere - * else. All CFI instructions are either DW_CFA_def_cfa_expression or - * DW_CFA_expression, and the expressions have the following format: - * - * DW_OP_breg31; sleb128 | Load x31 + struct member offset - * [DW_OP_deref] | Dereference, only for CFA + * else. */ -extern void * WINAPI call_consolidate_callback( CONTEXT *context, - void *(CALLBACK *callback)(EXCEPTION_RECORD *), - EXCEPTION_RECORD *rec, - TEB *teb ); -__ASM_GLOBAL_FUNC( call_consolidate_callback, - "stp x29, x30, [sp, #-0x30]!\n\t" - __ASM_CFI(".cfi_def_cfa_offset 48\n\t") - __ASM_CFI(".cfi_offset 29, -48\n\t") - __ASM_CFI(".cfi_offset 30, -40\n\t") - __ASM_SEH(".seh_nop\n\t") - "stp x1, x2, [sp, #0x10]\n\t" - __ASM_SEH(".seh_nop\n\t") - "str x3, [sp, #0x20]\n\t" - __ASM_SEH(".seh_nop\n\t") - "mov x29, sp\n\t" - __ASM_CFI(".cfi_def_cfa_register 29\n\t") - __ASM_SEH(".seh_nop\n\t") - __ASM_CFI(".cfi_remember_state\n\t") - /* Memcpy the context onto the stack */ +void WINAPI DECLSPEC_NORETURN consolidate_callback( CONTEXT *context, + void *(CALLBACK *callback)(EXCEPTION_RECORD *), + EXCEPTION_RECORD *rec ); +__ASM_GLOBAL_FUNC( consolidate_callback, + "stp x29, x30, [sp, #-16]!\n\t" + ".seh_save_fplr_x 16\n\t" "sub sp, sp, #0x390\n\t" - __ASM_SEH(".seh_nop\n\t") - "mov x1, x0\n\t" - __ASM_SEH(".seh_nop\n\t") - "mov x0, sp\n\t" - __ASM_SEH(".seh_nop\n\t") - "mov x2, #0x390\n\t" - __ASM_SEH(".seh_nop\n\t") - "bl " __ASM_NAME("memcpy") "\n\t" - __ASM_CFI(".cfi_def_cfa 31, 0\n\t") - __ASM_CFI(".cfi_escape 0x0f,0x04,0x8f,0x80,0x02,0x06\n\t") /* CFA, DW_OP_breg31 + 0x100, DW_OP_deref */ - __ASM_CFI(".cfi_escape 0x10,0x13,0x03,0x8f,0xa0,0x01\n\t") /* x19, DW_OP_breg31 + 0xA0 */ - __ASM_CFI(".cfi_escape 0x10,0x14,0x03,0x8f,0xa8,0x01\n\t") /* x20 */ - __ASM_CFI(".cfi_escape 0x10,0x15,0x03,0x8f,0xb0,0x01\n\t") /* x21 */ - __ASM_CFI(".cfi_escape 0x10,0x16,0x03,0x8f,0xb8,0x01\n\t") /* x22 */ - __ASM_CFI(".cfi_escape 0x10,0x17,0x03,0x8f,0xc0,0x01\n\t") /* x23 */ - __ASM_CFI(".cfi_escape 0x10,0x18,0x03,0x8f,0xc8,0x01\n\t") /* x24 */ - __ASM_CFI(".cfi_escape 0x10,0x19,0x03,0x8f,0xd0,0x01\n\t") /* x25 */ - __ASM_CFI(".cfi_escape 0x10,0x1a,0x03,0x8f,0xd8,0x01\n\t") /* x26 */ - __ASM_CFI(".cfi_escape 0x10,0x1b,0x03,0x8f,0xe0,0x01\n\t") /* x27 */ - __ASM_CFI(".cfi_escape 0x10,0x1c,0x03,0x8f,0xe8,0x01\n\t") /* x28 */ - __ASM_CFI(".cfi_escape 0x10,0x1d,0x03,0x8f,0xf0,0x01\n\t") /* x29 */ - __ASM_CFI(".cfi_escape 0x10,0x1e,0x03,0x8f,0xf8,0x01\n\t") /* x30 */ - __ASM_CFI(".cfi_escape 0x10,0x48,0x03,0x8f,0x90,0x03\n\t") /* d8 */ - __ASM_CFI(".cfi_escape 0x10,0x49,0x03,0x8f,0x98,0x03\n\t") /* d9 */ - __ASM_CFI(".cfi_escape 0x10,0x4a,0x03,0x8f,0xa0,0x03\n\t") /* d10 */ - __ASM_CFI(".cfi_escape 0x10,0x4b,0x03,0x8f,0xa8,0x03\n\t") /* d11 */ - __ASM_CFI(".cfi_escape 0x10,0x4c,0x03,0x8f,0xb0,0x03\n\t") /* d12 */ - __ASM_CFI(".cfi_escape 0x10,0x4d,0x03,0x8f,0xb8,0x03\n\t") /* d13 */ - __ASM_CFI(".cfi_escape 0x10,0x4e,0x03,0x8f,0xc0,0x03\n\t") /* d14 */ - __ASM_CFI(".cfi_escape 0x10,0x4f,0x03,0x8f,0xc8,0x03\n\t") /* d15 */ - __ASM_SEH(".seh_context\n\t") - __ASM_SEH(".seh_endprologue\n\t") - "ldp x1, x2, [x29, #0x10]\n\t" - "ldr x18, [x29, #0x20]\n\t" - "mov x0, x2\n\t" + ".seh_stackalloc 0x390\n\t" + ".seh_endprologue\n\t" + "mov x4, sp\n\t" + /* copy the context onto the stack */ + "mov x5, #0x390/16\n" + "1:\tldp x6, x7, [x0], #16\n\t" + "stp x6, x7, [x4], #16\n\t" + "subs x5, x5, #1\n\t" + "b.ne 1b\n\t" + "mov x0, x2\n\t" + "b invoke_callback" ) +__ASM_GLOBAL_FUNC( invoke_callback, + ".seh_context\n\t" + ".seh_endprologue\n\t" "blr x1\n\t" - "mov sp, x29\n\t" - __ASM_CFI(".cfi_restore_state\n\t") - "ldp x29, x30, [sp], #48\n\t" - __ASM_CFI(".cfi_restore 30\n\t") - __ASM_CFI(".cfi_restore 29\n\t") - __ASM_CFI(".cfi_def_cfa 31, 0\n\t") - "ret") + "str x0, [sp, #0x108]\n\t" /* context->Pc */ + "mov x0, sp\n\t" + "mov w1, #0\n\t" + "b NtContinue" ) + /******************************************************************* * RtlRestoreContext (NTDLL.@) @@ -477,7 +437,7 @@ void CDECL RtlRestoreContext( CONTEXT *context, EXCEPTION_RECORD *rec ) context->X27 = jmp->X27; context->X28 = jmp->X28; context->Fp = jmp->Fp; - context->Lr = jmp->Lr; + context->Pc = jmp->Lr; context->Sp = jmp->Sp; context->Fpcr = jmp->Fpcr; context->Fpsr = jmp->Fpsr; @@ -489,9 +449,7 @@ void CDECL RtlRestoreContext( CONTEXT *context, EXCEPTION_RECORD *rec ) { PVOID (CALLBACK *consolidate)(EXCEPTION_RECORD *) = (void *)rec->ExceptionInformation[0]; TRACE( "calling consolidate callback %p (rec=%p)\n", consolidate, rec ); - rec->ExceptionInformation[10] = (ULONG_PTR)&context->X19; - - context->Pc = (ULONG64)call_consolidate_callback( context, consolidate, rec, NtCurrentTeb() ); + consolidate_callback( context, consolidate, rec ); } /* hack: remove no longer accessible TEB frames */ @@ -635,8 +593,12 @@ void WINAPI RtlUnwindEx( PVOID end_frame, PVOID target_ip, EXCEPTION_RECORD *rec *context = new_context; } + if (rec->ExceptionCode != STATUS_UNWIND_CONSOLIDATE) + context->Pc = (ULONG64)target_ip; + else if (rec->ExceptionInformation[10] == -1) + rec->ExceptionInformation[10] = (ULONG_PTR)&nonvol_regs; + context->X0 = (ULONG64)retval; - context->Pc = (ULONG64)target_ip; RtlRestoreContext(context, rec); } diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index eb8e92d8292..e28b899dd8a 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -86,6 +86,7 @@ static BOOL (WINAPI *pWaitForDebugEventEx)(DEBUG_EVENT *, DWORD); static VOID (WINAPI *pRtlUnwindEx)(VOID*, VOID*, EXCEPTION_RECORD*, VOID*, CONTEXT*, UNWIND_HISTORY_TABLE*); static BOOLEAN (CDECL *pRtlAddFunctionTable)(RUNTIME_FUNCTION*, DWORD, DWORD64); static BOOLEAN (CDECL *pRtlDeleteFunctionTable)(RUNTIME_FUNCTION*); +static VOID (CDECL *pRtlRestoreContext)(CONTEXT*, EXCEPTION_RECORD*); #endif static void *pKiUserApcDispatcher; @@ -142,7 +143,6 @@ typedef struct _UNWIND_INFO static BOOL is_arm64ec; static EXCEPTION_DISPOSITION (WINAPI *p__C_specific_handler)(EXCEPTION_RECORD*, ULONG64, CONTEXT*, DISPATCHER_CONTEXT*); -static VOID (CDECL *pRtlRestoreContext)(CONTEXT*, EXCEPTION_RECORD*); static NTSTATUS (WINAPI *pRtlWow64GetThreadContext)(HANDLE, WOW64_CONTEXT *); static NTSTATUS (WINAPI *pRtlWow64SetThreadContext)(HANDLE, const WOW64_CONTEXT *); static NTSTATUS (WINAPI *pRtlWow64GetCpuAreaInfo)(WOW64_CPURESERVED*,ULONG,WOW64_CPU_AREA_INFO*); @@ -7324,6 +7324,192 @@ static void test_rtlraiseexception(void) run_rtlraiseexception_test(EXCEPTION_INVALID_HANDLE); } + +static LONG consolidate_dummy_called; +static LONG pass; + +static const DWORD call_rtlunwind[] = +{ + 0xa88150f3, /* stp x19, x20, [x7], #0x10 */ + 0xa88158f5, /* stp x21, x22, [x7], #0x10 */ + 0xa88160f7, /* stp x23, x24, [x7], #0x10 */ + 0xa88168f9, /* stp x25, x26, [x7], #0x10 */ + 0xa88170fb, /* stp x27, x28, [x7], #0x10 */ + 0xf80084fd, /* str x29, [x7], #0x8 */ + 0x6c8124e8, /* stp d8, d9, [x7], #0x10 */ + 0x6c812cea, /* stp d10, d11, [x7], #0x10 */ + 0x6c8134ec, /* stp d12, d13, [x7], #0x10 */ + 0x6c813cee, /* stp d14, d15, [x7], #0x10 */ + 0xd61f00c0, /* br x6 */ +}; + +static PVOID CALLBACK test_consolidate_dummy(EXCEPTION_RECORD *rec) +{ + CONTEXT *ctx = (CONTEXT *)rec->ExceptionInformation[1]; + DWORD64 *saved_regs = (DWORD64 *)rec->ExceptionInformation[3]; + DISPATCHER_CONTEXT_NONVOLREG_ARM64 *regs; + int i; + + switch (InterlockedIncrement(&consolidate_dummy_called)) + { + case 1: /* RtlRestoreContext */ + ok(ctx->Pc == 0xdeadbeef, "RtlRestoreContext wrong Pc, expected: 0xdeadbeef, got: %Ix\n", ctx->Pc); + ok( rec->ExceptionInformation[10] == -1, "wrong info %Ix\n", rec->ExceptionInformation[10] ); + break; + case 2: /* RtlUnwindEx */ + ok(ctx->Pc != 0xdeadbeef, "RtlUnwindEx wrong Pc, got: %Ix\n", ctx->Pc ); + ok( rec->ExceptionInformation[10] != -1, "wrong info %Ix\n", rec->ExceptionInformation[10] ); + regs = (DISPATCHER_CONTEXT_NONVOLREG_ARM64 *)rec->ExceptionInformation[10]; + for (i = 0; i < 11; i++) + ok( saved_regs[i] == regs->GpNvRegs[i], "wrong reg X%u, expected: %Ix, got: %Ix\n", + 19 + i, saved_regs[i], regs->GpNvRegs[i] ); + for (i = 0; i < 8; i++) + ok( saved_regs[i + 11] == *(DWORD64 *)®s->FpNvRegs[i], + "wrong reg D%u, expected: %Ix, got: %Ix\n", + i + 8, saved_regs[i + 11], *(DWORD64 *)®s->FpNvRegs[i] ); + break; + } + return (PVOID)rec->ExceptionInformation[2]; +} + +static void test_restore_context(void) +{ + EXCEPTION_RECORD rec; + _JUMP_BUFFER buf; + CONTEXT ctx; + int i; + + if (!pRtlUnwindEx || !pRtlRestoreContext || !pRtlCaptureContext) + { + skip("RtlUnwindEx/RtlCaptureContext/RtlRestoreContext not found\n"); + return; + } + + /* test simple case of capture and restore context */ + pass = 0; + InterlockedIncrement(&pass); /* interlocked to prevent compiler from moving after capture */ + pRtlCaptureContext(&ctx); + if (InterlockedIncrement(&pass) == 2) /* interlocked to prevent compiler from moving before capture */ + { + pRtlRestoreContext(&ctx, NULL); + ok(0, "shouldn't be reached\n"); + } + else + ok(pass < 4, "unexpected pass %ld\n", pass); + + /* test with jmp using RtlRestoreContext */ + pass = 0; + InterlockedIncrement(&pass); + RtlCaptureContext(&ctx); + InterlockedIncrement(&pass); /* only called once */ + setjmp((_JBTYPE *)&buf); + InterlockedIncrement(&pass); + if (pass == 3) + { + rec.ExceptionCode = STATUS_LONGJUMP; + rec.NumberParameters = 1; + rec.ExceptionInformation[0] = (DWORD64)&buf; + /* uses buf.Pc instead of ctx.Pc */ + pRtlRestoreContext(&ctx, &rec); + ok(0, "shouldn't be reached\n"); + } + else if (pass == 4) + { + ok(buf.X19 == ctx.X19, "longjmp failed for X19, expected: %Ix, got: %Ix\n", buf.X19, ctx.X19); + ok(buf.X20 == ctx.X20, "longjmp failed for X20, expected: %Ix, got: %Ix\n", buf.X20, ctx.X20); + ok(buf.X21 == ctx.X21, "longjmp failed for X21, expected: %Ix, got: %Ix\n", buf.X21, ctx.X21); + ok(buf.X22 == ctx.X22, "longjmp failed for X22, expected: %Ix, got: %Ix\n", buf.X22, ctx.X22); + ok(buf.X23 == ctx.X23, "longjmp failed for X23, expected: %Ix, got: %Ix\n", buf.X23, ctx.X23); + ok(buf.X24 == ctx.X24, "longjmp failed for X24, expected: %Ix, got: %Ix\n", buf.X24, ctx.X24); + ok(buf.X25 == ctx.X25, "longjmp failed for X25, expected: %Ix, got: %Ix\n", buf.X25, ctx.X25); + ok(buf.X26 == ctx.X26, "longjmp failed for X26, expected: %Ix, got: %Ix\n", buf.X26, ctx.X26); + ok(buf.X27 == ctx.X27, "longjmp failed for X27, expected: %Ix, got: %Ix\n", buf.X27, ctx.X27); + ok(buf.X28 == ctx.X28, "longjmp failed for X28, expected: %Ix, got: %Ix\n", buf.X28, ctx.X28); + ok(buf.Fp == ctx.Fp, "longjmp failed for Fp, expected: %Ix, got: %Ix\n", buf.Fp, ctx.Fp); + for (i = 0; i < 8; i++) + ok(buf.D[i] == ctx.V[i + 8].D[0], "longjmp failed for D%u, expected: %g, got: %g\n", + i + 8, buf.D[i], ctx.V[i + 8].D[0]); + pRtlRestoreContext(&ctx, &rec); + ok(0, "shouldn't be reached\n"); + } + else + ok(pass == 5, "unexpected pass %ld\n", pass); + + /* test with jmp through RtlUnwindEx */ + pass = 0; + InterlockedIncrement(&pass); + pRtlCaptureContext(&ctx); + InterlockedIncrement(&pass); /* only called once */ + setjmp((_JBTYPE *)&buf); + InterlockedIncrement(&pass); + if (pass == 3) + { + rec.ExceptionCode = STATUS_LONGJUMP; + rec.NumberParameters = 1; + rec.ExceptionInformation[0] = (DWORD64)&buf; + + /* uses buf.Pc instead of bogus 0xdeadbeef */ + pRtlUnwindEx((void*)buf.Sp, (void*)0xdeadbeef, &rec, NULL, &ctx, NULL); + ok(0, "shouldn't be reached\n"); + } + else + ok(pass == 4, "unexpected pass %ld\n", pass); + + + /* test with consolidate */ + pass = 0; + InterlockedIncrement(&pass); + RtlCaptureContext(&ctx); + InterlockedIncrement(&pass); + if (pass == 2) + { + rec.ExceptionCode = STATUS_UNWIND_CONSOLIDATE; + rec.NumberParameters = 3; + rec.ExceptionInformation[0] = (DWORD64)test_consolidate_dummy; + rec.ExceptionInformation[1] = (DWORD64)&ctx; + rec.ExceptionInformation[2] = ctx.Pc; + rec.ExceptionInformation[10] = -1; + ctx.Pc = 0xdeadbeef; + + pRtlRestoreContext(&ctx, &rec); + ok(0, "shouldn't be reached\n"); + } + else if (pass == 3) + ok(consolidate_dummy_called == 1, "test_consolidate_dummy not called\n"); + else + ok(0, "unexpected pass %ld\n", pass); + + /* test with consolidate through RtlUnwindEx */ + pass = 0; + InterlockedIncrement(&pass); + pRtlCaptureContext(&ctx); + InterlockedIncrement(&pass); + setjmp((_JBTYPE *)&buf); + if (pass == 2) + { + void (*func)(DWORD64,DWORD64,EXCEPTION_RECORD*,DWORD64,CONTEXT*,void*,void*,void*) = code_mem; + DWORD64 nonvol_regs[19]; + + rec.ExceptionCode = STATUS_UNWIND_CONSOLIDATE; + rec.NumberParameters = 4; + rec.ExceptionInformation[0] = (DWORD64)test_consolidate_dummy; + rec.ExceptionInformation[1] = (DWORD64)&ctx; + rec.ExceptionInformation[2] = ctx.Pc; + rec.ExceptionInformation[3] = (DWORD64)nonvol_regs; + rec.ExceptionInformation[10] = -1; /* otherwise it doesn't get set */ + ctx.Pc = 0xdeadbeef; + /* uses consolidate callback Pc instead of bogus 0xdeadbeef */ + memcpy( code_mem, call_rtlunwind, sizeof(call_rtlunwind) ); + FlushInstructionCache( GetCurrentProcess(), code_mem, sizeof(call_rtlunwind) ); + func( buf.Frame, 0xdeadbeef, &rec, 0, &ctx, NULL, pRtlUnwindEx, nonvol_regs ); + ok(0, "shouldn't be reached\n"); + } + else if (pass == 3) + ok(consolidate_dummy_called == 2, "test_consolidate_dummy not called\n"); + else + ok(0, "unexpected pass %ld\n", pass); +} + #endif /* __aarch64__ */ #if defined(__i386__) || defined(__x86_64__) @@ -10428,6 +10614,7 @@ START_TEST(exception) X(KiUserCallbackDispatcher); X(KiUserExceptionDispatcher); #ifndef __i386__ + X(RtlRestoreContext); X(RtlUnwindEx); X(RtlAddFunctionTable); X(RtlDeleteFunctionTable); @@ -10559,7 +10746,6 @@ START_TEST(exception) #define X(f) p##f = (void*)GetProcAddress(hntdll, #f) X(__C_specific_handler); - X(RtlRestoreContext); X(RtlWow64GetThreadContext); X(RtlWow64SetThreadContext); X(RtlWow64GetCpuAreaInfo); @@ -10599,6 +10785,7 @@ START_TEST(exception) test_continue(); test_nested_exception(); test_collided_unwind(); + test_restore_context(); #elif defined(__arm__)