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

ntdll: Port the RtlRestoreContext test to ARM64.

This commit is contained in:
Alexandre Julliard 2024-03-12 20:23:47 +01:00
parent 5286ed3b0a
commit 74d1dbb95a
2 changed files with 223 additions and 74 deletions

View file

@ -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 <OFFSET> | 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);
}

View file

@ -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 *)&regs->FpNvRegs[i],
"wrong reg D%u, expected: %Ix, got: %Ix\n",
i + 8, saved_regs[i + 11], *(DWORD64 *)&regs->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__)