ntdll/tests: Test exception codes on x86-64.

Signed-off-by: Zebediah Figura <z.figura12@gmail.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Andrew Wesie 2018-03-12 23:03:04 -05:00 committed by Alexandre Julliard
parent c0e49b0e2c
commit 453c670ab8
1 changed files with 282 additions and 0 deletions

View File

@ -111,6 +111,36 @@ typedef struct _JUMP_BUFFER
SETJMP_FLOAT128 Xmm15;
} _JUMP_BUFFER;
typedef union _UNWIND_CODE
{
struct
{
BYTE CodeOffset;
BYTE UnwindOp : 4;
BYTE OpInfo : 4;
} s;
USHORT FrameOffset;
} UNWIND_CODE;
typedef struct _UNWIND_INFO
{
BYTE Version : 3;
BYTE Flags : 5;
BYTE SizeOfProlog;
BYTE CountOfCodes;
BYTE FrameRegister : 4;
BYTE FrameOffset : 4;
UNWIND_CODE UnwindCode[1]; /* actually CountOfCodes (aligned) */
/*
* union
* {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry;
* };
* OPTIONAL ULONG ExceptionData[];
*/
} UNWIND_INFO;
static BOOLEAN (CDECL *pRtlAddFunctionTable)(RUNTIME_FUNCTION*, DWORD, DWORD64);
static BOOLEAN (CDECL *pRtlDeleteFunctionTable)(RUNTIME_FUNCTION*);
static BOOLEAN (CDECL *pRtlInstallFunctionTableCallback)(DWORD64, DWORD64, DWORD, PGET_RUNTIME_FUNCTION_CALLBACK, PVOID, PCWSTR);
@ -2150,6 +2180,257 @@ static void test___C_specific_handler(void)
ok(dispatch.ScopeIndex == 1, "dispatch.ScopeIndex = %d\n", dispatch.ScopeIndex);
}
/* This is heavily based on the i386 exception tests. */
static const struct exception
{
BYTE code[18]; /* asm code */
BYTE offset; /* offset of faulting instruction */
BYTE length; /* length of faulting instruction */
NTSTATUS status; /* expected status code */
DWORD nb_params; /* expected number of parameters */
ULONG64 params[4]; /* expected parameters */
NTSTATUS alt_status; /* alternative status code */
DWORD alt_nb_params; /* alternative number of parameters */
ULONG64 alt_params[4]; /* alternative parameters */
} exceptions[] =
{
/* 0 */
/* test some privileged instructions */
{ { 0xfb, 0xc3 }, /* 0: sti; ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x6c, 0xc3 }, /* 1: insb (%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x6d, 0xc3 }, /* 2: insl (%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x6e, 0xc3 }, /* 3: outsb (%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x6f, 0xc3 }, /* 4: outsl (%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* 5 */
{ { 0xe4, 0x11, 0xc3 }, /* 5: inb $0x11,%al; ret */
0, 2, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xe5, 0x11, 0xc3 }, /* 6: inl $0x11,%eax; ret */
0, 2, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xe6, 0x11, 0xc3 }, /* 7: outb %al,$0x11; ret */
0, 2, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xe7, 0x11, 0xc3 }, /* 8: outl %eax,$0x11; ret */
0, 2, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xed, 0xc3 }, /* 9: inl (%dx),%eax; ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* 10 */
{ { 0xee, 0xc3 }, /* 10: outb %al,(%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xef, 0xc3 }, /* 11: outl %eax,(%dx); ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xf4, 0xc3 }, /* 12: hlt; ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0xfa, 0xc3 }, /* 13: cli; ret */
0, 1, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* test iret to invalid selector */
{ { 0x6a, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0xcf, 0x83, 0xc4, 0x18, 0xc3 },
/* 15: pushq $0; pushq $0; pushq $0; iret; addl $24,%esp; ret */
6, 1, STATUS_ACCESS_VIOLATION, 2, { 0, 0xffffffffffffffff } },
/* 15 */
/* test loading an invalid selector */
{ { 0xb8, 0xef, 0xbe, 0x00, 0x00, 0x8e, 0xe8, 0xc3 }, /* 16: mov $beef,%ax; mov %ax,%gs; ret */
5, 2, STATUS_ACCESS_VIOLATION, 2, { 0, 0xbee8 } }, /* 0xbee8 or 0xffffffff */
/* test overlong instruction (limit is 15 bytes) */
{ { 0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0xfa,0xc3 },
0, 16, STATUS_ILLEGAL_INSTRUCTION, 0, { 0 },
STATUS_ACCESS_VIOLATION, 2, { 0, 0xffffffffffffffff } },
/* test invalid interrupt */
{ { 0xcd, 0xff, 0xc3 }, /* int $0xff; ret */
0, 2, STATUS_ACCESS_VIOLATION, 2, { 0, 0xffffffffffffffff } },
/* test moves to/from Crx */
{ { 0x0f, 0x20, 0xc0, 0xc3 }, /* movl %cr0,%eax; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x20, 0xe0, 0xc3 }, /* movl %cr4,%eax; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* 20 */
{ { 0x0f, 0x22, 0xc0, 0xc3 }, /* movl %eax,%cr0; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x22, 0xe0, 0xc3 }, /* movl %eax,%cr4; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* test moves to/from Drx */
{ { 0x0f, 0x21, 0xc0, 0xc3 }, /* movl %dr0,%eax; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x21, 0xc8, 0xc3 }, /* movl %dr1,%eax; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x21, 0xf8, 0xc3 }, /* movl %dr7,%eax; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* 25 */
{ { 0x0f, 0x23, 0xc0, 0xc3 }, /* movl %eax,%dr0; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x23, 0xc8, 0xc3 }, /* movl %eax,%dr1; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
{ { 0x0f, 0x23, 0xf8, 0xc3 }, /* movl %eax,%dr7; ret */
0, 3, STATUS_PRIVILEGED_INSTRUCTION, 0 },
/* test memory reads */
{ { 0xa1, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl 0xfffffffffffffffc,%eax; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 0, 0xfffffffffffffffc } },
{ { 0xa1, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl 0xfffffffffffffffd,%eax; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 0, 0xfffffffffffffffd } },
/* 30 */
{ { 0xa1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl 0xfffffffffffffffe,%eax; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 0, 0xfffffffffffffffe } },
{ { 0xa1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl 0xffffffffffffffff,%eax; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 0, 0xffffffffffffffff } },
/* test memory writes */
{ { 0xa3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl %eax,0xfffffffffffffffc; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 1, 0xfffffffffffffffc } },
{ { 0xa3, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl %eax,0xfffffffffffffffd; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 1, 0xfffffffffffffffd } },
{ { 0xa3, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl %eax,0xfffffffffffffffe; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 1, 0xfffffffffffffffe } },
/* 35 */
{ { 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3 }, /* movl %eax,0xffffffffffffffff; ret */
0, 9, STATUS_ACCESS_VIOLATION, 2, { 1, 0xffffffffffffffff } },
#if 0
{ { 0xf1, 0x90, 0xc3 }, /* icebp; nop; ret */
1, 1, STATUS_SINGLE_STEP, 0 },
#endif
{ { 0xcd, 0x2c, 0xc3 },
0, 2, STATUS_ASSERTION_FAILURE, 0 },
};
static int got_exception;
static void run_exception_test(void *handler, const void* context,
const void *code, unsigned int code_size,
DWORD access)
{
unsigned char buf[8 + 6 + 8 + 8];
RUNTIME_FUNCTION runtime_func;
UNWIND_INFO *unwind = (UNWIND_INFO *)buf;
void (*func)(void) = code_mem;
DWORD oldaccess, oldaccess2;
runtime_func.BeginAddress = 0;
runtime_func.EndAddress = code_size;
runtime_func.UnwindData = 0x1000;
unwind->Version = 1;
unwind->Flags = UNW_FLAG_EHANDLER;
unwind->SizeOfProlog = 0;
unwind->CountOfCodes = 0;
unwind->FrameRegister = 0;
unwind->FrameOffset = 0;
*(ULONG *)&buf[4] = 0x1010;
*(const void **)&buf[8] = context;
/* jmp near */
buf[16] = 0xff;
buf[17] = 0x25;
*(ULONG *)&buf[18] = 0;
*(void **)&buf[22] = handler;
memcpy((unsigned char *)code_mem + 0x1000, buf, sizeof(buf));
memcpy(code_mem, code, code_size);
if(access)
VirtualProtect(code_mem, code_size, access, &oldaccess);
pRtlAddFunctionTable(&runtime_func, 1, (ULONG_PTR)code_mem);
func();
pRtlDeleteFunctionTable(&runtime_func);
if(access)
VirtualProtect(code_mem, code_size, oldaccess, &oldaccess2);
}
static DWORD WINAPI handler( EXCEPTION_RECORD *rec, ULONG64 frame,
CONTEXT *context, DISPATCHER_CONTEXT *dispatcher )
{
const struct exception *except = *(const struct exception **)(dispatcher->HandlerData);
unsigned int i, parameter_count, entry = except - exceptions;
got_exception++;
trace( "exception %u: %x flags:%x addr:%p\n",
entry, rec->ExceptionCode, rec->ExceptionFlags, rec->ExceptionAddress );
todo_wine_if( rec->ExceptionCode != except->status &&
rec->ExceptionCode != except->alt_status )
ok( rec->ExceptionCode == except->status ||
(except->alt_status != 0 && rec->ExceptionCode == except->alt_status),
"%u: Wrong exception code %x/%x\n", entry, rec->ExceptionCode, except->status );
ok( context->Rip == (DWORD_PTR)code_mem + except->offset,
"%u: Unexpected eip %#lx/%#lx\n", entry,
context->Rip, (DWORD_PTR)code_mem + except->offset );
ok( rec->ExceptionAddress == (char*)context->Rip ||
(rec->ExceptionCode == STATUS_BREAKPOINT && rec->ExceptionAddress == (char*)context->Rip + 1),
"%u: Unexpected exception address %p/%p\n", entry,
rec->ExceptionAddress, (char*)context->Rip );
if (except->status == STATUS_BREAKPOINT && is_wow64)
parameter_count = 1;
else if (except->alt_status == 0 || rec->ExceptionCode != except->alt_status)
parameter_count = except->nb_params;
else
parameter_count = except->alt_nb_params;
todo_wine_if( rec->NumberParameters != parameter_count )
ok( rec->NumberParameters == parameter_count,
"%u: Unexpected parameter count %u/%u\n", entry, rec->NumberParameters, parameter_count );
/* Most CPUs (except Intel Core apparently) report a segment limit violation */
/* instead of page faults for accesses beyond 0xffffffffffffffff */
if (except->nb_params == 2 && except->params[1] >= 0xfffffffffffffffd)
{
if (rec->ExceptionInformation[0] == 0 && rec->ExceptionInformation[1] == 0xffffffffffffffff)
goto skip_params;
}
/* Seems that both 0xbee8 and 0xfffffffffffffffff can be returned in windows */
if (except->nb_params == 2 && rec->NumberParameters == 2
&& except->params[1] == 0xbee8 && rec->ExceptionInformation[1] == 0xffffffffffffffff
&& except->params[0] == rec->ExceptionInformation[0])
{
goto skip_params;
}
if (except->alt_status == 0 || rec->ExceptionCode != except->alt_status)
{
for (i = 0; i < rec->NumberParameters; i++)
ok( rec->ExceptionInformation[i] == except->params[i],
"%u: Wrong parameter %d: %lx/%lx\n",
entry, i, rec->ExceptionInformation[i], except->params[i] );
}
else
{
for (i = 0; i < rec->NumberParameters; i++)
ok( rec->ExceptionInformation[i] == except->alt_params[i],
"%u: Wrong parameter %d: %lx/%lx\n",
entry, i, rec->ExceptionInformation[i], except->alt_params[i] );
}
skip_params:
/* don't handle exception if it's not the address we expected */
if (context->Rip != (DWORD_PTR)code_mem + except->offset) return ExceptionContinueSearch;
context->Rip += except->length;
return ExceptionContinueExecution;
}
static void test_prot_fault(void)
{
unsigned int i;
for (i = 0; i < sizeof(exceptions)/sizeof(exceptions[0]); i++)
{
got_exception = 0;
run_exception_test(handler, &exceptions[i], &exceptions[i].code,
sizeof(exceptions[i].code), 0);
ok( got_exception == (exceptions[i].status != 0),
"%u: bad exception count %d\n", i, got_exception );
}
}
#endif /* __x86_64__ */
#if defined(__i386__) || defined(__x86_64__)
@ -2744,6 +3025,7 @@ START_TEST(exception)
test_virtual_unwind();
test___C_specific_handler();
test_restore_context();
test_prot_fault();
if (pRtlAddFunctionTable && pRtlDeleteFunctionTable && pRtlInstallFunctionTableCallback && pRtlLookupFunctionEntry)
test_dynamic_unwind();