ntdll: Port the RtlRestoreContext test to ARM.
This commit is contained in:
parent
74d1dbb95a
commit
f42316c86d
2 changed files with 198 additions and 19 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue