[RFC PATCH 1/1] ntdll: Add emulation for UMIP instructions

Brendan Shanks bshanks at codeweavers.com
Thu Sep 12 18:54:49 CDT 2019


Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47571
Signed-off-by: Brendan Shanks <bshanks at codeweavers.com>
---
 dlls/ntdll/Makefile.in     |   1 +
 dlls/ntdll/signal_i386.c   |   2 +
 dlls/ntdll/signal_x86_64.c |   3 +
 dlls/ntdll/umip.c          | 787 +++++++++++++++++++++++++++++++++++++
 dlls/ntdll/umip.h          |  21 +
 5 files changed, 814 insertions(+)
 create mode 100644 dlls/ntdll/umip.c
 create mode 100644 dlls/ntdll/umip.h

diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in
index 9b4d70f6e8..999fc1abf5 100644
--- a/dlls/ntdll/Makefile.in
+++ b/dlls/ntdll/Makefile.in
@@ -50,6 +50,7 @@ C_SRCS = \
 	thread.c \
 	threadpool.c \
 	time.c \
+	umip.c \
 	version.c \
 	virtual.c \
 	wcstring.c
diff --git a/dlls/ntdll/signal_i386.c b/dlls/ntdll/signal_i386.c
index a43599e375..94a828dcdf 100644
--- a/dlls/ntdll/signal_i386.c
+++ b/dlls/ntdll/signal_i386.c
@@ -59,6 +59,7 @@
 #include "ntdll_misc.h"
 #include "wine/exception.h"
 #include "wine/debug.h"
+#include "umip.h"
 
 #ifdef HAVE_VALGRIND_MEMCHECK_H
 #include <valgrind/memcheck.h>
@@ -2440,6 +2441,7 @@ void signal_init_process(void)
 #endif
 
     wine_ldt_init_locking( ldt_lock, ldt_unlock );
+    umip_add_handler();
     return;
 
  error:
diff --git a/dlls/ntdll/signal_x86_64.c b/dlls/ntdll/signal_x86_64.c
index feb2e69d3f..ebfef1c685 100644
--- a/dlls/ntdll/signal_x86_64.c
+++ b/dlls/ntdll/signal_x86_64.c
@@ -67,6 +67,7 @@
 #include "wine/list.h"
 #include "ntdll_misc.h"
 #include "wine/debug.h"
+#include "umip.h"
 
 #ifdef HAVE_VALGRIND_MEMCHECK_H
 #include <valgrind/memcheck.h>
@@ -3350,6 +3351,8 @@ void signal_init_process(void)
     sig_act.sa_sigaction = trap_handler;
     if (sigaction( SIGTRAP, &sig_act, NULL ) == -1) goto error;
 #endif
+
+    umip_add_handler();
     return;
 
  error:
diff --git a/dlls/ntdll/umip.c b/dlls/ntdll/umip.c
new file mode 100644
index 0000000000..c05211568b
--- /dev/null
+++ b/dlls/ntdll/umip.c
@@ -0,0 +1,787 @@
+/*
+ * Emulation for instructions covered by User-Mode Instruction Prevention
+ *
+ * Copyright (C) 2019 Brendan Shanks for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * 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:
+ * 32-bit processes: the kernel traps the GPF and emulates sgdt, sidt, and smsw.
+ *                   sldt and str are not emulated.
+ *
+ * 64-bit processes: no emulation is done for any instructions.
+ *                   A kernel patch to add emulation for sgdt, sidt, and smsw
+ *                   has been accepted and should be in v5.4.
+ *
+ * When the kernel doesn't emulate an instruction, the process receives a SIGSEGV.
+ * This file contains exception handlers for 32- and 64-bit code to emulate all
+ * instructions not emulated by the Linux kernel.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+
+#define NONAMELESSUNION
+#include "windef.h"
+#include "winbase.h"
+#include "winternl.h"
+#define WIN32_NO_STATUS
+#include "ddk/wdm.h"
+#include "excpt.h"
+#include "wine/asm.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(seh);
+
+/* 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
+
+#if defined(__i386__) && defined(linux)
+
+static DWORD *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 void *INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr,
+                                   int long_addr, int segprefix, int *len )
+{
+    int mod, rm, base = 0, index = 0, ss = 0, off;
+
+#define GET_VAL(val,type) \
+    { *val = *(type *)instr; instr += sizeof(type); *len += sizeof(type); }
+
+    *len = 0;
+    GET_VAL( &mod, BYTE );
+    rm = mod & 7;
+    mod >>= 6;
+
+    if (mod == 3) return get_reg_address( context, rm );
+
+    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 */
+    return (void *)(base + (index << ss));
+#undef GET_VAL
+}
+
+
+/***********************************************************************
+ *           emulate_instruction
+ *
+ * Emulate a privileged instruction.
+ * Returns exception continuation status.
+ */
+static DWORD emulate_instruction( EXCEPTION_RECORD *rec, CONTEXT *context )
+{
+    int prefix, segprefix, prefixlen, len, long_op, long_addr;
+    BYTE *instr;
+
+    long_op = long_addr = 1;
+    instr = (BYTE *)context->Eip;
+    if (!instr) return ExceptionContinueSearch;
+
+    /* First handle any possible prefix */
+
+    segprefix = -1;  /* no prefix */
+    prefix = 1;
+    prefixlen = 0;
+    while(prefix)
+    {
+        switch(*instr)
+        {
+        case 0x2e:
+            segprefix = context->SegCs;
+            break;
+        case 0x36:
+            segprefix = context->SegSs;
+            break;
+        case 0x3e:
+            segprefix = context->SegDs;
+            break;
+        case 0x26:
+            segprefix = context->SegEs;
+            break;
+        case 0x64:
+            segprefix = context->SegFs;
+            break;
+        case 0x65:
+            segprefix = context->SegGs;
+            break;
+        case 0x66:
+            long_op = !long_op;  /* opcode size prefix */
+            break;
+        case 0x67:
+            long_addr = !long_addr;  /* addr size prefix */
+            break;
+        case 0xf0:  /* lock */
+	    break;
+        case 0xf2:  /* repne */
+	    break;
+        case 0xf3:  /* repe */
+            break;
+        default:
+            prefix = 0;  /* no more prefixes */
+            break;
+        }
+        if (prefix)
+        {
+            instr++;
+            prefixlen++;
+        }
+    }
+
+    /* Now look at the actual instruction */
+
+    switch(*instr)
+    {
+    case 0x0f: /* extended instruction */
+        switch(instr[1])
+        {
+        case 0x00: /* sldt/str */
+        {
+            int reg = (instr[2] >> 3) & 7;
+            switch (reg)
+            {
+            case 0: /* sldt */
+            case 1: /* str */
+            {
+                BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr,
+                                                   segprefix, &len );
+                int mod = instr[2] >> 6;
+                UINT16 dummy_value;
+                
+                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. */
+                    memcpy( data, &dummy_value, sizeof(dummy_value) );
+                }
+                context->Eip += prefixlen + len + 2;
+                return ExceptionContinueExecution;
+            }
+            }
+        }
+        case 0x01: /* sgdt/sidt/smsw */
+        {
+            /* 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.
+             */
+            break;
+        }
+        }
+        break;
+    }
+    return ExceptionContinueSearch;  /* Unable to emulate it */
+}
+
+
+/***********************************************************************
+ *           vectored_handler
+ *
+ * Vectored exception handler used to emulate protected instructions
+ * from 32-bit code.
+ */
+static LONG CALLBACK vectored_handler( EXCEPTION_POINTERS *ptrs )
+{
+    EXCEPTION_RECORD *record = ptrs->ExceptionRecord;
+    CONTEXT *context = ptrs->ContextRecord;
+
+    if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
+        record->ExceptionInformation[0] == 0 &&
+        record->ExceptionInformation[1] == 0xffffffff)
+    {
+        if (emulate_instruction( record, context ) == ExceptionContinueExecution)
+            return EXCEPTION_CONTINUE_EXECUTION;
+    }
+    return EXCEPTION_CONTINUE_SEARCH;
+}
+
+#elif defined(__x86_64__) && defined(linux)     /* __i386__ && linux */
+
+#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( 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 BYTE *INSTR_GetOperandAddr( CONTEXT *context, BYTE *instr,
+                                   int long_addr, int rex, int segprefix, int *len )
+{
+    int mod, rm, ss = 0, off, have_sib = 0;
+    DWORD64 base = 0, index = 0;
+
+#define GET_VAL( val, type ) \
+    { *val = *(type *)instr; instr += sizeof(type); *len += sizeof(type); }
+
+    *len = 0;
+    GET_VAL( &mod, BYTE );
+    rm  = REGMODRM_RM( mod, rex );
+    mod = REGMODRM_MOD( mod, rex );
+
+    if (mod == 3)
+        return (BYTE *)get_int_reg( context, rm );
+
+    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( context, id ) : 0;
+        if (!long_addr) index &= 0xffffffff;
+        have_sib = 1;
+    }
+
+    base = *get_int_reg( 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 */
+    return (BYTE *)(base + (index << ss));
+#undef GET_VAL
+}
+
+
+/***********************************************************************
+ *           emulate_instruction
+ *
+ * Emulate a privileged instruction.
+ * Returns exception continuation status.
+ */
+static DWORD emulate_instruction( EXCEPTION_RECORD *rec, CONTEXT *context )
+{
+    int prefix, segprefix, prefixlen, len, long_op, long_addr, rex;
+    BYTE *instr;
+
+    long_op = long_addr = 1;
+    instr = (BYTE *)context->Rip;
+    if (!instr) return ExceptionContinueSearch;
+
+    /* First handle any possible prefix */
+
+    segprefix = -1;  /* no seg prefix */
+    rex = 0;  /* no rex prefix */
+    prefix = 1;
+    prefixlen = 0;
+    while(prefix)
+    {
+        switch(*instr)
+        {
+        case 0x2e:
+            segprefix = context->SegCs;
+            break;
+        case 0x36:
+            segprefix = context->SegSs;
+            break;
+        case 0x3e:
+            segprefix = context->SegDs;
+            break;
+        case 0x26:
+            segprefix = context->SegEs;
+            break;
+        case 0x64:
+            segprefix = context->SegFs;
+            break;
+        case 0x65:
+            segprefix = context->SegGs;
+            break;
+        case 0x66:
+            long_op = !long_op;  /* opcode size prefix */
+            break;
+        case 0x67:
+            long_addr = !long_addr;  /* addr size prefix */
+            break;
+        case 0x40:  /* rex */
+        case 0x41:
+        case 0x42:
+        case 0x43:
+        case 0x44:
+        case 0x45:
+        case 0x46:
+        case 0x47:
+        case 0x48:
+        case 0x49:
+        case 0x4a:
+        case 0x4b:
+        case 0x4c:
+        case 0x4d:
+        case 0x4e:
+        case 0x4f:
+            rex = *instr;
+            break;
+        case 0xf0:  /* lock */
+            break;
+        case 0xf2:  /* repne */
+            break;
+        case 0xf3:  /* repe */
+            break;
+        default:
+            prefix = 0;  /* no more prefixes */
+            break;
+        }
+        if (prefix)
+        {
+            instr++;
+            prefixlen++;
+        }
+    }
+
+    /* Now look at the actual instruction */
+
+    switch(*instr)
+    {
+    case 0x0f: /* extended instruction */
+        switch(instr[1])
+        {
+        case 0x00: /* sldt/str */
+        {
+            int reg = REGMODRM_REG( instr[2], rex );
+            switch (reg)
+            {
+            case 0: /* sldt */
+            case 1: /* str */
+            {
+                BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr,
+                                                   rex, segprefix, &len );
+                int rm = REGMODRM_RM( instr[2], rex );
+                UINT16 dummy_value;
+
+                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[2], 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, get_op_size( long_op, rex ) );
+                }
+                else
+                {
+                    /* Destination operand is a memory location.
+                     * Only copy 16 bits regardless of operand size. */
+                    memcpy( data, &dummy_value, sizeof(dummy_value) );
+                }
+                context->Rip += prefixlen + len + 2;
+                return ExceptionContinueExecution;
+            }
+            default: break;
+            }
+        }
+        case 0x01: /* sgdt/sidt/smsw */
+        {
+            int reg = REGMODRM_REG( instr[2], 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[2], rex ) == 3)
+                    return ExceptionContinueSearch;
+
+                data = INSTR_GetOperandAddr( context, instr + 2, long_addr,
+                                             rex, segprefix, &len );
+
+                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 );
+                }
+                memcpy( data, &dummy_limit, UMIP_GDT_IDT_LIMIT_SIZE );
+                memcpy( data + UMIP_GDT_IDT_LIMIT_SIZE, &dummy_base_addr, UMIP_GDT_IDT_BASE_SIZE_64BIT );
+
+                context->Rip += prefixlen + len + 2;
+                return ExceptionContinueExecution;
+            }
+            case 4: /* smsw */
+            {
+                BYTE *data = INSTR_GetOperandAddr( context, instr + 2, long_addr,
+                                                   rex, segprefix, &len );
+                int rm = REGMODRM_RM( instr[2], rex );
+
+                if (REGMODRM_MOD( instr[2], 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 );
+                    memcpy( data, &dummy_msw, sizeof(dummy_msw) );
+                }
+                context->Rip += prefixlen + len + 2;
+                return ExceptionContinueExecution;
+            }
+            default: break;
+            }
+        }
+        }
+        break;  /* Unable to emulate it */
+    }
+    return ExceptionContinueSearch;  /* Unable to emulate it */
+}
+
+
+/***********************************************************************
+ *           vectored_handler
+ *
+ * Vectored exception handler used to emulate UMIP instructions
+ * from 64-bit code.
+ */
+static LONG CALLBACK vectored_handler( EXCEPTION_POINTERS *ptrs )
+{
+    EXCEPTION_RECORD *record = ptrs->ExceptionRecord;
+    CONTEXT *context = ptrs->ContextRecord;
+
+    if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
+        record->ExceptionInformation[0] == 0 &&
+        record->ExceptionInformation[1] == 0xffffffffffffffff)
+    {
+        if (emulate_instruction( record, context ) == ExceptionContinueExecution)
+        {
+            TRACE( "next instruction rip=%lx\n", context->Rip );
+            TRACE( "  rax=%016lx rbx=%016lx rcx=%016lx rdx=%016lx\n",
+                   context->Rax, context->Rbx, context->Rcx, context->Rdx );
+            TRACE( "  rsi=%016lx rdi=%016lx rbp=%016lx rsp=%016lx\n",
+                   context->Rsi, context->Rdi, context->Rbp, context->Rsp );
+            TRACE( "   r8=%016lx  r9=%016lx r10=%016lx r11=%016lx\n",
+                   context->R8, context->R9, context->R10, context->R11 );
+            TRACE( "  r12=%016lx r13=%016lx r14=%016lx r15=%016lx\n",
+                   context->R12, context->R13, context->R14, context->R15 );
+
+            return EXCEPTION_CONTINUE_EXECUTION;
+        }
+    }
+    return EXCEPTION_CONTINUE_SEARCH;
+}
+#endif      /* __x86_64 && linux */
+
+#if (defined(__i386__) || defined(__x86_64__)) && defined(linux)
+extern int have_cpuid (void );
+
+extern void do_cpuid_cx( unsigned int ax, unsigned int cx, unsigned int *p );
+#ifdef __i386__
+__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" )
+#else
+__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" )
+#endif
+
+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];
+
+#ifdef __i386__
+    if (!have_cpuid()) return 0;
+#endif
+
+    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      /* (__i386__ || __x86_64__) && linux */
+
+void umip_add_handler( void )
+{
+#if (defined(__i386__) || defined(__x86_64__)) && defined(linux)
+    if (umip_enabled())
+        RtlAddVectoredExceptionHandler( TRUE, vectored_handler );
+#endif
+}
diff --git a/dlls/ntdll/umip.h b/dlls/ntdll/umip.h
new file mode 100644
index 0000000000..704d0a25d5
--- /dev/null
+++ b/dlls/ntdll/umip.h
@@ -0,0 +1,21 @@
+/*
+ * Emulation for instructions covered by User-Mode Instruction Prevention
+ *
+ * Copyright (C) 2019 Brendan Shanks for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+extern void umip_add_handler(void) DECLSPEC_HIDDEN;
-- 
2.17.1




More information about the wine-devel mailing list