This patch adds emulation for instructions protected by User-Mode Instruction Prevention (currently implemented by AMD Ryzen 3000 CPUs and some rare Intel CPUs).
Changes from previous patch: all code is now integrated into the signal_*.c files, and fixed a warning in the 32-bit code.
Brendan Shanks (2): ntdll: Add emulation for UMIP instructions. ntdll/tests: Add tests for UMIP instructions.
dlls/ntdll/signal_i386.c | 362 +++++++++++++++++++++++++++++++- dlls/ntdll/signal_x86_64.c | 395 ++++++++++++++++++++++++++++++++++- dlls/ntdll/tests/exception.c | 375 +++++++++++++++++++++++++++++++++ 3 files changed, 1116 insertions(+), 16 deletions(-)
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571 Signed-off-by: Brendan Shanks bshanks@codeweavers.com --- dlls/ntdll/signal_i386.c | 362 ++++++++++++++++++++++++++++++++- dlls/ntdll/signal_x86_64.c | 395 ++++++++++++++++++++++++++++++++++++- 2 files changed, 741 insertions(+), 16 deletions(-)
diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c index e9dd0de2fc..4310b682c6 100644 --- a/dlls/ntdll/signal_i386.c +++ b/dlls/ntdll/signal_i386.c @@ -1521,37 +1521,353 @@ __ASM_STDCALL_FUNC( NtGetContextThread, 8, __ASM_CFI(".cfi_same_value %ebp\n\t") "ret $8" )
+/*********************************************************************** + * User-Mode Instruction Prevention (UMIP) is an x86 feature that prevents user-space + * code (CPL > 0) from executing the sgdt, sidt, sldt, smsw, and str instructions. + * If one of these instructions is executed, a general protection fault is issued. + * + * UMIP was first implemented by the AMD Zen2 and Intel Sunny Cove microarchitectures + * (both released in 2019). + * + * On Linux: + * The kernel traps the GPF and emulates sgdt, sidt, and smsw. + * sldt and str are not emulated. + * + * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV. + * emulate_umip_instr() emulates all instructions not emulated by the Linux kernel. + */ +#ifdef linux +/* Dummy LDT, matches what I see on Windows and Linux */ +#define UMIP_DUMMY_LDT 0 + +/* Dummy task register, matches what I see on Windows and Linux */ +#define UMIP_DUMMY_TR 0x40 + +static void *get_reg_address( CONTEXT *context, BYTE rm ) +{ + switch (rm & 7) + { + case 0: return &context->Eax; + case 1: return &context->Ecx; + case 2: return &context->Edx; + case 3: return &context->Ebx; + case 4: return &context->Esp; + case 5: return &context->Ebp; + case 6: return &context->Esi; + case 7: return &context->Edi; + } + return NULL; +} + +/*********************************************************************** + * INSTR_GetOperandAddr + * + * Return the address of an instruction operand (from the mod/rm byte). + */ +static int INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, unsigned int instr_len, + int long_addr, int segprefix, int *len, void **addr ) +{ + int mod, rm, base = 0, index = 0, ss = 0, off; + unsigned int i = 0; + +#define GET_VAL( val, type ) \ + { if (sizeof(type) > (instr_len - i)) return 0; \ + *val = *(type *)&instr[i]; i += sizeof(type); *len += sizeof(type); } + + *len = 0; + GET_VAL( &mod, BYTE ); + rm = mod & 7; + mod >>= 6; + + if (mod == 3) + { + *addr = get_reg_address( context, rm ); + return 1; + } + + if (long_addr) + { + if (rm == 4) + { + BYTE sib; + GET_VAL( &sib, BYTE ); + rm = sib & 7; + ss = sib >> 6; + switch((sib >> 3) & 7) + { + case 0: index = context->Eax; break; + case 1: index = context->Ecx; break; + case 2: index = context->Edx; break; + case 3: index = context->Ebx; break; + case 4: index = 0; break; + case 5: index = context->Ebp; break; + case 6: index = context->Esi; break; + case 7: index = context->Edi; break; + } + } + + switch(rm) + { + case 0: base = context->Eax; break; + case 1: base = context->Ecx; break; + case 2: base = context->Edx; break; + case 3: base = context->Ebx; break; + case 4: base = context->Esp; break; + case 5: base = context->Ebp; break; + case 6: base = context->Esi; break; + case 7: base = context->Edi; break; + } + switch (mod) + { + case 0: + if (rm == 5) /* special case: ds:(disp32) */ + { + GET_VAL( &base, DWORD ); + } + break; + + case 1: /* 8-bit disp */ + GET_VAL( &off, BYTE ); + base += (signed char)off; + break; + + case 2: /* 32-bit disp */ + GET_VAL( &off, DWORD ); + base += (signed long)off; + break; + } + } + else /* short address */ + { + switch(rm) + { + case 0: /* ds:(bx,si) */ + base = LOWORD(context->Ebx) + LOWORD(context->Esi); + break; + case 1: /* ds:(bx,di) */ + base = LOWORD(context->Ebx) + LOWORD(context->Edi); + break; + case 2: /* ss:(bp,si) */ + base = LOWORD(context->Ebp) + LOWORD(context->Esi); + break; + case 3: /* ss:(bp,di) */ + base = LOWORD(context->Ebp) + LOWORD(context->Edi); + break; + case 4: /* ds:(si) */ + base = LOWORD(context->Esi); + break; + case 5: /* ds:(di) */ + base = LOWORD(context->Edi); + break; + case 6: /* ss:(bp) */ + base = LOWORD(context->Ebp); + break; + case 7: /* ds:(bx) */ + base = LOWORD(context->Ebx); + break; + } + + switch(mod) + { + case 0: + if (rm == 6) /* special case: ds:(disp16) */ + { + GET_VAL( &base, WORD ); + } + break; + + case 1: /* 8-bit disp */ + GET_VAL( &off, BYTE ); + base += (signed char)off; + break; + + case 2: /* 16-bit disp */ + GET_VAL( &off, WORD ); + base += (signed short)off; + break; + } + base &= 0xffff; + } + /* FIXME: we assume that all segments have a base of 0 */ + *addr = (void *)(base + (index << ss)); + return 1; +#undef GET_VAL +} + +extern int have_cpuid (void ); + +extern void do_cpuid_cx( unsigned int ax, unsigned int cx, unsigned int *p ); +__ASM_GLOBAL_FUNC( do_cpuid_cx, + "pushl %esi\n\t" + "pushl %ebx\n\t" + "movl 12(%esp),%eax\n\t" + "movl 16(%esp),%ecx\n\t" + "movl 20(%esp),%esi\n\t" + "cpuid\n\t" + "movl %eax,(%esi)\n\t" + "movl %ebx,4(%esi)\n\t" + "movl %ecx,8(%esi)\n\t" + "movl %edx,12(%esi)\n\t" + "popl %ebx\n\t" + "popl %esi\n\t" + "ret" ) + +static int umip_enabled( void ) +{ + /* Check cpuid to see if UMIP is supported. + * UMIP bit is EAX=0x07,ECX=0x0, ECX bit 2 + * (CPUID.07H.0H:ECX:UMIP[bit 2] in Intel syntax) + * + * It would be preferable to check if UMIP is actually enabled + * (CR4.UMIP), but that can't be done from userspace. + */ + unsigned int regs[4]; + + if (!have_cpuid()) return 0; + + do_cpuid_cx( 0x00000000, 0, regs ); /* get standard cpuid level and vendor name */ + if (regs[0] >= 0x00000007) /* Check for supported cpuid version */ + { + do_cpuid_cx( 0x00000007, 0, regs ); + if (regs[2] & (1 << 2)) + return 1; + } + + return 0; +} +#endif + +/*********************************************************************** + * emulate_umip_instr + * + * Emulate a UMIP-protected instruction. + * Returns: 0 if no instruction emulated, + * 1 if instruction emulated successfully, + * 2 if instruction's write to memory failed. + */ +static int emulate_umip_instr( CONTEXT *context, BYTE *instr, unsigned int len, void **err_addr, + int segprefix, unsigned int prefixlen, int long_op, int long_addr ) +{ +#ifdef linux + static int umip_enabled_cache = -1; + if (umip_enabled_cache == -1) umip_enabled_cache = umip_enabled(); + if (!umip_enabled_cache) return 0; + + if (len < 2) return 0; + + if (instr[0] == 0x00) /* sldt/str */ + { + int reg = (instr[1] >> 3) & 7; + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + int instr_len; + int mod = instr[1] >> 6; + void *data; + UINT16 dummy_value; + + if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, long_addr, + segprefix, &instr_len, &data )) + return 0; + + if (reg == 0) + { + /* sldt */ + dummy_value = UMIP_DUMMY_LDT; + TRACE( "sldt at 0x%08x\n", context->Eip ); + } + else + { + /* str */ + dummy_value = UMIP_DUMMY_TR; + TRACE( "str at 0x%08x\n", context->Eip ); + } + + if (mod == 3) + { + /* Destination operand is a register. + * Zero-extend the dummy value and store to the register. */ + UINT32 dummy_value_32 = dummy_value; + memcpy( data, &dummy_value_32, long_op ? 4 : 2 ); + } + else + { + /* Destination operand is a memory location. + * Only copy 16 bits regardless of operand size. */ + if (virtual_uninterrupted_write_memory( data, &dummy_value, sizeof(dummy_value) )) + { + TRACE( "memory write by 0x%08x to %p failed\n", context->Eip, data ); + *err_addr = data; + return 2; + } + } + context->Eip += prefixlen + instr_len + 2; + return 1; + } + } + } + else if (instr[0] == 0x01) /* sgdt/sidt/str */ + { + /* The Linux kernel already emulates these instructions for 32-bit processes, + * Wine should never get an exception for them. + * However, it may be necessary in the future to emulate these instructions + * for other OSes if they don't do any emulation. + */ + } + + return 0; +#else + return 0; +#endif +}
/*********************************************************************** * is_privileged_instr * - * Check if the fault location is a privileged instruction. + * Check if the fault location is a privileged instruction, and emulate a UMIP-protected instruction if found. * Based on the instruction emulation code in dlls/kernel/instr.c. + * Returns: 0 if no privileged instruction found or emulation failed, + * 1 if instruction found and ExceptionCode has been set, + * 2 if instruction was successfully emulated. */ -static inline DWORD is_privileged_instr( CONTEXT *context ) +static inline int is_privileged_instr( struct stack_layout *stack ) { BYTE instr[16]; + CONTEXT *context = &stack->context; unsigned int i, len, prefix_count = 0; + int long_op = 1, long_addr = 1; + int segprefix = -1; /* no prefix */
if (!wine_ldt_is_system( context->SegCs )) return 0; len = virtual_uninterrupted_read_memory( (BYTE *)context->Eip, instr, sizeof(instr) );
- for (i = 0; i < len; i++) switch (instr[i]) + for (i = 0; i < len; i++) + { + switch (instr[i]) { /* instruction prefixes */ case 0x2e: /* %cs: */ + segprefix = context->SegCs; prefix_count++; break; case 0x36: /* %ss: */ + segprefix = context->SegSs; prefix_count++; break; case 0x3e: /* %ds: */ + segprefix = context->SegDs; prefix_count++; break; case 0x26: /* %es: */ + segprefix = context->SegEs; prefix_count++; break; case 0x64: /* %fs: */ + segprefix = context->SegFs; prefix_count++; break; case 0x65: /* %gs: */ + segprefix = context->SegGs; prefix_count++; break; case 0x66: /* opcode size */ + long_op = !long_op; prefix_count++; break; case 0x67: /* addr size */ + long_addr = !long_addr; prefix_count++; break; case 0xf0: /* lock */ case 0xf2: /* repne */ case 0xf3: /* repe */ - if (++prefix_count >= 15) return EXCEPTION_ILLEGAL_INSTRUCTION; - continue; + prefix_count++; break;
case 0x0f: /* extended instruction */ if (i == len - 1) return 0; @@ -1561,7 +1877,28 @@ static inline DWORD is_privileged_instr( CONTEXT *context ) case 0x21: /* mov drX, reg */ case 0x22: /* mov reg, crX */ case 0x23: /* mov reg drX */ - return EXCEPTION_PRIV_INSTRUCTION; + stack->rec.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; + return 1; + + case 0x00: /* sldt/str */ + case 0x01: /* sgdt/sidt/smsw */ + { + void *err_addr; + int result = emulate_umip_instr( context, &instr[i+1], len - i - 1, &err_addr, + segprefix, prefix_count, long_op, long_addr ); + if (result == 1) + return 2; + else if (result == 2) + { + stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; + stack->rec.NumberParameters = 2; + stack->rec.ExceptionInformation[0] = 1; + stack->rec.ExceptionInformation[1] = (ULONG_PTR)err_addr; + return 1; + } + else + return 0; + } } return 0; case 0x6c: /* insb (%dx) */ @@ -1580,10 +1917,14 @@ static inline DWORD is_privileged_instr( CONTEXT *context ) case 0xf4: /* hlt */ case 0xfa: /* cli */ case 0xfb: /* sti */ - return EXCEPTION_PRIV_INSTRUCTION; + stack->rec.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; + return 1; default: return 0; } + + if (prefix_count >= 15) { stack->rec.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return 1; } + } return 0; }
@@ -2016,7 +2357,12 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext ) case TRAP_x86_UNKNOWN: /* Unknown fault code */ { WORD err = get_error_code(context); - if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break; + int result; + if (!err && (result = is_privileged_instr( stack ))) + { + if (result == 1) break; + else { restore_context( &stack->context, sigcontext ); return; } + } if ((err & 7) == 2 && handle_interrupt( err >> 3, context, stack )) return; stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; stack->rec.NumberParameters = 2; diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c index c372f65b34..af74bb2c0a 100644 --- a/dlls/ntdll/signal_x86_64.c +++ b/dlls/ntdll/signal_x86_64.c @@ -2752,25 +2752,370 @@ static void setup_raise_exception( ucontext_t *sigcontext, struct stack_layout * EFL_sig(sigcontext) &= ~(0x100|0x400|0x40000); }
+/*********************************************************************** + * User-Mode Instruction Prevention (UMIP) is an x86 feature that prevents user-space + * code (CPL > 0) from executing the sgdt, sidt, sldt, smsw, and str instructions. + * If one of these instructions is executed, a general protection fault is issued. + * + * UMIP was first implemented by the AMD Zen2 and Intel Sunny Cove microarchitectures + * (both released in 2019). + * + * On Linux: + * Kernel 5.4 and newer emulate sgdt, sidt, and smsw. sldt and str are not emulated. + * Kernels older than v5.4 do not emulate any instructions. + * + * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV. + * emulate_umip_instr() emulates all instructions not emulated by the Linux kernel. + */ +#ifdef linux +/* Dummy base addresses are the same as used by the kernel */ +#define UMIP_DUMMY_GDT_BASE 0xfffffffffffe0000ULL +#define UMIP_DUMMY_IDT_BASE 0xffffffffffff0000ULL +#define UMIP_DUMMY_GDT_IDT_LIMIT 0 + +#define UMIP_GDT_IDT_BASE_SIZE_64BIT 8 +#define UMIP_GDT_IDT_LIMIT_SIZE 2 + +/* Dummy LDT, matches what I see on Windows and Linux */ +#define UMIP_DUMMY_LDT 0 + +/* Dummy task register, matches what I see on Windows and Linux */ +#define UMIP_DUMMY_TR 0x40 + +/* Dummy MSW is same value used by kernel, defined as: + * (X86_CR0_PE | X86_CR0_MP | X86_CR0_ET | + * X86_CR0_NE | X86_CR0_WP | X86_CR0_AM | + * X86_CR0_PG) + */ +#define UMIP_DUMMY_MSW 0x33 + +#define REX_B 1 +#define REX_X 2 +#define REX_R 4 +#define REX_W 8 + +#define REGMODRM_MOD( regmodrm, rex ) ((regmodrm) >> 6) +#define REGMODRM_REG( regmodrm, rex ) (((regmodrm) >> 3) & 7) | (((rex) & REX_R) ? 8 : 0) +#define REGMODRM_RM( regmodrm, rex ) (((regmodrm) & 7) | (((rex) & REX_B) ? 8 : 0)) + +#define SIB_SS( sib, rex ) ((sib) >> 6) +#define SIB_INDEX( sib, rex ) (((sib) >> 3) & 7) | (((rex) & REX_X) ? 8 : 0) +#define SIB_BASE( sib, rex ) (((sib) & 7) | (((rex) & REX_B) ? 8 : 0)) + +static inline DWORD64 *get_int_reg_ptr( CONTEXT *context, int index ) +{ + return &context->Rax + index; /* index should be in range 0 .. 15 */ +} + +static inline int get_op_size( int long_op, int rex ) +{ + if (rex & REX_W) + return sizeof(DWORD64); + else if (long_op) + return sizeof(DWORD); + else + return sizeof(WORD); +} + +/*********************************************************************** + * INSTR_GetOperandAddr + * + * Return the address of an instruction operand (from the mod/rm byte). + */ +static int INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr, unsigned int instr_len, + int long_addr, int rex, int segprefix, int *len, BYTE **addr ) +{ + int mod, rm, ss = 0, off, have_sib = 0; + unsigned int i = 0; + DWORD64 base = 0, index = 0; + +#define GET_VAL( val, type ) \ + { if (sizeof(type) > (instr_len - i)) return 0; \ + *val = *(type *)&instr[i]; i += sizeof(type); *len += sizeof(type); } + + *len = 0; + GET_VAL( &mod, BYTE ); + rm = REGMODRM_RM( mod, rex ); + mod = REGMODRM_MOD( mod, rex ); + + if (mod == 3) + { + *addr = (BYTE *)get_int_reg_ptr( context, rm ); + return 1; + } + + if ((rm & 7) == 4) + { + BYTE sib; + int id; + + GET_VAL( &sib, BYTE ); + rm = SIB_BASE( sib, rex ); + id = SIB_INDEX( sib, rex ); + ss = SIB_SS( sib, rex ); + + index = (id != 4) ? *get_int_reg_ptr( context, id ) : 0; + if (!long_addr) index &= 0xffffffff; + have_sib = 1; + } + + base = *get_int_reg_ptr( context, rm ); + if (!long_addr) base &= 0xffffffff; + + switch (mod) + { + case 0: + if (rm == 5) /* special case */ + { + base = have_sib ? 0 : context->Rip; + if (!long_addr) base &= 0xffffffff; + GET_VAL( &off, DWORD ); + base += (signed long)off; + } + break; + + case 1: /* 8-bit disp */ + GET_VAL( &off, BYTE ); + base += (signed char)off; + break; + + case 2: /* 32-bit disp */ + GET_VAL( &off, DWORD ); + base += (signed long)off; + break; + } + + /* FIXME: we assume that all segments have a base of 0 */ + *addr = (BYTE *)(base + (index << ss)); + return 1; +#undef GET_VAL +} + +extern void do_cpuid_cx( unsigned int ax, unsigned int cx, unsigned int *p ); +__ASM_GLOBAL_FUNC( do_cpuid_cx, + "pushq %rbx\n\t" + "movq %rdx,%r8\n\t" + "movl %edi,%eax\n\t" + "movl %esi,%ecx\n\t" + "cpuid\n\t" + "movl %eax,(%r8)\n\t" + "movl %ebx,4(%r8)\n\t" + "movl %ecx,8(%r8)\n\t" + "movl %edx,12(%r8)\n\t" + "popq %rbx\n\t" + "ret" ) + +static int umip_enabled( void ) +{ + /* Check cpuid to see if UMIP is supported. + * UMIP bit is EAX=0x07,ECX=0x0, ECX bit 2 + * (CPUID.07H.0H:ECX:UMIP[bit 2] in Intel syntax) + * + * It would be preferable to check if UMIP is actually enabled + * (CR4.UMIP), but that can't be done from userspace. + */ + unsigned int regs[4]; + + do_cpuid_cx( 0x00000000, 0, regs ); /* get standard cpuid level and vendor name */ + if (regs[0] >= 0x00000007) /* Check for supported cpuid version */ + { + do_cpuid_cx( 0x00000007, 0, regs ); + if (regs[2] & (1 << 2)) + return 1; + } + + return 0; +} +#endif + +/*********************************************************************** + * emulate_umip_instr + * + * Emulate a UMIP-protected instruction. + * Returns: 0 if no instruction emulated, + * 1 if instruction emulated successfully, + * 2 if instruction's write to memory failed. + */ +static int emulate_umip_instr( CONTEXT *context, BYTE *instr, unsigned int len, void **err_addr, + int segprefix, unsigned int prefixlen, int long_op, int long_addr, int rex ) +{ +#ifdef linux + static int umip_enabled_cache = -1; + if (umip_enabled_cache == -1) umip_enabled_cache = umip_enabled(); + if (!umip_enabled_cache) return 0; + + if (len < 2) return 0; + + if (instr[0] == 0x00) /* sldt/str */ + { + int reg = REGMODRM_REG( instr[1], rex ); + switch (reg) + { + case 0: /* sldt */ + case 1: /* str */ + { + int instr_len; + BYTE *data; + UINT16 dummy_value; + + if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, long_addr, + rex, segprefix, &instr_len, &data )) + return 0; + + if (reg == 0) + { + /* sldt */ + dummy_value = UMIP_DUMMY_LDT; + TRACE( "sldt at 0x%lx\n", context->Rip ); + } + else + { + /* str */ + dummy_value = UMIP_DUMMY_TR; + TRACE( "str at 0x%lx\n", context->Rip ); + } + + if (REGMODRM_MOD( instr[1], rex ) == 3) + { + /* Destination operand is a register. + * Zero-extend the dummy LDT and store to the register. */ + UINT64 dummy_value_64 = dummy_value; + memcpy( data, &dummy_value_64, get_op_size( long_op, rex ) ); + } + else + { + /* Destination operand is a memory location. + * Only copy 16 bits regardless of operand size. */ + if (virtual_uninterrupted_write_memory( data, &dummy_value, sizeof(dummy_value) )) + { + TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data ); + *err_addr = data; + return 2; + } + } + context->Rip += prefixlen + instr_len + 2; + return 1; + } + default: break; + } + } + else if (instr[0] == 0x01) /* sgdt/sidt/str */ + { + int instr_len; + int reg = REGMODRM_REG( instr[1], rex ); + switch (reg) + { + case 0: /* sgdt */ + case 1: /* sidt */ + { + BYTE *data; + UINT16 dummy_limit = UMIP_DUMMY_GDT_IDT_LIMIT; + UINT64 dummy_base_addr; + + /* sgdt/sidt cannot use a register as the destination operand */ + if (REGMODRM_MOD( instr[1], rex ) == 3) + return 0; + + if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, long_addr, + rex, segprefix, &instr_len, &data )) + return 0; + + if (reg == 0) + { + /* sgdt */ + dummy_base_addr = UMIP_DUMMY_GDT_BASE; + TRACE( "sgdt at %lx\n", context->Rip ); + } + else if (reg == 1) + { + /* sidt */ + dummy_base_addr = UMIP_DUMMY_IDT_BASE; + TRACE( "sidt at %lx\n", context->Rip ); + } + if (virtual_uninterrupted_write_memory( data, &dummy_limit, UMIP_GDT_IDT_LIMIT_SIZE ) || + virtual_uninterrupted_write_memory( data + UMIP_GDT_IDT_LIMIT_SIZE, &dummy_base_addr, UMIP_GDT_IDT_BASE_SIZE_64BIT )) + { + TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data ); + *err_addr = data; + return 2; + } + + context->Rip += prefixlen + instr_len + 2; + return 1; + } + case 4: /* smsw */ + { + BYTE *data; + + if (!INSTR_GetOperandAddr( context, &instr[1], len - 1, long_addr, + rex, segprefix, &instr_len, &data )) + return 0; + + if (REGMODRM_MOD( instr[1], rex ) == 3) + { + /* Destination operand is a register. + * Zero-extend the dummy MSW and store to the register. */ + UINT64 dummy_msw = UMIP_DUMMY_MSW; + TRACE( "smsw at %lx\n", context->Rip ); + memcpy( data, &dummy_msw, get_op_size( long_op, rex ) ); + } + else + { + /* Destination operand is a memory location. + * Only copy 16 bits regardless of operand size. */ + UINT16 dummy_msw = UMIP_DUMMY_MSW; + TRACE( "smsw at %lx\n", context->Rip ); + if (virtual_uninterrupted_write_memory( data, &dummy_msw, sizeof(dummy_msw) )) + { + TRACE( "memory write by 0x%lx to %p failed\n", context->Rip, data ); + *err_addr = data; + return 2; + } + } + context->Rip += prefixlen + instr_len + 2; + return 1; + } + default: break; + } + } + + return 0; +#else + return 0; +#endif +}
/*********************************************************************** * is_privileged_instr * - * Check if the fault location is a privileged instruction. + * Check if the fault location is a privileged instruction, and emulate a UMIP-protected instruction if found. + * Returns: 0 if no privileged instruction found or emulation failed, + * 1 if instruction found and ExceptionCode has been set, + * 2 if instruction was successfully emulated. */ -static inline DWORD is_privileged_instr( CONTEXT *context ) +static inline int is_privileged_instr( struct stack_layout *stack ) { BYTE instr[16]; + CONTEXT *context = &stack->context; + int segprefix, long_op = 1, long_addr = 1, rex; unsigned int i, prefix_count = 0; unsigned int len = virtual_uninterrupted_read_memory( (BYTE *)context->Rip, instr, sizeof(instr) );
- for (i = 0; i < len; i++) switch (instr[i]) + segprefix = -1; /* no seg prefix */ + rex = 0; /* no rex prefix */ + for (i = 0; i < len; i++) + { + switch (instr[i]) { /* instruction prefixes */ case 0x2e: /* %cs: */ + segprefix = context->SegCs; prefix_count++; break; case 0x36: /* %ss: */ + segprefix = context->SegSs; prefix_count++; break; case 0x3e: /* %ds: */ + segprefix = context->SegDs; prefix_count++; break; case 0x26: /* %es: */ + segprefix = context->SegEs; prefix_count++; break; case 0x40: /* rex */ case 0x41: /* rex */ case 0x42: /* rex */ @@ -2787,15 +3132,19 @@ static inline DWORD is_privileged_instr( CONTEXT *context ) case 0x4d: /* rex */ case 0x4e: /* rex */ case 0x4f: /* rex */ + rex = instr[i]; prefix_count++; break; case 0x64: /* %fs: */ + segprefix = context->SegFs; prefix_count++; break; case 0x65: /* %gs: */ + segprefix = context->SegGs; prefix_count++; break; case 0x66: /* opcode size */ + long_op = !long_op; prefix_count++; break; case 0x67: /* addr size */ + long_addr = !long_addr; prefix_count++; break; case 0xf0: /* lock */ case 0xf2: /* repne */ case 0xf3: /* repe */ - if (++prefix_count >= 15) return EXCEPTION_ILLEGAL_INSTRUCTION; - continue; + prefix_count++; break;
case 0x0f: /* extended instruction */ if (i == len - 1) return 0; @@ -2808,7 +3157,28 @@ static inline DWORD is_privileged_instr( CONTEXT *context ) case 0x21: /* mov drX, reg */ case 0x22: /* mov reg, crX */ case 0x23: /* mov reg drX */ - return EXCEPTION_PRIV_INSTRUCTION; + stack->rec.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; + return 1; + + case 0x00: /* sldt/str */ + case 0x01: /* sgdt/sidt/smsw */ + { + void *err_addr; + int result = emulate_umip_instr( context, &instr[i+1], len - i - 1, &err_addr, + segprefix, prefix_count, long_op, long_addr, rex ); + if (result == 1) + return 2; + else if (result == 2) + { + stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; + stack->rec.NumberParameters = 2; + stack->rec.ExceptionInformation[0] = 1; + stack->rec.ExceptionInformation[1] = (ULONG_PTR)err_addr; + return 1; + } + else + return 0; + } } return 0; case 0x6c: /* insb (%dx) */ @@ -2827,10 +3197,14 @@ static inline DWORD is_privileged_instr( CONTEXT *context ) case 0xf4: /* hlt */ case 0xfa: /* cli */ case 0xfb: /* sti */ - return EXCEPTION_PRIV_INSTRUCTION; + stack->rec.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; + return 1; default: return 0; } + + if (prefix_count >= 15) { stack->rec.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return 1; } + } return 0; }
@@ -2925,8 +3299,13 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *sigcontext ) case TRAP_x86_PROTFLT: /* General protection fault */ case TRAP_x86_UNKNOWN: /* Unknown fault code */ { + int result; WORD err = ERROR_sig(ucontext); - if (!err && (stack->rec.ExceptionCode = is_privileged_instr( &stack->context ))) break; + if (!err && (result = is_privileged_instr( stack ))) + { + if (result == 1) break; + else { restore_context( &stack->context, sigcontext ); return; } + } if ((err & 7) == 2 && handle_interrupt( ucontext, stack )) return; stack->rec.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; stack->rec.NumberParameters = 2;
Brendan Shanks bshanks@codeweavers.com writes:
+extern int have_cpuid (void );
+extern void do_cpuid_cx( unsigned int ax, unsigned int cx, unsigned int *p ); +__ASM_GLOBAL_FUNC( do_cpuid_cx,
"pushl %esi\n\t"
"pushl %ebx\n\t"
"movl 12(%esp),%eax\n\t"
"movl 16(%esp),%ecx\n\t"
"movl 20(%esp),%esi\n\t"
"cpuid\n\t"
"movl %eax,(%esi)\n\t"
"movl %ebx,4(%esi)\n\t"
"movl %ecx,8(%esi)\n\t"
"movl %edx,12(%esi)\n\t"
"popl %ebx\n\t"
"popl %esi\n\t"
"ret" )
+static int umip_enabled( void ) +{
- /* Check cpuid to see if UMIP is supported.
* UMIP bit is EAX=0x07,ECX=0x0, ECX bit 2
* (CPUID.07H.0H:ECX:UMIP[bit 2] in Intel syntax)
*
* It would be preferable to check if UMIP is actually enabled
* (CR4.UMIP), but that can't be done from userspace.
*/
- unsigned int regs[4];
- if (!have_cpuid()) return 0;
- do_cpuid_cx( 0x00000000, 0, regs ); /* get standard cpuid level and vendor name */
- if (regs[0] >= 0x00000007) /* Check for supported cpuid version */
- {
do_cpuid_cx( 0x00000007, 0, regs );
if (regs[2] & (1 << 2))
return 1;
- }
- return 0;
+} +#endif
There shouldn't be any reason to check this. If UMIP is not enabled you won't get a fault in the first place.
Also I don't see why you use #ifdef linux, surely that code could work on other platforms too.
Signed-off-by: Brendan Shanks bshanks@codeweavers.com --- dlls/ntdll/tests/exception.c | 375 +++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+)
diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index fb58c0ee7a..0d63731b56 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -327,6 +327,19 @@ static const struct exception 0xba, 0xba, 0xba, 0xba, 0xba, /* mov $0xbabababa, %edx */ 0xcd, 0x2d, 0xc3 }, /* int $0x2d; ret */ 17, 0, FALSE, STATUS_BREAKPOINT, 3, { 0xb8b8b8b8, 0xb9b9b9b9, 0xbabababa } }, + + /* test UMIP-covered instructions storing to a NULL pointer */ + { { 0x31, 0xc0, 0x0f, 0x01, 0x00, 0xc3 }, /* xor %eax,%eax; sgdt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, +/* 45 */ + { { 0x31, 0xc0, 0x0f, 0x01, 0x08, 0xc3 }, /* xor %eax,%eax; sidt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x00, 0x00, 0xc3 }, /* xor %eax,%eax; sldt (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x01, 0x20, 0xc3 }, /* xor %eax,%eax; smsw (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x31, 0xc0, 0x0f, 0x00, 0x08, 0xc3 }, /* xor %eax,%eax; str (%eax); ret */ + 2, 3, FALSE, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, };
static int got_exception; @@ -2420,6 +2433,19 @@ static const struct exception 1, 1, STATUS_SINGLE_STEP, 0 }, { { 0xcd, 0x2c, 0xc3 }, 0, 2, STATUS_ASSERTION_FAILURE, 0 }, + + /* test UMIP-covered instructions storing to a NULL pointer */ + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x00, 0xc3 }, /* xor %rax,%rax; sgdt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x08, 0xc3 }, /* xor %rax,%rax; sidt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, +/* 40 */ + { { 0x48, 0x31, 0xc0, 0x0f, 0x00, 0x00, 0xc3 }, /* xor %rax,%rax; sldt (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x01, 0x20, 0xc3 }, /* xor %rax,%rax; smsw (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, + { { 0x48, 0x31, 0xc0, 0x0f, 0x00, 0x08, 0xc3 }, /* xor %rax,%rax; str (%rax); ret */ + 3, 3, STATUS_ACCESS_VIOLATION, 2, { 1, 0 } }, };
static int got_exception; @@ -3383,6 +3409,353 @@ static void test_unload_trace(void) ok(found, "Unloaded module wasn't found.\n"); }
+#if defined(__x86_64__) +static DWORD WINAPI umip_handler( EXCEPTION_RECORD *rec, ULONG64 frame, + CONTEXT *context, DISPATCHER_CONTEXT *dispatcher ) +{ + const UINT *umip_insn_len = *(const UINT **)(dispatcher->HandlerData); + + got_exception++; + trace( "umip_handler exception: %x flags:%x addr:%p\n", + rec->ExceptionCode, rec->ExceptionFlags, rec->ExceptionAddress ); + + context->Rip += *umip_insn_len; + return ExceptionContinueExecution; +} +#else +static DWORD umip_handler( EXCEPTION_RECORD *rec, EXCEPTION_REGISTRATION_RECORD *frame, + CONTEXT *context, EXCEPTION_REGISTRATION_RECORD **dispatcher ) +{ + const UINT *umip_insn_len = *(const UINT **)(frame + 1); + + got_exception++; + trace( "umip_handler exception: %x flags:%x addr:%p\n", + rec->ExceptionCode, rec->ExceptionFlags, rec->ExceptionAddress ); + + context->Eip += *umip_insn_len; + return ExceptionContinueExecution; +} +#endif + +/* Each UMIP test is run first using run_exception_test() in case it throws an exception. If it doesn't, the + * test is run again to check the returned value. + * Tests writing to memory have two versions: one used for run_exception_test() that writes to the stack, and the + * other writing to a pointer passed as an argument. + */ +static void umip_test_reg_mem_16(const char *insn_name, + const BYTE *reg16_code, UINT reg16_code_size, UINT reg16_insn_len, + const BYTE *reg32_code, UINT reg32_code_size, UINT reg32_insn_len, + const BYTE *reg64_code, UINT reg64_code_size, UINT reg64_insn_len, + const BYTE *mem16_arg_code, UINT mem16_arg_code_size, + const BYTE *mem16_stack_code, UINT mem16_stack_code_size, UINT mem16_stack_insn_len) +{ + /* sldt, str, smsw all store a 16-bit value to a register or memory. + * When the destination is a register, the 16-bit value is zero-extended to the destination register size. + * When the destination is memory, only the 16-bit value is stored. + */ + UINT16 value16; + UINT32 value32; + + UINT16 (*reg16_func)(void) = code_mem; + UINT32 (*reg32_func)(void) = code_mem; + void (*mem16_func)(UINT16 *value) = code_mem; + + /* Destination is 16-bit register */ + got_exception = 0; + run_exception_test(umip_handler, ®16_insn_len, reg16_code, reg16_code_size, 0); + ok(!got_exception, "%s reg16 caused exception\n", insn_name); + if (!got_exception) + { + memset(&value16, 0xcc, sizeof(value16)); + memcpy(code_mem, reg16_code, reg16_code_size); + value16 = reg16_func(); + trace("%s reg16: 0x%x\n", insn_name, value16); + } + + /* Destination is 32-bit register. */ + got_exception = 0; + run_exception_test(umip_handler, ®32_insn_len, reg32_code, reg32_code_size, 0); + ok(!got_exception, "%s reg32 caused exception\n", insn_name); + if (!got_exception) + { + memset(&value32, 0xcc, sizeof(value32)); + memcpy(code_mem, reg32_code, reg32_code_size); + value32 = reg32_func(); + trace("%s reg32: 0x%x\n", insn_name, value32); +#if defined(__x86_64__) + /* For sldt/str in 64-bit mode, the upper 16 bits is defined to be zero */ + if (!strcmp(insn_name, "sldt") || !strcmp(insn_name, "str")) + ok(value32 >> 16 == 0, "%s expected upper 16 bits = 0, got 0x%x\n", insn_name, value32 >> 16); +#endif + } + +#if defined(__x86_64__) + /* Destination is 64-bit register */ + got_exception = 0; + run_exception_test(umip_handler, ®64_insn_len, reg64_code, reg64_code_size, 0); + ok(!got_exception, "%s reg64 caused exception\n", insn_name); + if (!got_exception) + { + UINT64 value64; + UINT64 (*reg64_func)(void) = code_mem; + + memset(&value64, 0xcc, sizeof(value64)); + memcpy(code_mem, reg64_code, reg64_code_size); + value64 = reg64_func(); + trace("%s reg64: 0x%llx\n", insn_name, value64); + + /* For sldt/str the upper 48 bits is defined to be zero */ + if (!strcmp(insn_name, "sldt") || !strcmp(insn_name, "str")) + ok(value64 >> 16 == 0, "%s expected upper 48 bits = 0, got 0x%llx\n", insn_name, value64 >> 16); + } +#endif + + /* Destination is memory (only the low 16 bits are defined to be written) */ + got_exception = 0; + run_exception_test(umip_handler, &mem16_stack_insn_len, mem16_stack_code, mem16_stack_code_size, 0); + ok(!got_exception, "%s mem16_stack caused exception\n", insn_name); + if (!got_exception) + { + memset(&value32, 0xcc, sizeof(value32)); + memcpy(code_mem, mem16_arg_code, mem16_arg_code_size); + mem16_func((UINT16 *)&value32); + trace("%s mem: 0x%x\n", insn_name, value32); + ok(value32 >> 16 == 0xcccc, "%s expected upper 16 bits = 0xcccc, got 0x%x\n", insn_name, value32 >> 16); + } +} + +static void umip_test_mem_descriptor(const char *insn_name, + const BYTE *code_arg, UINT code_arg_size, + const BYTE *code_stack, UINT code_stack_size, UINT code_stack_insn_len) +{ + /* sgdt and sidt write the descriptor table register to memory. + * The descriptor consists of a 2-byte limit field, and a base field which + * is 4 bytes in 32-bit mode and 8 bytes in 64-bit mode. + */ + BYTE descriptor[10]; + void (*func)(BYTE *value) = code_mem; + + got_exception = 0; + run_exception_test(umip_handler, &code_stack_insn_len, code_stack, code_stack_size, 0); + ok(!got_exception, "%s caused exception\n", insn_name); + if (!got_exception) + { + memset(descriptor, 0xcc, sizeof(descriptor)); + memcpy(code_mem, code_arg, code_arg_size); + func(descriptor); + trace("%s limit: 0x%x\n", insn_name, *(UINT16 *)&descriptor[0]); + trace("%s base: 0x%p\n", insn_name, (void *) *(UINT_PTR *)&descriptor[2]); + } +} + +static void umip_test_sldt(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x00, 0xc0, /* sldtw %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x00, 0xc0, /* sldtl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x00, 0xc0, /* sldtq %rax */ + 0xc3, /* ret */ + }; +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x00, 0x01, /* sldtw (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x00, 0x44, 0x24, 0x08, /* sldtw 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x00, 0x01, /* sldtw (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x00, 0x04, 0x24, /* sldtw (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("sldt", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void umip_test_str(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x00, 0xc8, /* strw %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x00, 0xc8, /* strl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x00, 0xc8, /* strq %rax */ + 0xc3, /* ret */ + }; +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x00, 0x09, /* strw (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x00, 0x4c, 0x24, 0x08, /* strw 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x00, 0x09, /* strw (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x00, 0x0c, 0x24, /* strw (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("str", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void umip_test_sgdt(void) +{ + /* sgdt destination must be memory */ +#if defined(__x86_64__) + static const BYTE mem_arg[] = { + 0x0f, 0x01, 0x01, /* sgdtt (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x0f, 0x01, 0x44, 0x24, 0x08, /* sgdtt 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x01, /* sgdt (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x83, 0xec, 0x06, /* subl $0x6,%esp */ + 0x0f, 0x01, 0x04, 0x24, /* sgdt (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x06, /* addl $0x6,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_mem_descriptor("sgdt", mem_arg, sizeof(mem_arg), mem_stack, sizeof(mem_stack), 5); +} + +static void umip_test_sidt(void) +{ + /* sidt destination must be memory */ +#if defined(__x86_64__) + static const BYTE mem_arg[] = { + 0x0f, 0x01, 0x09, /* sidtt (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x0f, 0x01, 0x4c, 0x24, 0x08, /* sidtt 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x09, /* sidt (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem_stack[] = { + 0x83, 0xec, 0x06, /* subl $0x6,%esp */ + 0x0f, 0x01, 0x0c, 0x24, /* sidt (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x06, /* addl $0x6,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_mem_descriptor("sidt", mem_arg, sizeof(mem_arg), mem_stack, sizeof(mem_stack), 5); +} + +static void umip_test_smsw(void) +{ + static const BYTE reg16[] = { + 0x66, 0x0f, 0x01, 0xe0, /* smsww %ax */ + 0xc3, /* ret */ + }; + static const BYTE reg32[] = { + 0x0f, 0x01, 0xe0, /* smswl %eax */ + 0xc3, /* ret */ + }; + static const BYTE reg64[] = { + 0x48, 0x0f, 0x01, 0xe0, /* smswq %rax */ + 0xc3, /* ret */ + }; + +#if defined(__x86_64__) + static const BYTE mem16_arg[] = { + 0x0f, 0x01, 0x21, /* smsww (%rcx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x0f, 0x01, 0x64, 0x24, 0x08, /* smsww 0x8(%rsp) */ + 0xc3, /* ret */ + }; +#else + static const BYTE mem16_arg[] = { + 0x8b, 0x4c, 0x24, 0x04, /* movl 0x4(%esp), %ecx */ + 0x0f, 0x01, 0x21, /* smsww (%ecx) */ + 0xc3, /* ret */ + }; + static const BYTE mem16_stack[] = { + 0x83, 0xec, 0x02, /* subl $0x2,%esp */ + 0x0f, 0x01, 0x24, 0x24, /* smsww (%esp) */ + 0x90, /* nop (padding so mem16_stack_insn_len is same for x86_64 and i386) */ + 0x83, 0xc4, 0x02, /* addl $0x2,%esp */ + 0xc3, /* ret */ + }; +#endif + + umip_test_reg_mem_16("smsw", + reg16, sizeof(reg16), 4, + reg32, sizeof(reg32), 3, + reg64, sizeof(reg64), 4, + mem16_arg, sizeof(mem16_arg), + mem16_stack, sizeof(mem16_stack), 5); +} + +static void test_umip(void) +{ + umip_test_sldt(); + umip_test_str(); + umip_test_sgdt(); + umip_test_sidt(); + umip_test_smsw(); +} + START_TEST(exception) { HMODULE hntdll = GetModuleHandleA("ntdll.dll"); @@ -3513,6 +3886,7 @@ START_TEST(exception) test_suspend_thread(); test_suspend_process(); test_unload_trace(); + test_umip();
#elif defined(__x86_64__)
@@ -3551,6 +3925,7 @@ START_TEST(exception) test_suspend_thread(); test_suspend_process(); test_unload_trace(); + test_umip();
if (pRtlAddFunctionTable && pRtlDeleteFunctionTable && pRtlInstallFunctionTableCallback && pRtlLookupFunctionEntry) test_dynamic_unwind();
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=60608
Your paranoid android.
=== w2008s64 (32 bit report) ===
ntdll: exception.c:382: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:418: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:382: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:418: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:382: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:418: Test failed: ExceptionAddress at 003C0000 instead of 003C000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:382: Test failed: ExceptionAddress at 00000000 instead of 0034000B exception.c:418: Test failed: ExceptionAddress at 00000000 instead of 0034000B