ntdll: Port the RtlRestoreContext test to ARM.

This commit is contained in:
Alexandre Julliard 2024-03-12 20:24:40 +01:00
parent 74d1dbb95a
commit f42316c86d
2 changed files with 198 additions and 19 deletions

View file

@ -360,22 +360,18 @@ __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_breg13; sleb128 <OFFSET> | Load SP + 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 );
__ASM_GLOBAL_FUNC( call_consolidate_callback,
void WINAPI DECLSPEC_NORETURN consolidate_callback( CONTEXT *context,
void *(CALLBACK *callback)(EXCEPTION_RECORD *),
EXCEPTION_RECORD *rec );
__ASM_GLOBAL_FUNC( consolidate_callback,
"push {r0-r2,lr}\n\t"
".seh_nop\n\t"
"sub sp, sp, #0x1a0\n\t"
@ -386,14 +382,16 @@ __ASM_GLOBAL_FUNC( call_consolidate_callback,
".seh_nop\n\t"
"mov r2, #0x1a0\n\t"
".seh_nop_w\n\t"
"bl " __ASM_NAME("memcpy") "\n\t"
"bl memcpy\n\t"
".seh_custom 0xee,0x02\n\t" /* MSFT_OP_CONTEXT */
".seh_endprologue\n\t"
"ldrd r1, r2, [sp, #0x1a4]\n\t"
"mov r0, r2\n\t"
"blx r1\n\t"
"add sp, sp, #0x1ac\n\t"
"pop {pc}\n\t")
"str r0, [sp, #0x40]\n\t" /* context->Pc */
"mov r0, sp\n\t"
"mov r1, #1\n\t"
"b NtContinue" )
@ -410,7 +408,7 @@ void CDECL RtlRestoreContext( CONTEXT *context, EXCEPTION_RECORD *rec )
memcpy( &context->R4, &jmp->R4, 8 * sizeof(DWORD) );
memcpy( &context->D[8], &jmp->D[0], 8 * sizeof(ULONGLONG) );
context->Lr = jmp->Pc;
context->Pc = jmp->Pc;
context->Sp = jmp->Sp;
context->Fpscr = jmp->Fpscr;
}
@ -418,9 +416,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->R4;
context->Pc = (DWORD)call_consolidate_callback( context, consolidate, rec );
consolidate_callback( context, consolidate, rec );
}
/* hack: remove no longer accessible TEB frames */
@ -565,8 +561,12 @@ void WINAPI RtlUnwindEx( PVOID end_frame, PVOID target_ip, EXCEPTION_RECORD *rec
*context = new_context;
}
context->R0 = (DWORD)retval;
context->Pc = (DWORD)target_ip;
if (rec->ExceptionCode != STATUS_UNWIND_CONSOLIDATE)
context->Pc = (DWORD)target_ip;
else if (rec->ExceptionInformation[10] == -1)
rec->ExceptionInformation[10] = (ULONG_PTR)&nonvol_regs;
context->R0 = (DWORD)retval;
RtlRestoreContext(context, rec);
}

View file

@ -6220,6 +6220,184 @@ static void test_rtlraiseexception(void)
run_rtlraiseexception_test(EXCEPTION_INVALID_HANDLE);
}
static LONG consolidate_dummy_called;
static LONG pass;
static const WORD call_rtlunwind[] =
{
0xf8dd, 0xc00c, /* ldr r12, [sp, #0xc] */
0xe8ac, 0x0ff0, /* stm r12!, {r4-r11} */
0xec8c, 0x8b10, /* vstm r12, {d8-d15} */
0xf8dd, 0xc008, /* ldr r12, [sp, #0x8] */
0x4760, /* bx r12 */
};
static PVOID CALLBACK test_consolidate_dummy(EXCEPTION_RECORD *rec)
{
CONTEXT *ctx = (CONTEXT *)rec->ExceptionInformation[1];
DWORD *saved_regs = (DWORD *)rec->ExceptionInformation[3];
DWORD *regs = (DWORD *)rec->ExceptionInformation[10];
int i;
switch (InterlockedIncrement(&consolidate_dummy_called))
{
case 1: /* RtlRestoreContext */
ok(ctx->Pc == 0xdeadbeef, "RtlRestoreContext wrong Pc, expected: 0xdeadbeef, got: %lx\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: %lx\n", ctx->Pc );
ok( rec->ExceptionInformation[10] != -1, "wrong info %Ix\n", rec->ExceptionInformation[10] );
for (i = 0; i < 8; i++)
ok( saved_regs[i] == regs[i], "wrong reg R%u, expected: %lx, got: %lx\n",
i + 4, saved_regs[i], regs[i] );
regs += 8;
saved_regs += 8;
for (i = 0; i < 8; i++)
ok( ((DWORD64 *)saved_regs)[i] == ((DWORD64 *)regs)[i],
"wrong reg D%u, expected: %I64x, got: %I64x\n",
i + 8, ((DWORD64 *)saved_regs)[i], ((DWORD64 *)regs)[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] = (DWORD)&buf;
/* uses buf.Pc instead of ctx.Pc */
pRtlRestoreContext(&ctx, &rec);
ok(0, "shouldn't be reached\n");
}
else if (pass == 4)
{
ok(buf.R4 == ctx.R4 , "longjmp failed for R4, expected: %lx, got: %lx\n", buf.R4, ctx.R4 );
ok(buf.R5 == ctx.R5 , "longjmp failed for R5, expected: %lx, got: %lx\n", buf.R5, ctx.R5 );
ok(buf.R6 == ctx.R6 , "longjmp failed for R6, expected: %lx, got: %lx\n", buf.R6, ctx.R6 );
ok(buf.R7 == ctx.R7 , "longjmp failed for R7, expected: %lx, got: %lx\n", buf.R7, ctx.R7 );
ok(buf.R8 == ctx.R8 , "longjmp failed for R8, expected: %lx, got: %lx\n", buf.R8, ctx.R8 );
ok(buf.R9 == ctx.R9 , "longjmp failed for R9, expected: %lx, got: %lx\n", buf.R9, ctx.R9 );
ok(buf.R10 == ctx.R10, "longjmp failed for R10, expected: %lx, got: %lx\n", buf.R10, ctx.R10 );
ok(buf.R11 == ctx.R11, "longjmp failed for R11, expected: %lx, got: %lx\n", buf.R11, ctx.R11 );
for (i = 0; i < 8; i++)
ok(buf.D[i] == ctx.D[i + 8], "longjmp failed for D%u, expected: %I64x, got: %I64x\n",
i + 8, buf.D[i], ctx.D[i + 8]);
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] = (DWORD)&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] = (DWORD)test_consolidate_dummy;
rec.ExceptionInformation[1] = (DWORD)&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);
if (pass == 2)
{
void (*func)(DWORD,DWORD,EXCEPTION_RECORD*,DWORD,CONTEXT*,void*,void*,void*);
DWORD64 nonvol_regs[12];
func = (void *)((ULONG_PTR)code_mem | 1); /* thumb */
rec.ExceptionCode = STATUS_UNWIND_CONSOLIDATE;
rec.NumberParameters = 4;
rec.ExceptionInformation[0] = (DWORD)test_consolidate_dummy;
rec.ExceptionInformation[1] = (DWORD)&ctx;
rec.ExceptionInformation[2] = ctx.Pc;
rec.ExceptionInformation[3] = (DWORD)&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);
}
#elif defined(__aarch64__)
static void test_thread_context(void)
@ -10791,6 +10969,7 @@ START_TEST(exception)
test_nested_exception();
test_collided_unwind();
test_restore_context();
#endif