diff options
Diffstat (limited to 'src/VBox/VMM/VMMAll/IEMAll.cpp')
-rw-r--r-- | src/VBox/VMM/VMMAll/IEMAll.cpp | 11599 |
1 files changed, 11599 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMAll/IEMAll.cpp b/src/VBox/VMM/VMMAll/IEMAll.cpp new file mode 100644 index 00000000..106c51c5 --- /dev/null +++ b/src/VBox/VMM/VMMAll/IEMAll.cpp @@ -0,0 +1,11599 @@ +/* $Id: IEMAll.cpp $ */ +/** @file + * IEM - Interpreted Execution Manager - All Contexts. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_iem IEM - Interpreted Execution Manager + * + * The interpreted exeuction manager (IEM) is for executing short guest code + * sequences that are causing too many exits / virtualization traps. It will + * also be used to interpret single instructions, thus replacing the selective + * interpreters in EM and IOM. + * + * Design goals: + * - Relatively small footprint, although we favour speed and correctness + * over size. + * - Reasonably fast. + * - Correctly handle lock prefixed instructions. + * - Complete instruction set - eventually. + * - Refactorable into a recompiler, maybe. + * - Replace EMInterpret*. + * + * Using the existing disassembler has been considered, however this is thought + * to conflict with speed as the disassembler chews things a bit too much while + * leaving us with a somewhat complicated state to interpret afterwards. + * + * + * The current code is very much work in progress. You've been warned! + * + * + * @section sec_iem_fpu_instr FPU Instructions + * + * On x86 and AMD64 hosts, the FPU instructions are implemented by executing the + * same or equivalent instructions on the host FPU. To make life easy, we also + * let the FPU prioritize the unmasked exceptions for us. This however, only + * works reliably when CR0.NE is set, i.e. when using \#MF instead the IRQ 13 + * for FPU exception delivery, because with CR0.NE=0 there is a window where we + * can trigger spurious FPU exceptions. + * + * The guest FPU state is not loaded into the host CPU and kept there till we + * leave IEM because the calling conventions have declared an all year open + * season on much of the FPU state. For instance an innocent looking call to + * memcpy might end up using a whole bunch of XMM or MM registers if the + * particular implementation finds it worthwhile. + * + * + * @section sec_iem_logging Logging + * + * The IEM code uses the \"IEM\" log group for the main logging. The different + * logging levels/flags are generally used for the following purposes: + * - Level 1 (Log) : Errors, exceptions, interrupts and such major events. + * - Flow (LogFlow) : Basic enter/exit IEM state info. + * - Level 2 (Log2) : ? + * - Level 3 (Log3) : More detailed enter/exit IEM state info. + * - Level 4 (Log4) : Decoding mnemonics w/ EIP. + * - Level 5 (Log5) : Decoding details. + * - Level 6 (Log6) : Enables/disables the lockstep comparison with REM. + * - Level 7 (Log7) : iret++ execution logging. + * - Level 8 (Log8) : Memory writes. + * - Level 9 (Log9) : Memory reads. + * - Level 10 (Log10): TLBs. + * - Level 11 (Log11): Unmasked FPU exceptions. + * + * The SVM (AMD-V) and VMX (VT-x) code has the following assignments: + * - Level 1 (Log) : Errors and other major events. + * - Flow (LogFlow) : Misc flow stuff (cleanup?) + * - Level 2 (Log2) : VM exits. + */ + +/* Disabled warning C4505: 'iemRaisePageFaultJmp' : unreferenced local function has been removed */ +#ifdef _MSC_VER +# pragma warning(disable:4505) +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_IEM +#define VMCPU_INCL_CPUM_GST_CTX +#include <VBox/vmm/iem.h> +#include <VBox/vmm/cpum.h> +#include <VBox/vmm/apic.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/pgm.h> +#include <VBox/vmm/iom.h> +#include <VBox/vmm/em.h> +#include <VBox/vmm/hm.h> +#include <VBox/vmm/nem.h> +#include <VBox/vmm/gim.h> +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM +# include <VBox/vmm/em.h> +# include <VBox/vmm/hm_svm.h> +#endif +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX +# include <VBox/vmm/hmvmxinline.h> +#endif +#include <VBox/vmm/tm.h> +#include <VBox/vmm/dbgf.h> +#include <VBox/vmm/dbgftrace.h> +#include "IEMInternal.h" +#include <VBox/vmm/vmcc.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/dis.h> +#include <VBox/disopcode.h> +#include <iprt/asm-math.h> +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/asm-amd64-x86.h> +#elif defined(RT_ARCH_ARM64) || defined(RT_ARCH_ARM32) +# include <iprt/asm-arm.h> +#endif +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/x86.h> + +#include "IEMInline.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * CPU exception classes. + */ +typedef enum IEMXCPTCLASS +{ + IEMXCPTCLASS_BENIGN, + IEMXCPTCLASS_CONTRIBUTORY, + IEMXCPTCLASS_PAGE_FAULT, + IEMXCPTCLASS_DOUBLE_FAULT +} IEMXCPTCLASS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(IEM_LOG_MEMORY_WRITES) +/** What IEM just wrote. */ +uint8_t g_abIemWrote[256]; +/** How much IEM just wrote. */ +size_t g_cbIemWrote; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static VBOXSTRICTRC iemMemFetchSelDescWithErr(PVMCPUCC pVCpu, PIEMSELDESC pDesc, uint16_t uSel, + uint8_t uXcpt, uint16_t uErrorCode) RT_NOEXCEPT; + + +/** + * Slow path of iemInitDecoder() and iemInitExec() that checks what kind of + * breakpoints are enabled. + * + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + */ +void iemInitPendingBreakpointsSlow(PVMCPUCC pVCpu) +{ + /* + * Process guest breakpoints. + */ +#define PROCESS_ONE_BP(a_fDr7, a_iBp) do { \ + if (a_fDr7 & X86_DR7_L_G(a_iBp)) \ + { \ + switch (X86_DR7_GET_RW(a_fDr7, a_iBp)) \ + { \ + case X86_DR7_RW_EO: \ + pVCpu->iem.s.fPendingInstructionBreakpoints = true; \ + break; \ + case X86_DR7_RW_WO: \ + case X86_DR7_RW_RW: \ + pVCpu->iem.s.fPendingDataBreakpoints = true; \ + break; \ + case X86_DR7_RW_IO: \ + pVCpu->iem.s.fPendingIoBreakpoints = true; \ + break; \ + } \ + } \ + } while (0) + uint32_t const fGstDr7 = (uint32_t)pVCpu->cpum.GstCtx.dr[7]; + if (fGstDr7 & X86_DR7_ENABLED_MASK) + { + PROCESS_ONE_BP(fGstDr7, 0); + PROCESS_ONE_BP(fGstDr7, 1); + PROCESS_ONE_BP(fGstDr7, 2); + PROCESS_ONE_BP(fGstDr7, 3); + } + + /* + * Process hypervisor breakpoints. + */ + uint32_t const fHyperDr7 = DBGFBpGetDR7(pVCpu->CTX_SUFF(pVM)); + if (fHyperDr7 & X86_DR7_ENABLED_MASK) + { + PROCESS_ONE_BP(fHyperDr7, 0); + PROCESS_ONE_BP(fHyperDr7, 1); + PROCESS_ONE_BP(fHyperDr7, 2); + PROCESS_ONE_BP(fHyperDr7, 3); + } +} + + +/** + * Initializes the decoder state. + * + * iemReInitDecoder is mostly a copy of this function. + * + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param fBypassHandlers Whether to bypass access handlers. + * @param fDisregardLock Whether to disregard the LOCK prefix. + */ +DECLINLINE(void) iemInitDecoder(PVMCPUCC pVCpu, bool fBypassHandlers, bool fDisregardLock) +{ + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_MUST_MASK); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_IEM)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.es)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ds)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.fs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.gs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ldtr)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.tr)); + + pVCpu->iem.s.uCpl = CPUMGetGuestCPL(pVCpu); + IEMMODE enmMode = iemCalcCpuMode(pVCpu); + pVCpu->iem.s.enmCpuMode = enmMode; + pVCpu->iem.s.enmDefAddrMode = enmMode; /** @todo check if this is correct... */ + pVCpu->iem.s.enmEffAddrMode = enmMode; + if (enmMode != IEMMODE_64BIT) + { + pVCpu->iem.s.enmDefOpSize = enmMode; /** @todo check if this is correct... */ + pVCpu->iem.s.enmEffOpSize = enmMode; + } + else + { + pVCpu->iem.s.enmDefOpSize = IEMMODE_32BIT; + pVCpu->iem.s.enmEffOpSize = IEMMODE_32BIT; + } + pVCpu->iem.s.fPrefixes = 0; + pVCpu->iem.s.uRexReg = 0; + pVCpu->iem.s.uRexB = 0; + pVCpu->iem.s.uRexIndex = 0; + pVCpu->iem.s.idxPrefix = 0; + pVCpu->iem.s.uVex3rdReg = 0; + pVCpu->iem.s.uVexLength = 0; + pVCpu->iem.s.fEvexStuff = 0; + pVCpu->iem.s.iEffSeg = X86_SREG_DS; +#ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.pbInstrBuf = NULL; + pVCpu->iem.s.offInstrNextByte = 0; + pVCpu->iem.s.offCurInstrStart = 0; +# ifdef VBOX_STRICT + pVCpu->iem.s.cbInstrBuf = UINT16_MAX; + pVCpu->iem.s.cbInstrBufTotal = UINT16_MAX; + pVCpu->iem.s.uInstrBufPc = UINT64_C(0xc0ffc0ffcff0c0ff); +# endif +#else + pVCpu->iem.s.offOpcode = 0; + pVCpu->iem.s.cbOpcode = 0; +#endif + pVCpu->iem.s.offModRm = 0; + pVCpu->iem.s.cActiveMappings = 0; + pVCpu->iem.s.iNextMapping = 0; + pVCpu->iem.s.rcPassUp = VINF_SUCCESS; + pVCpu->iem.s.fBypassHandlers = fBypassHandlers; + pVCpu->iem.s.fDisregardLock = fDisregardLock; + pVCpu->iem.s.fPendingInstructionBreakpoints = false; + pVCpu->iem.s.fPendingDataBreakpoints = false; + pVCpu->iem.s.fPendingIoBreakpoints = false; + if (RT_LIKELY( !(pVCpu->cpum.GstCtx.dr[7] & X86_DR7_ENABLED_MASK) + && pVCpu->CTX_SUFF(pVM)->dbgf.ro.cEnabledHwBreakpoints == 0)) + { /* likely */ } + else + iemInitPendingBreakpointsSlow(pVCpu); + +#ifdef DBGFTRACE_ENABLED + switch (enmMode) + { + case IEMMODE_64BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I64/%u %08llx", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.rip); + break; + case IEMMODE_32BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I32/%u %04x:%08x", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.eip); + break; + case IEMMODE_16BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I16/%u %04x:%04x", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.eip); + break; + } +#endif +} + + +/** + * Reinitializes the decoder state 2nd+ loop of IEMExecLots. + * + * This is mostly a copy of iemInitDecoder. + * + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + */ +DECLINLINE(void) iemReInitDecoder(PVMCPUCC pVCpu) +{ + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_IEM)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.es)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ds)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.fs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.gs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ldtr)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.tr)); + + pVCpu->iem.s.uCpl = CPUMGetGuestCPL(pVCpu); /** @todo this should be updated during execution! */ + IEMMODE enmMode = iemCalcCpuMode(pVCpu); + pVCpu->iem.s.enmCpuMode = enmMode; /** @todo this should be updated during execution! */ + pVCpu->iem.s.enmDefAddrMode = enmMode; /** @todo check if this is correct... */ + pVCpu->iem.s.enmEffAddrMode = enmMode; + if (enmMode != IEMMODE_64BIT) + { + pVCpu->iem.s.enmDefOpSize = enmMode; /** @todo check if this is correct... */ + pVCpu->iem.s.enmEffOpSize = enmMode; + } + else + { + pVCpu->iem.s.enmDefOpSize = IEMMODE_32BIT; + pVCpu->iem.s.enmEffOpSize = IEMMODE_32BIT; + } + pVCpu->iem.s.fPrefixes = 0; + pVCpu->iem.s.uRexReg = 0; + pVCpu->iem.s.uRexB = 0; + pVCpu->iem.s.uRexIndex = 0; + pVCpu->iem.s.idxPrefix = 0; + pVCpu->iem.s.uVex3rdReg = 0; + pVCpu->iem.s.uVexLength = 0; + pVCpu->iem.s.fEvexStuff = 0; + pVCpu->iem.s.iEffSeg = X86_SREG_DS; +#ifdef IEM_WITH_CODE_TLB + if (pVCpu->iem.s.pbInstrBuf) + { + uint64_t off = (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT + ? pVCpu->cpum.GstCtx.rip + : pVCpu->cpum.GstCtx.eip + (uint32_t)pVCpu->cpum.GstCtx.cs.u64Base) + - pVCpu->iem.s.uInstrBufPc; + if (off < pVCpu->iem.s.cbInstrBufTotal) + { + pVCpu->iem.s.offInstrNextByte = (uint32_t)off; + pVCpu->iem.s.offCurInstrStart = (uint16_t)off; + if ((uint16_t)off + 15 <= pVCpu->iem.s.cbInstrBufTotal) + pVCpu->iem.s.cbInstrBuf = (uint16_t)off + 15; + else + pVCpu->iem.s.cbInstrBuf = pVCpu->iem.s.cbInstrBufTotal; + } + else + { + pVCpu->iem.s.pbInstrBuf = NULL; + pVCpu->iem.s.offInstrNextByte = 0; + pVCpu->iem.s.offCurInstrStart = 0; + pVCpu->iem.s.cbInstrBuf = 0; + pVCpu->iem.s.cbInstrBufTotal = 0; + } + } + else + { + pVCpu->iem.s.offInstrNextByte = 0; + pVCpu->iem.s.offCurInstrStart = 0; + pVCpu->iem.s.cbInstrBuf = 0; + pVCpu->iem.s.cbInstrBufTotal = 0; + } +#else + pVCpu->iem.s.cbOpcode = 0; + pVCpu->iem.s.offOpcode = 0; +#endif + pVCpu->iem.s.offModRm = 0; + Assert(pVCpu->iem.s.cActiveMappings == 0); + pVCpu->iem.s.iNextMapping = 0; + Assert(pVCpu->iem.s.rcPassUp == VINF_SUCCESS); + Assert(pVCpu->iem.s.fBypassHandlers == false); + +#ifdef DBGFTRACE_ENABLED + switch (enmMode) + { + case IEMMODE_64BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I64/%u %08llx", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.rip); + break; + case IEMMODE_32BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I32/%u %04x:%08x", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.eip); + break; + case IEMMODE_16BIT: + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "I16/%u %04x:%04x", pVCpu->iem.s.uCpl, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.eip); + break; + } +#endif +} + + + +/** + * Prefetch opcodes the first time when starting executing. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param fBypassHandlers Whether to bypass access handlers. + * @param fDisregardLock Whether to disregard LOCK prefixes. + * + * @todo Combine fDisregardLock and fBypassHandlers into a flag parameter and + * store them as such. + */ +static VBOXSTRICTRC iemInitDecoderAndPrefetchOpcodes(PVMCPUCC pVCpu, bool fBypassHandlers, bool fDisregardLock) RT_NOEXCEPT +{ + iemInitDecoder(pVCpu, fBypassHandlers, fDisregardLock); + +#ifndef IEM_WITH_CODE_TLB + /* + * What we're doing here is very similar to iemMemMap/iemMemBounceBufferMap. + * + * First translate CS:rIP to a physical address. + * + * Note! The iemOpcodeFetchMoreBytes code depends on this here code to fetch + * all relevant bytes from the first page, as it ASSUMES it's only ever + * called for dealing with CS.LIM, page crossing and instructions that + * are too long. + */ + uint32_t cbToTryRead; + RTGCPTR GCPtrPC; + if (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT) + { + cbToTryRead = GUEST_PAGE_SIZE; + GCPtrPC = pVCpu->cpum.GstCtx.rip; + if (IEM_IS_CANONICAL(GCPtrPC)) + cbToTryRead = GUEST_PAGE_SIZE - (GCPtrPC & GUEST_PAGE_OFFSET_MASK); + else + return iemRaiseGeneralProtectionFault0(pVCpu); + } + else + { + uint32_t GCPtrPC32 = pVCpu->cpum.GstCtx.eip; + AssertMsg(!(GCPtrPC32 & ~(uint32_t)UINT16_MAX) || pVCpu->iem.s.enmCpuMode == IEMMODE_32BIT, ("%04x:%RX64\n", pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip)); + if (GCPtrPC32 <= pVCpu->cpum.GstCtx.cs.u32Limit) + cbToTryRead = pVCpu->cpum.GstCtx.cs.u32Limit - GCPtrPC32 + 1; + else + return iemRaiseSelectorBounds(pVCpu, X86_SREG_CS, IEM_ACCESS_INSTRUCTION); + if (cbToTryRead) { /* likely */ } + else /* overflowed */ + { + Assert(GCPtrPC32 == 0); Assert(pVCpu->cpum.GstCtx.cs.u32Limit == UINT32_MAX); + cbToTryRead = UINT32_MAX; + } + GCPtrPC = (uint32_t)pVCpu->cpum.GstCtx.cs.u64Base + GCPtrPC32; + Assert(GCPtrPC <= UINT32_MAX); + } + + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrPC, &Walk); + if (RT_SUCCESS(rc)) + Assert(Walk.fSucceeded); /* probable. */ + else + { + Log(("iemInitDecoderAndPrefetchOpcodes: %RGv - rc=%Rrc\n", GCPtrPC, rc)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PHYS_ADDR, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrPC, 1, IEM_ACCESS_INSTRUCTION, rc); + } + if ((Walk.fEffective & X86_PTE_US) || pVCpu->iem.s.uCpl != 3) { /* likely */ } + else + { + Log(("iemInitDecoderAndPrefetchOpcodes: %RGv - supervisor page\n", GCPtrPC)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrPC, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + if (!(Walk.fEffective & X86_PTE_PAE_NX) || !(pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_NXE)) { /* likely */ } + else + { + Log(("iemInitDecoderAndPrefetchOpcodes: %RGv - NX\n", GCPtrPC)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrPC, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + RTGCPHYS const GCPhys = Walk.GCPhys | (GCPtrPC & GUEST_PAGE_OFFSET_MASK); + /** @todo Check reserved bits and such stuff. PGM is better at doing + * that, so do it when implementing the guest virtual address + * TLB... */ + + /* + * Read the bytes at this address. + */ + uint32_t cbLeftOnPage = GUEST_PAGE_SIZE - (GCPtrPC & GUEST_PAGE_OFFSET_MASK); + if (cbToTryRead > cbLeftOnPage) + cbToTryRead = cbLeftOnPage; + if (cbToTryRead > sizeof(pVCpu->iem.s.abOpcode)) + cbToTryRead = sizeof(pVCpu->iem.s.abOpcode); + + if (!pVCpu->iem.s.fBypassHandlers) + { + VBOXSTRICTRC rcStrict = PGMPhysRead(pVCpu->CTX_SUFF(pVM), GCPhys, pVCpu->iem.s.abOpcode, cbToTryRead, PGMACCESSORIGIN_IEM); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { /* likely */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + Log(("iemInitDecoderAndPrefetchOpcodes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n", + GCPtrPC, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } + else + { + Log((RT_SUCCESS(rcStrict) + ? "iemInitDecoderAndPrefetchOpcodes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n" + : "iemInitDecoderAndPrefetchOpcodes: %RGv/%RGp LB %#x - read error - rcStrict=%Rrc (!!)\n", + GCPtrPC, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + return rcStrict; + } + } + else + { + rc = PGMPhysSimpleReadGCPhys(pVCpu->CTX_SUFF(pVM), pVCpu->iem.s.abOpcode, GCPhys, cbToTryRead); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + Log(("iemInitDecoderAndPrefetchOpcodes: %RGv/%RGp LB %#x - read error - rc=%Rrc (!!)\n", + GCPtrPC, GCPhys, rc, cbToTryRead)); + return rc; + } + } + pVCpu->iem.s.cbOpcode = cbToTryRead; +#endif /* !IEM_WITH_CODE_TLB */ + return VINF_SUCCESS; +} + + +/** + * Invalidates the IEM TLBs. + * + * This is called internally as well as by PGM when moving GC mappings. + * + * @param pVCpu The cross context virtual CPU structure of the calling + * thread. + */ +VMM_INT_DECL(void) IEMTlbInvalidateAll(PVMCPUCC pVCpu) +{ +#if defined(IEM_WITH_CODE_TLB) || defined(IEM_WITH_DATA_TLB) + Log10(("IEMTlbInvalidateAll\n")); +# ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.cbInstrBufTotal = 0; + pVCpu->iem.s.CodeTlb.uTlbRevision += IEMTLB_REVISION_INCR; + if (pVCpu->iem.s.CodeTlb.uTlbRevision != 0) + { /* very likely */ } + else + { + pVCpu->iem.s.CodeTlb.uTlbRevision = IEMTLB_REVISION_INCR; + unsigned i = RT_ELEMENTS(pVCpu->iem.s.CodeTlb.aEntries); + while (i-- > 0) + pVCpu->iem.s.CodeTlb.aEntries[i].uTag = 0; + } +# endif + +# ifdef IEM_WITH_DATA_TLB + pVCpu->iem.s.DataTlb.uTlbRevision += IEMTLB_REVISION_INCR; + if (pVCpu->iem.s.DataTlb.uTlbRevision != 0) + { /* very likely */ } + else + { + pVCpu->iem.s.DataTlb.uTlbRevision = IEMTLB_REVISION_INCR; + unsigned i = RT_ELEMENTS(pVCpu->iem.s.DataTlb.aEntries); + while (i-- > 0) + pVCpu->iem.s.DataTlb.aEntries[i].uTag = 0; + } +# endif +#else + RT_NOREF(pVCpu); +#endif +} + + +/** + * Invalidates a page in the TLBs. + * + * @param pVCpu The cross context virtual CPU structure of the calling + * thread. + * @param GCPtr The address of the page to invalidate + * @thread EMT(pVCpu) + */ +VMM_INT_DECL(void) IEMTlbInvalidatePage(PVMCPUCC pVCpu, RTGCPTR GCPtr) +{ +#if defined(IEM_WITH_CODE_TLB) || defined(IEM_WITH_DATA_TLB) + Log10(("IEMTlbInvalidatePage: GCPtr=%RGv\n", GCPtr)); + GCPtr = IEMTLB_CALC_TAG_NO_REV(GCPtr); + Assert(!(GCPtr >> (48 - X86_PAGE_SHIFT))); + uintptr_t const idx = IEMTLB_TAG_TO_INDEX(GCPtr); + +# ifdef IEM_WITH_CODE_TLB + if (pVCpu->iem.s.CodeTlb.aEntries[idx].uTag == (GCPtr | pVCpu->iem.s.CodeTlb.uTlbRevision)) + { + pVCpu->iem.s.CodeTlb.aEntries[idx].uTag = 0; + if (GCPtr == IEMTLB_CALC_TAG_NO_REV(pVCpu->iem.s.uInstrBufPc)) + pVCpu->iem.s.cbInstrBufTotal = 0; + } +# endif + +# ifdef IEM_WITH_DATA_TLB + if (pVCpu->iem.s.DataTlb.aEntries[idx].uTag == (GCPtr | pVCpu->iem.s.DataTlb.uTlbRevision)) + pVCpu->iem.s.DataTlb.aEntries[idx].uTag = 0; +# endif +#else + NOREF(pVCpu); NOREF(GCPtr); +#endif +} + + +#if defined(IEM_WITH_CODE_TLB) || defined(IEM_WITH_DATA_TLB) +/** + * Invalid both TLBs slow fashion following a rollover. + * + * Worker for IEMTlbInvalidateAllPhysical, + * IEMTlbInvalidateAllPhysicalAllCpus, iemOpcodeFetchBytesJmp, iemMemMap, + * iemMemMapJmp and others. + * + * @thread EMT(pVCpu) + */ +static void IEMTlbInvalidateAllPhysicalSlow(PVMCPUCC pVCpu) +{ + Log10(("IEMTlbInvalidateAllPhysicalSlow\n")); + ASMAtomicWriteU64(&pVCpu->iem.s.CodeTlb.uTlbPhysRev, IEMTLB_PHYS_REV_INCR * 2); + ASMAtomicWriteU64(&pVCpu->iem.s.DataTlb.uTlbPhysRev, IEMTLB_PHYS_REV_INCR * 2); + + unsigned i; +# ifdef IEM_WITH_CODE_TLB + i = RT_ELEMENTS(pVCpu->iem.s.CodeTlb.aEntries); + while (i-- > 0) + { + pVCpu->iem.s.CodeTlb.aEntries[i].pbMappingR3 = NULL; + pVCpu->iem.s.CodeTlb.aEntries[i].fFlagsAndPhysRev &= ~( IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_NO_READ + | IEMTLBE_F_PG_UNASSIGNED | IEMTLBE_F_PHYS_REV); + } +# endif +# ifdef IEM_WITH_DATA_TLB + i = RT_ELEMENTS(pVCpu->iem.s.DataTlb.aEntries); + while (i-- > 0) + { + pVCpu->iem.s.DataTlb.aEntries[i].pbMappingR3 = NULL; + pVCpu->iem.s.DataTlb.aEntries[i].fFlagsAndPhysRev &= ~( IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_NO_READ + | IEMTLBE_F_PG_UNASSIGNED | IEMTLBE_F_PHYS_REV); + } +# endif + +} +#endif + + +/** + * Invalidates the host physical aspects of the IEM TLBs. + * + * This is called internally as well as by PGM when moving GC mappings. + * + * @param pVCpu The cross context virtual CPU structure of the calling + * thread. + * @note Currently not used. + */ +VMM_INT_DECL(void) IEMTlbInvalidateAllPhysical(PVMCPUCC pVCpu) +{ +#if defined(IEM_WITH_CODE_TLB) || defined(IEM_WITH_DATA_TLB) + /* Note! This probably won't end up looking exactly like this, but it give an idea... */ + Log10(("IEMTlbInvalidateAllPhysical\n")); + +# ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.cbInstrBufTotal = 0; +# endif + uint64_t uTlbPhysRev = pVCpu->iem.s.CodeTlb.uTlbPhysRev + IEMTLB_PHYS_REV_INCR; + if (RT_LIKELY(uTlbPhysRev > IEMTLB_PHYS_REV_INCR * 2)) + { + pVCpu->iem.s.CodeTlb.uTlbPhysRev = uTlbPhysRev; + pVCpu->iem.s.DataTlb.uTlbPhysRev = uTlbPhysRev; + } + else + IEMTlbInvalidateAllPhysicalSlow(pVCpu); +#else + NOREF(pVCpu); +#endif +} + + +/** + * Invalidates the host physical aspects of the IEM TLBs. + * + * This is called internally as well as by PGM when moving GC mappings. + * + * @param pVM The cross context VM structure. + * @param idCpuCaller The ID of the calling EMT if available to the caller, + * otherwise NIL_VMCPUID. + * + * @remarks Caller holds the PGM lock. + */ +VMM_INT_DECL(void) IEMTlbInvalidateAllPhysicalAllCpus(PVMCC pVM, VMCPUID idCpuCaller) +{ +#if defined(IEM_WITH_CODE_TLB) || defined(IEM_WITH_DATA_TLB) + PVMCPUCC const pVCpuCaller = idCpuCaller >= pVM->cCpus ? VMMGetCpu(pVM) : VMMGetCpuById(pVM, idCpuCaller); + if (pVCpuCaller) + VMCPU_ASSERT_EMT(pVCpuCaller); + Log10(("IEMTlbInvalidateAllPhysicalAllCpus\n")); + + VMCC_FOR_EACH_VMCPU(pVM) + { +# ifdef IEM_WITH_CODE_TLB + if (pVCpuCaller == pVCpu) + pVCpu->iem.s.cbInstrBufTotal = 0; +# endif + + uint64_t const uTlbPhysRevPrev = ASMAtomicUoReadU64(&pVCpu->iem.s.CodeTlb.uTlbPhysRev); + uint64_t uTlbPhysRevNew = uTlbPhysRevPrev + IEMTLB_PHYS_REV_INCR; + if (RT_LIKELY(uTlbPhysRevNew > IEMTLB_PHYS_REV_INCR * 2)) + { /* likely */} + else if (pVCpuCaller == pVCpu) + uTlbPhysRevNew = IEMTLB_PHYS_REV_INCR; + else + { + IEMTlbInvalidateAllPhysicalSlow(pVCpu); + continue; + } + ASMAtomicCmpXchgU64(&pVCpu->iem.s.CodeTlb.uTlbPhysRev, uTlbPhysRevNew, uTlbPhysRevPrev); + ASMAtomicCmpXchgU64(&pVCpu->iem.s.DataTlb.uTlbPhysRev, uTlbPhysRevNew, uTlbPhysRevPrev); + } + VMCC_FOR_EACH_VMCPU_END(pVM); + +#else + RT_NOREF(pVM, idCpuCaller); +#endif +} + +#ifdef IEM_WITH_CODE_TLB + +/** + * Tries to fetches @a cbDst opcode bytes, raise the appropriate exception on + * failure and jumps. + * + * We end up here for a number of reasons: + * - pbInstrBuf isn't yet initialized. + * - Advancing beyond the buffer boundrary (e.g. cross page). + * - Advancing beyond the CS segment limit. + * - Fetching from non-mappable page (e.g. MMIO). + * + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param pvDst Where to return the bytes. + * @param cbDst Number of bytes to read. + * + * @todo Make cbDst = 0 a way of initializing pbInstrBuf? + */ +void iemOpcodeFetchBytesJmp(PVMCPUCC pVCpu, size_t cbDst, void *pvDst) IEM_NOEXCEPT_MAY_LONGJMP +{ +# ifdef IN_RING3 + for (;;) + { + Assert(cbDst <= 8); + uint32_t offBuf = pVCpu->iem.s.offInstrNextByte; + + /* + * We might have a partial buffer match, deal with that first to make the + * rest simpler. This is the first part of the cross page/buffer case. + */ + if (pVCpu->iem.s.pbInstrBuf != NULL) + { + if (offBuf < pVCpu->iem.s.cbInstrBuf) + { + Assert(offBuf + cbDst > pVCpu->iem.s.cbInstrBuf); + uint32_t const cbCopy = pVCpu->iem.s.cbInstrBuf - pVCpu->iem.s.offInstrNextByte; + memcpy(pvDst, &pVCpu->iem.s.pbInstrBuf[offBuf], cbCopy); + + cbDst -= cbCopy; + pvDst = (uint8_t *)pvDst + cbCopy; + offBuf += cbCopy; + pVCpu->iem.s.offInstrNextByte += offBuf; + } + } + + /* + * Check segment limit, figuring how much we're allowed to access at this point. + * + * We will fault immediately if RIP is past the segment limit / in non-canonical + * territory. If we do continue, there are one or more bytes to read before we + * end up in trouble and we need to do that first before faulting. + */ + RTGCPTR GCPtrFirst; + uint32_t cbMaxRead; + if (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT) + { + GCPtrFirst = pVCpu->cpum.GstCtx.rip + (offBuf - (uint32_t)(int32_t)pVCpu->iem.s.offCurInstrStart); + if (RT_LIKELY(IEM_IS_CANONICAL(GCPtrFirst))) + { /* likely */ } + else + iemRaiseGeneralProtectionFault0Jmp(pVCpu); + cbMaxRead = X86_PAGE_SIZE - ((uint32_t)GCPtrFirst & X86_PAGE_OFFSET_MASK); + } + else + { + GCPtrFirst = pVCpu->cpum.GstCtx.eip + (offBuf - (uint32_t)(int32_t)pVCpu->iem.s.offCurInstrStart); + /* Assert(!(GCPtrFirst & ~(uint32_t)UINT16_MAX) || pVCpu->iem.s.enmCpuMode == IEMMODE_32BIT); - this is allowed */ + if (RT_LIKELY((uint32_t)GCPtrFirst <= pVCpu->cpum.GstCtx.cs.u32Limit)) + { /* likely */ } + else /** @todo For CPUs older than the 386, we should not necessarily generate \#GP here but wrap around! */ + iemRaiseSelectorBoundsJmp(pVCpu, X86_SREG_CS, IEM_ACCESS_INSTRUCTION); + cbMaxRead = pVCpu->cpum.GstCtx.cs.u32Limit - (uint32_t)GCPtrFirst + 1; + if (cbMaxRead != 0) + { /* likely */ } + else + { + /* Overflowed because address is 0 and limit is max. */ + Assert(GCPtrFirst == 0); Assert(pVCpu->cpum.GstCtx.cs.u32Limit == UINT32_MAX); + cbMaxRead = X86_PAGE_SIZE; + } + GCPtrFirst = (uint32_t)GCPtrFirst + (uint32_t)pVCpu->cpum.GstCtx.cs.u64Base; + uint32_t cbMaxRead2 = X86_PAGE_SIZE - ((uint32_t)GCPtrFirst & X86_PAGE_OFFSET_MASK); + if (cbMaxRead2 < cbMaxRead) + cbMaxRead = cbMaxRead2; + /** @todo testcase: unreal modes, both huge 16-bit and 32-bit. */ + } + + /* + * Get the TLB entry for this piece of code. + */ + uint64_t const uTag = IEMTLB_CALC_TAG( &pVCpu->iem.s.CodeTlb, GCPtrFirst); + PIEMTLBENTRY const pTlbe = IEMTLB_TAG_TO_ENTRY(&pVCpu->iem.s.CodeTlb, uTag); + if (pTlbe->uTag == uTag) + { + /* likely when executing lots of code, otherwise unlikely */ +# ifdef VBOX_WITH_STATISTICS + pVCpu->iem.s.CodeTlb.cTlbHits++; +# endif + } + else + { + pVCpu->iem.s.CodeTlb.cTlbMisses++; + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrFirst, &Walk); + if (RT_FAILURE(rc)) + { +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + /** @todo Nested VMX: Need to handle EPT violation/misconfig here? */ + Assert(!(Walk.fFailed & PGM_WALKFAIL_EPT)); +#endif + Log(("iemOpcodeFetchMoreBytes: %RGv - rc=%Rrc\n", GCPtrFirst, rc)); + iemRaisePageFaultJmp(pVCpu, GCPtrFirst, 1, IEM_ACCESS_INSTRUCTION, rc); + } + + AssertCompile(IEMTLBE_F_PT_NO_EXEC == 1); + Assert(Walk.fSucceeded); + pTlbe->uTag = uTag; + pTlbe->fFlagsAndPhysRev = (~Walk.fEffective & (X86_PTE_US | X86_PTE_RW | X86_PTE_D | X86_PTE_A)) + | (Walk.fEffective >> X86_PTE_PAE_BIT_NX); + pTlbe->GCPhys = Walk.GCPhys; + pTlbe->pbMappingR3 = NULL; + } + + /* + * Check TLB page table level access flags. + */ + if (pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PT_NO_USER | IEMTLBE_F_PT_NO_EXEC)) + { + if ((pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PT_NO_USER) && pVCpu->iem.s.uCpl == 3) + { + Log(("iemOpcodeFetchBytesJmp: %RGv - supervisor page\n", GCPtrFirst)); + iemRaisePageFaultJmp(pVCpu, GCPtrFirst, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + if ((pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PT_NO_EXEC) && (pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_NXE)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv - NX\n", GCPtrFirst)); + iemRaisePageFaultJmp(pVCpu, GCPtrFirst, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + } + + /* + * Look up the physical page info if necessary. + */ + if ((pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PHYS_REV) == pVCpu->iem.s.CodeTlb.uTlbPhysRev) + { /* not necessary */ } + else + { + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_WRITE == IEMTLBE_F_PG_NO_WRITE); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_READ == IEMTLBE_F_PG_NO_READ); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_MAPPINGR3 == IEMTLBE_F_NO_MAPPINGR3); + AssertCompile(PGMIEMGCPHYS2PTR_F_UNASSIGNED == IEMTLBE_F_PG_UNASSIGNED); + if (RT_LIKELY(pVCpu->iem.s.CodeTlb.uTlbPhysRev > IEMTLB_PHYS_REV_INCR)) + { /* likely */ } + else + IEMTlbInvalidateAllPhysicalSlow(pVCpu); + pTlbe->fFlagsAndPhysRev &= ~( IEMTLBE_F_PHYS_REV + | IEMTLBE_F_NO_MAPPINGR3 | IEMTLBE_F_PG_NO_READ | IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_UNASSIGNED); + int rc = PGMPhysIemGCPhys2PtrNoLock(pVCpu->CTX_SUFF(pVM), pVCpu, pTlbe->GCPhys, &pVCpu->iem.s.CodeTlb.uTlbPhysRev, + &pTlbe->pbMappingR3, &pTlbe->fFlagsAndPhysRev); + AssertRCStmt(rc, IEM_DO_LONGJMP(pVCpu, rc)); + } + +# if defined(IN_RING3) || defined(IN_RING0) /** @todo fixme */ + /* + * Try do a direct read using the pbMappingR3 pointer. + */ + if ( (pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PHYS_REV | IEMTLBE_F_NO_MAPPINGR3 | IEMTLBE_F_PG_NO_READ)) + == pVCpu->iem.s.CodeTlb.uTlbPhysRev) + { + uint32_t const offPg = (GCPtrFirst & X86_PAGE_OFFSET_MASK); + pVCpu->iem.s.cbInstrBufTotal = offPg + cbMaxRead; + if (offBuf == (uint32_t)(int32_t)pVCpu->iem.s.offCurInstrStart) + { + pVCpu->iem.s.cbInstrBuf = offPg + RT_MIN(15, cbMaxRead); + pVCpu->iem.s.offCurInstrStart = (int16_t)offPg; + } + else + { + uint32_t const cbInstr = offBuf - (uint32_t)(int32_t)pVCpu->iem.s.offCurInstrStart; + if (cbInstr + (uint32_t)cbDst <= 15) + { + pVCpu->iem.s.cbInstrBuf = offPg + RT_MIN(cbMaxRead + cbInstr, 15) - cbInstr; + pVCpu->iem.s.offCurInstrStart = (int16_t)(offPg - cbInstr); + } + else + { + Log(("iemOpcodeFetchMoreBytes: %04x:%08RX64 LB %#x + %#zx -> #GP(0)\n", + pVCpu->cpum.GstCtx.cs, pVCpu->cpum.GstCtx.rip, cbInstr, cbDst)); + iemRaiseGeneralProtectionFault0Jmp(pVCpu); + } + } + if (cbDst <= cbMaxRead) + { + pVCpu->iem.s.offInstrNextByte = offPg + (uint32_t)cbDst; + pVCpu->iem.s.uInstrBufPc = GCPtrFirst & ~(RTGCPTR)X86_PAGE_OFFSET_MASK; + pVCpu->iem.s.pbInstrBuf = pTlbe->pbMappingR3; + memcpy(pvDst, &pTlbe->pbMappingR3[offPg], cbDst); + return; + } + pVCpu->iem.s.pbInstrBuf = NULL; + + memcpy(pvDst, &pTlbe->pbMappingR3[offPg], cbMaxRead); + pVCpu->iem.s.offInstrNextByte = offPg + cbMaxRead; + } +# else +# error "refactor as needed" + /* + * If there is no special read handling, so we can read a bit more and + * put it in the prefetch buffer. + */ + if ( cbDst < cbMaxRead + && (pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PHYS_REV | IEMTLBE_F_PG_NO_READ)) == pVCpu->iem.s.CodeTlb.uTlbPhysRev) + { + VBOXSTRICTRC rcStrict = PGMPhysRead(pVCpu->CTX_SUFF(pVM), pTlbe->GCPhys, + &pVCpu->iem.s.abOpcode[0], cbToTryRead, PGMACCESSORIGIN_IEM); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { /* likely */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n", + GCPtrNext, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + AssertStmt(rcStrict == VINF_SUCCESS, IEM_DO_LONGJMP(pVCpu, VBOXSTRICRC_VAL(rcStrict))); + } + else + { + Log((RT_SUCCESS(rcStrict) + ? "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n" + : "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read error - rcStrict=%Rrc (!!)\n", + GCPtrNext, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + } +# endif + /* + * Special read handling, so only read exactly what's needed. + * This is a highly unlikely scenario. + */ + else + { + pVCpu->iem.s.CodeTlb.cTlbSlowReadPath++; + + /* Check instruction length. */ + uint32_t const cbInstr = offBuf - (uint32_t)(int32_t)pVCpu->iem.s.offCurInstrStart; + if (RT_LIKELY(cbInstr + cbDst <= 15)) + { /* likely */ } + else + { + Log(("iemOpcodeFetchMoreBytes: %04x:%08RX64 LB %#x + %#zx -> #GP(0) [slow]\n", + pVCpu->cpum.GstCtx.cs, pVCpu->cpum.GstCtx.rip, cbInstr, cbDst)); + iemRaiseGeneralProtectionFault0Jmp(pVCpu); + } + + /* Do the reading. */ + uint32_t const cbToRead = RT_MIN((uint32_t)cbDst, cbMaxRead); + VBOXSTRICTRC rcStrict = PGMPhysRead(pVCpu->CTX_SUFF(pVM), pTlbe->GCPhys + (GCPtrFirst & X86_PAGE_OFFSET_MASK), + pvDst, cbToRead, PGMACCESSORIGIN_IEM); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { /* likely */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n", + GCPtrFirst, pTlbe->GCPhys + (GCPtrFirst & X86_PAGE_OFFSET_MASK), VBOXSTRICTRC_VAL(rcStrict), cbToRead)); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + AssertStmt(rcStrict == VINF_SUCCESS, IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict))); + } + else + { + Log((RT_SUCCESS(rcStrict) + ? "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n" + : "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read error - rcStrict=%Rrc (!!)\n", + GCPtrFirst, pTlbe->GCPhys + (GCPtrFirst & X86_PAGE_OFFSET_MASK), VBOXSTRICTRC_VAL(rcStrict), cbToRead)); + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + pVCpu->iem.s.offInstrNextByte = offBuf + cbToRead; + if (cbToRead == cbDst) + return; + } + + /* + * More to read, loop. + */ + cbDst -= cbMaxRead; + pvDst = (uint8_t *)pvDst + cbMaxRead; + } +# else /* !IN_RING3 */ + RT_NOREF(pvDst, cbDst); + if (pvDst || cbDst) + IEM_DO_LONGJMP(pVCpu, VERR_INTERNAL_ERROR); +# endif /* !IN_RING3 */ +} + +#else + +/** + * Try fetch at least @a cbMin bytes more opcodes, raise the appropriate + * exception if it fails. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param cbMin The minimum number of bytes relative offOpcode + * that must be read. + */ +VBOXSTRICTRC iemOpcodeFetchMoreBytes(PVMCPUCC pVCpu, size_t cbMin) RT_NOEXCEPT +{ + /* + * What we're doing here is very similar to iemMemMap/iemMemBounceBufferMap. + * + * First translate CS:rIP to a physical address. + */ + uint8_t const cbOpcode = pVCpu->iem.s.cbOpcode; + uint8_t const offOpcode = pVCpu->iem.s.offOpcode; + uint8_t const cbLeft = cbOpcode - offOpcode; + Assert(cbLeft < cbMin); + Assert(cbOpcode <= sizeof(pVCpu->iem.s.abOpcode)); + + uint32_t cbToTryRead; + RTGCPTR GCPtrNext; + if (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT) + { + GCPtrNext = pVCpu->cpum.GstCtx.rip + cbOpcode; + if (!IEM_IS_CANONICAL(GCPtrNext)) + return iemRaiseGeneralProtectionFault0(pVCpu); + cbToTryRead = GUEST_PAGE_SIZE - (GCPtrNext & GUEST_PAGE_OFFSET_MASK); + } + else + { + uint32_t GCPtrNext32 = pVCpu->cpum.GstCtx.eip; + /* Assert(!(GCPtrNext32 & ~(uint32_t)UINT16_MAX) || pVCpu->iem.s.enmCpuMode == IEMMODE_32BIT); - this is allowed */ + GCPtrNext32 += cbOpcode; + if (GCPtrNext32 > pVCpu->cpum.GstCtx.cs.u32Limit) + /** @todo For CPUs older than the 386, we should not generate \#GP here but wrap around! */ + return iemRaiseSelectorBounds(pVCpu, X86_SREG_CS, IEM_ACCESS_INSTRUCTION); + cbToTryRead = pVCpu->cpum.GstCtx.cs.u32Limit - GCPtrNext32 + 1; + if (!cbToTryRead) /* overflowed */ + { + Assert(GCPtrNext32 == 0); Assert(pVCpu->cpum.GstCtx.cs.u32Limit == UINT32_MAX); + cbToTryRead = UINT32_MAX; + /** @todo check out wrapping around the code segment. */ + } + if (cbToTryRead < cbMin - cbLeft) + return iemRaiseSelectorBounds(pVCpu, X86_SREG_CS, IEM_ACCESS_INSTRUCTION); + GCPtrNext = (uint32_t)pVCpu->cpum.GstCtx.cs.u64Base + GCPtrNext32; + + uint32_t cbLeftOnPage = GUEST_PAGE_SIZE - (GCPtrNext & GUEST_PAGE_OFFSET_MASK); + if (cbToTryRead > cbLeftOnPage) + cbToTryRead = cbLeftOnPage; + } + + /* Restrict to opcode buffer space. + + We're making ASSUMPTIONS here based on work done previously in + iemInitDecoderAndPrefetchOpcodes, where bytes from the first page will + be fetched in case of an instruction crossing two pages. */ + if (cbToTryRead > sizeof(pVCpu->iem.s.abOpcode) - cbOpcode) + cbToTryRead = sizeof(pVCpu->iem.s.abOpcode) - cbOpcode; + if (RT_LIKELY(cbToTryRead + cbLeft >= cbMin)) + { /* likely */ } + else + { + Log(("iemOpcodeFetchMoreBytes: %04x:%08RX64 LB %#x + %#zx -> #GP(0)\n", + pVCpu->cpum.GstCtx.cs, pVCpu->cpum.GstCtx.rip, offOpcode, cbMin)); + return iemRaiseGeneralProtectionFault0(pVCpu); + } + + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrNext, &Walk); + if (RT_FAILURE(rc)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv - rc=%Rrc\n", GCPtrNext, rc)); +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PHYS_ADDR, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrNext, 1, IEM_ACCESS_INSTRUCTION, rc); + } + if (!(Walk.fEffective & X86_PTE_US) && pVCpu->iem.s.uCpl == 3) + { + Log(("iemOpcodeFetchMoreBytes: %RGv - supervisor page\n", GCPtrNext)); +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrNext, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + if ((Walk.fEffective & X86_PTE_PAE_NX) && (pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_NXE)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv - NX\n", GCPtrNext)); +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, IEM_ACCESS_INSTRUCTION, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrNext, 1, IEM_ACCESS_INSTRUCTION, VERR_ACCESS_DENIED); + } + RTGCPHYS const GCPhys = Walk.GCPhys | (GCPtrNext & GUEST_PAGE_OFFSET_MASK); + Log5(("GCPtrNext=%RGv GCPhys=%RGp cbOpcodes=%#x\n", GCPtrNext, GCPhys, cbOpcode)); + /** @todo Check reserved bits and such stuff. PGM is better at doing + * that, so do it when implementing the guest virtual address + * TLB... */ + + /* + * Read the bytes at this address. + * + * We read all unpatched bytes in iemInitDecoderAndPrefetchOpcodes already, + * and since PATM should only patch the start of an instruction there + * should be no need to check again here. + */ + if (!pVCpu->iem.s.fBypassHandlers) + { + VBOXSTRICTRC rcStrict = PGMPhysRead(pVCpu->CTX_SUFF(pVM), GCPhys, &pVCpu->iem.s.abOpcode[cbOpcode], + cbToTryRead, PGMACCESSORIGIN_IEM); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { /* likely */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + Log(("iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n", + GCPtrNext, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } + else + { + Log((RT_SUCCESS(rcStrict) + ? "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read status - rcStrict=%Rrc\n" + : "iemOpcodeFetchMoreBytes: %RGv/%RGp LB %#x - read error - rcStrict=%Rrc (!!)\n", + GCPtrNext, GCPhys, VBOXSTRICTRC_VAL(rcStrict), cbToTryRead)); + return rcStrict; + } + } + else + { + rc = PGMPhysSimpleReadGCPhys(pVCpu->CTX_SUFF(pVM), &pVCpu->iem.s.abOpcode[cbOpcode], GCPhys, cbToTryRead); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + Log(("iemOpcodeFetchMoreBytes: %RGv - read error - rc=%Rrc (!!)\n", GCPtrNext, rc)); + return rc; + } + } + pVCpu->iem.s.cbOpcode = cbOpcode + cbToTryRead; + Log5(("%.*Rhxs\n", pVCpu->iem.s.cbOpcode, pVCpu->iem.s.abOpcode)); + + return VINF_SUCCESS; +} + +#endif /* !IEM_WITH_CODE_TLB */ +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU8 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param pb Where to return the opcode byte. + */ +VBOXSTRICTRC iemOpcodeGetNextU8Slow(PVMCPUCC pVCpu, uint8_t *pb) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 1); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + *pb = pVCpu->iem.s.abOpcode[offOpcode]; + pVCpu->iem.s.offOpcode = offOpcode + 1; + } + else + *pb = 0; + return rcStrict; +} + +#else /* IEM_WITH_SETJMP */ + +/** + * Deals with the problematic cases that iemOpcodeGetNextU8Jmp doesn't like, longjmp on error. + * + * @returns The opcode byte. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +uint8_t iemOpcodeGetNextU8SlowJmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ +# ifdef IEM_WITH_CODE_TLB + uint8_t u8; + iemOpcodeFetchBytesJmp(pVCpu, sizeof(u8), &u8); + return u8; +# else + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 1); + if (rcStrict == VINF_SUCCESS) + return pVCpu->iem.s.abOpcode[pVCpu->iem.s.offOpcode++]; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); +# endif +} + +#endif /* IEM_WITH_SETJMP */ + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextS8SxU16 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16 Where to return the opcode dword. + */ +VBOXSTRICTRC iemOpcodeGetNextS8SxU16Slow(PVMCPUCC pVCpu, uint16_t *pu16) RT_NOEXCEPT +{ + uint8_t u8; + VBOXSTRICTRC rcStrict = iemOpcodeGetNextU8Slow(pVCpu, &u8); + if (rcStrict == VINF_SUCCESS) + *pu16 = (int8_t)u8; + return rcStrict; +} + + +/** + * Deals with the problematic cases that iemOpcodeGetNextS8SxU32 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32 Where to return the opcode dword. + */ +VBOXSTRICTRC iemOpcodeGetNextS8SxU32Slow(PVMCPUCC pVCpu, uint32_t *pu32) RT_NOEXCEPT +{ + uint8_t u8; + VBOXSTRICTRC rcStrict = iemOpcodeGetNextU8Slow(pVCpu, &u8); + if (rcStrict == VINF_SUCCESS) + *pu32 = (int8_t)u8; + return rcStrict; +} + + +/** + * Deals with the problematic cases that iemOpcodeGetNextS8SxU64 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64 Where to return the opcode qword. + */ +VBOXSTRICTRC iemOpcodeGetNextS8SxU64Slow(PVMCPUCC pVCpu, uint64_t *pu64) RT_NOEXCEPT +{ + uint8_t u8; + VBOXSTRICTRC rcStrict = iemOpcodeGetNextU8Slow(pVCpu, &u8); + if (rcStrict == VINF_SUCCESS) + *pu64 = (int8_t)u8; + return rcStrict; +} + +#endif /* !IEM_WITH_SETJMP */ + + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU16 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16 Where to return the opcode word. + */ +VBOXSTRICTRC iemOpcodeGetNextU16Slow(PVMCPUCC pVCpu, uint16_t *pu16) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 2); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + *pu16 = *(uint16_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + *pu16 = RT_MAKE_U16(pVCpu->iem.s.abOpcode[offOpcode], pVCpu->iem.s.abOpcode[offOpcode + 1]); +# endif + pVCpu->iem.s.offOpcode = offOpcode + 2; + } + else + *pu16 = 0; + return rcStrict; +} + +#else /* IEM_WITH_SETJMP */ + +/** + * Deals with the problematic cases that iemOpcodeGetNextU16Jmp doesn't like, longjmp on error + * + * @returns The opcode word. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +uint16_t iemOpcodeGetNextU16SlowJmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ +# ifdef IEM_WITH_CODE_TLB + uint16_t u16; + iemOpcodeFetchBytesJmp(pVCpu, sizeof(u16), &u16); + return u16; +# else + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 2); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + pVCpu->iem.s.offOpcode += 2; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + return *(uint16_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + return RT_MAKE_U16(pVCpu->iem.s.abOpcode[offOpcode], pVCpu->iem.s.abOpcode[offOpcode + 1]); +# endif + } + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); +# endif +} + +#endif /* IEM_WITH_SETJMP */ + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU16ZxU32 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32 Where to return the opcode double word. + */ +VBOXSTRICTRC iemOpcodeGetNextU16ZxU32Slow(PVMCPUCC pVCpu, uint32_t *pu32) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 2); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + *pu32 = RT_MAKE_U16(pVCpu->iem.s.abOpcode[offOpcode], pVCpu->iem.s.abOpcode[offOpcode + 1]); + pVCpu->iem.s.offOpcode = offOpcode + 2; + } + else + *pu32 = 0; + return rcStrict; +} + + +/** + * Deals with the problematic cases that iemOpcodeGetNextU16ZxU64 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64 Where to return the opcode quad word. + */ +VBOXSTRICTRC iemOpcodeGetNextU16ZxU64Slow(PVMCPUCC pVCpu, uint64_t *pu64) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 2); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + *pu64 = RT_MAKE_U16(pVCpu->iem.s.abOpcode[offOpcode], pVCpu->iem.s.abOpcode[offOpcode + 1]); + pVCpu->iem.s.offOpcode = offOpcode + 2; + } + else + *pu64 = 0; + return rcStrict; +} + +#endif /* !IEM_WITH_SETJMP */ + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU32 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32 Where to return the opcode dword. + */ +VBOXSTRICTRC iemOpcodeGetNextU32Slow(PVMCPUCC pVCpu, uint32_t *pu32) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 4); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + *pu32 = *(uint32_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + *pu32 = RT_MAKE_U32_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3]); +# endif + pVCpu->iem.s.offOpcode = offOpcode + 4; + } + else + *pu32 = 0; + return rcStrict; +} + +#else /* IEM_WITH_SETJMP */ + +/** + * Deals with the problematic cases that iemOpcodeGetNextU32Jmp doesn't like, longjmp on error. + * + * @returns The opcode dword. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +uint32_t iemOpcodeGetNextU32SlowJmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ +# ifdef IEM_WITH_CODE_TLB + uint32_t u32; + iemOpcodeFetchBytesJmp(pVCpu, sizeof(u32), &u32); + return u32; +# else + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 4); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + pVCpu->iem.s.offOpcode = offOpcode + 4; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + return *(uint32_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + return RT_MAKE_U32_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3]); +# endif + } + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); +# endif +} + +#endif /* IEM_WITH_SETJMP */ + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU32ZxU64 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64 Where to return the opcode dword. + */ +VBOXSTRICTRC iemOpcodeGetNextU32ZxU64Slow(PVMCPUCC pVCpu, uint64_t *pu64) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 4); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + *pu64 = RT_MAKE_U32_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3]); + pVCpu->iem.s.offOpcode = offOpcode + 4; + } + else + *pu64 = 0; + return rcStrict; +} + + +/** + * Deals with the problematic cases that iemOpcodeGetNextS32SxU64 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64 Where to return the opcode qword. + */ +VBOXSTRICTRC iemOpcodeGetNextS32SxU64Slow(PVMCPUCC pVCpu, uint64_t *pu64) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 4); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + *pu64 = (int32_t)RT_MAKE_U32_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3]); + pVCpu->iem.s.offOpcode = offOpcode + 4; + } + else + *pu64 = 0; + return rcStrict; +} + +#endif /* !IEM_WITH_SETJMP */ + +#ifndef IEM_WITH_SETJMP + +/** + * Deals with the problematic cases that iemOpcodeGetNextU64 doesn't like. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64 Where to return the opcode qword. + */ +VBOXSTRICTRC iemOpcodeGetNextU64Slow(PVMCPUCC pVCpu, uint64_t *pu64) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 8); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + *pu64 = *(uint64_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + *pu64 = RT_MAKE_U64_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3], + pVCpu->iem.s.abOpcode[offOpcode + 4], + pVCpu->iem.s.abOpcode[offOpcode + 5], + pVCpu->iem.s.abOpcode[offOpcode + 6], + pVCpu->iem.s.abOpcode[offOpcode + 7]); +# endif + pVCpu->iem.s.offOpcode = offOpcode + 8; + } + else + *pu64 = 0; + return rcStrict; +} + +#else /* IEM_WITH_SETJMP */ + +/** + * Deals with the problematic cases that iemOpcodeGetNextU64Jmp doesn't like, longjmp on error. + * + * @returns The opcode qword. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +uint64_t iemOpcodeGetNextU64SlowJmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ +# ifdef IEM_WITH_CODE_TLB + uint64_t u64; + iemOpcodeFetchBytesJmp(pVCpu, sizeof(u64), &u64); + return u64; +# else + VBOXSTRICTRC rcStrict = iemOpcodeFetchMoreBytes(pVCpu, 8); + if (rcStrict == VINF_SUCCESS) + { + uint8_t offOpcode = pVCpu->iem.s.offOpcode; + pVCpu->iem.s.offOpcode = offOpcode + 8; +# ifdef IEM_USE_UNALIGNED_DATA_ACCESS + return *(uint64_t const *)&pVCpu->iem.s.abOpcode[offOpcode]; +# else + return RT_MAKE_U64_FROM_U8(pVCpu->iem.s.abOpcode[offOpcode], + pVCpu->iem.s.abOpcode[offOpcode + 1], + pVCpu->iem.s.abOpcode[offOpcode + 2], + pVCpu->iem.s.abOpcode[offOpcode + 3], + pVCpu->iem.s.abOpcode[offOpcode + 4], + pVCpu->iem.s.abOpcode[offOpcode + 5], + pVCpu->iem.s.abOpcode[offOpcode + 6], + pVCpu->iem.s.abOpcode[offOpcode + 7]); +# endif + } + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); +# endif +} + +#endif /* IEM_WITH_SETJMP */ + + + +/** @name Misc Worker Functions. + * @{ + */ + +/** + * Gets the exception class for the specified exception vector. + * + * @returns The class of the specified exception. + * @param uVector The exception vector. + */ +static IEMXCPTCLASS iemGetXcptClass(uint8_t uVector) RT_NOEXCEPT +{ + Assert(uVector <= X86_XCPT_LAST); + switch (uVector) + { + case X86_XCPT_DE: + case X86_XCPT_TS: + case X86_XCPT_NP: + case X86_XCPT_SS: + case X86_XCPT_GP: + case X86_XCPT_SX: /* AMD only */ + return IEMXCPTCLASS_CONTRIBUTORY; + + case X86_XCPT_PF: + case X86_XCPT_VE: /* Intel only */ + return IEMXCPTCLASS_PAGE_FAULT; + + case X86_XCPT_DF: + return IEMXCPTCLASS_DOUBLE_FAULT; + } + return IEMXCPTCLASS_BENIGN; +} + + +/** + * Evaluates how to handle an exception caused during delivery of another event + * (exception / interrupt). + * + * @returns How to handle the recursive exception. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param fPrevFlags The flags of the previous event. + * @param uPrevVector The vector of the previous event. + * @param fCurFlags The flags of the current exception. + * @param uCurVector The vector of the current exception. + * @param pfXcptRaiseInfo Where to store additional information about the + * exception condition. Optional. + */ +VMM_INT_DECL(IEMXCPTRAISE) IEMEvaluateRecursiveXcpt(PVMCPUCC pVCpu, uint32_t fPrevFlags, uint8_t uPrevVector, uint32_t fCurFlags, + uint8_t uCurVector, PIEMXCPTRAISEINFO pfXcptRaiseInfo) +{ + /* + * Only CPU exceptions can be raised while delivering other events, software interrupt + * (INTn/INT3/INTO/ICEBP) generated exceptions cannot occur as the current (second) exception. + */ + AssertReturn(fCurFlags & IEM_XCPT_FLAGS_T_CPU_XCPT, IEMXCPTRAISE_INVALID); + Assert(pVCpu); RT_NOREF(pVCpu); + Log2(("IEMEvaluateRecursiveXcpt: uPrevVector=%#x uCurVector=%#x\n", uPrevVector, uCurVector)); + + IEMXCPTRAISE enmRaise = IEMXCPTRAISE_CURRENT_XCPT; + IEMXCPTRAISEINFO fRaiseInfo = IEMXCPTRAISEINFO_NONE; + if (fPrevFlags & IEM_XCPT_FLAGS_T_CPU_XCPT) + { + IEMXCPTCLASS enmPrevXcptClass = iemGetXcptClass(uPrevVector); + if (enmPrevXcptClass != IEMXCPTCLASS_BENIGN) + { + IEMXCPTCLASS enmCurXcptClass = iemGetXcptClass(uCurVector); + if ( enmPrevXcptClass == IEMXCPTCLASS_PAGE_FAULT + && ( enmCurXcptClass == IEMXCPTCLASS_PAGE_FAULT + || enmCurXcptClass == IEMXCPTCLASS_CONTRIBUTORY)) + { + enmRaise = IEMXCPTRAISE_DOUBLE_FAULT; + fRaiseInfo = enmCurXcptClass == IEMXCPTCLASS_PAGE_FAULT ? IEMXCPTRAISEINFO_PF_PF + : IEMXCPTRAISEINFO_PF_CONTRIBUTORY_XCPT; + Log2(("IEMEvaluateRecursiveXcpt: Vectoring page fault. uPrevVector=%#x uCurVector=%#x uCr2=%#RX64\n", uPrevVector, + uCurVector, pVCpu->cpum.GstCtx.cr2)); + } + else if ( enmPrevXcptClass == IEMXCPTCLASS_CONTRIBUTORY + && enmCurXcptClass == IEMXCPTCLASS_CONTRIBUTORY) + { + enmRaise = IEMXCPTRAISE_DOUBLE_FAULT; + Log2(("IEMEvaluateRecursiveXcpt: uPrevVector=%#x uCurVector=%#x -> #DF\n", uPrevVector, uCurVector)); + } + else if ( enmPrevXcptClass == IEMXCPTCLASS_DOUBLE_FAULT + && ( enmCurXcptClass == IEMXCPTCLASS_CONTRIBUTORY + || enmCurXcptClass == IEMXCPTCLASS_PAGE_FAULT)) + { + enmRaise = IEMXCPTRAISE_TRIPLE_FAULT; + Log2(("IEMEvaluateRecursiveXcpt: #DF handler raised a %#x exception -> triple fault\n", uCurVector)); + } + } + else + { + if (uPrevVector == X86_XCPT_NMI) + { + fRaiseInfo = IEMXCPTRAISEINFO_NMI_XCPT; + if (uCurVector == X86_XCPT_PF) + { + fRaiseInfo |= IEMXCPTRAISEINFO_NMI_PF; + Log2(("IEMEvaluateRecursiveXcpt: NMI delivery caused a page fault\n")); + } + } + else if ( uPrevVector == X86_XCPT_AC + && uCurVector == X86_XCPT_AC) + { + enmRaise = IEMXCPTRAISE_CPU_HANG; + fRaiseInfo = IEMXCPTRAISEINFO_AC_AC; + Log2(("IEMEvaluateRecursiveXcpt: Recursive #AC - Bad guest\n")); + } + } + } + else if (fPrevFlags & IEM_XCPT_FLAGS_T_EXT_INT) + { + fRaiseInfo = IEMXCPTRAISEINFO_EXT_INT_XCPT; + if (uCurVector == X86_XCPT_PF) + fRaiseInfo |= IEMXCPTRAISEINFO_EXT_INT_PF; + } + else + { + Assert(fPrevFlags & IEM_XCPT_FLAGS_T_SOFT_INT); + fRaiseInfo = IEMXCPTRAISEINFO_SOFT_INT_XCPT; + } + + if (pfXcptRaiseInfo) + *pfXcptRaiseInfo = fRaiseInfo; + return enmRaise; +} + + +/** + * Enters the CPU shutdown state initiated by a triple fault or other + * unrecoverable conditions. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + */ +static VBOXSTRICTRC iemInitiateCpuShutdown(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + IEM_VMX_VMEXIT_TRIPLE_FAULT_RET(pVCpu, VMX_EXIT_TRIPLE_FAULT, 0 /* u64ExitQual */); + + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_SHUTDOWN)) + { + Log2(("shutdown: Guest intercept -> #VMEXIT\n")); + IEM_SVM_VMEXIT_RET(pVCpu, SVM_EXIT_SHUTDOWN, 0 /* uExitInfo1 */, 0 /* uExitInfo2 */); + } + + RT_NOREF(pVCpu); + return VINF_EM_TRIPLE_FAULT; +} + + +/** + * Validates a new SS segment. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the + * calling thread. + * @param NewSS The new SS selctor. + * @param uCpl The CPL to load the stack for. + * @param pDesc Where to return the descriptor. + */ +static VBOXSTRICTRC iemMiscValidateNewSS(PVMCPUCC pVCpu, RTSEL NewSS, uint8_t uCpl, PIEMSELDESC pDesc) RT_NOEXCEPT +{ + /* Null selectors are not allowed (we're not called for dispatching + interrupts with SS=0 in long mode). */ + if (!(NewSS & X86_SEL_MASK_OFF_RPL)) + { + Log(("iemMiscValidateNewSSandRsp: %#x - null selector -> #TS(0)\n", NewSS)); + return iemRaiseTaskSwitchFault0(pVCpu); + } + + /** @todo testcase: check that the TSS.ssX RPL is checked. Also check when. */ + if ((NewSS & X86_SEL_RPL) != uCpl) + { + Log(("iemMiscValidateNewSSandRsp: %#x - RPL and CPL (%d) differs -> #TS\n", NewSS, uCpl)); + return iemRaiseTaskSwitchFaultBySelector(pVCpu, NewSS); + } + + /* + * Read the descriptor. + */ + VBOXSTRICTRC rcStrict = iemMemFetchSelDesc(pVCpu, pDesc, NewSS, X86_XCPT_TS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * Perform the descriptor validation documented for LSS, POP SS and MOV SS. + */ + if (!pDesc->Legacy.Gen.u1DescType) + { + Log(("iemMiscValidateNewSSandRsp: %#x - system selector (%#x) -> #TS\n", NewSS, pDesc->Legacy.Gen.u4Type)); + return iemRaiseTaskSwitchFaultBySelector(pVCpu, NewSS); + } + + if ( (pDesc->Legacy.Gen.u4Type & X86_SEL_TYPE_CODE) + || !(pDesc->Legacy.Gen.u4Type & X86_SEL_TYPE_WRITE) ) + { + Log(("iemMiscValidateNewSSandRsp: %#x - code or read only (%#x) -> #TS\n", NewSS, pDesc->Legacy.Gen.u4Type)); + return iemRaiseTaskSwitchFaultBySelector(pVCpu, NewSS); + } + if (pDesc->Legacy.Gen.u2Dpl != uCpl) + { + Log(("iemMiscValidateNewSSandRsp: %#x - DPL (%d) and CPL (%d) differs -> #TS\n", NewSS, pDesc->Legacy.Gen.u2Dpl, uCpl)); + return iemRaiseTaskSwitchFaultBySelector(pVCpu, NewSS); + } + + /* Is it there? */ + /** @todo testcase: Is this checked before the canonical / limit check below? */ + if (!pDesc->Legacy.Gen.u1Present) + { + Log(("iemMiscValidateNewSSandRsp: %#x - segment not present -> #NP\n", NewSS)); + return iemRaiseSelectorNotPresentBySelector(pVCpu, NewSS); + } + + return VINF_SUCCESS; +} + +/** @} */ + + +/** @name Raising Exceptions. + * + * @{ + */ + + +/** + * Loads the specified stack far pointer from the TSS. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uCpl The CPL to load the stack for. + * @param pSelSS Where to return the new stack segment. + * @param puEsp Where to return the new stack pointer. + */ +static VBOXSTRICTRC iemRaiseLoadStackFromTss32Or16(PVMCPUCC pVCpu, uint8_t uCpl, PRTSEL pSelSS, uint32_t *puEsp) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict; + Assert(uCpl < 4); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_TR | CPUMCTX_EXTRN_GDTR | CPUMCTX_EXTRN_LDTR); + switch (pVCpu->cpum.GstCtx.tr.Attr.n.u4Type) + { + /* + * 16-bit TSS (X86TSS16). + */ + case X86_SEL_TYPE_SYS_286_TSS_AVAIL: AssertFailed(); RT_FALL_THRU(); + case X86_SEL_TYPE_SYS_286_TSS_BUSY: + { + uint32_t off = uCpl * 4 + 2; + if (off + 4 <= pVCpu->cpum.GstCtx.tr.u32Limit) + { + /** @todo check actual access pattern here. */ + uint32_t u32Tmp = 0; /* gcc maybe... */ + rcStrict = iemMemFetchSysU32(pVCpu, &u32Tmp, UINT8_MAX, pVCpu->cpum.GstCtx.tr.u64Base + off); + if (rcStrict == VINF_SUCCESS) + { + *puEsp = RT_LOWORD(u32Tmp); + *pSelSS = RT_HIWORD(u32Tmp); + return VINF_SUCCESS; + } + } + else + { + Log(("LoadStackFromTss32Or16: out of bounds! uCpl=%d, u32Limit=%#x TSS16\n", uCpl, pVCpu->cpum.GstCtx.tr.u32Limit)); + rcStrict = iemRaiseTaskSwitchFaultCurrentTSS(pVCpu); + } + break; + } + + /* + * 32-bit TSS (X86TSS32). + */ + case X86_SEL_TYPE_SYS_386_TSS_AVAIL: AssertFailed(); RT_FALL_THRU(); + case X86_SEL_TYPE_SYS_386_TSS_BUSY: + { + uint32_t off = uCpl * 8 + 4; + if (off + 7 <= pVCpu->cpum.GstCtx.tr.u32Limit) + { +/** @todo check actual access pattern here. */ + uint64_t u64Tmp; + rcStrict = iemMemFetchSysU64(pVCpu, &u64Tmp, UINT8_MAX, pVCpu->cpum.GstCtx.tr.u64Base + off); + if (rcStrict == VINF_SUCCESS) + { + *puEsp = u64Tmp & UINT32_MAX; + *pSelSS = (RTSEL)(u64Tmp >> 32); + return VINF_SUCCESS; + } + } + else + { + Log(("LoadStackFromTss32Or16: out of bounds! uCpl=%d, u32Limit=%#x TSS16\n", uCpl, pVCpu->cpum.GstCtx.tr.u32Limit)); + rcStrict = iemRaiseTaskSwitchFaultCurrentTSS(pVCpu); + } + break; + } + + default: + AssertFailed(); + rcStrict = VERR_IEM_IPE_4; + break; + } + + *puEsp = 0; /* make gcc happy */ + *pSelSS = 0; /* make gcc happy */ + return rcStrict; +} + + +/** + * Loads the specified stack pointer from the 64-bit TSS. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uCpl The CPL to load the stack for. + * @param uIst The interrupt stack table index, 0 if to use uCpl. + * @param puRsp Where to return the new stack pointer. + */ +static VBOXSTRICTRC iemRaiseLoadStackFromTss64(PVMCPUCC pVCpu, uint8_t uCpl, uint8_t uIst, uint64_t *puRsp) RT_NOEXCEPT +{ + Assert(uCpl < 4); + Assert(uIst < 8); + *puRsp = 0; /* make gcc happy */ + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_TR | CPUMCTX_EXTRN_GDTR | CPUMCTX_EXTRN_LDTR); + AssertReturn(pVCpu->cpum.GstCtx.tr.Attr.n.u4Type == AMD64_SEL_TYPE_SYS_TSS_BUSY, VERR_IEM_IPE_5); + + uint32_t off; + if (uIst) + off = (uIst - 1) * sizeof(uint64_t) + RT_UOFFSETOF(X86TSS64, ist1); + else + off = uCpl * sizeof(uint64_t) + RT_UOFFSETOF(X86TSS64, rsp0); + if (off + sizeof(uint64_t) > pVCpu->cpum.GstCtx.tr.u32Limit) + { + Log(("iemRaiseLoadStackFromTss64: out of bounds! uCpl=%d uIst=%d, u32Limit=%#x\n", uCpl, uIst, pVCpu->cpum.GstCtx.tr.u32Limit)); + return iemRaiseTaskSwitchFaultCurrentTSS(pVCpu); + } + + return iemMemFetchSysU64(pVCpu, puRsp, UINT8_MAX, pVCpu->cpum.GstCtx.tr.u64Base + off); +} + + +/** + * Adjust the CPU state according to the exception being raised. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u8Vector The exception that has been raised. + */ +DECLINLINE(void) iemRaiseXcptAdjustState(PVMCPUCC pVCpu, uint8_t u8Vector) +{ + switch (u8Vector) + { + case X86_XCPT_DB: + IEM_CTX_ASSERT(pVCpu, CPUMCTX_EXTRN_DR7); + pVCpu->cpum.GstCtx.dr[7] &= ~X86_DR7_GD; + break; + /** @todo Read the AMD and Intel exception reference... */ + } +} + + +/** + * Implements exceptions and interrupts for real mode. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr The number of bytes to offset rIP by in the return + * address. + * @param u8Vector The interrupt / exception vector number. + * @param fFlags The flags. + * @param uErr The error value if IEM_XCPT_FLAGS_ERR is set. + * @param uCr2 The CR2 value if IEM_XCPT_FLAGS_CR2 is set. + */ +static VBOXSTRICTRC +iemRaiseXcptOrIntInRealMode(PVMCPUCC pVCpu, + uint8_t cbInstr, + uint8_t u8Vector, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2) RT_NOEXCEPT +{ + NOREF(uErr); NOREF(uCr2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + + /* + * Read the IDT entry. + */ + if (pVCpu->cpum.GstCtx.idtr.cbIdt < UINT32_C(4) * u8Vector + 3) + { + Log(("RaiseXcptOrIntInRealMode: %#x is out of bounds (%#x)\n", u8Vector, pVCpu->cpum.GstCtx.idtr.cbIdt)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + RTFAR16 Idte; + VBOXSTRICTRC rcStrict = iemMemFetchDataU32(pVCpu, (uint32_t *)&Idte, UINT8_MAX, pVCpu->cpum.GstCtx.idtr.pIdt + UINT32_C(4) * u8Vector); + if (RT_UNLIKELY(rcStrict != VINF_SUCCESS)) + { + Log(("iemRaiseXcptOrIntInRealMode: failed to fetch IDT entry! vec=%#x rc=%Rrc\n", u8Vector, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* + * Push the stack frame. + */ + uint16_t *pu16Frame; + uint64_t uNewRsp; + rcStrict = iemMemStackPushBeginSpecial(pVCpu, 6, 3, (void **)&pu16Frame, &uNewRsp); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint32_t fEfl = IEMMISC_GET_EFL(pVCpu); +#if IEM_CFG_TARGET_CPU == IEMTARGETCPU_DYNAMIC + AssertCompile(IEMTARGETCPU_8086 <= IEMTARGETCPU_186 && IEMTARGETCPU_V20 <= IEMTARGETCPU_186 && IEMTARGETCPU_286 > IEMTARGETCPU_186); + if (pVCpu->iem.s.uTargetCpu <= IEMTARGETCPU_186) + fEfl |= UINT16_C(0xf000); +#endif + pu16Frame[2] = (uint16_t)fEfl; + pu16Frame[1] = (uint16_t)pVCpu->cpum.GstCtx.cs.Sel; + pu16Frame[0] = (fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) ? pVCpu->cpum.GstCtx.ip + cbInstr : pVCpu->cpum.GstCtx.ip; + rcStrict = iemMemStackPushCommitSpecial(pVCpu, pu16Frame, uNewRsp); + if (RT_UNLIKELY(rcStrict != VINF_SUCCESS)) + return rcStrict; + + /* + * Load the vector address into cs:ip and make exception specific state + * adjustments. + */ + pVCpu->cpum.GstCtx.cs.Sel = Idte.sel; + pVCpu->cpum.GstCtx.cs.ValidSel = Idte.sel; + pVCpu->cpum.GstCtx.cs.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.cs.u64Base = (uint32_t)Idte.sel << 4; + /** @todo do we load attribs and limit as well? Should we check against limit like far jump? */ + pVCpu->cpum.GstCtx.rip = Idte.off; + fEfl &= ~(X86_EFL_IF | X86_EFL_TF | X86_EFL_AC); + IEMMISC_SET_EFL(pVCpu, fEfl); + + /** @todo do we actually do this in real mode? */ + if (fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT) + iemRaiseXcptAdjustState(pVCpu, u8Vector); + + return fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT ? VINF_IEM_RAISED_XCPT : VINF_SUCCESS; +} + + +/** + * Loads a NULL data selector into when coming from V8086 mode. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pSReg Pointer to the segment register. + */ +DECLINLINE(void) iemHlpLoadNullDataSelectorOnV86Xcpt(PVMCPUCC pVCpu, PCPUMSELREG pSReg) +{ + pSReg->Sel = 0; + pSReg->ValidSel = 0; + if (IEM_IS_GUEST_CPU_INTEL(pVCpu)) + { + /* VT-x (Intel 3960x) doesn't change the base and limit, clears and sets the following attributes */ + pSReg->Attr.u &= X86DESCATTR_DT | X86DESCATTR_TYPE | X86DESCATTR_DPL | X86DESCATTR_G | X86DESCATTR_D; + pSReg->Attr.u |= X86DESCATTR_UNUSABLE; + } + else + { + pSReg->fFlags = CPUMSELREG_FLAGS_VALID; + /** @todo check this on AMD-V */ + pSReg->u64Base = 0; + pSReg->u32Limit = 0; + } +} + + +/** + * Loads a segment selector during a task switch in V8086 mode. + * + * @param pSReg Pointer to the segment register. + * @param uSel The selector value to load. + */ +DECLINLINE(void) iemHlpLoadSelectorInV86Mode(PCPUMSELREG pSReg, uint16_t uSel) +{ + /* See Intel spec. 26.3.1.2 "Checks on Guest Segment Registers". */ + pSReg->Sel = uSel; + pSReg->ValidSel = uSel; + pSReg->fFlags = CPUMSELREG_FLAGS_VALID; + pSReg->u64Base = uSel << 4; + pSReg->u32Limit = 0xffff; + pSReg->Attr.u = 0xf3; +} + + +/** + * Loads a segment selector during a task switch in protected mode. + * + * In this task switch scenario, we would throw \#TS exceptions rather than + * \#GPs. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pSReg Pointer to the segment register. + * @param uSel The new selector value. + * + * @remarks This does _not_ handle CS or SS. + * @remarks This expects pVCpu->iem.s.uCpl to be up to date. + */ +static VBOXSTRICTRC iemHlpTaskSwitchLoadDataSelectorInProtMode(PVMCPUCC pVCpu, PCPUMSELREG pSReg, uint16_t uSel) RT_NOEXCEPT +{ + Assert(pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT); + + /* Null data selector. */ + if (!(uSel & X86_SEL_MASK_OFF_RPL)) + { + iemHlpLoadNullDataSelectorProt(pVCpu, pSReg, uSel); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, pSReg)); + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_HIDDEN_SEL_REGS); + return VINF_SUCCESS; + } + + /* Fetch the descriptor. */ + IEMSELDESC Desc; + VBOXSTRICTRC rcStrict = iemMemFetchSelDesc(pVCpu, &Desc, uSel, X86_XCPT_TS); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemHlpTaskSwitchLoadDataSelectorInProtMode: failed to fetch selector. uSel=%u rc=%Rrc\n", uSel, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* Must be a data segment or readable code segment. */ + if ( !Desc.Legacy.Gen.u1DescType + || (Desc.Legacy.Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_READ)) == X86_SEL_TYPE_CODE) + { + Log(("iemHlpTaskSwitchLoadDataSelectorInProtMode: invalid segment type. uSel=%u Desc.u4Type=%#x\n", uSel, + Desc.Legacy.Gen.u4Type)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uSel & X86_SEL_MASK_OFF_RPL); + } + + /* Check privileges for data segments and non-conforming code segments. */ + if ( (Desc.Legacy.Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + != (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) + { + /* The RPL and the new CPL must be less than or equal to the DPL. */ + if ( (unsigned)(uSel & X86_SEL_RPL) > Desc.Legacy.Gen.u2Dpl + || (pVCpu->iem.s.uCpl > Desc.Legacy.Gen.u2Dpl)) + { + Log(("iemHlpTaskSwitchLoadDataSelectorInProtMode: Invalid priv. uSel=%u uSel.RPL=%u DPL=%u CPL=%u\n", + uSel, (uSel & X86_SEL_RPL), Desc.Legacy.Gen.u2Dpl, pVCpu->iem.s.uCpl)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uSel & X86_SEL_MASK_OFF_RPL); + } + } + + /* Is it there? */ + if (!Desc.Legacy.Gen.u1Present) + { + Log(("iemHlpTaskSwitchLoadDataSelectorInProtMode: Segment not present. uSel=%u\n", uSel)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, uSel & X86_SEL_MASK_OFF_RPL); + } + + /* The base and limit. */ + uint32_t cbLimit = X86DESC_LIMIT_G(&Desc.Legacy); + uint64_t u64Base = X86DESC_BASE(&Desc.Legacy); + + /* + * Ok, everything checked out fine. Now set the accessed bit before + * committing the result into the registers. + */ + if (!(Desc.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, uSel); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + Desc.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* Commit */ + pSReg->Sel = uSel; + pSReg->Attr.u = X86DESC_GET_HID_ATTR(&Desc.Legacy); + pSReg->u32Limit = cbLimit; + pSReg->u64Base = u64Base; /** @todo testcase/investigate: seen claims that the upper half of the base remains unchanged... */ + pSReg->ValidSel = uSel; + pSReg->fFlags = CPUMSELREG_FLAGS_VALID; + if (IEM_IS_GUEST_CPU_INTEL(pVCpu)) + pSReg->Attr.u &= ~X86DESCATTR_UNUSABLE; + + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, pSReg)); + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_HIDDEN_SEL_REGS); + return VINF_SUCCESS; +} + + +/** + * Performs a task switch. + * + * If the task switch is the result of a JMP, CALL or IRET instruction, the + * caller is responsible for performing the necessary checks (like DPL, TSS + * present etc.) which are specific to JMP/CALL/IRET. See Intel Instruction + * reference for JMP, CALL, IRET. + * + * If the task switch is the due to a software interrupt or hardware exception, + * the caller is responsible for validating the TSS selector and descriptor. See + * Intel Instruction reference for INT n. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param enmTaskSwitch The cause of the task switch. + * @param uNextEip The EIP effective after the task switch. + * @param fFlags The flags, see IEM_XCPT_FLAGS_XXX. + * @param uErr The error value if IEM_XCPT_FLAGS_ERR is set. + * @param uCr2 The CR2 value if IEM_XCPT_FLAGS_CR2 is set. + * @param SelTSS The TSS selector of the new task. + * @param pNewDescTSS Pointer to the new TSS descriptor. + */ +VBOXSTRICTRC +iemTaskSwitch(PVMCPUCC pVCpu, + IEMTASKSWITCH enmTaskSwitch, + uint32_t uNextEip, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2, + RTSEL SelTSS, + PIEMSELDESC pNewDescTSS) RT_NOEXCEPT +{ + Assert(!IEM_IS_REAL_MODE(pVCpu)); + Assert(pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + + uint32_t const uNewTSSType = pNewDescTSS->Legacy.Gate.u4Type; + Assert( uNewTSSType == X86_SEL_TYPE_SYS_286_TSS_AVAIL + || uNewTSSType == X86_SEL_TYPE_SYS_286_TSS_BUSY + || uNewTSSType == X86_SEL_TYPE_SYS_386_TSS_AVAIL + || uNewTSSType == X86_SEL_TYPE_SYS_386_TSS_BUSY); + + bool const fIsNewTSS386 = ( uNewTSSType == X86_SEL_TYPE_SYS_386_TSS_AVAIL + || uNewTSSType == X86_SEL_TYPE_SYS_386_TSS_BUSY); + + Log(("iemTaskSwitch: enmTaskSwitch=%u NewTSS=%#x fIsNewTSS386=%RTbool EIP=%#RX32 uNextEip=%#RX32\n", enmTaskSwitch, SelTSS, + fIsNewTSS386, pVCpu->cpum.GstCtx.eip, uNextEip)); + + /* Update CR2 in case it's a page-fault. */ + /** @todo This should probably be done much earlier in IEM/PGM. See + * @bugref{5653#c49}. */ + if (fFlags & IEM_XCPT_FLAGS_CR2) + pVCpu->cpum.GstCtx.cr2 = uCr2; + + /* + * Check the new TSS limit. See Intel spec. 6.15 "Exception and Interrupt Reference" + * subsection "Interrupt 10 - Invalid TSS Exception (#TS)". + */ + uint32_t const uNewTSSLimit = pNewDescTSS->Legacy.Gen.u16LimitLow | (pNewDescTSS->Legacy.Gen.u4LimitHigh << 16); + uint32_t const uNewTSSLimitMin = fIsNewTSS386 ? X86_SEL_TYPE_SYS_386_TSS_LIMIT_MIN : X86_SEL_TYPE_SYS_286_TSS_LIMIT_MIN; + if (uNewTSSLimit < uNewTSSLimitMin) + { + Log(("iemTaskSwitch: Invalid new TSS limit. enmTaskSwitch=%u uNewTSSLimit=%#x uNewTSSLimitMin=%#x -> #TS\n", + enmTaskSwitch, uNewTSSLimit, uNewTSSLimitMin)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, SelTSS & X86_SEL_MASK_OFF_RPL); + } + + /* + * Task switches in VMX non-root mode always cause task switches. + * The new TSS must have been read and validated (DPL, limits etc.) before a + * task-switch VM-exit commences. + * + * See Intel spec. 25.4.2 "Treatment of Task Switches". + */ + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + Log(("iemTaskSwitch: Guest intercept (source=%u, sel=%#x) -> VM-exit.\n", enmTaskSwitch, SelTSS)); + IEM_VMX_VMEXIT_TASK_SWITCH_RET(pVCpu, enmTaskSwitch, SelTSS, uNextEip - pVCpu->cpum.GstCtx.eip); + } + + /* + * The SVM nested-guest intercept for task-switch takes priority over all exceptions + * after validating the incoming (new) TSS, see AMD spec. 15.14.1 "Task Switch Intercept". + */ + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_TASK_SWITCH)) + { + uint32_t const uExitInfo1 = SelTSS; + uint32_t uExitInfo2 = uErr; + switch (enmTaskSwitch) + { + case IEMTASKSWITCH_JUMP: uExitInfo2 |= SVM_EXIT2_TASK_SWITCH_JUMP; break; + case IEMTASKSWITCH_IRET: uExitInfo2 |= SVM_EXIT2_TASK_SWITCH_IRET; break; + default: break; + } + if (fFlags & IEM_XCPT_FLAGS_ERR) + uExitInfo2 |= SVM_EXIT2_TASK_SWITCH_HAS_ERROR_CODE; + if (pVCpu->cpum.GstCtx.eflags.Bits.u1RF) + uExitInfo2 |= SVM_EXIT2_TASK_SWITCH_EFLAGS_RF; + + Log(("iemTaskSwitch: Guest intercept -> #VMEXIT. uExitInfo1=%#RX64 uExitInfo2=%#RX64\n", uExitInfo1, uExitInfo2)); + IEM_SVM_VMEXIT_RET(pVCpu, SVM_EXIT_TASK_SWITCH, uExitInfo1, uExitInfo2); + RT_NOREF2(uExitInfo1, uExitInfo2); + } + + /* + * Check the current TSS limit. The last written byte to the current TSS during the + * task switch will be 2 bytes at offset 0x5C (32-bit) and 1 byte at offset 0x28 (16-bit). + * See Intel spec. 7.2.1 "Task-State Segment (TSS)" for static and dynamic fields. + * + * The AMD docs doesn't mention anything about limit checks with LTR which suggests you can + * end up with smaller than "legal" TSS limits. + */ + uint32_t const uCurTSSLimit = pVCpu->cpum.GstCtx.tr.u32Limit; + uint32_t const uCurTSSLimitMin = fIsNewTSS386 ? 0x5F : 0x29; + if (uCurTSSLimit < uCurTSSLimitMin) + { + Log(("iemTaskSwitch: Invalid current TSS limit. enmTaskSwitch=%u uCurTSSLimit=%#x uCurTSSLimitMin=%#x -> #TS\n", + enmTaskSwitch, uCurTSSLimit, uCurTSSLimitMin)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, SelTSS & X86_SEL_MASK_OFF_RPL); + } + + /* + * Verify that the new TSS can be accessed and map it. Map only the required contents + * and not the entire TSS. + */ + void *pvNewTSS; + uint32_t const cbNewTSS = uNewTSSLimitMin + 1; + RTGCPTR const GCPtrNewTSS = X86DESC_BASE(&pNewDescTSS->Legacy); + AssertCompile(sizeof(X86TSS32) == X86_SEL_TYPE_SYS_386_TSS_LIMIT_MIN + 1); + /** @todo Handle if the TSS crosses a page boundary. Intel specifies that it may + * not perform correct translation if this happens. See Intel spec. 7.2.1 + * "Task-State Segment". */ + VBOXSTRICTRC rcStrict = iemMemMap(pVCpu, &pvNewTSS, cbNewTSS, UINT8_MAX, GCPtrNewTSS, IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to read new TSS. enmTaskSwitch=%u cbNewTSS=%u uNewTSSLimit=%u rc=%Rrc\n", enmTaskSwitch, + cbNewTSS, uNewTSSLimit, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* + * Clear the busy bit in current task's TSS descriptor if it's a task switch due to JMP/IRET. + */ + uint32_t fEFlags = pVCpu->cpum.GstCtx.eflags.u; + if ( enmTaskSwitch == IEMTASKSWITCH_JUMP + || enmTaskSwitch == IEMTASKSWITCH_IRET) + { + PX86DESC pDescCurTSS; + rcStrict = iemMemMap(pVCpu, (void **)&pDescCurTSS, sizeof(*pDescCurTSS), UINT8_MAX, + pVCpu->cpum.GstCtx.gdtr.pGdt + (pVCpu->cpum.GstCtx.tr.Sel & X86_SEL_MASK), IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to read new TSS descriptor in GDT. enmTaskSwitch=%u pGdt=%#RX64 rc=%Rrc\n", + enmTaskSwitch, pVCpu->cpum.GstCtx.gdtr.pGdt, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + pDescCurTSS->Gate.u4Type &= ~X86_SEL_TYPE_SYS_TSS_BUSY_MASK; + rcStrict = iemMemCommitAndUnmap(pVCpu, pDescCurTSS, IEM_ACCESS_SYS_RW); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to commit new TSS descriptor in GDT. enmTaskSwitch=%u pGdt=%#RX64 rc=%Rrc\n", + enmTaskSwitch, pVCpu->cpum.GstCtx.gdtr.pGdt, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* Clear EFLAGS.NT (Nested Task) in the eflags memory image, if it's a task switch due to an IRET. */ + if (enmTaskSwitch == IEMTASKSWITCH_IRET) + { + Assert( uNewTSSType == X86_SEL_TYPE_SYS_286_TSS_BUSY + || uNewTSSType == X86_SEL_TYPE_SYS_386_TSS_BUSY); + fEFlags &= ~X86_EFL_NT; + } + } + + /* + * Save the CPU state into the current TSS. + */ + RTGCPTR const GCPtrCurTSS = pVCpu->cpum.GstCtx.tr.u64Base; + if (GCPtrNewTSS == GCPtrCurTSS) + { + Log(("iemTaskSwitch: Switching to the same TSS! enmTaskSwitch=%u GCPtr[Cur|New]TSS=%#RGv\n", enmTaskSwitch, GCPtrCurTSS)); + Log(("uCurCr3=%#x uCurEip=%#x uCurEflags=%#x uCurEax=%#x uCurEsp=%#x uCurEbp=%#x uCurCS=%#04x uCurSS=%#04x uCurLdt=%#x\n", + pVCpu->cpum.GstCtx.cr3, pVCpu->cpum.GstCtx.eip, pVCpu->cpum.GstCtx.eflags.u, pVCpu->cpum.GstCtx.eax, + pVCpu->cpum.GstCtx.esp, pVCpu->cpum.GstCtx.ebp, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.ss.Sel, + pVCpu->cpum.GstCtx.ldtr.Sel)); + } + if (fIsNewTSS386) + { + /* + * Verify that the current TSS (32-bit) can be accessed, only the minimum required size. + * See Intel spec. 7.2.1 "Task-State Segment (TSS)" for static and dynamic fields. + */ + void *pvCurTSS32; + uint32_t const offCurTSS = RT_UOFFSETOF(X86TSS32, eip); + uint32_t const cbCurTSS = RT_UOFFSETOF(X86TSS32, selLdt) - RT_UOFFSETOF(X86TSS32, eip); + AssertCompile(RTASSERT_OFFSET_OF(X86TSS32, selLdt) - RTASSERT_OFFSET_OF(X86TSS32, eip) == 64); + rcStrict = iemMemMap(pVCpu, &pvCurTSS32, cbCurTSS, UINT8_MAX, GCPtrCurTSS + offCurTSS, IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to read current 32-bit TSS. enmTaskSwitch=%u GCPtrCurTSS=%#RGv cb=%u rc=%Rrc\n", + enmTaskSwitch, GCPtrCurTSS, cbCurTSS, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* !! WARNING !! Access -only- the members (dynamic fields) that are mapped, i.e interval [offCurTSS..cbCurTSS). */ + PX86TSS32 pCurTSS32 = (PX86TSS32)((uintptr_t)pvCurTSS32 - offCurTSS); + pCurTSS32->eip = uNextEip; + pCurTSS32->eflags = fEFlags; + pCurTSS32->eax = pVCpu->cpum.GstCtx.eax; + pCurTSS32->ecx = pVCpu->cpum.GstCtx.ecx; + pCurTSS32->edx = pVCpu->cpum.GstCtx.edx; + pCurTSS32->ebx = pVCpu->cpum.GstCtx.ebx; + pCurTSS32->esp = pVCpu->cpum.GstCtx.esp; + pCurTSS32->ebp = pVCpu->cpum.GstCtx.ebp; + pCurTSS32->esi = pVCpu->cpum.GstCtx.esi; + pCurTSS32->edi = pVCpu->cpum.GstCtx.edi; + pCurTSS32->es = pVCpu->cpum.GstCtx.es.Sel; + pCurTSS32->cs = pVCpu->cpum.GstCtx.cs.Sel; + pCurTSS32->ss = pVCpu->cpum.GstCtx.ss.Sel; + pCurTSS32->ds = pVCpu->cpum.GstCtx.ds.Sel; + pCurTSS32->fs = pVCpu->cpum.GstCtx.fs.Sel; + pCurTSS32->gs = pVCpu->cpum.GstCtx.gs.Sel; + + rcStrict = iemMemCommitAndUnmap(pVCpu, pvCurTSS32, IEM_ACCESS_SYS_RW); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to commit current 32-bit TSS. enmTaskSwitch=%u rc=%Rrc\n", enmTaskSwitch, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } + else + { + /* + * Verify that the current TSS (16-bit) can be accessed. Again, only the minimum required size. + */ + void *pvCurTSS16; + uint32_t const offCurTSS = RT_UOFFSETOF(X86TSS16, ip); + uint32_t const cbCurTSS = RT_UOFFSETOF(X86TSS16, selLdt) - RT_UOFFSETOF(X86TSS16, ip); + AssertCompile(RTASSERT_OFFSET_OF(X86TSS16, selLdt) - RTASSERT_OFFSET_OF(X86TSS16, ip) == 28); + rcStrict = iemMemMap(pVCpu, &pvCurTSS16, cbCurTSS, UINT8_MAX, GCPtrCurTSS + offCurTSS, IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to read current 16-bit TSS. enmTaskSwitch=%u GCPtrCurTSS=%#RGv cb=%u rc=%Rrc\n", + enmTaskSwitch, GCPtrCurTSS, cbCurTSS, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* !! WARNING !! Access -only- the members (dynamic fields) that are mapped, i.e interval [offCurTSS..cbCurTSS). */ + PX86TSS16 pCurTSS16 = (PX86TSS16)((uintptr_t)pvCurTSS16 - offCurTSS); + pCurTSS16->ip = uNextEip; + pCurTSS16->flags = (uint16_t)fEFlags; + pCurTSS16->ax = pVCpu->cpum.GstCtx.ax; + pCurTSS16->cx = pVCpu->cpum.GstCtx.cx; + pCurTSS16->dx = pVCpu->cpum.GstCtx.dx; + pCurTSS16->bx = pVCpu->cpum.GstCtx.bx; + pCurTSS16->sp = pVCpu->cpum.GstCtx.sp; + pCurTSS16->bp = pVCpu->cpum.GstCtx.bp; + pCurTSS16->si = pVCpu->cpum.GstCtx.si; + pCurTSS16->di = pVCpu->cpum.GstCtx.di; + pCurTSS16->es = pVCpu->cpum.GstCtx.es.Sel; + pCurTSS16->cs = pVCpu->cpum.GstCtx.cs.Sel; + pCurTSS16->ss = pVCpu->cpum.GstCtx.ss.Sel; + pCurTSS16->ds = pVCpu->cpum.GstCtx.ds.Sel; + + rcStrict = iemMemCommitAndUnmap(pVCpu, pvCurTSS16, IEM_ACCESS_SYS_RW); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to commit current 16-bit TSS. enmTaskSwitch=%u rc=%Rrc\n", enmTaskSwitch, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } + + /* + * Update the previous task link field for the new TSS, if the task switch is due to a CALL/INT_XCPT. + */ + if ( enmTaskSwitch == IEMTASKSWITCH_CALL + || enmTaskSwitch == IEMTASKSWITCH_INT_XCPT) + { + /* 16 or 32-bit TSS doesn't matter, we only access the first, common 16-bit field (selPrev) here. */ + PX86TSS32 pNewTSS = (PX86TSS32)pvNewTSS; + pNewTSS->selPrev = pVCpu->cpum.GstCtx.tr.Sel; + } + + /* + * Read the state from the new TSS into temporaries. Setting it immediately as the new CPU state is tricky, + * it's done further below with error handling (e.g. CR3 changes will go through PGM). + */ + uint32_t uNewCr3, uNewEip, uNewEflags, uNewEax, uNewEcx, uNewEdx, uNewEbx, uNewEsp, uNewEbp, uNewEsi, uNewEdi; + uint16_t uNewES, uNewCS, uNewSS, uNewDS, uNewFS, uNewGS, uNewLdt; + bool fNewDebugTrap; + if (fIsNewTSS386) + { + PCX86TSS32 pNewTSS32 = (PCX86TSS32)pvNewTSS; + uNewCr3 = (pVCpu->cpum.GstCtx.cr0 & X86_CR0_PG) ? pNewTSS32->cr3 : 0; + uNewEip = pNewTSS32->eip; + uNewEflags = pNewTSS32->eflags; + uNewEax = pNewTSS32->eax; + uNewEcx = pNewTSS32->ecx; + uNewEdx = pNewTSS32->edx; + uNewEbx = pNewTSS32->ebx; + uNewEsp = pNewTSS32->esp; + uNewEbp = pNewTSS32->ebp; + uNewEsi = pNewTSS32->esi; + uNewEdi = pNewTSS32->edi; + uNewES = pNewTSS32->es; + uNewCS = pNewTSS32->cs; + uNewSS = pNewTSS32->ss; + uNewDS = pNewTSS32->ds; + uNewFS = pNewTSS32->fs; + uNewGS = pNewTSS32->gs; + uNewLdt = pNewTSS32->selLdt; + fNewDebugTrap = RT_BOOL(pNewTSS32->fDebugTrap); + } + else + { + PCX86TSS16 pNewTSS16 = (PCX86TSS16)pvNewTSS; + uNewCr3 = 0; + uNewEip = pNewTSS16->ip; + uNewEflags = pNewTSS16->flags; + uNewEax = UINT32_C(0xffff0000) | pNewTSS16->ax; + uNewEcx = UINT32_C(0xffff0000) | pNewTSS16->cx; + uNewEdx = UINT32_C(0xffff0000) | pNewTSS16->dx; + uNewEbx = UINT32_C(0xffff0000) | pNewTSS16->bx; + uNewEsp = UINT32_C(0xffff0000) | pNewTSS16->sp; + uNewEbp = UINT32_C(0xffff0000) | pNewTSS16->bp; + uNewEsi = UINT32_C(0xffff0000) | pNewTSS16->si; + uNewEdi = UINT32_C(0xffff0000) | pNewTSS16->di; + uNewES = pNewTSS16->es; + uNewCS = pNewTSS16->cs; + uNewSS = pNewTSS16->ss; + uNewDS = pNewTSS16->ds; + uNewFS = 0; + uNewGS = 0; + uNewLdt = pNewTSS16->selLdt; + fNewDebugTrap = false; + } + + if (GCPtrNewTSS == GCPtrCurTSS) + Log(("uNewCr3=%#x uNewEip=%#x uNewEflags=%#x uNewEax=%#x uNewEsp=%#x uNewEbp=%#x uNewCS=%#04x uNewSS=%#04x uNewLdt=%#x\n", + uNewCr3, uNewEip, uNewEflags, uNewEax, uNewEsp, uNewEbp, uNewCS, uNewSS, uNewLdt)); + + /* + * We're done accessing the new TSS. + */ + rcStrict = iemMemCommitAndUnmap(pVCpu, pvNewTSS, IEM_ACCESS_SYS_RW); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to commit new TSS. enmTaskSwitch=%u rc=%Rrc\n", enmTaskSwitch, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* + * Set the busy bit in the new TSS descriptor, if the task switch is a JMP/CALL/INT_XCPT. + */ + if (enmTaskSwitch != IEMTASKSWITCH_IRET) + { + rcStrict = iemMemMap(pVCpu, (void **)&pNewDescTSS, sizeof(*pNewDescTSS), UINT8_MAX, + pVCpu->cpum.GstCtx.gdtr.pGdt + (SelTSS & X86_SEL_MASK), IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to read new TSS descriptor in GDT (2). enmTaskSwitch=%u pGdt=%#RX64 rc=%Rrc\n", + enmTaskSwitch, pVCpu->cpum.GstCtx.gdtr.pGdt, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* Check that the descriptor indicates the new TSS is available (not busy). */ + AssertMsg( pNewDescTSS->Legacy.Gate.u4Type == X86_SEL_TYPE_SYS_286_TSS_AVAIL + || pNewDescTSS->Legacy.Gate.u4Type == X86_SEL_TYPE_SYS_386_TSS_AVAIL, + ("Invalid TSS descriptor type=%#x", pNewDescTSS->Legacy.Gate.u4Type)); + + pNewDescTSS->Legacy.Gate.u4Type |= X86_SEL_TYPE_SYS_TSS_BUSY_MASK; + rcStrict = iemMemCommitAndUnmap(pVCpu, pNewDescTSS, IEM_ACCESS_SYS_RW); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Failed to commit new TSS descriptor in GDT (2). enmTaskSwitch=%u pGdt=%#RX64 rc=%Rrc\n", + enmTaskSwitch, pVCpu->cpum.GstCtx.gdtr.pGdt, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } + + /* + * From this point on, we're technically in the new task. We will defer exceptions + * until the completion of the task switch but before executing any instructions in the new task. + */ + pVCpu->cpum.GstCtx.tr.Sel = SelTSS; + pVCpu->cpum.GstCtx.tr.ValidSel = SelTSS; + pVCpu->cpum.GstCtx.tr.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.tr.Attr.u = X86DESC_GET_HID_ATTR(&pNewDescTSS->Legacy); + pVCpu->cpum.GstCtx.tr.u32Limit = X86DESC_LIMIT_G(&pNewDescTSS->Legacy); + pVCpu->cpum.GstCtx.tr.u64Base = X86DESC_BASE(&pNewDescTSS->Legacy); + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_TR); + + /* Set the busy bit in TR. */ + pVCpu->cpum.GstCtx.tr.Attr.n.u4Type |= X86_SEL_TYPE_SYS_TSS_BUSY_MASK; + + /* Set EFLAGS.NT (Nested Task) in the eflags loaded from the new TSS, if it's a task switch due to a CALL/INT_XCPT. */ + if ( enmTaskSwitch == IEMTASKSWITCH_CALL + || enmTaskSwitch == IEMTASKSWITCH_INT_XCPT) + { + uNewEflags |= X86_EFL_NT; + } + + pVCpu->cpum.GstCtx.dr[7] &= ~X86_DR7_LE_ALL; /** @todo Should we clear DR7.LE bit too? */ + pVCpu->cpum.GstCtx.cr0 |= X86_CR0_TS; + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_CR0); + + pVCpu->cpum.GstCtx.eip = uNewEip; + pVCpu->cpum.GstCtx.eax = uNewEax; + pVCpu->cpum.GstCtx.ecx = uNewEcx; + pVCpu->cpum.GstCtx.edx = uNewEdx; + pVCpu->cpum.GstCtx.ebx = uNewEbx; + pVCpu->cpum.GstCtx.esp = uNewEsp; + pVCpu->cpum.GstCtx.ebp = uNewEbp; + pVCpu->cpum.GstCtx.esi = uNewEsi; + pVCpu->cpum.GstCtx.edi = uNewEdi; + + uNewEflags &= X86_EFL_LIVE_MASK; + uNewEflags |= X86_EFL_RA1_MASK; + IEMMISC_SET_EFL(pVCpu, uNewEflags); + + /* + * Switch the selectors here and do the segment checks later. If we throw exceptions, the selectors + * will be valid in the exception handler. We cannot update the hidden parts until we've switched CR3 + * due to the hidden part data originating from the guest LDT/GDT which is accessed through paging. + */ + pVCpu->cpum.GstCtx.es.Sel = uNewES; + pVCpu->cpum.GstCtx.es.Attr.u &= ~X86DESCATTR_P; + + pVCpu->cpum.GstCtx.cs.Sel = uNewCS; + pVCpu->cpum.GstCtx.cs.Attr.u &= ~X86DESCATTR_P; + + pVCpu->cpum.GstCtx.ss.Sel = uNewSS; + pVCpu->cpum.GstCtx.ss.Attr.u &= ~X86DESCATTR_P; + + pVCpu->cpum.GstCtx.ds.Sel = uNewDS; + pVCpu->cpum.GstCtx.ds.Attr.u &= ~X86DESCATTR_P; + + pVCpu->cpum.GstCtx.fs.Sel = uNewFS; + pVCpu->cpum.GstCtx.fs.Attr.u &= ~X86DESCATTR_P; + + pVCpu->cpum.GstCtx.gs.Sel = uNewGS; + pVCpu->cpum.GstCtx.gs.Attr.u &= ~X86DESCATTR_P; + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_HIDDEN_SEL_REGS); + + pVCpu->cpum.GstCtx.ldtr.Sel = uNewLdt; + pVCpu->cpum.GstCtx.ldtr.fFlags = CPUMSELREG_FLAGS_STALE; + pVCpu->cpum.GstCtx.ldtr.Attr.u &= ~X86DESCATTR_P; + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_LDTR); + + if (IEM_IS_GUEST_CPU_INTEL(pVCpu)) + { + pVCpu->cpum.GstCtx.es.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.cs.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.ss.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.ds.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.fs.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.gs.Attr.u |= X86DESCATTR_UNUSABLE; + pVCpu->cpum.GstCtx.ldtr.Attr.u |= X86DESCATTR_UNUSABLE; + } + + /* + * Switch CR3 for the new task. + */ + if ( fIsNewTSS386 + && (pVCpu->cpum.GstCtx.cr0 & X86_CR0_PG)) + { + /** @todo Should we update and flush TLBs only if CR3 value actually changes? */ + int rc = CPUMSetGuestCR3(pVCpu, uNewCr3); + AssertRCSuccessReturn(rc, rc); + + /* Inform PGM. */ + /** @todo Should we raise \#GP(0) here when PAE PDPEs are invalid? */ + rc = PGMFlushTLB(pVCpu, pVCpu->cpum.GstCtx.cr3, !(pVCpu->cpum.GstCtx.cr4 & X86_CR4_PGE)); + AssertRCReturn(rc, rc); + /* ignore informational status codes */ + + CPUMSetChangedFlags(pVCpu, CPUM_CHANGED_CR3); + } + + /* + * Switch LDTR for the new task. + */ + if (!(uNewLdt & X86_SEL_MASK_OFF_RPL)) + iemHlpLoadNullDataSelectorProt(pVCpu, &pVCpu->cpum.GstCtx.ldtr, uNewLdt); + else + { + Assert(!pVCpu->cpum.GstCtx.ldtr.Attr.n.u1Present); /* Ensures that LDT.TI check passes in iemMemFetchSelDesc() below. */ + + IEMSELDESC DescNewLdt; + rcStrict = iemMemFetchSelDesc(pVCpu, &DescNewLdt, uNewLdt, X86_XCPT_TS); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: fetching LDT failed. enmTaskSwitch=%u uNewLdt=%u cbGdt=%u rc=%Rrc\n", enmTaskSwitch, + uNewLdt, pVCpu->cpum.GstCtx.gdtr.cbGdt, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + if ( !DescNewLdt.Legacy.Gen.u1Present + || DescNewLdt.Legacy.Gen.u1DescType + || DescNewLdt.Legacy.Gen.u4Type != X86_SEL_TYPE_SYS_LDT) + { + Log(("iemTaskSwitch: Invalid LDT. enmTaskSwitch=%u uNewLdt=%u DescNewLdt.Legacy.u=%#RX64 -> #TS\n", enmTaskSwitch, + uNewLdt, DescNewLdt.Legacy.u)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewLdt & X86_SEL_MASK_OFF_RPL); + } + + pVCpu->cpum.GstCtx.ldtr.ValidSel = uNewLdt; + pVCpu->cpum.GstCtx.ldtr.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.ldtr.u64Base = X86DESC_BASE(&DescNewLdt.Legacy); + pVCpu->cpum.GstCtx.ldtr.u32Limit = X86DESC_LIMIT_G(&DescNewLdt.Legacy); + pVCpu->cpum.GstCtx.ldtr.Attr.u = X86DESC_GET_HID_ATTR(&DescNewLdt.Legacy); + if (IEM_IS_GUEST_CPU_INTEL(pVCpu)) + pVCpu->cpum.GstCtx.ldtr.Attr.u &= ~X86DESCATTR_UNUSABLE; + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ldtr)); + } + + IEMSELDESC DescSS; + if (IEM_IS_V86_MODE(pVCpu)) + { + pVCpu->iem.s.uCpl = 3; + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.es, uNewES); + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.cs, uNewCS); + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.ss, uNewSS); + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.ds, uNewDS); + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.fs, uNewFS); + iemHlpLoadSelectorInV86Mode(&pVCpu->cpum.GstCtx.gs, uNewGS); + + /* Quick fix: fake DescSS. */ /** @todo fix the code further down? */ + DescSS.Legacy.u = 0; + DescSS.Legacy.Gen.u16LimitLow = (uint16_t)pVCpu->cpum.GstCtx.ss.u32Limit; + DescSS.Legacy.Gen.u4LimitHigh = pVCpu->cpum.GstCtx.ss.u32Limit >> 16; + DescSS.Legacy.Gen.u16BaseLow = (uint16_t)pVCpu->cpum.GstCtx.ss.u64Base; + DescSS.Legacy.Gen.u8BaseHigh1 = (uint8_t)(pVCpu->cpum.GstCtx.ss.u64Base >> 16); + DescSS.Legacy.Gen.u8BaseHigh2 = (uint8_t)(pVCpu->cpum.GstCtx.ss.u64Base >> 24); + DescSS.Legacy.Gen.u4Type = X86_SEL_TYPE_RW_ACC; + DescSS.Legacy.Gen.u2Dpl = 3; + } + else + { + uint8_t const uNewCpl = (uNewCS & X86_SEL_RPL); + + /* + * Load the stack segment for the new task. + */ + if (!(uNewSS & X86_SEL_MASK_OFF_RPL)) + { + Log(("iemTaskSwitch: Null stack segment. enmTaskSwitch=%u uNewSS=%#x -> #TS\n", enmTaskSwitch, uNewSS)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewSS & X86_SEL_MASK_OFF_RPL); + } + + /* Fetch the descriptor. */ + rcStrict = iemMemFetchSelDesc(pVCpu, &DescSS, uNewSS, X86_XCPT_TS); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: failed to fetch SS. uNewSS=%#x rc=%Rrc\n", uNewSS, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* SS must be a data segment and writable. */ + if ( !DescSS.Legacy.Gen.u1DescType + || (DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_CODE) + || !(DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_WRITE)) + { + Log(("iemTaskSwitch: SS invalid descriptor type. uNewSS=%#x u1DescType=%u u4Type=%#x\n", + uNewSS, DescSS.Legacy.Gen.u1DescType, DescSS.Legacy.Gen.u4Type)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewSS & X86_SEL_MASK_OFF_RPL); + } + + /* The SS.RPL, SS.DPL, CS.RPL (CPL) must be equal. */ + if ( (uNewSS & X86_SEL_RPL) != uNewCpl + || DescSS.Legacy.Gen.u2Dpl != uNewCpl) + { + Log(("iemTaskSwitch: Invalid priv. for SS. uNewSS=%#x SS.DPL=%u uNewCpl=%u -> #TS\n", uNewSS, DescSS.Legacy.Gen.u2Dpl, + uNewCpl)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewSS & X86_SEL_MASK_OFF_RPL); + } + + /* Is it there? */ + if (!DescSS.Legacy.Gen.u1Present) + { + Log(("iemTaskSwitch: SS not present. uNewSS=%#x -> #NP\n", uNewSS)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, uNewSS & X86_SEL_MASK_OFF_RPL); + } + + uint32_t cbLimit = X86DESC_LIMIT_G(&DescSS.Legacy); + uint64_t u64Base = X86DESC_BASE(&DescSS.Legacy); + + /* Set the accessed bit before committing the result into SS. */ + if (!(DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, uNewSS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescSS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* Commit SS. */ + pVCpu->cpum.GstCtx.ss.Sel = uNewSS; + pVCpu->cpum.GstCtx.ss.ValidSel = uNewSS; + pVCpu->cpum.GstCtx.ss.Attr.u = X86DESC_GET_HID_ATTR(&DescSS.Legacy); + pVCpu->cpum.GstCtx.ss.u32Limit = cbLimit; + pVCpu->cpum.GstCtx.ss.u64Base = u64Base; + pVCpu->cpum.GstCtx.ss.fFlags = CPUMSELREG_FLAGS_VALID; + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + + /* CPL has changed, update IEM before loading rest of segments. */ + pVCpu->iem.s.uCpl = uNewCpl; + + /* + * Load the data segments for the new task. + */ + rcStrict = iemHlpTaskSwitchLoadDataSelectorInProtMode(pVCpu, &pVCpu->cpum.GstCtx.es, uNewES); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + rcStrict = iemHlpTaskSwitchLoadDataSelectorInProtMode(pVCpu, &pVCpu->cpum.GstCtx.ds, uNewDS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + rcStrict = iemHlpTaskSwitchLoadDataSelectorInProtMode(pVCpu, &pVCpu->cpum.GstCtx.fs, uNewFS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + rcStrict = iemHlpTaskSwitchLoadDataSelectorInProtMode(pVCpu, &pVCpu->cpum.GstCtx.gs, uNewGS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * Load the code segment for the new task. + */ + if (!(uNewCS & X86_SEL_MASK_OFF_RPL)) + { + Log(("iemTaskSwitch #TS: Null code segment. enmTaskSwitch=%u uNewCS=%#x\n", enmTaskSwitch, uNewCS)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewCS & X86_SEL_MASK_OFF_RPL); + } + + /* Fetch the descriptor. */ + IEMSELDESC DescCS; + rcStrict = iemMemFetchSelDesc(pVCpu, &DescCS, uNewCS, X86_XCPT_TS); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: failed to fetch CS. uNewCS=%u rc=%Rrc\n", uNewCS, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* CS must be a code segment. */ + if ( !DescCS.Legacy.Gen.u1DescType + || !(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CODE)) + { + Log(("iemTaskSwitch: CS invalid descriptor type. uNewCS=%#x u1DescType=%u u4Type=%#x -> #TS\n", uNewCS, + DescCS.Legacy.Gen.u1DescType, DescCS.Legacy.Gen.u4Type)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewCS & X86_SEL_MASK_OFF_RPL); + } + + /* For conforming CS, DPL must be less than or equal to the RPL. */ + if ( (DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CONF) + && DescCS.Legacy.Gen.u2Dpl > (uNewCS & X86_SEL_RPL)) + { + Log(("iemTaskSwitch: confirming CS DPL > RPL. uNewCS=%#x u4Type=%#x DPL=%u -> #TS\n", uNewCS, DescCS.Legacy.Gen.u4Type, + DescCS.Legacy.Gen.u2Dpl)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewCS & X86_SEL_MASK_OFF_RPL); + } + + /* For non-conforming CS, DPL must match RPL. */ + if ( !(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CONF) + && DescCS.Legacy.Gen.u2Dpl != (uNewCS & X86_SEL_RPL)) + { + Log(("iemTaskSwitch: non-confirming CS DPL RPL mismatch. uNewCS=%#x u4Type=%#x DPL=%u -> #TS\n", uNewCS, + DescCS.Legacy.Gen.u4Type, DescCS.Legacy.Gen.u2Dpl)); + return iemRaiseTaskSwitchFaultWithErr(pVCpu, uNewCS & X86_SEL_MASK_OFF_RPL); + } + + /* Is it there? */ + if (!DescCS.Legacy.Gen.u1Present) + { + Log(("iemTaskSwitch: CS not present. uNewCS=%#x -> #NP\n", uNewCS)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, uNewCS & X86_SEL_MASK_OFF_RPL); + } + + cbLimit = X86DESC_LIMIT_G(&DescCS.Legacy); + u64Base = X86DESC_BASE(&DescCS.Legacy); + + /* Set the accessed bit before committing the result into CS. */ + if (!(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, uNewCS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescCS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* Commit CS. */ + pVCpu->cpum.GstCtx.cs.Sel = uNewCS; + pVCpu->cpum.GstCtx.cs.ValidSel = uNewCS; + pVCpu->cpum.GstCtx.cs.Attr.u = X86DESC_GET_HID_ATTR(&DescCS.Legacy); + pVCpu->cpum.GstCtx.cs.u32Limit = cbLimit; + pVCpu->cpum.GstCtx.cs.u64Base = u64Base; + pVCpu->cpum.GstCtx.cs.fFlags = CPUMSELREG_FLAGS_VALID; + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + } + + /** @todo Debug trap. */ + if (fIsNewTSS386 && fNewDebugTrap) + Log(("iemTaskSwitch: Debug Trap set in new TSS. Not implemented!\n")); + + /* + * Construct the error code masks based on what caused this task switch. + * See Intel Instruction reference for INT. + */ + uint16_t uExt; + if ( enmTaskSwitch == IEMTASKSWITCH_INT_XCPT + && ( !(fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) + || (fFlags & IEM_XCPT_FLAGS_ICEBP_INSTR))) + { + uExt = 1; + } + else + uExt = 0; + + /* + * Push any error code on to the new stack. + */ + if (fFlags & IEM_XCPT_FLAGS_ERR) + { + Assert(enmTaskSwitch == IEMTASKSWITCH_INT_XCPT); + uint32_t cbLimitSS = X86DESC_LIMIT_G(&DescSS.Legacy); + uint8_t const cbStackFrame = fIsNewTSS386 ? 4 : 2; + + /* Check that there is sufficient space on the stack. */ + /** @todo Factor out segment limit checking for normal/expand down segments + * into a separate function. */ + if (!(DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_DOWN)) + { + if ( pVCpu->cpum.GstCtx.esp - 1 > cbLimitSS + || pVCpu->cpum.GstCtx.esp < cbStackFrame) + { + /** @todo Intel says \#SS(EXT) for INT/XCPT, I couldn't figure out AMD yet. */ + Log(("iemTaskSwitch: SS=%#x ESP=%#x cbStackFrame=%#x is out of bounds -> #SS\n", + pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.esp, cbStackFrame)); + return iemRaiseStackSelectorNotPresentWithErr(pVCpu, uExt); + } + } + else + { + if ( pVCpu->cpum.GstCtx.esp - 1 > (DescSS.Legacy.Gen.u1DefBig ? UINT32_MAX : UINT32_C(0xffff)) + || pVCpu->cpum.GstCtx.esp - cbStackFrame < cbLimitSS + UINT32_C(1)) + { + Log(("iemTaskSwitch: SS=%#x ESP=%#x cbStackFrame=%#x (expand down) is out of bounds -> #SS\n", + pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.esp, cbStackFrame)); + return iemRaiseStackSelectorNotPresentWithErr(pVCpu, uExt); + } + } + + + if (fIsNewTSS386) + rcStrict = iemMemStackPushU32(pVCpu, uErr); + else + rcStrict = iemMemStackPushU16(pVCpu, uErr); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemTaskSwitch: Can't push error code to new task's stack. %s-bit TSS. rc=%Rrc\n", + fIsNewTSS386 ? "32" : "16", VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } + + /* Check the new EIP against the new CS limit. */ + if (pVCpu->cpum.GstCtx.eip > pVCpu->cpum.GstCtx.cs.u32Limit) + { + Log(("iemHlpTaskSwitchLoadDataSelectorInProtMode: New EIP exceeds CS limit. uNewEIP=%#RX32 CS limit=%u -> #GP(0)\n", + pVCpu->cpum.GstCtx.eip, pVCpu->cpum.GstCtx.cs.u32Limit)); + /** @todo Intel says \#GP(EXT) for INT/XCPT, I couldn't figure out AMD yet. */ + return iemRaiseGeneralProtectionFault(pVCpu, uExt); + } + + Log(("iemTaskSwitch: Success! New CS:EIP=%#04x:%#x SS=%#04x\n", pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.eip, + pVCpu->cpum.GstCtx.ss.Sel)); + return fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT ? VINF_IEM_RAISED_XCPT : VINF_SUCCESS; +} + + +/** + * Implements exceptions and interrupts for protected mode. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr The number of bytes to offset rIP by in the return + * address. + * @param u8Vector The interrupt / exception vector number. + * @param fFlags The flags. + * @param uErr The error value if IEM_XCPT_FLAGS_ERR is set. + * @param uCr2 The CR2 value if IEM_XCPT_FLAGS_CR2 is set. + */ +static VBOXSTRICTRC +iemRaiseXcptOrIntInProtMode(PVMCPUCC pVCpu, + uint8_t cbInstr, + uint8_t u8Vector, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2) RT_NOEXCEPT +{ + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + + /* + * Read the IDT entry. + */ + if (pVCpu->cpum.GstCtx.idtr.cbIdt < UINT32_C(8) * u8Vector + 7) + { + Log(("RaiseXcptOrIntInProtMode: %#x is out of bounds (%#x)\n", u8Vector, pVCpu->cpum.GstCtx.idtr.cbIdt)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + X86DESC Idte; + VBOXSTRICTRC rcStrict = iemMemFetchSysU64(pVCpu, &Idte.u, UINT8_MAX, + pVCpu->cpum.GstCtx.idtr.pIdt + UINT32_C(8) * u8Vector); + if (RT_UNLIKELY(rcStrict != VINF_SUCCESS)) + { + Log(("iemRaiseXcptOrIntInProtMode: failed to fetch IDT entry! vec=%#x rc=%Rrc\n", u8Vector, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + Log(("iemRaiseXcptOrIntInProtMode: vec=%#x P=%u DPL=%u DT=%u:%u A=%u %04x:%04x%04x\n", + u8Vector, Idte.Gate.u1Present, Idte.Gate.u2Dpl, Idte.Gate.u1DescType, Idte.Gate.u4Type, + Idte.Gate.u5ParmCount, Idte.Gate.u16Sel, Idte.Gate.u16OffsetHigh, Idte.Gate.u16OffsetLow)); + + /* + * Check the descriptor type, DPL and such. + * ASSUMES this is done in the same order as described for call-gate calls. + */ + if (Idte.Gate.u1DescType) + { + Log(("RaiseXcptOrIntInProtMode %#x - not system selector (%#x) -> #GP\n", u8Vector, Idte.Gate.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + bool fTaskGate = false; + uint8_t f32BitGate = true; + uint32_t fEflToClear = X86_EFL_TF | X86_EFL_NT | X86_EFL_RF | X86_EFL_VM; + switch (Idte.Gate.u4Type) + { + case X86_SEL_TYPE_SYS_UNDEFINED: + case X86_SEL_TYPE_SYS_286_TSS_AVAIL: + case X86_SEL_TYPE_SYS_LDT: + case X86_SEL_TYPE_SYS_286_TSS_BUSY: + case X86_SEL_TYPE_SYS_286_CALL_GATE: + case X86_SEL_TYPE_SYS_UNDEFINED2: + case X86_SEL_TYPE_SYS_386_TSS_AVAIL: + case X86_SEL_TYPE_SYS_UNDEFINED3: + case X86_SEL_TYPE_SYS_386_TSS_BUSY: + case X86_SEL_TYPE_SYS_386_CALL_GATE: + case X86_SEL_TYPE_SYS_UNDEFINED4: + { + /** @todo check what actually happens when the type is wrong... + * esp. call gates. */ + Log(("RaiseXcptOrIntInProtMode %#x - invalid type (%#x) -> #GP\n", u8Vector, Idte.Gate.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + + case X86_SEL_TYPE_SYS_286_INT_GATE: + f32BitGate = false; + RT_FALL_THRU(); + case X86_SEL_TYPE_SYS_386_INT_GATE: + fEflToClear |= X86_EFL_IF; + break; + + case X86_SEL_TYPE_SYS_TASK_GATE: + fTaskGate = true; +#ifndef IEM_IMPLEMENTS_TASKSWITCH + IEM_RETURN_ASPECT_NOT_IMPLEMENTED_LOG(("Task gates\n")); +#endif + break; + + case X86_SEL_TYPE_SYS_286_TRAP_GATE: + f32BitGate = false; + case X86_SEL_TYPE_SYS_386_TRAP_GATE: + break; + + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + /* Check DPL against CPL if applicable. */ + if ((fFlags & (IEM_XCPT_FLAGS_T_SOFT_INT | IEM_XCPT_FLAGS_ICEBP_INSTR)) == IEM_XCPT_FLAGS_T_SOFT_INT) + { + if (pVCpu->iem.s.uCpl > Idte.Gate.u2Dpl) + { + Log(("RaiseXcptOrIntInProtMode %#x - CPL (%d) > DPL (%d) -> #GP\n", u8Vector, pVCpu->iem.s.uCpl, Idte.Gate.u2Dpl)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + } + + /* Is it there? */ + if (!Idte.Gate.u1Present) + { + Log(("RaiseXcptOrIntInProtMode %#x - not present -> #NP\n", u8Vector)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + + /* Is it a task-gate? */ + if (fTaskGate) + { + /* + * Construct the error code masks based on what caused this task switch. + * See Intel Instruction reference for INT. + */ + uint16_t const uExt = ( (fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) + && !(fFlags & IEM_XCPT_FLAGS_ICEBP_INSTR)) ? 0 : 1; + uint16_t const uSelMask = X86_SEL_MASK_OFF_RPL; + RTSEL SelTSS = Idte.Gate.u16Sel; + + /* + * Fetch the TSS descriptor in the GDT. + */ + IEMSELDESC DescTSS; + rcStrict = iemMemFetchSelDescWithErr(pVCpu, &DescTSS, SelTSS, X86_XCPT_GP, (SelTSS & uSelMask) | uExt); + if (rcStrict != VINF_SUCCESS) + { + Log(("RaiseXcptOrIntInProtMode %#x - failed to fetch TSS selector %#x, rc=%Rrc\n", u8Vector, SelTSS, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* The TSS descriptor must be a system segment and be available (not busy). */ + if ( DescTSS.Legacy.Gen.u1DescType + || ( DescTSS.Legacy.Gen.u4Type != X86_SEL_TYPE_SYS_286_TSS_AVAIL + && DescTSS.Legacy.Gen.u4Type != X86_SEL_TYPE_SYS_386_TSS_AVAIL)) + { + Log(("RaiseXcptOrIntInProtMode %#x - TSS selector %#x of task gate not a system descriptor or not available %#RX64\n", + u8Vector, SelTSS, DescTSS.Legacy.au64)); + return iemRaiseGeneralProtectionFault(pVCpu, (SelTSS & uSelMask) | uExt); + } + + /* The TSS must be present. */ + if (!DescTSS.Legacy.Gen.u1Present) + { + Log(("RaiseXcptOrIntInProtMode %#x - TSS selector %#x not present %#RX64\n", u8Vector, SelTSS, DescTSS.Legacy.au64)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, (SelTSS & uSelMask) | uExt); + } + + /* Do the actual task switch. */ + return iemTaskSwitch(pVCpu, IEMTASKSWITCH_INT_XCPT, + (fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) ? pVCpu->cpum.GstCtx.eip + cbInstr : pVCpu->cpum.GstCtx.eip, + fFlags, uErr, uCr2, SelTSS, &DescTSS); + } + + /* A null CS is bad. */ + RTSEL NewCS = Idte.Gate.u16Sel; + if (!(NewCS & X86_SEL_MASK_OFF_RPL)) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x -> #GP\n", u8Vector, NewCS)); + return iemRaiseGeneralProtectionFault0(pVCpu); + } + + /* Fetch the descriptor for the new CS. */ + IEMSELDESC DescCS; + rcStrict = iemMemFetchSelDesc(pVCpu, &DescCS, NewCS, X86_XCPT_GP); /** @todo correct exception? */ + if (rcStrict != VINF_SUCCESS) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - rc=%Rrc\n", u8Vector, NewCS, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* Must be a code segment. */ + if (!DescCS.Legacy.Gen.u1DescType) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - system selector (%#x) -> #GP\n", u8Vector, NewCS, DescCS.Legacy.Gen.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + if (!(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CODE)) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - data selector (%#x) -> #GP\n", u8Vector, NewCS, DescCS.Legacy.Gen.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + + /* Don't allow lowering the privilege level. */ + /** @todo Does the lowering of privileges apply to software interrupts + * only? This has bearings on the more-privileged or + * same-privilege stack behavior further down. A testcase would + * be nice. */ + if (DescCS.Legacy.Gen.u2Dpl > pVCpu->iem.s.uCpl) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - DPL (%d) > CPL (%d) -> #GP\n", + u8Vector, NewCS, DescCS.Legacy.Gen.u2Dpl, pVCpu->iem.s.uCpl)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + + /* Make sure the selector is present. */ + if (!DescCS.Legacy.Gen.u1Present) + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - segment not present -> #NP\n", u8Vector, NewCS)); + return iemRaiseSelectorNotPresentBySelector(pVCpu, NewCS); + } + + /* Check the new EIP against the new CS limit. */ + uint32_t const uNewEip = Idte.Gate.u4Type == X86_SEL_TYPE_SYS_286_INT_GATE + || Idte.Gate.u4Type == X86_SEL_TYPE_SYS_286_TRAP_GATE + ? Idte.Gate.u16OffsetLow + : Idte.Gate.u16OffsetLow | ((uint32_t)Idte.Gate.u16OffsetHigh << 16); + uint32_t cbLimitCS = X86DESC_LIMIT_G(&DescCS.Legacy); + if (uNewEip > cbLimitCS) + { + Log(("RaiseXcptOrIntInProtMode %#x - EIP=%#x > cbLimitCS=%#x (CS=%#x) -> #GP(0)\n", + u8Vector, uNewEip, cbLimitCS, NewCS)); + return iemRaiseGeneralProtectionFault(pVCpu, 0); + } + Log7(("iemRaiseXcptOrIntInProtMode: new EIP=%#x CS=%#x\n", uNewEip, NewCS)); + + /* Calc the flag image to push. */ + uint32_t fEfl = IEMMISC_GET_EFL(pVCpu); + if (fFlags & (IEM_XCPT_FLAGS_DRx_INSTR_BP | IEM_XCPT_FLAGS_T_SOFT_INT)) + fEfl &= ~X86_EFL_RF; + else + fEfl |= X86_EFL_RF; /* Vagueness is all I've found on this so far... */ /** @todo Automatically pushing EFLAGS.RF. */ + + /* From V8086 mode only go to CPL 0. */ + uint8_t const uNewCpl = DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CONF + ? pVCpu->iem.s.uCpl : DescCS.Legacy.Gen.u2Dpl; + if ((fEfl & X86_EFL_VM) && uNewCpl != 0) /** @todo When exactly is this raised? */ + { + Log(("RaiseXcptOrIntInProtMode %#x - CS=%#x - New CPL (%d) != 0 w/ VM=1 -> #GP\n", u8Vector, NewCS, uNewCpl)); + return iemRaiseGeneralProtectionFault(pVCpu, 0); + } + + /* + * If the privilege level changes, we need to get a new stack from the TSS. + * This in turns means validating the new SS and ESP... + */ + if (uNewCpl != pVCpu->iem.s.uCpl) + { + RTSEL NewSS; + uint32_t uNewEsp; + rcStrict = iemRaiseLoadStackFromTss32Or16(pVCpu, uNewCpl, &NewSS, &uNewEsp); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + IEMSELDESC DescSS; + rcStrict = iemMiscValidateNewSS(pVCpu, NewSS, uNewCpl, &DescSS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + /* If the new SS is 16-bit, we are only going to use SP, not ESP. */ + if (!DescSS.Legacy.Gen.u1DefBig) + { + Log(("iemRaiseXcptOrIntInProtMode: Forcing ESP=%#x to 16 bits\n", uNewEsp)); + uNewEsp = (uint16_t)uNewEsp; + } + + Log7(("iemRaiseXcptOrIntInProtMode: New SS=%#x ESP=%#x (from TSS); current SS=%#x ESP=%#x\n", NewSS, uNewEsp, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.esp)); + + /* Check that there is sufficient space for the stack frame. */ + uint32_t cbLimitSS = X86DESC_LIMIT_G(&DescSS.Legacy); + uint8_t const cbStackFrame = !(fEfl & X86_EFL_VM) + ? (fFlags & IEM_XCPT_FLAGS_ERR ? 12 : 10) << f32BitGate + : (fFlags & IEM_XCPT_FLAGS_ERR ? 20 : 18) << f32BitGate; + + if (!(DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_DOWN)) + { + if ( uNewEsp - 1 > cbLimitSS + || uNewEsp < cbStackFrame) + { + Log(("RaiseXcptOrIntInProtMode: %#x - SS=%#x ESP=%#x cbStackFrame=%#x is out of bounds -> #GP\n", + u8Vector, NewSS, uNewEsp, cbStackFrame)); + return iemRaiseSelectorBoundsBySelector(pVCpu, NewSS); + } + } + else + { + if ( uNewEsp - 1 > (DescSS.Legacy.Gen.u1DefBig ? UINT32_MAX : UINT16_MAX) + || uNewEsp - cbStackFrame < cbLimitSS + UINT32_C(1)) + { + Log(("RaiseXcptOrIntInProtMode: %#x - SS=%#x ESP=%#x cbStackFrame=%#x (expand down) is out of bounds -> #GP\n", + u8Vector, NewSS, uNewEsp, cbStackFrame)); + return iemRaiseSelectorBoundsBySelector(pVCpu, NewSS); + } + } + + /* + * Start making changes. + */ + + /* Set the new CPL so that stack accesses use it. */ + uint8_t const uOldCpl = pVCpu->iem.s.uCpl; + pVCpu->iem.s.uCpl = uNewCpl; + + /* Create the stack frame. */ + RTPTRUNION uStackFrame; + rcStrict = iemMemMap(pVCpu, &uStackFrame.pv, cbStackFrame, UINT8_MAX, + uNewEsp - cbStackFrame + X86DESC_BASE(&DescSS.Legacy), + IEM_ACCESS_STACK_W | IEM_ACCESS_WHAT_SYS, 0); /* _SYS is a hack ... */ + if (rcStrict != VINF_SUCCESS) + return rcStrict; + void * const pvStackFrame = uStackFrame.pv; + if (f32BitGate) + { + if (fFlags & IEM_XCPT_FLAGS_ERR) + *uStackFrame.pu32++ = uErr; + uStackFrame.pu32[0] = (fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) ? pVCpu->cpum.GstCtx.eip + cbInstr : pVCpu->cpum.GstCtx.eip; + uStackFrame.pu32[1] = (pVCpu->cpum.GstCtx.cs.Sel & ~X86_SEL_RPL) | uOldCpl; + uStackFrame.pu32[2] = fEfl; + uStackFrame.pu32[3] = pVCpu->cpum.GstCtx.esp; + uStackFrame.pu32[4] = pVCpu->cpum.GstCtx.ss.Sel; + Log7(("iemRaiseXcptOrIntInProtMode: 32-bit push SS=%#x ESP=%#x\n", pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.esp)); + if (fEfl & X86_EFL_VM) + { + uStackFrame.pu32[1] = pVCpu->cpum.GstCtx.cs.Sel; + uStackFrame.pu32[5] = pVCpu->cpum.GstCtx.es.Sel; + uStackFrame.pu32[6] = pVCpu->cpum.GstCtx.ds.Sel; + uStackFrame.pu32[7] = pVCpu->cpum.GstCtx.fs.Sel; + uStackFrame.pu32[8] = pVCpu->cpum.GstCtx.gs.Sel; + } + } + else + { + if (fFlags & IEM_XCPT_FLAGS_ERR) + *uStackFrame.pu16++ = uErr; + uStackFrame.pu16[0] = (fFlags & IEM_XCPT_FLAGS_T_SOFT_INT) ? pVCpu->cpum.GstCtx.ip + cbInstr : pVCpu->cpum.GstCtx.ip; + uStackFrame.pu16[1] = (pVCpu->cpum.GstCtx.cs.Sel & ~X86_SEL_RPL) | uOldCpl; + uStackFrame.pu16[2] = fEfl; + uStackFrame.pu16[3] = pVCpu->cpum.GstCtx.sp; + uStackFrame.pu16[4] = pVCpu->cpum.GstCtx.ss.Sel; + Log7(("iemRaiseXcptOrIntInProtMode: 16-bit push SS=%#x SP=%#x\n", pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.sp)); + if (fEfl & X86_EFL_VM) + { + uStackFrame.pu16[1] = pVCpu->cpum.GstCtx.cs.Sel; + uStackFrame.pu16[5] = pVCpu->cpum.GstCtx.es.Sel; + uStackFrame.pu16[6] = pVCpu->cpum.GstCtx.ds.Sel; + uStackFrame.pu16[7] = pVCpu->cpum.GstCtx.fs.Sel; + uStackFrame.pu16[8] = pVCpu->cpum.GstCtx.gs.Sel; + } + } + rcStrict = iemMemCommitAndUnmap(pVCpu, pvStackFrame, IEM_ACCESS_STACK_W | IEM_ACCESS_WHAT_SYS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* Mark the selectors 'accessed' (hope this is the correct time). */ + /** @todo testcase: excatly _when_ are the accessed bits set - before or + * after pushing the stack frame? (Write protect the gdt + stack to + * find out.) */ + if (!(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, NewCS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescCS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + if (!(DescSS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, NewSS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescSS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* + * Start comitting the register changes (joins with the DPL=CPL branch). + */ + pVCpu->cpum.GstCtx.ss.Sel = NewSS; + pVCpu->cpum.GstCtx.ss.ValidSel = NewSS; + pVCpu->cpum.GstCtx.ss.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.ss.u32Limit = cbLimitSS; + pVCpu->cpum.GstCtx.ss.u64Base = X86DESC_BASE(&DescSS.Legacy); + pVCpu->cpum.GstCtx.ss.Attr.u = X86DESC_GET_HID_ATTR(&DescSS.Legacy); + /** @todo When coming from 32-bit code and operating with a 16-bit TSS and + * 16-bit handler, the high word of ESP remains unchanged (i.e. only + * SP is loaded). + * Need to check the other combinations too: + * - 16-bit TSS, 32-bit handler + * - 32-bit TSS, 16-bit handler */ + if (!pVCpu->cpum.GstCtx.ss.Attr.n.u1DefBig) + pVCpu->cpum.GstCtx.sp = (uint16_t)(uNewEsp - cbStackFrame); + else + pVCpu->cpum.GstCtx.rsp = uNewEsp - cbStackFrame; + + if (fEfl & X86_EFL_VM) + { + iemHlpLoadNullDataSelectorOnV86Xcpt(pVCpu, &pVCpu->cpum.GstCtx.gs); + iemHlpLoadNullDataSelectorOnV86Xcpt(pVCpu, &pVCpu->cpum.GstCtx.fs); + iemHlpLoadNullDataSelectorOnV86Xcpt(pVCpu, &pVCpu->cpum.GstCtx.es); + iemHlpLoadNullDataSelectorOnV86Xcpt(pVCpu, &pVCpu->cpum.GstCtx.ds); + } + } + /* + * Same privilege, no stack change and smaller stack frame. + */ + else + { + uint64_t uNewRsp; + RTPTRUNION uStackFrame; + uint8_t const cbStackFrame = (fFlags & IEM_XCPT_FLAGS_ERR ? 8 : 6) << f32BitGate; + rcStrict = iemMemStackPushBeginSpecial(pVCpu, cbStackFrame, f32BitGate ? 3 : 1, &uStackFrame.pv, &uNewRsp); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + void * const pvStackFrame = uStackFrame.pv; + + if (f32BitGate) + { + if (fFlags & IEM_XCPT_FLAGS_ERR) + *uStackFrame.pu32++ = uErr; + uStackFrame.pu32[0] = fFlags & IEM_XCPT_FLAGS_T_SOFT_INT ? pVCpu->cpum.GstCtx.eip + cbInstr : pVCpu->cpum.GstCtx.eip; + uStackFrame.pu32[1] = (pVCpu->cpum.GstCtx.cs.Sel & ~X86_SEL_RPL) | pVCpu->iem.s.uCpl; + uStackFrame.pu32[2] = fEfl; + } + else + { + if (fFlags & IEM_XCPT_FLAGS_ERR) + *uStackFrame.pu16++ = uErr; + uStackFrame.pu16[0] = fFlags & IEM_XCPT_FLAGS_T_SOFT_INT ? pVCpu->cpum.GstCtx.eip + cbInstr : pVCpu->cpum.GstCtx.eip; + uStackFrame.pu16[1] = (pVCpu->cpum.GstCtx.cs.Sel & ~X86_SEL_RPL) | pVCpu->iem.s.uCpl; + uStackFrame.pu16[2] = fEfl; + } + rcStrict = iemMemCommitAndUnmap(pVCpu, pvStackFrame, IEM_ACCESS_STACK_W); /* don't use the commit here */ + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* Mark the CS selector as 'accessed'. */ + if (!(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, NewCS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescCS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* + * Start committing the register changes (joins with the other branch). + */ + pVCpu->cpum.GstCtx.rsp = uNewRsp; + } + + /* ... register committing continues. */ + pVCpu->cpum.GstCtx.cs.Sel = (NewCS & ~X86_SEL_RPL) | uNewCpl; + pVCpu->cpum.GstCtx.cs.ValidSel = (NewCS & ~X86_SEL_RPL) | uNewCpl; + pVCpu->cpum.GstCtx.cs.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.cs.u32Limit = cbLimitCS; + pVCpu->cpum.GstCtx.cs.u64Base = X86DESC_BASE(&DescCS.Legacy); + pVCpu->cpum.GstCtx.cs.Attr.u = X86DESC_GET_HID_ATTR(&DescCS.Legacy); + + pVCpu->cpum.GstCtx.rip = uNewEip; /* (The entire register is modified, see pe16_32 bs3kit tests.) */ + fEfl &= ~fEflToClear; + IEMMISC_SET_EFL(pVCpu, fEfl); + + if (fFlags & IEM_XCPT_FLAGS_CR2) + pVCpu->cpum.GstCtx.cr2 = uCr2; + + if (fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT) + iemRaiseXcptAdjustState(pVCpu, u8Vector); + + return fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT ? VINF_IEM_RAISED_XCPT : VINF_SUCCESS; +} + + +/** + * Implements exceptions and interrupts for long mode. + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr The number of bytes to offset rIP by in the return + * address. + * @param u8Vector The interrupt / exception vector number. + * @param fFlags The flags. + * @param uErr The error value if IEM_XCPT_FLAGS_ERR is set. + * @param uCr2 The CR2 value if IEM_XCPT_FLAGS_CR2 is set. + */ +static VBOXSTRICTRC +iemRaiseXcptOrIntInLongMode(PVMCPUCC pVCpu, + uint8_t cbInstr, + uint8_t u8Vector, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2) RT_NOEXCEPT +{ + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + + /* + * Read the IDT entry. + */ + uint16_t offIdt = (uint16_t)u8Vector << 4; + if (pVCpu->cpum.GstCtx.idtr.cbIdt < offIdt + 7) + { + Log(("iemRaiseXcptOrIntInLongMode: %#x is out of bounds (%#x)\n", u8Vector, pVCpu->cpum.GstCtx.idtr.cbIdt)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + X86DESC64 Idte; +#ifdef _MSC_VER /* Shut up silly compiler warning. */ + Idte.au64[0] = 0; + Idte.au64[1] = 0; +#endif + VBOXSTRICTRC rcStrict = iemMemFetchSysU64(pVCpu, &Idte.au64[0], UINT8_MAX, pVCpu->cpum.GstCtx.idtr.pIdt + offIdt); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + rcStrict = iemMemFetchSysU64(pVCpu, &Idte.au64[1], UINT8_MAX, pVCpu->cpum.GstCtx.idtr.pIdt + offIdt + 8); + if (RT_UNLIKELY(rcStrict != VINF_SUCCESS)) + { + Log(("iemRaiseXcptOrIntInLongMode: failed to fetch IDT entry! vec=%#x rc=%Rrc\n", u8Vector, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + Log(("iemRaiseXcptOrIntInLongMode: vec=%#x P=%u DPL=%u DT=%u:%u IST=%u %04x:%08x%04x%04x\n", + u8Vector, Idte.Gate.u1Present, Idte.Gate.u2Dpl, Idte.Gate.u1DescType, Idte.Gate.u4Type, + Idte.Gate.u3IST, Idte.Gate.u16Sel, Idte.Gate.u32OffsetTop, Idte.Gate.u16OffsetHigh, Idte.Gate.u16OffsetLow)); + + /* + * Check the descriptor type, DPL and such. + * ASSUMES this is done in the same order as described for call-gate calls. + */ + if (Idte.Gate.u1DescType) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - not system selector (%#x) -> #GP\n", u8Vector, Idte.Gate.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + uint32_t fEflToClear = X86_EFL_TF | X86_EFL_NT | X86_EFL_RF | X86_EFL_VM; + switch (Idte.Gate.u4Type) + { + case AMD64_SEL_TYPE_SYS_INT_GATE: + fEflToClear |= X86_EFL_IF; + break; + case AMD64_SEL_TYPE_SYS_TRAP_GATE: + break; + + default: + Log(("iemRaiseXcptOrIntInLongMode %#x - invalid type (%#x) -> #GP\n", u8Vector, Idte.Gate.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + + /* Check DPL against CPL if applicable. */ + if ((fFlags & (IEM_XCPT_FLAGS_T_SOFT_INT | IEM_XCPT_FLAGS_ICEBP_INSTR)) == IEM_XCPT_FLAGS_T_SOFT_INT) + { + if (pVCpu->iem.s.uCpl > Idte.Gate.u2Dpl) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CPL (%d) > DPL (%d) -> #GP\n", u8Vector, pVCpu->iem.s.uCpl, Idte.Gate.u2Dpl)); + return iemRaiseGeneralProtectionFault(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + } + + /* Is it there? */ + if (!Idte.Gate.u1Present) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - not present -> #NP\n", u8Vector)); + return iemRaiseSelectorNotPresentWithErr(pVCpu, X86_TRAP_ERR_IDT | ((uint16_t)u8Vector << X86_TRAP_ERR_SEL_SHIFT)); + } + + /* A null CS is bad. */ + RTSEL NewCS = Idte.Gate.u16Sel; + if (!(NewCS & X86_SEL_MASK_OFF_RPL)) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x -> #GP\n", u8Vector, NewCS)); + return iemRaiseGeneralProtectionFault0(pVCpu); + } + + /* Fetch the descriptor for the new CS. */ + IEMSELDESC DescCS; + rcStrict = iemMemFetchSelDesc(pVCpu, &DescCS, NewCS, X86_XCPT_GP); + if (rcStrict != VINF_SUCCESS) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x - rc=%Rrc\n", u8Vector, NewCS, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + + /* Must be a 64-bit code segment. */ + if (!DescCS.Long.Gen.u1DescType) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x - system selector (%#x) -> #GP\n", u8Vector, NewCS, DescCS.Legacy.Gen.u4Type)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + if ( !DescCS.Long.Gen.u1Long + || DescCS.Long.Gen.u1DefBig + || !(DescCS.Long.Gen.u4Type & X86_SEL_TYPE_CODE) ) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x - not 64-bit code selector (%#x, L=%u, D=%u) -> #GP\n", + u8Vector, NewCS, DescCS.Legacy.Gen.u4Type, DescCS.Long.Gen.u1Long, DescCS.Long.Gen.u1DefBig)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + + /* Don't allow lowering the privilege level. For non-conforming CS + selectors, the CS.DPL sets the privilege level the trap/interrupt + handler runs at. For conforming CS selectors, the CPL remains + unchanged, but the CS.DPL must be <= CPL. */ + /** @todo Testcase: Interrupt handler with CS.DPL=1, interrupt dispatched + * when CPU in Ring-0. Result \#GP? */ + if (DescCS.Legacy.Gen.u2Dpl > pVCpu->iem.s.uCpl) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x - DPL (%d) > CPL (%d) -> #GP\n", + u8Vector, NewCS, DescCS.Legacy.Gen.u2Dpl, pVCpu->iem.s.uCpl)); + return iemRaiseGeneralProtectionFault(pVCpu, NewCS & X86_SEL_MASK_OFF_RPL); + } + + + /* Make sure the selector is present. */ + if (!DescCS.Legacy.Gen.u1Present) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - CS=%#x - segment not present -> #NP\n", u8Vector, NewCS)); + return iemRaiseSelectorNotPresentBySelector(pVCpu, NewCS); + } + + /* Check that the new RIP is canonical. */ + uint64_t const uNewRip = Idte.Gate.u16OffsetLow + | ((uint32_t)Idte.Gate.u16OffsetHigh << 16) + | ((uint64_t)Idte.Gate.u32OffsetTop << 32); + if (!IEM_IS_CANONICAL(uNewRip)) + { + Log(("iemRaiseXcptOrIntInLongMode %#x - RIP=%#RX64 - Not canonical -> #GP(0)\n", u8Vector, uNewRip)); + return iemRaiseGeneralProtectionFault0(pVCpu); + } + + /* + * If the privilege level changes or if the IST isn't zero, we need to get + * a new stack from the TSS. + */ + uint64_t uNewRsp; + uint8_t const uNewCpl = DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_CONF + ? pVCpu->iem.s.uCpl : DescCS.Legacy.Gen.u2Dpl; + if ( uNewCpl != pVCpu->iem.s.uCpl + || Idte.Gate.u3IST != 0) + { + rcStrict = iemRaiseLoadStackFromTss64(pVCpu, uNewCpl, Idte.Gate.u3IST, &uNewRsp); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + } + else + uNewRsp = pVCpu->cpum.GstCtx.rsp; + uNewRsp &= ~(uint64_t)0xf; + + /* + * Calc the flag image to push. + */ + uint32_t fEfl = IEMMISC_GET_EFL(pVCpu); + if (fFlags & (IEM_XCPT_FLAGS_DRx_INSTR_BP | IEM_XCPT_FLAGS_T_SOFT_INT)) + fEfl &= ~X86_EFL_RF; + else + fEfl |= X86_EFL_RF; /* Vagueness is all I've found on this so far... */ /** @todo Automatically pushing EFLAGS.RF. */ + + /* + * Start making changes. + */ + /* Set the new CPL so that stack accesses use it. */ + uint8_t const uOldCpl = pVCpu->iem.s.uCpl; + pVCpu->iem.s.uCpl = uNewCpl; + + /* Create the stack frame. */ + uint32_t cbStackFrame = sizeof(uint64_t) * (5 + !!(fFlags & IEM_XCPT_FLAGS_ERR)); + RTPTRUNION uStackFrame; + rcStrict = iemMemMap(pVCpu, &uStackFrame.pv, cbStackFrame, UINT8_MAX, + uNewRsp - cbStackFrame, IEM_ACCESS_STACK_W | IEM_ACCESS_WHAT_SYS, 0); /* _SYS is a hack ... */ + if (rcStrict != VINF_SUCCESS) + return rcStrict; + void * const pvStackFrame = uStackFrame.pv; + + if (fFlags & IEM_XCPT_FLAGS_ERR) + *uStackFrame.pu64++ = uErr; + uStackFrame.pu64[0] = fFlags & IEM_XCPT_FLAGS_T_SOFT_INT ? pVCpu->cpum.GstCtx.rip + cbInstr : pVCpu->cpum.GstCtx.rip; + uStackFrame.pu64[1] = (pVCpu->cpum.GstCtx.cs.Sel & ~X86_SEL_RPL) | uOldCpl; /* CPL paranoia */ + uStackFrame.pu64[2] = fEfl; + uStackFrame.pu64[3] = pVCpu->cpum.GstCtx.rsp; + uStackFrame.pu64[4] = pVCpu->cpum.GstCtx.ss.Sel; + rcStrict = iemMemCommitAndUnmap(pVCpu, pvStackFrame, IEM_ACCESS_STACK_W | IEM_ACCESS_WHAT_SYS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* Mark the CS selectors 'accessed' (hope this is the correct time). */ + /** @todo testcase: excatly _when_ are the accessed bits set - before or + * after pushing the stack frame? (Write protect the gdt + stack to + * find out.) */ + if (!(DescCS.Legacy.Gen.u4Type & X86_SEL_TYPE_ACCESSED)) + { + rcStrict = iemMemMarkSelDescAccessed(pVCpu, NewCS); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + DescCS.Legacy.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; + } + + /* + * Start comitting the register changes. + */ + /** @todo research/testcase: Figure out what VT-x and AMD-V loads into the + * hidden registers when interrupting 32-bit or 16-bit code! */ + if (uNewCpl != uOldCpl) + { + pVCpu->cpum.GstCtx.ss.Sel = 0 | uNewCpl; + pVCpu->cpum.GstCtx.ss.ValidSel = 0 | uNewCpl; + pVCpu->cpum.GstCtx.ss.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.ss.u32Limit = UINT32_MAX; + pVCpu->cpum.GstCtx.ss.u64Base = 0; + pVCpu->cpum.GstCtx.ss.Attr.u = (uNewCpl << X86DESCATTR_DPL_SHIFT) | X86DESCATTR_UNUSABLE; + } + pVCpu->cpum.GstCtx.rsp = uNewRsp - cbStackFrame; + pVCpu->cpum.GstCtx.cs.Sel = (NewCS & ~X86_SEL_RPL) | uNewCpl; + pVCpu->cpum.GstCtx.cs.ValidSel = (NewCS & ~X86_SEL_RPL) | uNewCpl; + pVCpu->cpum.GstCtx.cs.fFlags = CPUMSELREG_FLAGS_VALID; + pVCpu->cpum.GstCtx.cs.u32Limit = X86DESC_LIMIT_G(&DescCS.Legacy); + pVCpu->cpum.GstCtx.cs.u64Base = X86DESC_BASE(&DescCS.Legacy); + pVCpu->cpum.GstCtx.cs.Attr.u = X86DESC_GET_HID_ATTR(&DescCS.Legacy); + pVCpu->cpum.GstCtx.rip = uNewRip; + + fEfl &= ~fEflToClear; + IEMMISC_SET_EFL(pVCpu, fEfl); + + if (fFlags & IEM_XCPT_FLAGS_CR2) + pVCpu->cpum.GstCtx.cr2 = uCr2; + + if (fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT) + iemRaiseXcptAdjustState(pVCpu, u8Vector); + + return fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT ? VINF_IEM_RAISED_XCPT : VINF_SUCCESS; +} + + +/** + * Implements exceptions and interrupts. + * + * All exceptions and interrupts goes thru this function! + * + * @returns VBox strict status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr The number of bytes to offset rIP by in the return + * address. + * @param u8Vector The interrupt / exception vector number. + * @param fFlags The flags. + * @param uErr The error value if IEM_XCPT_FLAGS_ERR is set. + * @param uCr2 The CR2 value if IEM_XCPT_FLAGS_CR2 is set. + */ +VBOXSTRICTRC +iemRaiseXcptOrInt(PVMCPUCC pVCpu, + uint8_t cbInstr, + uint8_t u8Vector, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2) RT_NOEXCEPT +{ + /* + * Get all the state that we might need here. + */ + IEM_CTX_IMPORT_RET(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); + +#ifndef IEM_WITH_CODE_TLB /** @todo we're doing it afterwards too, that should suffice... */ + /* + * Flush prefetch buffer + */ + pVCpu->iem.s.cbOpcode = pVCpu->iem.s.offOpcode; +#endif + + /* + * Perform the V8086 IOPL check and upgrade the fault without nesting. + */ + if ( pVCpu->cpum.GstCtx.eflags.Bits.u1VM + && pVCpu->cpum.GstCtx.eflags.Bits.u2IOPL != 3 + && (fFlags & ( IEM_XCPT_FLAGS_T_SOFT_INT + | IEM_XCPT_FLAGS_BP_INSTR + | IEM_XCPT_FLAGS_ICEBP_INSTR + | IEM_XCPT_FLAGS_OF_INSTR)) == IEM_XCPT_FLAGS_T_SOFT_INT + && (pVCpu->cpum.GstCtx.cr0 & X86_CR0_PE) ) + { + Log(("iemRaiseXcptOrInt: V8086 IOPL check failed for int %#x -> #GP(0)\n", u8Vector)); + fFlags = IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR; + u8Vector = X86_XCPT_GP; + uErr = 0; + } +#ifdef DBGFTRACE_ENABLED + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "Xcpt/%u: %02x %u %x %x %llx %04x:%04llx %04x:%04llx", + pVCpu->iem.s.cXcptRecursions, u8Vector, cbInstr, fFlags, uErr, uCr2, + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp); +#endif + + /* + * Evaluate whether NMI blocking should be in effect. + * Normally, NMI blocking is in effect whenever we inject an NMI. + */ + bool fBlockNmi = u8Vector == X86_XCPT_NMI + && (fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT); + +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VBOXSTRICTRC rcStrict0 = iemVmxVmexitEvent(pVCpu, u8Vector, fFlags, uErr, uCr2, cbInstr); + if (rcStrict0 != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict0; + + /* If virtual-NMI blocking is in effect for the nested-guest, guest NMIs are not blocked. */ + if (pVCpu->cpum.GstCtx.hwvirt.vmx.fVirtNmiBlocking) + { + Assert(CPUMIsGuestVmxPinCtlsSet(&pVCpu->cpum.GstCtx, VMX_PIN_CTLS_VIRT_NMI)); + fBlockNmi = false; + } + } +#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (CPUMIsGuestInSvmNestedHwVirtMode(IEM_GET_CTX(pVCpu))) + { + /* + * If the event is being injected as part of VMRUN, it isn't subject to event + * intercepts in the nested-guest. However, secondary exceptions that occur + * during injection of any event -are- subject to exception intercepts. + * + * See AMD spec. 15.20 "Event Injection". + */ + if (!pVCpu->cpum.GstCtx.hwvirt.svm.fInterceptEvents) + pVCpu->cpum.GstCtx.hwvirt.svm.fInterceptEvents = true; + else + { + /* + * Check and handle if the event being raised is intercepted. + */ + VBOXSTRICTRC rcStrict0 = iemHandleSvmEventIntercept(pVCpu, u8Vector, fFlags, uErr, uCr2); + if (rcStrict0 != VINF_SVM_INTERCEPT_NOT_ACTIVE) + return rcStrict0; + } + } +#endif + + /* + * Set NMI blocking if necessary. + */ + if (fBlockNmi) + CPUMSetInterruptInhibitingByNmi(&pVCpu->cpum.GstCtx); + + /* + * Do recursion accounting. + */ + uint8_t const uPrevXcpt = pVCpu->iem.s.uCurXcpt; + uint32_t const fPrevXcpt = pVCpu->iem.s.fCurXcpt; + if (pVCpu->iem.s.cXcptRecursions == 0) + Log(("iemRaiseXcptOrInt: %#x at %04x:%RGv cbInstr=%#x fFlags=%#x uErr=%#x uCr2=%llx\n", + u8Vector, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, cbInstr, fFlags, uErr, uCr2)); + else + { + Log(("iemRaiseXcptOrInt: %#x at %04x:%RGv cbInstr=%#x fFlags=%#x uErr=%#x uCr2=%llx; prev=%#x depth=%d flags=%#x\n", + u8Vector, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, cbInstr, fFlags, uErr, uCr2, pVCpu->iem.s.uCurXcpt, + pVCpu->iem.s.cXcptRecursions + 1, fPrevXcpt)); + + if (pVCpu->iem.s.cXcptRecursions >= 4) + { +#ifdef DEBUG_bird + AssertFailed(); +#endif + IEM_RETURN_ASPECT_NOT_IMPLEMENTED_LOG(("Too many fault nestings.\n")); + } + + /* + * Evaluate the sequence of recurring events. + */ + IEMXCPTRAISE enmRaise = IEMEvaluateRecursiveXcpt(pVCpu, fPrevXcpt, uPrevXcpt, fFlags, u8Vector, + NULL /* pXcptRaiseInfo */); + if (enmRaise == IEMXCPTRAISE_CURRENT_XCPT) + { /* likely */ } + else if (enmRaise == IEMXCPTRAISE_DOUBLE_FAULT) + { + Log2(("iemRaiseXcptOrInt: Raising double fault. uPrevXcpt=%#x\n", uPrevXcpt)); + fFlags = IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR; + u8Vector = X86_XCPT_DF; + uErr = 0; +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + /* VMX nested-guest #DF intercept needs to be checked here. */ + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VBOXSTRICTRC rcStrict0 = iemVmxVmexitEventDoubleFault(pVCpu); + if (rcStrict0 != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict0; + } +#endif + /* SVM nested-guest #DF intercepts need to be checked now. See AMD spec. 15.12 "Exception Intercepts". */ + if (IEM_SVM_IS_XCPT_INTERCEPT_SET(pVCpu, X86_XCPT_DF)) + IEM_SVM_VMEXIT_RET(pVCpu, SVM_EXIT_XCPT_DF, 0 /* uExitInfo1 */, 0 /* uExitInfo2 */); + } + else if (enmRaise == IEMXCPTRAISE_TRIPLE_FAULT) + { + Log2(("iemRaiseXcptOrInt: Raising triple fault. uPrevXcpt=%#x\n", uPrevXcpt)); + return iemInitiateCpuShutdown(pVCpu); + } + else if (enmRaise == IEMXCPTRAISE_CPU_HANG) + { + /* If a nested-guest enters an endless CPU loop condition, we'll emulate it; otherwise guru. */ + Log2(("iemRaiseXcptOrInt: CPU hang condition detected\n")); + if ( !CPUMIsGuestInSvmNestedHwVirtMode(IEM_GET_CTX(pVCpu)) + && !CPUMIsGuestInVmxNonRootMode(IEM_GET_CTX(pVCpu))) + return VERR_EM_GUEST_CPU_HANG; + } + else + { + AssertMsgFailed(("Unexpected condition! enmRaise=%#x uPrevXcpt=%#x fPrevXcpt=%#x, u8Vector=%#x fFlags=%#x\n", + enmRaise, uPrevXcpt, fPrevXcpt, u8Vector, fFlags)); + return VERR_IEM_IPE_9; + } + + /* + * The 'EXT' bit is set when an exception occurs during deliver of an external + * event (such as an interrupt or earlier exception)[1]. Privileged software + * exception (INT1) also sets the EXT bit[2]. Exceptions generated by software + * interrupts and INTO, INT3 instructions, the 'EXT' bit will not be set. + * + * [1] - Intel spec. 6.13 "Error Code" + * [2] - Intel spec. 26.5.1.1 "Details of Vectored-Event Injection". + * [3] - Intel Instruction reference for INT n. + */ + if ( (fPrevXcpt & (IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_T_EXT_INT | IEM_XCPT_FLAGS_ICEBP_INSTR)) + && (fFlags & IEM_XCPT_FLAGS_ERR) + && u8Vector != X86_XCPT_PF + && u8Vector != X86_XCPT_DF) + { + uErr |= X86_TRAP_ERR_EXTERNAL; + } + } + + pVCpu->iem.s.cXcptRecursions++; + pVCpu->iem.s.uCurXcpt = u8Vector; + pVCpu->iem.s.fCurXcpt = fFlags; + pVCpu->iem.s.uCurXcptErr = uErr; + pVCpu->iem.s.uCurXcptCr2 = uCr2; + + /* + * Extensive logging. + */ +#if defined(LOG_ENABLED) && defined(IN_RING3) + if (LogIs3Enabled()) + { + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_DR_MASK); + PVM pVM = pVCpu->CTX_SUFF(pVM); + char szRegs[4096]; + DBGFR3RegPrintf(pVM->pUVM, pVCpu->idCpu, &szRegs[0], sizeof(szRegs), + "rax=%016VR{rax} rbx=%016VR{rbx} rcx=%016VR{rcx} rdx=%016VR{rdx}\n" + "rsi=%016VR{rsi} rdi=%016VR{rdi} r8 =%016VR{r8} r9 =%016VR{r9}\n" + "r10=%016VR{r10} r11=%016VR{r11} r12=%016VR{r12} r13=%016VR{r13}\n" + "r14=%016VR{r14} r15=%016VR{r15} %VRF{rflags}\n" + "rip=%016VR{rip} rsp=%016VR{rsp} rbp=%016VR{rbp}\n" + "cs={%04VR{cs} base=%016VR{cs_base} limit=%08VR{cs_lim} flags=%04VR{cs_attr}} cr0=%016VR{cr0}\n" + "ds={%04VR{ds} base=%016VR{ds_base} limit=%08VR{ds_lim} flags=%04VR{ds_attr}} cr2=%016VR{cr2}\n" + "es={%04VR{es} base=%016VR{es_base} limit=%08VR{es_lim} flags=%04VR{es_attr}} cr3=%016VR{cr3}\n" + "fs={%04VR{fs} base=%016VR{fs_base} limit=%08VR{fs_lim} flags=%04VR{fs_attr}} cr4=%016VR{cr4}\n" + "gs={%04VR{gs} base=%016VR{gs_base} limit=%08VR{gs_lim} flags=%04VR{gs_attr}} cr8=%016VR{cr8}\n" + "ss={%04VR{ss} base=%016VR{ss_base} limit=%08VR{ss_lim} flags=%04VR{ss_attr}}\n" + "dr0=%016VR{dr0} dr1=%016VR{dr1} dr2=%016VR{dr2} dr3=%016VR{dr3}\n" + "dr6=%016VR{dr6} dr7=%016VR{dr7}\n" + "gdtr=%016VR{gdtr_base}:%04VR{gdtr_lim} idtr=%016VR{idtr_base}:%04VR{idtr_lim} rflags=%08VR{rflags}\n" + "ldtr={%04VR{ldtr} base=%016VR{ldtr_base} limit=%08VR{ldtr_lim} flags=%08VR{ldtr_attr}}\n" + "tr ={%04VR{tr} base=%016VR{tr_base} limit=%08VR{tr_lim} flags=%08VR{tr_attr}}\n" + " sysenter={cs=%04VR{sysenter_cs} eip=%08VR{sysenter_eip} esp=%08VR{sysenter_esp}}\n" + " efer=%016VR{efer}\n" + " pat=%016VR{pat}\n" + " sf_mask=%016VR{sf_mask}\n" + "krnl_gs_base=%016VR{krnl_gs_base}\n" + " lstar=%016VR{lstar}\n" + " star=%016VR{star} cstar=%016VR{cstar}\n" + "fcw=%04VR{fcw} fsw=%04VR{fsw} ftw=%04VR{ftw} mxcsr=%04VR{mxcsr} mxcsr_mask=%04VR{mxcsr_mask}\n" + ); + + char szInstr[256]; + DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, 0, 0, + DBGF_DISAS_FLAGS_CURRENT_GUEST | DBGF_DISAS_FLAGS_DEFAULT_MODE, + szInstr, sizeof(szInstr), NULL); + Log3(("%s%s\n", szRegs, szInstr)); + } +#endif /* LOG_ENABLED */ + + /* + * Stats. + */ + if (!(fFlags & IEM_XCPT_FLAGS_T_CPU_XCPT)) + STAM_REL_STATS({ pVCpu->iem.s.aStatInts[u8Vector] += 1; }); + else if (u8Vector <= X86_XCPT_LAST) + { + STAM_REL_COUNTER_INC(&pVCpu->iem.s.aStatXcpts[u8Vector]); + EMHistoryAddExit(pVCpu, EMEXIT_MAKE_FT(EMEXIT_F_KIND_XCPT, u8Vector), + pVCpu->cpum.GstCtx.rip + pVCpu->cpum.GstCtx.cs.u64Base, ASMReadTSC()); + } + + /* + * #PF's implies a INVLPG for the CR2 value (see 4.10.1.1 in Intel SDM Vol 3) + * to ensure that a stale TLB or paging cache entry will only cause one + * spurious #PF. + */ + if ( u8Vector == X86_XCPT_PF + && (fFlags & (IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_CR2)) == (IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_CR2)) + IEMTlbInvalidatePage(pVCpu, uCr2); + + /* + * Call the mode specific worker function. + */ + VBOXSTRICTRC rcStrict; + if (!(pVCpu->cpum.GstCtx.cr0 & X86_CR0_PE)) + rcStrict = iemRaiseXcptOrIntInRealMode(pVCpu, cbInstr, u8Vector, fFlags, uErr, uCr2); + else if (pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_LMA) + rcStrict = iemRaiseXcptOrIntInLongMode(pVCpu, cbInstr, u8Vector, fFlags, uErr, uCr2); + else + rcStrict = iemRaiseXcptOrIntInProtMode(pVCpu, cbInstr, u8Vector, fFlags, uErr, uCr2); + + /* Flush the prefetch buffer. */ +#ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.pbInstrBuf = NULL; +#else + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Unwind. + */ + pVCpu->iem.s.cXcptRecursions--; + pVCpu->iem.s.uCurXcpt = uPrevXcpt; + pVCpu->iem.s.fCurXcpt = fPrevXcpt; + Log(("iemRaiseXcptOrInt: returns %Rrc (vec=%#x); cs:rip=%04x:%RGv ss:rsp=%04x:%RGv cpl=%u depth=%d\n", + VBOXSTRICTRC_VAL(rcStrict), u8Vector, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.esp, pVCpu->iem.s.uCpl, + pVCpu->iem.s.cXcptRecursions + 1)); + return rcStrict; +} + +#ifdef IEM_WITH_SETJMP +/** + * See iemRaiseXcptOrInt. Will not return. + */ +DECL_NO_RETURN(void) +iemRaiseXcptOrIntJmp(PVMCPUCC pVCpu, + uint8_t cbInstr, + uint8_t u8Vector, + uint32_t fFlags, + uint16_t uErr, + uint64_t uCr2) IEM_NOEXCEPT_MAY_LONGJMP +{ + VBOXSTRICTRC rcStrict = iemRaiseXcptOrInt(pVCpu, cbInstr, u8Vector, fFlags, uErr, uCr2); + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); +} +#endif + + +/** \#DE - 00. */ +VBOXSTRICTRC iemRaiseDivideError(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_DE, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** \#DB - 01. + * @note This automatically clear DR7.GD. */ +VBOXSTRICTRC iemRaiseDebugException(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + /* This always clears RF (via IEM_XCPT_FLAGS_DRx_INSTR_BP). */ + pVCpu->cpum.GstCtx.dr[7] &= ~X86_DR7_GD; + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_DB, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_DRx_INSTR_BP, 0, 0); +} + + +/** \#BR - 05. */ +VBOXSTRICTRC iemRaiseBoundRangeExceeded(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_BR, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** \#UD - 06. */ +VBOXSTRICTRC iemRaiseUndefinedOpcode(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_UD, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** \#NM - 07. */ +VBOXSTRICTRC iemRaiseDeviceNotAvailable(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_NM, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** \#TS(err) - 0a. */ +VBOXSTRICTRC iemRaiseTaskSwitchFaultWithErr(PVMCPUCC pVCpu, uint16_t uErr) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_TS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, uErr, 0); +} + + +/** \#TS(tr) - 0a. */ +VBOXSTRICTRC iemRaiseTaskSwitchFaultCurrentTSS(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_TS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + pVCpu->cpum.GstCtx.tr.Sel, 0); +} + + +/** \#TS(0) - 0a. */ +VBOXSTRICTRC iemRaiseTaskSwitchFault0(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_TS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + 0, 0); +} + + +/** \#TS(err) - 0a. */ +VBOXSTRICTRC iemRaiseTaskSwitchFaultBySelector(PVMCPUCC pVCpu, uint16_t uSel) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_TS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + uSel & X86_SEL_MASK_OFF_RPL, 0); +} + + +/** \#NP(err) - 0b. */ +VBOXSTRICTRC iemRaiseSelectorNotPresentWithErr(PVMCPUCC pVCpu, uint16_t uErr) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_NP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, uErr, 0); +} + + +/** \#NP(sel) - 0b. */ +VBOXSTRICTRC iemRaiseSelectorNotPresentBySelector(PVMCPUCC pVCpu, uint16_t uSel) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_NP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + uSel & ~X86_SEL_RPL, 0); +} + + +/** \#SS(seg) - 0c. */ +VBOXSTRICTRC iemRaiseStackSelectorNotPresentBySelector(PVMCPUCC pVCpu, uint16_t uSel) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_SS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + uSel & ~X86_SEL_RPL, 0); +} + + +/** \#SS(err) - 0c. */ +VBOXSTRICTRC iemRaiseStackSelectorNotPresentWithErr(PVMCPUCC pVCpu, uint16_t uErr) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_SS, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, uErr, 0); +} + + +/** \#GP(n) - 0d. */ +VBOXSTRICTRC iemRaiseGeneralProtectionFault(PVMCPUCC pVCpu, uint16_t uErr) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, uErr, 0); +} + + +/** \#GP(0) - 0d. */ +VBOXSTRICTRC iemRaiseGeneralProtectionFault0(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + +#ifdef IEM_WITH_SETJMP +/** \#GP(0) - 0d. */ +DECL_NO_RETURN(void) iemRaiseGeneralProtectionFault0Jmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ + iemRaiseXcptOrIntJmp(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} +#endif + + +/** \#GP(sel) - 0d. */ +VBOXSTRICTRC iemRaiseGeneralProtectionFaultBySelector(PVMCPUCC pVCpu, RTSEL Sel) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + Sel & ~X86_SEL_RPL, 0); +} + + +/** \#GP(0) - 0d. */ +VBOXSTRICTRC iemRaiseNotCanonical(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + + +/** \#GP(sel) - 0d. */ +VBOXSTRICTRC iemRaiseSelectorBounds(PVMCPUCC pVCpu, uint32_t iSegReg, uint32_t fAccess) RT_NOEXCEPT +{ + NOREF(iSegReg); NOREF(fAccess); + return iemRaiseXcptOrInt(pVCpu, 0, iSegReg == X86_SREG_SS ? X86_XCPT_SS : X86_XCPT_GP, + IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + +#ifdef IEM_WITH_SETJMP +/** \#GP(sel) - 0d, longjmp. */ +DECL_NO_RETURN(void) iemRaiseSelectorBoundsJmp(PVMCPUCC pVCpu, uint32_t iSegReg, uint32_t fAccess) IEM_NOEXCEPT_MAY_LONGJMP +{ + NOREF(iSegReg); NOREF(fAccess); + iemRaiseXcptOrIntJmp(pVCpu, 0, iSegReg == X86_SREG_SS ? X86_XCPT_SS : X86_XCPT_GP, + IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} +#endif + +/** \#GP(sel) - 0d. */ +VBOXSTRICTRC iemRaiseSelectorBoundsBySelector(PVMCPUCC pVCpu, RTSEL Sel) RT_NOEXCEPT +{ + NOREF(Sel); + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + +#ifdef IEM_WITH_SETJMP +/** \#GP(sel) - 0d, longjmp. */ +DECL_NO_RETURN(void) iemRaiseSelectorBoundsBySelectorJmp(PVMCPUCC pVCpu, RTSEL Sel) IEM_NOEXCEPT_MAY_LONGJMP +{ + NOREF(Sel); + iemRaiseXcptOrIntJmp(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} +#endif + + +/** \#GP(sel) - 0d. */ +VBOXSTRICTRC iemRaiseSelectorInvalidAccess(PVMCPUCC pVCpu, uint32_t iSegReg, uint32_t fAccess) RT_NOEXCEPT +{ + NOREF(iSegReg); NOREF(fAccess); + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + +#ifdef IEM_WITH_SETJMP +/** \#GP(sel) - 0d, longjmp. */ +DECL_NO_RETURN(void) iemRaiseSelectorInvalidAccessJmp(PVMCPUCC pVCpu, uint32_t iSegReg, uint32_t fAccess) IEM_NOEXCEPT_MAY_LONGJMP +{ + NOREF(iSegReg); NOREF(fAccess); + iemRaiseXcptOrIntJmp(pVCpu, 0, X86_XCPT_GP, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} +#endif + + +/** \#PF(n) - 0e. */ +VBOXSTRICTRC iemRaisePageFault(PVMCPUCC pVCpu, RTGCPTR GCPtrWhere, uint32_t cbAccess, uint32_t fAccess, int rc) RT_NOEXCEPT +{ + uint16_t uErr; + switch (rc) + { + case VERR_PAGE_NOT_PRESENT: + case VERR_PAGE_TABLE_NOT_PRESENT: + case VERR_PAGE_DIRECTORY_PTR_NOT_PRESENT: + case VERR_PAGE_MAP_LEVEL4_NOT_PRESENT: + uErr = 0; + break; + + default: + AssertMsgFailed(("%Rrc\n", rc)); + RT_FALL_THRU(); + case VERR_ACCESS_DENIED: + uErr = X86_TRAP_PF_P; + break; + + /** @todo reserved */ + } + + if (pVCpu->iem.s.uCpl == 3) + uErr |= X86_TRAP_PF_US; + + if ( (fAccess & IEM_ACCESS_WHAT_MASK) == IEM_ACCESS_WHAT_CODE + && ( (pVCpu->cpum.GstCtx.cr4 & X86_CR4_PAE) + && (pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_NXE) ) ) + uErr |= X86_TRAP_PF_ID; + +#if 0 /* This is so much non-sense, really. Why was it done like that? */ + /* Note! RW access callers reporting a WRITE protection fault, will clear + the READ flag before calling. So, read-modify-write accesses (RW) + can safely be reported as READ faults. */ + if ((fAccess & (IEM_ACCESS_TYPE_WRITE | IEM_ACCESS_TYPE_READ)) == IEM_ACCESS_TYPE_WRITE) + uErr |= X86_TRAP_PF_RW; +#else + if (fAccess & IEM_ACCESS_TYPE_WRITE) + { + /// @todo r=bird: bs3-cpu-basic-2 wants X86_TRAP_PF_RW for xchg and cmpxchg + /// (regardless of outcome of the comparison in the latter case). + //if (!(fAccess & IEM_ACCESS_TYPE_READ)) + uErr |= X86_TRAP_PF_RW; + } +#endif + + /* For FXSAVE and FRSTOR the #PF is typically reported at the max address + of the memory operand rather than at the start of it. (Not sure what + happens if it crosses a page boundrary.) The current heuristics for + this is to report the #PF for the last byte if the access is more than + 64 bytes. This is probably not correct, but we can work that out later, + main objective now is to get FXSAVE to work like for real hardware and + make bs3-cpu-basic2 work. */ + if (cbAccess <= 64) + { /* likely*/ } + else + GCPtrWhere += cbAccess - 1; + + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_PF, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR | IEM_XCPT_FLAGS_CR2, + uErr, GCPtrWhere); +} + +#ifdef IEM_WITH_SETJMP +/** \#PF(n) - 0e, longjmp. */ +DECL_NO_RETURN(void) iemRaisePageFaultJmp(PVMCPUCC pVCpu, RTGCPTR GCPtrWhere, uint32_t cbAccess, + uint32_t fAccess, int rc) IEM_NOEXCEPT_MAY_LONGJMP +{ + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(iemRaisePageFault(pVCpu, GCPtrWhere, cbAccess, fAccess, rc))); +} +#endif + + +/** \#MF(0) - 10. */ +VBOXSTRICTRC iemRaiseMathFault(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + if (pVCpu->cpum.GstCtx.cr0 & X86_CR0_NE) + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_MF, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); + + /* Convert a #MF into a FERR -> IRQ 13. See @bugref{6117}. */ + PDMIsaSetIrq(pVCpu->CTX_SUFF(pVM), 13 /* u8Irq */, 1 /* u8Level */, 0 /* uTagSrc */); + return iemRegUpdateRipAndFinishClearingRF(pVCpu); +} + + +/** \#AC(0) - 11. */ +VBOXSTRICTRC iemRaiseAlignmentCheckException(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_AC, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, 0, 0); +} + +#ifdef IEM_WITH_SETJMP +/** \#AC(0) - 11, longjmp. */ +DECL_NO_RETURN(void) iemRaiseAlignmentCheckExceptionJmp(PVMCPUCC pVCpu) IEM_NOEXCEPT_MAY_LONGJMP +{ + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(iemRaiseAlignmentCheckException(pVCpu))); +} +#endif + + +/** \#XF(0)/\#XM(0) - 19. */ +VBOXSTRICTRC iemRaiseSimdFpException(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_XF, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** Accessed via IEMOP_RAISE_DIVIDE_ERROR. */ +IEM_CIMPL_DEF_0(iemCImplRaiseDivideError) +{ + NOREF(cbInstr); + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_DE, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** Accessed via IEMOP_RAISE_INVALID_LOCK_PREFIX. */ +IEM_CIMPL_DEF_0(iemCImplRaiseInvalidLockPrefix) +{ + NOREF(cbInstr); + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_UD, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** Accessed via IEMOP_RAISE_INVALID_OPCODE. */ +IEM_CIMPL_DEF_0(iemCImplRaiseInvalidOpcode) +{ + NOREF(cbInstr); + return iemRaiseXcptOrInt(pVCpu, 0, X86_XCPT_UD, IEM_XCPT_FLAGS_T_CPU_XCPT, 0, 0); +} + + +/** @} */ + +/** @name Common opcode decoders. + * @{ + */ +//#include <iprt/mem.h> + +/** + * Used to add extra details about a stub case. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +void iemOpStubMsg2(PVMCPUCC pVCpu) RT_NOEXCEPT +{ +#if defined(LOG_ENABLED) && defined(IN_RING3) + PVM pVM = pVCpu->CTX_SUFF(pVM); + char szRegs[4096]; + DBGFR3RegPrintf(pVM->pUVM, pVCpu->idCpu, &szRegs[0], sizeof(szRegs), + "rax=%016VR{rax} rbx=%016VR{rbx} rcx=%016VR{rcx} rdx=%016VR{rdx}\n" + "rsi=%016VR{rsi} rdi=%016VR{rdi} r8 =%016VR{r8} r9 =%016VR{r9}\n" + "r10=%016VR{r10} r11=%016VR{r11} r12=%016VR{r12} r13=%016VR{r13}\n" + "r14=%016VR{r14} r15=%016VR{r15} %VRF{rflags}\n" + "rip=%016VR{rip} rsp=%016VR{rsp} rbp=%016VR{rbp}\n" + "cs={%04VR{cs} base=%016VR{cs_base} limit=%08VR{cs_lim} flags=%04VR{cs_attr}} cr0=%016VR{cr0}\n" + "ds={%04VR{ds} base=%016VR{ds_base} limit=%08VR{ds_lim} flags=%04VR{ds_attr}} cr2=%016VR{cr2}\n" + "es={%04VR{es} base=%016VR{es_base} limit=%08VR{es_lim} flags=%04VR{es_attr}} cr3=%016VR{cr3}\n" + "fs={%04VR{fs} base=%016VR{fs_base} limit=%08VR{fs_lim} flags=%04VR{fs_attr}} cr4=%016VR{cr4}\n" + "gs={%04VR{gs} base=%016VR{gs_base} limit=%08VR{gs_lim} flags=%04VR{gs_attr}} cr8=%016VR{cr8}\n" + "ss={%04VR{ss} base=%016VR{ss_base} limit=%08VR{ss_lim} flags=%04VR{ss_attr}}\n" + "dr0=%016VR{dr0} dr1=%016VR{dr1} dr2=%016VR{dr2} dr3=%016VR{dr3}\n" + "dr6=%016VR{dr6} dr7=%016VR{dr7}\n" + "gdtr=%016VR{gdtr_base}:%04VR{gdtr_lim} idtr=%016VR{idtr_base}:%04VR{idtr_lim} rflags=%08VR{rflags}\n" + "ldtr={%04VR{ldtr} base=%016VR{ldtr_base} limit=%08VR{ldtr_lim} flags=%08VR{ldtr_attr}}\n" + "tr ={%04VR{tr} base=%016VR{tr_base} limit=%08VR{tr_lim} flags=%08VR{tr_attr}}\n" + " sysenter={cs=%04VR{sysenter_cs} eip=%08VR{sysenter_eip} esp=%08VR{sysenter_esp}}\n" + " efer=%016VR{efer}\n" + " pat=%016VR{pat}\n" + " sf_mask=%016VR{sf_mask}\n" + "krnl_gs_base=%016VR{krnl_gs_base}\n" + " lstar=%016VR{lstar}\n" + " star=%016VR{star} cstar=%016VR{cstar}\n" + "fcw=%04VR{fcw} fsw=%04VR{fsw} ftw=%04VR{ftw} mxcsr=%04VR{mxcsr} mxcsr_mask=%04VR{mxcsr_mask}\n" + ); + + char szInstr[256]; + DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, 0, 0, + DBGF_DISAS_FLAGS_CURRENT_GUEST | DBGF_DISAS_FLAGS_DEFAULT_MODE, + szInstr, sizeof(szInstr), NULL); + + RTAssertMsg2Weak("%s%s\n", szRegs, szInstr); +#else + RTAssertMsg2Weak("cs:rip=%04x:%RX64\n", pVCpu->cpum.GstCtx.cs, pVCpu->cpum.GstCtx.rip); +#endif +} + +/** @} */ + + + +/** @name Register Access. + * @{ + */ + +/** + * Adds a 8-bit signed jump offset to RIP/EIP/IP. + * + * May raise a \#GP(0) if the new RIP is non-canonical or outside the code + * segment limit. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr Instruction size. + * @param offNextInstr The offset of the next instruction. + * @param enmEffOpSize Effective operand size. + */ +VBOXSTRICTRC iemRegRipRelativeJumpS8AndFinishClearingRF(PVMCPUCC pVCpu, uint8_t cbInstr, int8_t offNextInstr, + IEMMODE enmEffOpSize) RT_NOEXCEPT +{ + switch (enmEffOpSize) + { + case IEMMODE_16BIT: + { + uint16_t const uNewIp = pVCpu->cpum.GstCtx.ip + cbInstr + (int16_t)offNextInstr; + if (RT_LIKELY( uNewIp <= pVCpu->cpum.GstCtx.cs.u32Limit + || pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT /* no CS limit checks in 64-bit mode */)) + pVCpu->cpum.GstCtx.rip = uNewIp; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + break; + } + + case IEMMODE_32BIT: + { + Assert(pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT); + Assert(pVCpu->cpum.GstCtx.rip <= UINT32_MAX); + + uint32_t const uNewEip = pVCpu->cpum.GstCtx.eip + cbInstr + (int32_t)offNextInstr; + if (RT_LIKELY(uNewEip <= pVCpu->cpum.GstCtx.cs.u32Limit)) + pVCpu->cpum.GstCtx.rip = uNewEip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + break; + } + + case IEMMODE_64BIT: + { + Assert(pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT); + + uint64_t const uNewRip = pVCpu->cpum.GstCtx.rip + cbInstr + (int64_t)offNextInstr; + if (RT_LIKELY(IEM_IS_CANONICAL(uNewRip))) + pVCpu->cpum.GstCtx.rip = uNewRip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + break; + } + + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = cbInstr; +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + + +/** + * Adds a 16-bit signed jump offset to RIP/EIP/IP. + * + * May raise a \#GP(0) if the new RIP is non-canonical or outside the code + * segment limit. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr Instruction size. + * @param offNextInstr The offset of the next instruction. + */ +VBOXSTRICTRC iemRegRipRelativeJumpS16AndFinishClearingRF(PVMCPUCC pVCpu, uint8_t cbInstr, int16_t offNextInstr) RT_NOEXCEPT +{ + Assert(pVCpu->iem.s.enmEffOpSize == IEMMODE_16BIT); + + uint16_t const uNewIp = pVCpu->cpum.GstCtx.ip + cbInstr + offNextInstr; + if (RT_LIKELY( uNewIp <= pVCpu->cpum.GstCtx.cs.u32Limit + || pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT /* no limit checking in 64-bit mode */)) + pVCpu->cpum.GstCtx.rip = uNewIp; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + + +/** + * Adds a 32-bit signed jump offset to RIP/EIP/IP. + * + * May raise a \#GP(0) if the new RIP is non-canonical or outside the code + * segment limit. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbInstr Instruction size. + * @param offNextInstr The offset of the next instruction. + * @param enmEffOpSize Effective operand size. + */ +VBOXSTRICTRC iemRegRipRelativeJumpS32AndFinishClearingRF(PVMCPUCC pVCpu, uint8_t cbInstr, int32_t offNextInstr, + IEMMODE enmEffOpSize) RT_NOEXCEPT +{ + if (enmEffOpSize == IEMMODE_32BIT) + { + Assert(pVCpu->cpum.GstCtx.rip <= UINT32_MAX); Assert(pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT); + + uint32_t const uNewEip = pVCpu->cpum.GstCtx.eip + cbInstr + offNextInstr; + if (RT_LIKELY(uNewEip <= pVCpu->cpum.GstCtx.cs.u32Limit)) + pVCpu->cpum.GstCtx.rip = uNewEip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + } + else + { + Assert(enmEffOpSize == IEMMODE_64BIT); + + uint64_t const uNewRip = pVCpu->cpum.GstCtx.rip + cbInstr + (int64_t)offNextInstr; + if (RT_LIKELY(IEM_IS_CANONICAL(uNewRip))) + pVCpu->cpum.GstCtx.rip = uNewRip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + } + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + + +/** + * Performs a near jump to the specified address. + * + * May raise a \#GP(0) if the new IP outside the code segment limit. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uNewIp The new IP value. + */ +VBOXSTRICTRC iemRegRipJumpU16AndFinishClearningRF(PVMCPUCC pVCpu, uint16_t uNewIp) RT_NOEXCEPT +{ + if (RT_LIKELY( uNewIp <= pVCpu->cpum.GstCtx.cs.u32Limit + || pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT /* no limit checks in 64-bit mode */)) + pVCpu->cpum.GstCtx.rip = uNewIp; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + /** @todo Test 16-bit jump in 64-bit mode. */ + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + + +/** + * Performs a near jump to the specified address. + * + * May raise a \#GP(0) if the new RIP is outside the code segment limit. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uNewEip The new EIP value. + */ +VBOXSTRICTRC iemRegRipJumpU32AndFinishClearningRF(PVMCPUCC pVCpu, uint32_t uNewEip) RT_NOEXCEPT +{ + Assert(pVCpu->cpum.GstCtx.rip <= UINT32_MAX); + Assert(pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT); + + if (RT_LIKELY(uNewEip <= pVCpu->cpum.GstCtx.cs.u32Limit)) + pVCpu->cpum.GstCtx.rip = uNewEip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + + +/** + * Performs a near jump to the specified address. + * + * May raise a \#GP(0) if the new RIP is non-canonical or outside the code + * segment limit. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uNewRip The new RIP value. + */ +VBOXSTRICTRC iemRegRipJumpU64AndFinishClearningRF(PVMCPUCC pVCpu, uint64_t uNewRip) RT_NOEXCEPT +{ + Assert(pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT); + + if (RT_LIKELY(IEM_IS_CANONICAL(uNewRip))) + pVCpu->cpum.GstCtx.rip = uNewRip; + else + return iemRaiseGeneralProtectionFault0(pVCpu); + +#ifndef IEM_WITH_CODE_TLB + /* Flush the prefetch buffer. */ + pVCpu->iem.s.cbOpcode = IEM_GET_INSTR_LEN(pVCpu); +#endif + + /* + * Clear RF and finish the instruction (maybe raise #DB). + */ + return iemRegFinishClearingRF(pVCpu); +} + +/** @} */ + + +/** @name FPU access and helpers. + * + * @{ + */ + +/** + * Updates the x87.DS and FPUDP registers. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pFpuCtx The FPU context. + * @param iEffSeg The effective segment register. + * @param GCPtrEff The effective address relative to @a iEffSeg. + */ +DECLINLINE(void) iemFpuUpdateDP(PVMCPUCC pVCpu, PX86FXSTATE pFpuCtx, uint8_t iEffSeg, RTGCPTR GCPtrEff) +{ + RTSEL sel; + switch (iEffSeg) + { + case X86_SREG_DS: sel = pVCpu->cpum.GstCtx.ds.Sel; break; + case X86_SREG_SS: sel = pVCpu->cpum.GstCtx.ss.Sel; break; + case X86_SREG_CS: sel = pVCpu->cpum.GstCtx.cs.Sel; break; + case X86_SREG_ES: sel = pVCpu->cpum.GstCtx.es.Sel; break; + case X86_SREG_FS: sel = pVCpu->cpum.GstCtx.fs.Sel; break; + case X86_SREG_GS: sel = pVCpu->cpum.GstCtx.gs.Sel; break; + default: + AssertMsgFailed(("%d\n", iEffSeg)); + sel = pVCpu->cpum.GstCtx.ds.Sel; + } + /** @todo pFpuCtx->DS and FPUDP needs to be kept seperately. */ + if (IEM_IS_REAL_OR_V86_MODE(pVCpu)) + { + pFpuCtx->DS = 0; + pFpuCtx->FPUDP = (uint32_t)GCPtrEff + ((uint32_t)sel << 4); + } + else if (!IEM_IS_LONG_MODE(pVCpu)) + { + pFpuCtx->DS = sel; + pFpuCtx->FPUDP = GCPtrEff; + } + else + *(uint64_t *)&pFpuCtx->FPUDP = GCPtrEff; +} + + +/** + * Rotates the stack registers in the push direction. + * + * @param pFpuCtx The FPU context. + * @remarks This is a complete waste of time, but fxsave stores the registers in + * stack order. + */ +DECLINLINE(void) iemFpuRotateStackPush(PX86FXSTATE pFpuCtx) +{ + RTFLOAT80U r80Tmp = pFpuCtx->aRegs[7].r80; + pFpuCtx->aRegs[7].r80 = pFpuCtx->aRegs[6].r80; + pFpuCtx->aRegs[6].r80 = pFpuCtx->aRegs[5].r80; + pFpuCtx->aRegs[5].r80 = pFpuCtx->aRegs[4].r80; + pFpuCtx->aRegs[4].r80 = pFpuCtx->aRegs[3].r80; + pFpuCtx->aRegs[3].r80 = pFpuCtx->aRegs[2].r80; + pFpuCtx->aRegs[2].r80 = pFpuCtx->aRegs[1].r80; + pFpuCtx->aRegs[1].r80 = pFpuCtx->aRegs[0].r80; + pFpuCtx->aRegs[0].r80 = r80Tmp; +} + + +/** + * Rotates the stack registers in the pop direction. + * + * @param pFpuCtx The FPU context. + * @remarks This is a complete waste of time, but fxsave stores the registers in + * stack order. + */ +DECLINLINE(void) iemFpuRotateStackPop(PX86FXSTATE pFpuCtx) +{ + RTFLOAT80U r80Tmp = pFpuCtx->aRegs[0].r80; + pFpuCtx->aRegs[0].r80 = pFpuCtx->aRegs[1].r80; + pFpuCtx->aRegs[1].r80 = pFpuCtx->aRegs[2].r80; + pFpuCtx->aRegs[2].r80 = pFpuCtx->aRegs[3].r80; + pFpuCtx->aRegs[3].r80 = pFpuCtx->aRegs[4].r80; + pFpuCtx->aRegs[4].r80 = pFpuCtx->aRegs[5].r80; + pFpuCtx->aRegs[5].r80 = pFpuCtx->aRegs[6].r80; + pFpuCtx->aRegs[6].r80 = pFpuCtx->aRegs[7].r80; + pFpuCtx->aRegs[7].r80 = r80Tmp; +} + + +/** + * Updates FSW and pushes a FPU result onto the FPU stack if no pending + * exception prevents it. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The FPU operation result to push. + * @param pFpuCtx The FPU context. + */ +static void iemFpuMaybePushResult(PVMCPU pVCpu, PIEMFPURESULT pResult, PX86FXSTATE pFpuCtx) RT_NOEXCEPT +{ + /* Update FSW and bail if there are pending exceptions afterwards. */ + uint16_t fFsw = pFpuCtx->FSW & ~X86_FSW_C_MASK; + fFsw |= pResult->FSW & ~X86_FSW_TOP_MASK; + if ( (fFsw & (X86_FSW_IE | X86_FSW_ZE | X86_FSW_DE)) + & ~(pFpuCtx->FCW & (X86_FCW_IM | X86_FCW_ZM | X86_FCW_DM))) + { + if ((fFsw & X86_FSW_ES) && !(pFpuCtx->FCW & X86_FSW_ES)) + Log11(("iemFpuMaybePushResult: %04x:%08RX64: FSW %#x -> %#x\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW, fFsw)); + pFpuCtx->FSW = fFsw; + return; + } + + uint16_t iNewTop = (X86_FSW_TOP_GET(fFsw) + 7) & X86_FSW_TOP_SMASK; + if (!(pFpuCtx->FTW & RT_BIT(iNewTop))) + { + /* All is fine, push the actual value. */ + pFpuCtx->FTW |= RT_BIT(iNewTop); + pFpuCtx->aRegs[7].r80 = pResult->r80Result; + } + else if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked stack overflow, push QNaN. */ + fFsw |= X86_FSW_IE | X86_FSW_SF | X86_FSW_C1; + iemFpuStoreQNan(&pFpuCtx->aRegs[7].r80); + } + else + { + /* Raise stack overflow, don't push anything. */ + pFpuCtx->FSW |= pResult->FSW & ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF | X86_FSW_C1 | X86_FSW_B | X86_FSW_ES; + Log11(("iemFpuMaybePushResult: %04x:%08RX64: stack overflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + return; + } + + fFsw &= ~X86_FSW_TOP_MASK; + fFsw |= iNewTop << X86_FSW_TOP_SHIFT; + pFpuCtx->FSW = fFsw; + + iemFpuRotateStackPush(pFpuCtx); + RT_NOREF(pVCpu); +} + + +/** + * Stores a result in a FPU register and updates the FSW and FTW. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pFpuCtx The FPU context. + * @param pResult The result to store. + * @param iStReg Which FPU register to store it in. + */ +static void iemFpuStoreResultOnly(PVMCPU pVCpu, PX86FXSTATE pFpuCtx, PIEMFPURESULT pResult, uint8_t iStReg) RT_NOEXCEPT +{ + Assert(iStReg < 8); + uint16_t fNewFsw = pFpuCtx->FSW; + uint16_t const iReg = (X86_FSW_TOP_GET(fNewFsw) + iStReg) & X86_FSW_TOP_SMASK; + fNewFsw &= ~X86_FSW_C_MASK; + fNewFsw |= pResult->FSW & ~X86_FSW_TOP_MASK; + if ((fNewFsw & X86_FSW_ES) && !(pFpuCtx->FSW & X86_FSW_ES)) + Log11(("iemFpuStoreResultOnly: %04x:%08RX64: FSW %#x -> %#x\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW, fNewFsw)); + pFpuCtx->FSW = fNewFsw; + pFpuCtx->FTW |= RT_BIT(iReg); + pFpuCtx->aRegs[iStReg].r80 = pResult->r80Result; + RT_NOREF(pVCpu); +} + + +/** + * Only updates the FPU status word (FSW) with the result of the current + * instruction. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pFpuCtx The FPU context. + * @param u16FSW The FSW output of the current instruction. + */ +static void iemFpuUpdateFSWOnly(PVMCPU pVCpu, PX86FXSTATE pFpuCtx, uint16_t u16FSW) RT_NOEXCEPT +{ + uint16_t fNewFsw = pFpuCtx->FSW; + fNewFsw &= ~X86_FSW_C_MASK; + fNewFsw |= u16FSW & ~X86_FSW_TOP_MASK; + if ((fNewFsw & X86_FSW_ES) && !(pFpuCtx->FSW & X86_FSW_ES)) + Log11(("iemFpuStoreResultOnly: %04x:%08RX64: FSW %#x -> %#x\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW, fNewFsw)); + pFpuCtx->FSW = fNewFsw; + RT_NOREF(pVCpu); +} + + +/** + * Pops one item off the FPU stack if no pending exception prevents it. + * + * @param pFpuCtx The FPU context. + */ +static void iemFpuMaybePopOne(PX86FXSTATE pFpuCtx) RT_NOEXCEPT +{ + /* Check pending exceptions. */ + uint16_t uFSW = pFpuCtx->FSW; + if ( (pFpuCtx->FSW & (X86_FSW_IE | X86_FSW_ZE | X86_FSW_DE)) + & ~(pFpuCtx->FCW & (X86_FCW_IM | X86_FCW_ZM | X86_FCW_DM))) + return; + + /* TOP--. */ + uint16_t iOldTop = uFSW & X86_FSW_TOP_MASK; + uFSW &= ~X86_FSW_TOP_MASK; + uFSW |= (iOldTop + (UINT16_C(9) << X86_FSW_TOP_SHIFT)) & X86_FSW_TOP_MASK; + pFpuCtx->FSW = uFSW; + + /* Mark the previous ST0 as empty. */ + iOldTop >>= X86_FSW_TOP_SHIFT; + pFpuCtx->FTW &= ~RT_BIT(iOldTop); + + /* Rotate the registers. */ + iemFpuRotateStackPop(pFpuCtx); +} + + +/** + * Pushes a FPU result onto the FPU stack if no pending exception prevents it. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The FPU operation result to push. + */ +void iemFpuPushResult(PVMCPUCC pVCpu, PIEMFPURESULT pResult) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuMaybePushResult(pVCpu, pResult, pFpuCtx); +} + + +/** + * Pushes a FPU result onto the FPU stack if no pending exception prevents it, + * and sets FPUDP and FPUDS. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The FPU operation result to push. + * @param iEffSeg The effective segment register. + * @param GCPtrEff The effective address relative to @a iEffSeg. + */ +void iemFpuPushResultWithMemOp(PVMCPUCC pVCpu, PIEMFPURESULT pResult, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuMaybePushResult(pVCpu, pResult, pFpuCtx); +} + + +/** + * Replace ST0 with the first value and push the second onto the FPU stack, + * unless a pending exception prevents it. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The FPU operation result to store and push. + */ +void iemFpuPushResultTwo(PVMCPUCC pVCpu, PIEMFPURESULTTWO pResult) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + + /* Update FSW and bail if there are pending exceptions afterwards. */ + uint16_t fFsw = pFpuCtx->FSW & ~X86_FSW_C_MASK; + fFsw |= pResult->FSW & ~X86_FSW_TOP_MASK; + if ( (fFsw & (X86_FSW_IE | X86_FSW_ZE | X86_FSW_DE)) + & ~(pFpuCtx->FCW & (X86_FCW_IM | X86_FCW_ZM | X86_FCW_DM))) + { + if ((fFsw & X86_FSW_ES) && !(pFpuCtx->FSW & X86_FSW_ES)) + Log11(("iemFpuPushResultTwo: %04x:%08RX64: FSW %#x -> %#x\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW, fFsw)); + pFpuCtx->FSW = fFsw; + return; + } + + uint16_t iNewTop = (X86_FSW_TOP_GET(fFsw) + 7) & X86_FSW_TOP_SMASK; + if (!(pFpuCtx->FTW & RT_BIT(iNewTop))) + { + /* All is fine, push the actual value. */ + pFpuCtx->FTW |= RT_BIT(iNewTop); + pFpuCtx->aRegs[0].r80 = pResult->r80Result1; + pFpuCtx->aRegs[7].r80 = pResult->r80Result2; + } + else if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked stack overflow, push QNaN. */ + fFsw |= X86_FSW_IE | X86_FSW_SF | X86_FSW_C1; + iemFpuStoreQNan(&pFpuCtx->aRegs[0].r80); + iemFpuStoreQNan(&pFpuCtx->aRegs[7].r80); + } + else + { + /* Raise stack overflow, don't push anything. */ + pFpuCtx->FSW |= pResult->FSW & ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF | X86_FSW_C1 | X86_FSW_B | X86_FSW_ES; + Log11(("iemFpuPushResultTwo: %04x:%08RX64: stack overflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + return; + } + + fFsw &= ~X86_FSW_TOP_MASK; + fFsw |= iNewTop << X86_FSW_TOP_SHIFT; + pFpuCtx->FSW = fFsw; + + iemFpuRotateStackPush(pFpuCtx); +} + + +/** + * Stores a result in a FPU register, updates the FSW, FTW, FPUIP, FPUCS, and + * FOP. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The result to store. + * @param iStReg Which FPU register to store it in. + */ +void iemFpuStoreResult(PVMCPUCC pVCpu, PIEMFPURESULT pResult, uint8_t iStReg) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStoreResultOnly(pVCpu, pFpuCtx, pResult, iStReg); +} + + +/** + * Stores a result in a FPU register, updates the FSW, FTW, FPUIP, FPUCS, and + * FOP, and then pops the stack. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The result to store. + * @param iStReg Which FPU register to store it in. + */ +void iemFpuStoreResultThenPop(PVMCPUCC pVCpu, PIEMFPURESULT pResult, uint8_t iStReg) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStoreResultOnly(pVCpu, pFpuCtx, pResult, iStReg); + iemFpuMaybePopOne(pFpuCtx); +} + + +/** + * Stores a result in a FPU register, updates the FSW, FTW, FPUIP, FPUCS, FOP, + * FPUDP, and FPUDS. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The result to store. + * @param iStReg Which FPU register to store it in. + * @param iEffSeg The effective memory operand selector register. + * @param GCPtrEff The effective memory operand offset. + */ +void iemFpuStoreResultWithMemOp(PVMCPUCC pVCpu, PIEMFPURESULT pResult, uint8_t iStReg, + uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStoreResultOnly(pVCpu, pFpuCtx, pResult, iStReg); +} + + +/** + * Stores a result in a FPU register, updates the FSW, FTW, FPUIP, FPUCS, FOP, + * FPUDP, and FPUDS, and then pops the stack. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The result to store. + * @param iStReg Which FPU register to store it in. + * @param iEffSeg The effective memory operand selector register. + * @param GCPtrEff The effective memory operand offset. + */ +void iemFpuStoreResultWithMemOpThenPop(PVMCPUCC pVCpu, PIEMFPURESULT pResult, + uint8_t iStReg, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStoreResultOnly(pVCpu, pFpuCtx, pResult, iStReg); + iemFpuMaybePopOne(pFpuCtx); +} + + +/** + * Updates the FOP, FPUIP, and FPUCS. For FNOP. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +void iemFpuUpdateOpcodeAndIp(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); +} + + +/** + * Updates the FSW, FOP, FPUIP, and FPUCS. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16FSW The FSW from the current instruction. + */ +void iemFpuUpdateFSW(PVMCPUCC pVCpu, uint16_t u16FSW) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuUpdateFSWOnly(pVCpu, pFpuCtx, u16FSW); +} + + +/** + * Updates the FSW, FOP, FPUIP, and FPUCS, then pops the stack. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16FSW The FSW from the current instruction. + */ +void iemFpuUpdateFSWThenPop(PVMCPUCC pVCpu, uint16_t u16FSW) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuUpdateFSWOnly(pVCpu, pFpuCtx, u16FSW); + iemFpuMaybePopOne(pFpuCtx); +} + + +/** + * Updates the FSW, FOP, FPUIP, FPUCS, FPUDP, and FPUDS. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16FSW The FSW from the current instruction. + * @param iEffSeg The effective memory operand selector register. + * @param GCPtrEff The effective memory operand offset. + */ +void iemFpuUpdateFSWWithMemOp(PVMCPUCC pVCpu, uint16_t u16FSW, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuUpdateFSWOnly(pVCpu, pFpuCtx, u16FSW); +} + + +/** + * Updates the FSW, FOP, FPUIP, and FPUCS, then pops the stack twice. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16FSW The FSW from the current instruction. + */ +void iemFpuUpdateFSWThenPopPop(PVMCPUCC pVCpu, uint16_t u16FSW) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuUpdateFSWOnly(pVCpu, pFpuCtx, u16FSW); + iemFpuMaybePopOne(pFpuCtx); + iemFpuMaybePopOne(pFpuCtx); +} + + +/** + * Updates the FSW, FOP, FPUIP, FPUCS, FPUDP, and FPUDS, then pops the stack. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16FSW The FSW from the current instruction. + * @param iEffSeg The effective memory operand selector register. + * @param GCPtrEff The effective memory operand offset. + */ +void iemFpuUpdateFSWWithMemOpThenPop(PVMCPUCC pVCpu, uint16_t u16FSW, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuUpdateFSWOnly(pVCpu, pFpuCtx, u16FSW); + iemFpuMaybePopOne(pFpuCtx); +} + + +/** + * Worker routine for raising an FPU stack underflow exception. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pFpuCtx The FPU context. + * @param iStReg The stack register being accessed. + */ +static void iemFpuStackUnderflowOnly(PVMCPU pVCpu, PX86FXSTATE pFpuCtx, uint8_t iStReg) +{ + Assert(iStReg < 8 || iStReg == UINT8_MAX); + if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked underflow. */ + pFpuCtx->FSW &= ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF; + uint16_t iReg = (X86_FSW_TOP_GET(pFpuCtx->FSW) + iStReg) & X86_FSW_TOP_SMASK; + if (iStReg != UINT8_MAX) + { + pFpuCtx->FTW |= RT_BIT(iReg); + iemFpuStoreQNan(&pFpuCtx->aRegs[iStReg].r80); + } + } + else + { + pFpuCtx->FSW &= ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF | X86_FSW_ES | X86_FSW_B; + Log11(("iemFpuStackUnderflowOnly: %04x:%08RX64: underflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + } + RT_NOREF(pVCpu); +} + + +/** + * Raises a FPU stack underflow exception. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iStReg The destination register that should be loaded + * with QNaN if \#IS is not masked. Specify + * UINT8_MAX if none (like for fcom). + */ +void iemFpuStackUnderflow(PVMCPUCC pVCpu, uint8_t iStReg) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackUnderflowOnly(pVCpu, pFpuCtx, iStReg); +} + + +void iemFpuStackUnderflowWithMemOp(PVMCPUCC pVCpu, uint8_t iStReg, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackUnderflowOnly(pVCpu, pFpuCtx, iStReg); +} + + +void iemFpuStackUnderflowThenPop(PVMCPUCC pVCpu, uint8_t iStReg) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackUnderflowOnly(pVCpu, pFpuCtx, iStReg); + iemFpuMaybePopOne(pFpuCtx); +} + + +void iemFpuStackUnderflowWithMemOpThenPop(PVMCPUCC pVCpu, uint8_t iStReg, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackUnderflowOnly(pVCpu, pFpuCtx, iStReg); + iemFpuMaybePopOne(pFpuCtx); +} + + +void iemFpuStackUnderflowThenPopPop(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackUnderflowOnly(pVCpu, pFpuCtx, UINT8_MAX); + iemFpuMaybePopOne(pFpuCtx); + iemFpuMaybePopOne(pFpuCtx); +} + + +void iemFpuStackPushUnderflow(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + + if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked overflow - Push QNaN. */ + uint16_t iNewTop = (X86_FSW_TOP_GET(pFpuCtx->FSW) + 7) & X86_FSW_TOP_SMASK; + pFpuCtx->FSW &= ~(X86_FSW_TOP_MASK | X86_FSW_C_MASK); + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF; + pFpuCtx->FSW |= iNewTop << X86_FSW_TOP_SHIFT; + pFpuCtx->FTW |= RT_BIT(iNewTop); + iemFpuStoreQNan(&pFpuCtx->aRegs[7].r80); + iemFpuRotateStackPush(pFpuCtx); + } + else + { + /* Exception pending - don't change TOP or the register stack. */ + pFpuCtx->FSW &= ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF | X86_FSW_ES | X86_FSW_B; + Log11(("iemFpuStackPushUnderflow: %04x:%08RX64: underflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + } +} + + +void iemFpuStackPushUnderflowTwo(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + + if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked overflow - Push QNaN. */ + uint16_t iNewTop = (X86_FSW_TOP_GET(pFpuCtx->FSW) + 7) & X86_FSW_TOP_SMASK; + pFpuCtx->FSW &= ~(X86_FSW_TOP_MASK | X86_FSW_C_MASK); + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF; + pFpuCtx->FSW |= iNewTop << X86_FSW_TOP_SHIFT; + pFpuCtx->FTW |= RT_BIT(iNewTop); + iemFpuStoreQNan(&pFpuCtx->aRegs[0].r80); + iemFpuStoreQNan(&pFpuCtx->aRegs[7].r80); + iemFpuRotateStackPush(pFpuCtx); + } + else + { + /* Exception pending - don't change TOP or the register stack. */ + pFpuCtx->FSW &= ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_IE | X86_FSW_SF | X86_FSW_ES | X86_FSW_B; + Log11(("iemFpuStackPushUnderflowTwo: %04x:%08RX64: underflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + } +} + + +/** + * Worker routine for raising an FPU stack overflow exception on a push. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pFpuCtx The FPU context. + */ +static void iemFpuStackPushOverflowOnly(PVMCPU pVCpu, PX86FXSTATE pFpuCtx) RT_NOEXCEPT +{ + if (pFpuCtx->FCW & X86_FCW_IM) + { + /* Masked overflow. */ + uint16_t iNewTop = (X86_FSW_TOP_GET(pFpuCtx->FSW) + 7) & X86_FSW_TOP_SMASK; + pFpuCtx->FSW &= ~(X86_FSW_TOP_MASK | X86_FSW_C_MASK); + pFpuCtx->FSW |= X86_FSW_C1 | X86_FSW_IE | X86_FSW_SF; + pFpuCtx->FSW |= iNewTop << X86_FSW_TOP_SHIFT; + pFpuCtx->FTW |= RT_BIT(iNewTop); + iemFpuStoreQNan(&pFpuCtx->aRegs[7].r80); + iemFpuRotateStackPush(pFpuCtx); + } + else + { + /* Exception pending - don't change TOP or the register stack. */ + pFpuCtx->FSW &= ~X86_FSW_C_MASK; + pFpuCtx->FSW |= X86_FSW_C1 | X86_FSW_IE | X86_FSW_SF | X86_FSW_ES | X86_FSW_B; + Log11(("iemFpuStackPushOverflowOnly: %04x:%08RX64: overflow (FSW=%#x)\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pFpuCtx->FSW)); + } + RT_NOREF(pVCpu); +} + + +/** + * Raises a FPU stack overflow exception on a push. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +void iemFpuStackPushOverflow(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackPushOverflowOnly(pVCpu, pFpuCtx); +} + + +/** + * Raises a FPU stack overflow exception on a push with a memory operand. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iEffSeg The effective memory operand selector register. + * @param GCPtrEff The effective memory operand offset. + */ +void iemFpuStackPushOverflowWithMemOp(PVMCPUCC pVCpu, uint8_t iEffSeg, RTGCPTR GCPtrEff) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + iemFpuUpdateDP(pVCpu, pFpuCtx, iEffSeg, GCPtrEff); + iemFpuUpdateOpcodeAndIpWorker(pVCpu, pFpuCtx); + iemFpuStackPushOverflowOnly(pVCpu, pFpuCtx); +} + +/** @} */ + + +/** @name SSE+AVX SIMD access and helpers. + * + * @{ + */ +/** + * Stores a result in a SIMD XMM register, updates the MXCSR. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pResult The result to store. + * @param iXmmReg Which SIMD XMM register to store the result in. + */ +void iemSseStoreResult(PVMCPUCC pVCpu, PCIEMSSERESULT pResult, uint8_t iXmmReg) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + pFpuCtx->MXCSR |= pResult->MXCSR & X86_MXCSR_XCPT_FLAGS; + + /* The result is only updated if there is no unmasked exception pending. */ + if (( ~((pFpuCtx->MXCSR & X86_MXCSR_XCPT_MASK) >> X86_MXCSR_XCPT_MASK_SHIFT) + & (pFpuCtx->MXCSR & X86_MXCSR_XCPT_FLAGS)) == 0) + pVCpu->cpum.GstCtx.XState.x87.aXMM[iXmmReg] = pResult->uResult; +} + + +/** + * Updates the MXCSR. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param fMxcsr The new MXCSR value. + */ +void iemSseUpdateMxcsr(PVMCPUCC pVCpu, uint32_t fMxcsr) RT_NOEXCEPT +{ + PX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + pFpuCtx->MXCSR |= fMxcsr & X86_MXCSR_XCPT_FLAGS; +} +/** @} */ + + +/** @name Memory access. + * + * @{ + */ + + +/** + * Updates the IEMCPU::cbWritten counter if applicable. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param fAccess The access being accounted for. + * @param cbMem The access size. + */ +DECL_FORCE_INLINE(void) iemMemUpdateWrittenCounter(PVMCPUCC pVCpu, uint32_t fAccess, size_t cbMem) +{ + if ( (fAccess & (IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_WRITE)) == (IEM_ACCESS_WHAT_STACK | IEM_ACCESS_TYPE_WRITE) + || (fAccess & (IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_WRITE)) == (IEM_ACCESS_WHAT_DATA | IEM_ACCESS_TYPE_WRITE) ) + pVCpu->iem.s.cbWritten += (uint32_t)cbMem; +} + + +/** + * Applies the segment limit, base and attributes. + * + * This may raise a \#GP or \#SS. + * + * @returns VBox strict status code. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param fAccess The kind of access which is being performed. + * @param iSegReg The index of the segment register to apply. + * This is UINT8_MAX if none (for IDT, GDT, LDT, + * TSS, ++). + * @param cbMem The access size. + * @param pGCPtrMem Pointer to the guest memory address to apply + * segmentation to. Input and output parameter. + */ +VBOXSTRICTRC iemMemApplySegment(PVMCPUCC pVCpu, uint32_t fAccess, uint8_t iSegReg, size_t cbMem, PRTGCPTR pGCPtrMem) RT_NOEXCEPT +{ + if (iSegReg == UINT8_MAX) + return VINF_SUCCESS; + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_SREG_FROM_IDX(iSegReg)); + PCPUMSELREGHID pSel = iemSRegGetHid(pVCpu, iSegReg); + switch (pVCpu->iem.s.enmCpuMode) + { + case IEMMODE_16BIT: + case IEMMODE_32BIT: + { + RTGCPTR32 GCPtrFirst32 = (RTGCPTR32)*pGCPtrMem; + RTGCPTR32 GCPtrLast32 = GCPtrFirst32 + (uint32_t)cbMem - 1; + + if ( pSel->Attr.n.u1Present + && !pSel->Attr.n.u1Unusable) + { + Assert(pSel->Attr.n.u1DescType); + if (!(pSel->Attr.n.u4Type & X86_SEL_TYPE_CODE)) + { + if ( (fAccess & IEM_ACCESS_TYPE_WRITE) + && !(pSel->Attr.n.u4Type & X86_SEL_TYPE_WRITE) ) + return iemRaiseSelectorInvalidAccess(pVCpu, iSegReg, fAccess); + + if (!IEM_IS_REAL_OR_V86_MODE(pVCpu)) + { + /** @todo CPL check. */ + } + + /* + * There are two kinds of data selectors, normal and expand down. + */ + if (!(pSel->Attr.n.u4Type & X86_SEL_TYPE_DOWN)) + { + if ( GCPtrFirst32 > pSel->u32Limit + || GCPtrLast32 > pSel->u32Limit) /* yes, in real mode too (since 80286). */ + return iemRaiseSelectorBounds(pVCpu, iSegReg, fAccess); + } + else + { + /* + * The upper boundary is defined by the B bit, not the G bit! + */ + if ( GCPtrFirst32 < pSel->u32Limit + UINT32_C(1) + || GCPtrLast32 > (pSel->Attr.n.u1DefBig ? UINT32_MAX : UINT32_C(0xffff))) + return iemRaiseSelectorBounds(pVCpu, iSegReg, fAccess); + } + *pGCPtrMem = GCPtrFirst32 += (uint32_t)pSel->u64Base; + } + else + { + /* + * Code selector and usually be used to read thru, writing is + * only permitted in real and V8086 mode. + */ + if ( ( (fAccess & IEM_ACCESS_TYPE_WRITE) + || ( (fAccess & IEM_ACCESS_TYPE_READ) + && !(pSel->Attr.n.u4Type & X86_SEL_TYPE_READ)) ) + && !IEM_IS_REAL_OR_V86_MODE(pVCpu) ) + return iemRaiseSelectorInvalidAccess(pVCpu, iSegReg, fAccess); + + if ( GCPtrFirst32 > pSel->u32Limit + || GCPtrLast32 > pSel->u32Limit) /* yes, in real mode too (since 80286). */ + return iemRaiseSelectorBounds(pVCpu, iSegReg, fAccess); + + if (!IEM_IS_REAL_OR_V86_MODE(pVCpu)) + { + /** @todo CPL check. */ + } + + *pGCPtrMem = GCPtrFirst32 += (uint32_t)pSel->u64Base; + } + } + else + return iemRaiseGeneralProtectionFault0(pVCpu); + return VINF_SUCCESS; + } + + case IEMMODE_64BIT: + { + RTGCPTR GCPtrMem = *pGCPtrMem; + if (iSegReg == X86_SREG_GS || iSegReg == X86_SREG_FS) + *pGCPtrMem = GCPtrMem + pSel->u64Base; + + Assert(cbMem >= 1); + if (RT_LIKELY(X86_IS_CANONICAL(GCPtrMem) && X86_IS_CANONICAL(GCPtrMem + cbMem - 1))) + return VINF_SUCCESS; + /** @todo We should probably raise \#SS(0) here if segment is SS; see AMD spec. + * 4.12.2 "Data Limit Checks in 64-bit Mode". */ + return iemRaiseGeneralProtectionFault0(pVCpu); + } + + default: + AssertFailedReturn(VERR_IEM_IPE_7); + } +} + + +/** + * Translates a virtual address to a physical physical address and checks if we + * can access the page as specified. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param GCPtrMem The virtual address. + * @param cbAccess The access size, for raising \#PF correctly for + * FXSAVE and such. + * @param fAccess The intended access. + * @param pGCPhysMem Where to return the physical address. + */ +VBOXSTRICTRC iemMemPageTranslateAndCheckAccess(PVMCPUCC pVCpu, RTGCPTR GCPtrMem, uint32_t cbAccess, + uint32_t fAccess, PRTGCPHYS pGCPhysMem) RT_NOEXCEPT +{ + /** @todo Need a different PGM interface here. We're currently using + * generic / REM interfaces. this won't cut it for R0. */ + /** @todo If/when PGM handles paged real-mode, we can remove the hack in + * iemSvmWorldSwitch/iemVmxWorldSwitch to work around raising a page-fault + * here. */ + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrMem, &Walk); + if (RT_FAILURE(rc)) + { + Log(("iemMemPageTranslateAndCheckAccess: GCPtrMem=%RGv - failed to fetch page -> #PF\n", GCPtrMem)); + /** @todo Check unassigned memory in unpaged mode. */ + /** @todo Reserved bits in page tables. Requires new PGM interface. */ +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PHYS_ADDR, 0 /* cbInstr */); +#endif + *pGCPhysMem = NIL_RTGCPHYS; + return iemRaisePageFault(pVCpu, GCPtrMem, cbAccess, fAccess, rc); + } + + /* If the page is writable and does not have the no-exec bit set, all + access is allowed. Otherwise we'll have to check more carefully... */ + if ((Walk.fEffective & (X86_PTE_RW | X86_PTE_US | X86_PTE_PAE_NX)) != (X86_PTE_RW | X86_PTE_US)) + { + /* Write to read only memory? */ + if ( (fAccess & IEM_ACCESS_TYPE_WRITE) + && !(Walk.fEffective & X86_PTE_RW) + && ( ( pVCpu->iem.s.uCpl == 3 + && !(fAccess & IEM_ACCESS_WHAT_SYS)) + || (pVCpu->cpum.GstCtx.cr0 & X86_CR0_WP))) + { + Log(("iemMemPageTranslateAndCheckAccess: GCPtrMem=%RGv - read-only page -> #PF\n", GCPtrMem)); + *pGCPhysMem = NIL_RTGCPHYS; +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrMem, cbAccess, fAccess & ~IEM_ACCESS_TYPE_READ, VERR_ACCESS_DENIED); + } + + /* Kernel memory accessed by userland? */ + if ( !(Walk.fEffective & X86_PTE_US) + && pVCpu->iem.s.uCpl == 3 + && !(fAccess & IEM_ACCESS_WHAT_SYS)) + { + Log(("iemMemPageTranslateAndCheckAccess: GCPtrMem=%RGv - user access to kernel page -> #PF\n", GCPtrMem)); + *pGCPhysMem = NIL_RTGCPHYS; +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrMem, cbAccess, fAccess, VERR_ACCESS_DENIED); + } + + /* Executing non-executable memory? */ + if ( (fAccess & IEM_ACCESS_TYPE_EXEC) + && (Walk.fEffective & X86_PTE_PAE_NX) + && (pVCpu->cpum.GstCtx.msrEFER & MSR_K6_EFER_NXE) ) + { + Log(("iemMemPageTranslateAndCheckAccess: GCPtrMem=%RGv - NX -> #PF\n", GCPtrMem)); + *pGCPhysMem = NIL_RTGCPHYS; +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +#endif + return iemRaisePageFault(pVCpu, GCPtrMem, cbAccess, fAccess & ~(IEM_ACCESS_TYPE_READ | IEM_ACCESS_TYPE_WRITE), + VERR_ACCESS_DENIED); + } + } + + /* + * Set the dirty / access flags. + * ASSUMES this is set when the address is translated rather than on committ... + */ + /** @todo testcase: check when A and D bits are actually set by the CPU. */ + uint32_t fAccessedDirty = fAccess & IEM_ACCESS_TYPE_WRITE ? X86_PTE_D | X86_PTE_A : X86_PTE_A; + if ((Walk.fEffective & fAccessedDirty) != fAccessedDirty) + { + int rc2 = PGMGstModifyPage(pVCpu, GCPtrMem, 1, fAccessedDirty, ~(uint64_t)fAccessedDirty); + AssertRC(rc2); + /** @todo Nested VMX: Accessed/dirty bit currently not supported, asserted below. */ + Assert(!(CPUMGetGuestIa32VmxEptVpidCap(pVCpu) & VMX_BF_EPT_VPID_CAP_ACCESS_DIRTY_MASK)); + } + + RTGCPHYS const GCPhys = Walk.GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK); + *pGCPhysMem = GCPhys; + return VINF_SUCCESS; +} + + +/** + * Looks up a memory mapping entry. + * + * @returns The mapping index (positive) or VERR_NOT_FOUND (negative). + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The memory address. + * @param fAccess The access to. + */ +DECLINLINE(int) iemMapLookup(PVMCPUCC pVCpu, void *pvMem, uint32_t fAccess) +{ + Assert(pVCpu->iem.s.cActiveMappings <= RT_ELEMENTS(pVCpu->iem.s.aMemMappings)); + fAccess &= IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_MASK; + if ( pVCpu->iem.s.aMemMappings[0].pv == pvMem + && (pVCpu->iem.s.aMemMappings[0].fAccess & (IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_MASK)) == fAccess) + return 0; + if ( pVCpu->iem.s.aMemMappings[1].pv == pvMem + && (pVCpu->iem.s.aMemMappings[1].fAccess & (IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_MASK)) == fAccess) + return 1; + if ( pVCpu->iem.s.aMemMappings[2].pv == pvMem + && (pVCpu->iem.s.aMemMappings[2].fAccess & (IEM_ACCESS_WHAT_MASK | IEM_ACCESS_TYPE_MASK)) == fAccess) + return 2; + return VERR_NOT_FOUND; +} + + +/** + * Finds a free memmap entry when using iNextMapping doesn't work. + * + * @returns Memory mapping index, 1024 on failure. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +static unsigned iemMemMapFindFree(PVMCPUCC pVCpu) +{ + /* + * The easy case. + */ + if (pVCpu->iem.s.cActiveMappings == 0) + { + pVCpu->iem.s.iNextMapping = 1; + return 0; + } + + /* There should be enough mappings for all instructions. */ + AssertReturn(pVCpu->iem.s.cActiveMappings < RT_ELEMENTS(pVCpu->iem.s.aMemMappings), 1024); + + for (unsigned i = 0; i < RT_ELEMENTS(pVCpu->iem.s.aMemMappings); i++) + if (pVCpu->iem.s.aMemMappings[i].fAccess == IEM_ACCESS_INVALID) + return i; + + AssertFailedReturn(1024); +} + + +/** + * Commits a bounce buffer that needs writing back and unmaps it. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iMemMap The index of the buffer to commit. + * @param fPostponeFail Whether we can postpone writer failures to ring-3. + * Always false in ring-3, obviously. + */ +static VBOXSTRICTRC iemMemBounceBufferCommitAndUnmap(PVMCPUCC pVCpu, unsigned iMemMap, bool fPostponeFail) +{ + Assert(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_BOUNCE_BUFFERED); + Assert(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_TYPE_WRITE); +#ifdef IN_RING3 + Assert(!fPostponeFail); + RT_NOREF_PV(fPostponeFail); +#endif + + /* + * Do the writing. + */ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + if (!pVCpu->iem.s.aMemBbMappings[iMemMap].fUnassigned) + { + uint16_t const cbFirst = pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst; + uint16_t const cbSecond = pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond; + uint8_t const *pbBuf = &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0]; + if (!pVCpu->iem.s.fBypassHandlers) + { + /* + * Carefully and efficiently dealing with access handler return + * codes make this a little bloated. + */ + VBOXSTRICTRC rcStrict = PGMPhysWrite(pVM, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, + pbBuf, + cbFirst, + PGMACCESSORIGIN_IEM); + if (rcStrict == VINF_SUCCESS) + { + if (cbSecond) + { + rcStrict = PGMPhysWrite(pVM, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, + pbBuf + cbFirst, + cbSecond, + PGMACCESSORIGIN_IEM); + if (rcStrict == VINF_SUCCESS) + { /* nothing */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict) )); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } +#ifndef IN_RING3 + else if (fPostponeFail) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc (postponed)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict) )); + pVCpu->iem.s.aMemMappings[iMemMap].fAccess |= IEM_ACCESS_PENDING_R3_WRITE_2ND; + VMCPU_FF_SET(pVCpu, VMCPU_FF_IEM); + return iemSetPassUpStatus(pVCpu, rcStrict); + } +#endif + else + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc (!!)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + } + } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + if (!cbSecond) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x %Rrc\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, VBOXSTRICTRC_VAL(rcStrict) )); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } + else + { + VBOXSTRICTRC rcStrict2 = PGMPhysWrite(pVM, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, + pbBuf + cbFirst, + cbSecond, + PGMACCESSORIGIN_IEM); + if (rcStrict2 == VINF_SUCCESS) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x %Rrc GCPhysSecond=%RGp/%#x\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, VBOXSTRICTRC_VAL(rcStrict), + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond)); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict2)) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x %Rrc GCPhysSecond=%RGp/%#x %Rrc\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, VBOXSTRICTRC_VAL(rcStrict), + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict2) )); + PGM_PHYS_RW_DO_UPDATE_STRICT_RC(rcStrict, rcStrict2); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } +#ifndef IN_RING3 + else if (fPostponeFail) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc (postponed)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict) )); + pVCpu->iem.s.aMemMappings[iMemMap].fAccess |= IEM_ACCESS_PENDING_R3_WRITE_2ND; + VMCPU_FF_SET(pVCpu, VMCPU_FF_IEM); + return iemSetPassUpStatus(pVCpu, rcStrict); + } +#endif + else + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x %Rrc GCPhysSecond=%RGp/%#x %Rrc (!!)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, VBOXSTRICTRC_VAL(rcStrict), + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict2) )); + return rcStrict2; + } + } + } +#ifndef IN_RING3 + else if (fPostponeFail) + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc (postponed)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, VBOXSTRICTRC_VAL(rcStrict) )); + if (!cbSecond) + pVCpu->iem.s.aMemMappings[iMemMap].fAccess |= IEM_ACCESS_PENDING_R3_WRITE_1ST; + else + pVCpu->iem.s.aMemMappings[iMemMap].fAccess |= IEM_ACCESS_PENDING_R3_WRITE_1ST | IEM_ACCESS_PENDING_R3_WRITE_2ND; + VMCPU_FF_SET(pVCpu, VMCPU_FF_IEM); + return iemSetPassUpStatus(pVCpu, rcStrict); + } +#endif + else + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysWrite GCPhysFirst=%RGp/%#x %Rrc [GCPhysSecond=%RGp/%#x] (!!)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, VBOXSTRICTRC_VAL(rcStrict), + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond)); + return rcStrict; + } + } + else + { + /* + * No access handlers, much simpler. + */ + int rc = PGMPhysSimpleWriteGCPhys(pVM, pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, pbBuf, cbFirst); + if (RT_SUCCESS(rc)) + { + if (cbSecond) + { + rc = PGMPhysSimpleWriteGCPhys(pVM, pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, pbBuf + cbFirst, cbSecond); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysSimpleWriteGCPhys GCPhysFirst=%RGp/%#x GCPhysSecond=%RGp/%#x %Rrc (!!)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, rc)); + return rc; + } + } + } + else + { + Log(("iemMemBounceBufferCommitAndUnmap: PGMPhysSimpleWriteGCPhys GCPhysFirst=%RGp/%#x %Rrc [GCPhysSecond=%RGp/%#x] (!!)\n", + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, rc, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond)); + return rc; + } + } + } + +#if defined(IEM_LOG_MEMORY_WRITES) + Log(("IEM Wrote %RGp: %.*Rhxs\n", pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, + RT_MAX(RT_MIN(pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst, 64), 1), &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0])); + if (pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond) + Log(("IEM Wrote %RGp: %.*Rhxs [2nd page]\n", pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, + RT_MIN(pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond, 64), + &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst])); + + size_t cbWrote = pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst + pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond; + g_cbIemWrote = cbWrote; + memcpy(g_abIemWrote, &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0], RT_MIN(cbWrote, sizeof(g_abIemWrote))); +#endif + + /* + * Free the mapping entry. + */ + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + Assert(pVCpu->iem.s.cActiveMappings != 0); + pVCpu->iem.s.cActiveMappings--; + return VINF_SUCCESS; +} + + +/** + * iemMemMap worker that deals with a request crossing pages. + */ +static VBOXSTRICTRC +iemMemBounceBufferMapCrossPage(PVMCPUCC pVCpu, int iMemMap, void **ppvMem, size_t cbMem, RTGCPTR GCPtrFirst, uint32_t fAccess) +{ + Assert(cbMem <= GUEST_PAGE_SIZE); + + /* + * Do the address translations. + */ + uint32_t const cbFirstPage = GUEST_PAGE_SIZE - (uint32_t)(GCPtrFirst & GUEST_PAGE_OFFSET_MASK); + RTGCPHYS GCPhysFirst; + VBOXSTRICTRC rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, GCPtrFirst, cbFirstPage, fAccess, &GCPhysFirst); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + Assert((GCPhysFirst & GUEST_PAGE_OFFSET_MASK) == (GCPtrFirst & GUEST_PAGE_OFFSET_MASK)); + + uint32_t const cbSecondPage = (uint32_t)cbMem - cbFirstPage; + RTGCPHYS GCPhysSecond; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, (GCPtrFirst + (cbMem - 1)) & ~(RTGCPTR)GUEST_PAGE_OFFSET_MASK, + cbSecondPage, fAccess, &GCPhysSecond); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + Assert((GCPhysSecond & GUEST_PAGE_OFFSET_MASK) == 0); + GCPhysSecond &= ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; /** @todo why? */ + + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Read in the current memory content if it's a read, execute or partial + * write access. + */ + uint8_t * const pbBuf = &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0]; + + if (fAccess & (IEM_ACCESS_TYPE_READ | IEM_ACCESS_TYPE_EXEC | IEM_ACCESS_PARTIAL_WRITE)) + { + if (!pVCpu->iem.s.fBypassHandlers) + { + /* + * Must carefully deal with access handler status codes here, + * makes the code a bit bloated. + */ + rcStrict = PGMPhysRead(pVM, GCPhysFirst, pbBuf, cbFirstPage, PGMACCESSORIGIN_IEM); + if (rcStrict == VINF_SUCCESS) + { + rcStrict = PGMPhysRead(pVM, GCPhysSecond, pbBuf + cbFirstPage, cbSecondPage, PGMACCESSORIGIN_IEM); + if (rcStrict == VINF_SUCCESS) + { /*likely */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysRead GCPhysSecond=%RGp rcStrict2=%Rrc (!!)\n", + GCPhysSecond, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + { + VBOXSTRICTRC rcStrict2 = PGMPhysRead(pVM, GCPhysSecond, pbBuf + cbFirstPage, cbSecondPage, PGMACCESSORIGIN_IEM); + if (PGM_PHYS_RW_IS_SUCCESS(rcStrict2)) + { + PGM_PHYS_RW_DO_UPDATE_STRICT_RC(rcStrict, rcStrict2); + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + } + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysRead GCPhysSecond=%RGp rcStrict2=%Rrc (rcStrict=%Rrc) (!!)\n", + GCPhysSecond, VBOXSTRICTRC_VAL(rcStrict2), VBOXSTRICTRC_VAL(rcStrict2) )); + return rcStrict2; + } + } + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysRead GCPhysFirst=%RGp rcStrict=%Rrc (!!)\n", + GCPhysFirst, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + } + else + { + /* + * No informational status codes here, much more straight forward. + */ + int rc = PGMPhysSimpleReadGCPhys(pVM, pbBuf, GCPhysFirst, cbFirstPage); + if (RT_SUCCESS(rc)) + { + Assert(rc == VINF_SUCCESS); + rc = PGMPhysSimpleReadGCPhys(pVM, pbBuf + cbFirstPage, GCPhysSecond, cbSecondPage); + if (RT_SUCCESS(rc)) + Assert(rc == VINF_SUCCESS); + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysSimpleReadGCPhys GCPhysSecond=%RGp rc=%Rrc (!!)\n", GCPhysSecond, rc)); + return rc; + } + } + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysSimpleReadGCPhys GCPhysFirst=%RGp rc=%Rrc (!!)\n", GCPhysFirst, rc)); + return rc; + } + } + } +#ifdef VBOX_STRICT + else + memset(pbBuf, 0xcc, cbMem); + if (cbMem < sizeof(pVCpu->iem.s.aBounceBuffers[iMemMap].ab)) + memset(pbBuf + cbMem, 0xaa, sizeof(pVCpu->iem.s.aBounceBuffers[iMemMap].ab) - cbMem); +#endif + AssertCompileMemberAlignment(VMCPU, iem.s.aBounceBuffers, 64); + + /* + * Commit the bounce buffer entry. + */ + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst = GCPhysFirst; + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond = GCPhysSecond; + pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst = (uint16_t)cbFirstPage; + pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond = (uint16_t)cbSecondPage; + pVCpu->iem.s.aMemBbMappings[iMemMap].fUnassigned = false; + pVCpu->iem.s.aMemMappings[iMemMap].pv = pbBuf; + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = fAccess | IEM_ACCESS_BOUNCE_BUFFERED; + pVCpu->iem.s.iNextMapping = iMemMap + 1; + pVCpu->iem.s.cActiveMappings++; + + iemMemUpdateWrittenCounter(pVCpu, fAccess, cbMem); + *ppvMem = pbBuf; + return VINF_SUCCESS; +} + + +/** + * iemMemMap woker that deals with iemMemPageMap failures. + */ +static VBOXSTRICTRC iemMemBounceBufferMapPhys(PVMCPUCC pVCpu, unsigned iMemMap, void **ppvMem, size_t cbMem, + RTGCPHYS GCPhysFirst, uint32_t fAccess, VBOXSTRICTRC rcMap) +{ + /* + * Filter out conditions we can handle and the ones which shouldn't happen. + */ + if ( rcMap != VERR_PGM_PHYS_TLB_CATCH_WRITE + && rcMap != VERR_PGM_PHYS_TLB_CATCH_ALL + && rcMap != VERR_PGM_PHYS_TLB_UNASSIGNED) + { + AssertReturn(RT_FAILURE_NP(rcMap), VERR_IEM_IPE_8); + return rcMap; + } + pVCpu->iem.s.cPotentialExits++; + + /* + * Read in the current memory content if it's a read, execute or partial + * write access. + */ + uint8_t *pbBuf = &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0]; + if (fAccess & (IEM_ACCESS_TYPE_READ | IEM_ACCESS_TYPE_EXEC | IEM_ACCESS_PARTIAL_WRITE)) + { + if (rcMap == VERR_PGM_PHYS_TLB_UNASSIGNED) + memset(pbBuf, 0xff, cbMem); + else + { + int rc; + if (!pVCpu->iem.s.fBypassHandlers) + { + VBOXSTRICTRC rcStrict = PGMPhysRead(pVCpu->CTX_SUFF(pVM), GCPhysFirst, pbBuf, cbMem, PGMACCESSORIGIN_IEM); + if (rcStrict == VINF_SUCCESS) + { /* nothing */ } + else if (PGM_PHYS_RW_IS_SUCCESS(rcStrict)) + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysRead GCPhysFirst=%RGp rcStrict=%Rrc (!!)\n", + GCPhysFirst, VBOXSTRICTRC_VAL(rcStrict) )); + return rcStrict; + } + } + else + { + rc = PGMPhysSimpleReadGCPhys(pVCpu->CTX_SUFF(pVM), pbBuf, GCPhysFirst, cbMem); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + Log(("iemMemBounceBufferMapPhys: PGMPhysSimpleReadGCPhys GCPhysFirst=%RGp rcStrict=%Rrc (!!)\n", + GCPhysFirst, rc)); + return rc; + } + } + } + } +#ifdef VBOX_STRICT + else + memset(pbBuf, 0xcc, cbMem); +#endif +#ifdef VBOX_STRICT + if (cbMem < sizeof(pVCpu->iem.s.aBounceBuffers[iMemMap].ab)) + memset(pbBuf + cbMem, 0xaa, sizeof(pVCpu->iem.s.aBounceBuffers[iMemMap].ab) - cbMem); +#endif + + /* + * Commit the bounce buffer entry. + */ + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst = GCPhysFirst; + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond = NIL_RTGCPHYS; + pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst = (uint16_t)cbMem; + pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond = 0; + pVCpu->iem.s.aMemBbMappings[iMemMap].fUnassigned = rcMap == VERR_PGM_PHYS_TLB_UNASSIGNED; + pVCpu->iem.s.aMemMappings[iMemMap].pv = pbBuf; + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = fAccess | IEM_ACCESS_BOUNCE_BUFFERED; + pVCpu->iem.s.iNextMapping = iMemMap + 1; + pVCpu->iem.s.cActiveMappings++; + + iemMemUpdateWrittenCounter(pVCpu, fAccess, cbMem); + *ppvMem = pbBuf; + return VINF_SUCCESS; +} + + + +/** + * Maps the specified guest memory for the given kind of access. + * + * This may be using bounce buffering of the memory if it's crossing a page + * boundary or if there is an access handler installed for any of it. Because + * of lock prefix guarantees, we're in for some extra clutter when this + * happens. + * + * This may raise a \#GP, \#SS, \#PF or \#AC. + * + * @returns VBox strict status code. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param ppvMem Where to return the pointer to the mapped memory. + * @param cbMem The number of bytes to map. This is usually 1, 2, 4, 6, + * 8, 12, 16, 32 or 512. When used by string operations + * it can be up to a page. + * @param iSegReg The index of the segment register to use for this + * access. The base and limits are checked. Use UINT8_MAX + * to indicate that no segmentation is required (for IDT, + * GDT and LDT accesses). + * @param GCPtrMem The address of the guest memory. + * @param fAccess How the memory is being accessed. The + * IEM_ACCESS_TYPE_XXX bit is used to figure out how to map + * the memory, while the IEM_ACCESS_WHAT_XXX bit is used + * when raising exceptions. + * @param uAlignCtl Alignment control: + * - Bits 15:0 is the alignment mask. + * - Bits 31:16 for flags like IEM_MEMMAP_F_ALIGN_GP, + * IEM_MEMMAP_F_ALIGN_SSE, and + * IEM_MEMMAP_F_ALIGN_GP_OR_AC. + * Pass zero to skip alignment. + */ +VBOXSTRICTRC iemMemMap(PVMCPUCC pVCpu, void **ppvMem, size_t cbMem, uint8_t iSegReg, RTGCPTR GCPtrMem, + uint32_t fAccess, uint32_t uAlignCtl) RT_NOEXCEPT +{ + /* + * Check the input and figure out which mapping entry to use. + */ + Assert(cbMem <= sizeof(pVCpu->iem.s.aBounceBuffers[0])); + Assert( cbMem <= 64 || cbMem == 512 || cbMem == 256 || cbMem == 108 || cbMem == 104 || cbMem == 102 || cbMem == 94 + || (iSegReg == UINT8_MAX && uAlignCtl == 0 && fAccess == IEM_ACCESS_DATA_R /* for the CPUID logging interface */) ); + Assert(~(fAccess & ~(IEM_ACCESS_TYPE_MASK | IEM_ACCESS_WHAT_MASK))); + Assert(pVCpu->iem.s.cActiveMappings < RT_ELEMENTS(pVCpu->iem.s.aMemMappings)); + + unsigned iMemMap = pVCpu->iem.s.iNextMapping; + if ( iMemMap >= RT_ELEMENTS(pVCpu->iem.s.aMemMappings) + || pVCpu->iem.s.aMemMappings[iMemMap].fAccess != IEM_ACCESS_INVALID) + { + iMemMap = iemMemMapFindFree(pVCpu); + AssertLogRelMsgReturn(iMemMap < RT_ELEMENTS(pVCpu->iem.s.aMemMappings), + ("active=%d fAccess[0] = {%#x, %#x, %#x}\n", pVCpu->iem.s.cActiveMappings, + pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemMappings[1].fAccess, + pVCpu->iem.s.aMemMappings[2].fAccess), + VERR_IEM_IPE_9); + } + + /* + * Map the memory, checking that we can actually access it. If something + * slightly complicated happens, fall back on bounce buffering. + */ + VBOXSTRICTRC rcStrict = iemMemApplySegment(pVCpu, fAccess, iSegReg, cbMem, &GCPtrMem); + if (rcStrict == VINF_SUCCESS) + { /* likely */ } + else + return rcStrict; + + if ((GCPtrMem & GUEST_PAGE_OFFSET_MASK) + cbMem <= GUEST_PAGE_SIZE) /* Crossing a page boundary? */ + { /* likely */ } + else + return iemMemBounceBufferMapCrossPage(pVCpu, iMemMap, ppvMem, cbMem, GCPtrMem, fAccess); + + /* + * Alignment check. + */ + if ( (GCPtrMem & (uAlignCtl & UINT16_MAX)) == 0 ) + { /* likelyish */ } + else + { + /* Misaligned access. */ + if ((fAccess & IEM_ACCESS_WHAT_MASK) != IEM_ACCESS_WHAT_SYS) + { + if ( !(uAlignCtl & IEM_MEMMAP_F_ALIGN_GP) + || ( (uAlignCtl & IEM_MEMMAP_F_ALIGN_SSE) + && (pVCpu->cpum.GstCtx.XState.x87.MXCSR & X86_MXCSR_MM)) ) + { + AssertCompile(X86_CR0_AM == X86_EFL_AC); + + if (iemMemAreAlignmentChecksEnabled(pVCpu)) + return iemRaiseAlignmentCheckException(pVCpu); + } + else if ( (uAlignCtl & IEM_MEMMAP_F_ALIGN_GP_OR_AC) + && (GCPtrMem & 3) /* The value 4 matches 10980xe's FXSAVE and helps make bs3-cpu-basic2 work. */ + /** @todo may only apply to 2, 4 or 8 byte misalignments depending on the CPU + * implementation. See FXSAVE/FRSTOR/XSAVE/XRSTOR/++. Using 4 for now as + * that's what FXSAVE does on a 10980xe. */ + && iemMemAreAlignmentChecksEnabled(pVCpu)) + return iemRaiseAlignmentCheckException(pVCpu); + else + return iemRaiseGeneralProtectionFault0(pVCpu); + } + } + +#ifdef IEM_WITH_DATA_TLB + Assert(!(fAccess & IEM_ACCESS_TYPE_EXEC)); + + /* + * Get the TLB entry for this page. + */ + uint64_t const uTag = IEMTLB_CALC_TAG( &pVCpu->iem.s.DataTlb, GCPtrMem); + PIEMTLBENTRY const pTlbe = IEMTLB_TAG_TO_ENTRY(&pVCpu->iem.s.DataTlb, uTag); + if (pTlbe->uTag == uTag) + { +# ifdef VBOX_WITH_STATISTICS + pVCpu->iem.s.DataTlb.cTlbHits++; +# endif + } + else + { + pVCpu->iem.s.DataTlb.cTlbMisses++; + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrMem, &Walk); + if (RT_FAILURE(rc)) + { + Log(("iemMemMap: GCPtrMem=%RGv - failed to fetch page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PHYS_ADDR, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, rc); + } + + Assert(Walk.fSucceeded); + pTlbe->uTag = uTag; + pTlbe->fFlagsAndPhysRev = ~Walk.fEffective & (X86_PTE_US | X86_PTE_RW | X86_PTE_D | X86_PTE_A); /* skipping NX */ + pTlbe->GCPhys = Walk.GCPhys; + pTlbe->pbMappingR3 = NULL; + } + + /* + * Check TLB page table level access flags. + */ + /* If the page is either supervisor only or non-writable, we need to do + more careful access checks. */ + if (pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PT_NO_USER | IEMTLBE_F_PT_NO_WRITE)) + { + /* Write to read only memory? */ + if ( (pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PT_NO_WRITE) + && (fAccess & IEM_ACCESS_TYPE_WRITE) + && ( ( pVCpu->iem.s.uCpl == 3 + && !(fAccess & IEM_ACCESS_WHAT_SYS)) + || (pVCpu->cpum.GstCtx.cr0 & X86_CR0_WP))) + { + Log(("iemMemMap: GCPtrMem=%RGv - read-only page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess & ~IEM_ACCESS_TYPE_READ, VERR_ACCESS_DENIED); + } + + /* Kernel memory accessed by userland? */ + if ( (pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PT_NO_USER) + && pVCpu->iem.s.uCpl == 3 + && !(fAccess & IEM_ACCESS_WHAT_SYS)) + { + Log(("iemMemMap: GCPtrMem=%RGv - user access to kernel page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + return iemRaisePageFault(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, VERR_ACCESS_DENIED); + } + } + + /* + * Set the dirty / access flags. + * ASSUMES this is set when the address is translated rather than on commit... + */ + /** @todo testcase: check when A and D bits are actually set by the CPU. */ + uint64_t const fTlbAccessedDirty = (fAccess & IEM_ACCESS_TYPE_WRITE ? IEMTLBE_F_PT_NO_DIRTY : 0) | IEMTLBE_F_PT_NO_ACCESSED; + if (pTlbe->fFlagsAndPhysRev & fTlbAccessedDirty) + { + uint32_t const fAccessedDirty = fAccess & IEM_ACCESS_TYPE_WRITE ? X86_PTE_D | X86_PTE_A : X86_PTE_A; + int rc2 = PGMGstModifyPage(pVCpu, GCPtrMem, 1, fAccessedDirty, ~(uint64_t)fAccessedDirty); + AssertRC(rc2); + /** @todo Nested VMX: Accessed/dirty bit currently not supported, asserted below. */ + Assert(!(CPUMGetGuestIa32VmxEptVpidCap(pVCpu) & VMX_BF_EPT_VPID_CAP_ACCESS_DIRTY_MASK)); + pTlbe->fFlagsAndPhysRev &= ~fTlbAccessedDirty; + } + + /* + * Look up the physical page info if necessary. + */ + uint8_t *pbMem = NULL; + if ((pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PHYS_REV) == pVCpu->iem.s.DataTlb.uTlbPhysRev) +# ifdef IN_RING3 + pbMem = pTlbe->pbMappingR3; +# else + pbMem = NULL; +# endif + else + { + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_WRITE == IEMTLBE_F_PG_NO_WRITE); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_READ == IEMTLBE_F_PG_NO_READ); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_MAPPINGR3 == IEMTLBE_F_NO_MAPPINGR3); + AssertCompile(PGMIEMGCPHYS2PTR_F_UNASSIGNED == IEMTLBE_F_PG_UNASSIGNED); + if (RT_LIKELY(pVCpu->iem.s.CodeTlb.uTlbPhysRev > IEMTLB_PHYS_REV_INCR)) + { /* likely */ } + else + IEMTlbInvalidateAllPhysicalSlow(pVCpu); + pTlbe->pbMappingR3 = NULL; + pTlbe->fFlagsAndPhysRev &= ~( IEMTLBE_F_PHYS_REV + | IEMTLBE_F_NO_MAPPINGR3 | IEMTLBE_F_PG_NO_READ | IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_UNASSIGNED); + int rc = PGMPhysIemGCPhys2PtrNoLock(pVCpu->CTX_SUFF(pVM), pVCpu, pTlbe->GCPhys, &pVCpu->iem.s.DataTlb.uTlbPhysRev, + &pbMem, &pTlbe->fFlagsAndPhysRev); + AssertRCReturn(rc, rc); +# ifdef IN_RING3 + pTlbe->pbMappingR3 = pbMem; +# endif + } + + /* + * Check the physical page level access and mapping. + */ + if ( !(pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_NO_READ)) + || !(pTlbe->fFlagsAndPhysRev & ( (fAccess & IEM_ACCESS_TYPE_WRITE ? IEMTLBE_F_PG_NO_WRITE : 0) + | (fAccess & IEM_ACCESS_TYPE_READ ? IEMTLBE_F_PG_NO_READ : 0))) ) + { /* probably likely */ } + else + return iemMemBounceBufferMapPhys(pVCpu, iMemMap, ppvMem, cbMem, + pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), fAccess, + pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PG_UNASSIGNED ? VERR_PGM_PHYS_TLB_UNASSIGNED + : pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PG_NO_READ ? VERR_PGM_PHYS_TLB_CATCH_ALL + : VERR_PGM_PHYS_TLB_CATCH_WRITE); + Assert(!(pTlbe->fFlagsAndPhysRev & IEMTLBE_F_NO_MAPPINGR3)); /* ASSUMPTIONS about PGMPhysIemGCPhys2PtrNoLock behaviour. */ + + if (pbMem) + { + Assert(!((uintptr_t)pbMem & GUEST_PAGE_OFFSET_MASK)); + pbMem = pbMem + (GCPtrMem & GUEST_PAGE_OFFSET_MASK); + fAccess |= IEM_ACCESS_NOT_LOCKED; + } + else + { + Assert(!(fAccess & IEM_ACCESS_NOT_LOCKED)); + RTGCPHYS const GCPhysFirst = pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK); + rcStrict = iemMemPageMap(pVCpu, GCPhysFirst, fAccess, (void **)&pbMem, &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + if (rcStrict != VINF_SUCCESS) + return iemMemBounceBufferMapPhys(pVCpu, iMemMap, ppvMem, cbMem, GCPhysFirst, fAccess, rcStrict); + } + + void * const pvMem = pbMem; + + if (fAccess & IEM_ACCESS_TYPE_WRITE) + Log8(("IEM WR %RGv (%RGp) LB %#zx\n", GCPtrMem, pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), cbMem)); + if (fAccess & IEM_ACCESS_TYPE_READ) + Log9(("IEM RD %RGv (%RGp) LB %#zx\n", GCPtrMem, pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), cbMem)); + +#else /* !IEM_WITH_DATA_TLB */ + + RTGCPHYS GCPhysFirst; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, &GCPhysFirst); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + if (fAccess & IEM_ACCESS_TYPE_WRITE) + Log8(("IEM WR %RGv (%RGp) LB %#zx\n", GCPtrMem, GCPhysFirst, cbMem)); + if (fAccess & IEM_ACCESS_TYPE_READ) + Log9(("IEM RD %RGv (%RGp) LB %#zx\n", GCPtrMem, GCPhysFirst, cbMem)); + + void *pvMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysFirst, fAccess, &pvMem, &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + if (rcStrict != VINF_SUCCESS) + return iemMemBounceBufferMapPhys(pVCpu, iMemMap, ppvMem, cbMem, GCPhysFirst, fAccess, rcStrict); + +#endif /* !IEM_WITH_DATA_TLB */ + + /* + * Fill in the mapping table entry. + */ + pVCpu->iem.s.aMemMappings[iMemMap].pv = pvMem; + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = fAccess; + pVCpu->iem.s.iNextMapping = iMemMap + 1; + pVCpu->iem.s.cActiveMappings += 1; + + iemMemUpdateWrittenCounter(pVCpu, fAccess, cbMem); + *ppvMem = pvMem; + + return VINF_SUCCESS; +} + + +/** + * Commits the guest memory if bounce buffered and unmaps it. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The mapping. + * @param fAccess The kind of access. + */ +VBOXSTRICTRC iemMemCommitAndUnmap(PVMCPUCC pVCpu, void *pvMem, uint32_t fAccess) RT_NOEXCEPT +{ + int iMemMap = iemMapLookup(pVCpu, pvMem, fAccess); + AssertReturn(iMemMap >= 0, iMemMap); + + /* If it's bounce buffered, we may need to write back the buffer. */ + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_BOUNCE_BUFFERED) + { + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_TYPE_WRITE) + return iemMemBounceBufferCommitAndUnmap(pVCpu, iMemMap, false /*fPostponeFail*/); + } + /* Otherwise unlock it. */ + else if (!(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_NOT_LOCKED)) + PGMPhysReleasePageMappingLock(pVCpu->CTX_SUFF(pVM), &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + + /* Free the entry. */ + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + Assert(pVCpu->iem.s.cActiveMappings != 0); + pVCpu->iem.s.cActiveMappings--; + return VINF_SUCCESS; +} + +#ifdef IEM_WITH_SETJMP + +/** + * Maps the specified guest memory for the given kind of access, longjmp on + * error. + * + * This may be using bounce buffering of the memory if it's crossing a page + * boundary or if there is an access handler installed for any of it. Because + * of lock prefix guarantees, we're in for some extra clutter when this + * happens. + * + * This may raise a \#GP, \#SS, \#PF or \#AC. + * + * @returns Pointer to the mapped memory. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbMem The number of bytes to map. This is usually 1, + * 2, 4, 6, 8, 12, 16, 32 or 512. When used by + * string operations it can be up to a page. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * Use UINT8_MAX to indicate that no segmentation + * is required (for IDT, GDT and LDT accesses). + * @param GCPtrMem The address of the guest memory. + * @param fAccess How the memory is being accessed. The + * IEM_ACCESS_TYPE_XXX bit is used to figure out + * how to map the memory, while the + * IEM_ACCESS_WHAT_XXX bit is used when raising + * exceptions. + * @param uAlignCtl Alignment control: + * - Bits 15:0 is the alignment mask. + * - Bits 31:16 for flags like IEM_MEMMAP_F_ALIGN_GP, + * IEM_MEMMAP_F_ALIGN_SSE, and + * IEM_MEMMAP_F_ALIGN_GP_OR_AC. + * Pass zero to skip alignment. + */ +void *iemMemMapJmp(PVMCPUCC pVCpu, size_t cbMem, uint8_t iSegReg, RTGCPTR GCPtrMem, uint32_t fAccess, + uint32_t uAlignCtl) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* + * Check the input, check segment access and adjust address + * with segment base. + */ + Assert(cbMem <= 64 || cbMem == 512 || cbMem == 108 || cbMem == 104 || cbMem == 94); /* 512 is the max! */ + Assert(~(fAccess & ~(IEM_ACCESS_TYPE_MASK | IEM_ACCESS_WHAT_MASK))); + Assert(pVCpu->iem.s.cActiveMappings < RT_ELEMENTS(pVCpu->iem.s.aMemMappings)); + + VBOXSTRICTRC rcStrict = iemMemApplySegment(pVCpu, fAccess, iSegReg, cbMem, &GCPtrMem); + if (rcStrict == VINF_SUCCESS) { /*likely*/ } + else IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + + /* + * Alignment check. + */ + if ( (GCPtrMem & (uAlignCtl & UINT16_MAX)) == 0 ) + { /* likelyish */ } + else + { + /* Misaligned access. */ + if ((fAccess & IEM_ACCESS_WHAT_MASK) != IEM_ACCESS_WHAT_SYS) + { + if ( !(uAlignCtl & IEM_MEMMAP_F_ALIGN_GP) + || ( (uAlignCtl & IEM_MEMMAP_F_ALIGN_SSE) + && (pVCpu->cpum.GstCtx.XState.x87.MXCSR & X86_MXCSR_MM)) ) + { + AssertCompile(X86_CR0_AM == X86_EFL_AC); + + if (iemMemAreAlignmentChecksEnabled(pVCpu)) + iemRaiseAlignmentCheckExceptionJmp(pVCpu); + } + else if ( (uAlignCtl & IEM_MEMMAP_F_ALIGN_GP_OR_AC) + && (GCPtrMem & 3) /* The value 4 matches 10980xe's FXSAVE and helps make bs3-cpu-basic2 work. */ + /** @todo may only apply to 2, 4 or 8 byte misalignments depending on the CPU + * implementation. See FXSAVE/FRSTOR/XSAVE/XRSTOR/++. Using 4 for now as + * that's what FXSAVE does on a 10980xe. */ + && iemMemAreAlignmentChecksEnabled(pVCpu)) + iemRaiseAlignmentCheckExceptionJmp(pVCpu); + else + iemRaiseGeneralProtectionFault0Jmp(pVCpu); + } + } + + /* + * Figure out which mapping entry to use. + */ + unsigned iMemMap = pVCpu->iem.s.iNextMapping; + if ( iMemMap >= RT_ELEMENTS(pVCpu->iem.s.aMemMappings) + || pVCpu->iem.s.aMemMappings[iMemMap].fAccess != IEM_ACCESS_INVALID) + { + iMemMap = iemMemMapFindFree(pVCpu); + AssertLogRelMsgStmt(iMemMap < RT_ELEMENTS(pVCpu->iem.s.aMemMappings), + ("active=%d fAccess[0] = {%#x, %#x, %#x}\n", pVCpu->iem.s.cActiveMappings, + pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemMappings[1].fAccess, + pVCpu->iem.s.aMemMappings[2].fAccess), + IEM_DO_LONGJMP(pVCpu, VERR_IEM_IPE_9)); + } + + /* + * Crossing a page boundary? + */ + if ((GCPtrMem & GUEST_PAGE_OFFSET_MASK) + cbMem <= GUEST_PAGE_SIZE) + { /* No (likely). */ } + else + { + void *pvMem; + rcStrict = iemMemBounceBufferMapCrossPage(pVCpu, iMemMap, &pvMem, cbMem, GCPtrMem, fAccess); + if (rcStrict == VINF_SUCCESS) + return pvMem; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + +#ifdef IEM_WITH_DATA_TLB + Assert(!(fAccess & IEM_ACCESS_TYPE_EXEC)); + + /* + * Get the TLB entry for this page. + */ + uint64_t const uTag = IEMTLB_CALC_TAG( &pVCpu->iem.s.DataTlb, GCPtrMem); + PIEMTLBENTRY const pTlbe = IEMTLB_TAG_TO_ENTRY(&pVCpu->iem.s.DataTlb, uTag); + if (pTlbe->uTag == uTag) + STAM_STATS({pVCpu->iem.s.DataTlb.cTlbHits++;}); + else + { + pVCpu->iem.s.DataTlb.cTlbMisses++; + PGMPTWALK Walk; + int rc = PGMGstGetPage(pVCpu, GCPtrMem, &Walk); + if (RT_FAILURE(rc)) + { + Log(("iemMemMap: GCPtrMem=%RGv - failed to fetch page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PHYS_ADDR, 0 /* cbInstr */); +# endif + iemRaisePageFaultJmp(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, rc); + } + + Assert(Walk.fSucceeded); + pTlbe->uTag = uTag; + pTlbe->fFlagsAndPhysRev = ~Walk.fEffective & (X86_PTE_US | X86_PTE_RW | X86_PTE_D | X86_PTE_A); /* skipping NX */ + pTlbe->GCPhys = Walk.GCPhys; + pTlbe->pbMappingR3 = NULL; + } + + /* + * Check the flags and physical revision. + */ + /** @todo make the caller pass these in with fAccess. */ + uint64_t const fNoUser = (fAccess & IEM_ACCESS_WHAT_MASK) != IEM_ACCESS_WHAT_SYS && pVCpu->iem.s.uCpl == 3 + ? IEMTLBE_F_PT_NO_USER : 0; + uint64_t const fNoWriteNoDirty = fAccess & IEM_ACCESS_TYPE_WRITE + ? IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PT_NO_DIRTY + | ( (pVCpu->cpum.GstCtx.cr0 & X86_CR0_WP) + || (pVCpu->iem.s.uCpl == 3 && (fAccess & IEM_ACCESS_WHAT_MASK) != IEM_ACCESS_WHAT_SYS) + ? IEMTLBE_F_PT_NO_WRITE : 0) + : 0; + uint64_t const fNoRead = fAccess & IEM_ACCESS_TYPE_READ ? IEMTLBE_F_PG_NO_READ : 0; + uint8_t *pbMem = NULL; + if ( (pTlbe->fFlagsAndPhysRev & (IEMTLBE_F_PHYS_REV | IEMTLBE_F_PT_NO_ACCESSED | fNoRead | fNoWriteNoDirty | fNoUser)) + == pVCpu->iem.s.DataTlb.uTlbPhysRev) +# ifdef IN_RING3 + pbMem = pTlbe->pbMappingR3; +# else + pbMem = NULL; +# endif + else + { + /* + * Okay, something isn't quite right or needs refreshing. + */ + /* Write to read only memory? */ + if (pTlbe->fFlagsAndPhysRev & fNoWriteNoDirty & IEMTLBE_F_PT_NO_WRITE) + { + Log(("iemMemMapJmp: GCPtrMem=%RGv - read-only page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + iemRaisePageFaultJmp(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess & ~IEM_ACCESS_TYPE_READ, VERR_ACCESS_DENIED); + } + + /* Kernel memory accessed by userland? */ + if (pTlbe->fFlagsAndPhysRev & fNoUser & IEMTLBE_F_PT_NO_USER) + { + Log(("iemMemMapJmp: GCPtrMem=%RGv - user access to kernel page -> #PF\n", GCPtrMem)); +# ifdef VBOX_WITH_NESTED_HWVIRT_VMX_EPT + if (Walk.fFailed & PGM_WALKFAIL_EPT) + IEM_VMX_VMEXIT_EPT_RET(pVCpu, &Walk, fAccess, IEM_SLAT_FAIL_LINEAR_TO_PAGE_TABLE, 0 /* cbInstr */); +# endif + iemRaisePageFaultJmp(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, VERR_ACCESS_DENIED); + } + + /* Set the dirty / access flags. + ASSUMES this is set when the address is translated rather than on commit... */ + /** @todo testcase: check when A and D bits are actually set by the CPU. */ + if (pTlbe->fFlagsAndPhysRev & ((fNoWriteNoDirty & IEMTLBE_F_PT_NO_DIRTY) | IEMTLBE_F_PT_NO_ACCESSED)) + { + uint32_t const fAccessedDirty = fAccess & IEM_ACCESS_TYPE_WRITE ? X86_PTE_D | X86_PTE_A : X86_PTE_A; + int rc2 = PGMGstModifyPage(pVCpu, GCPtrMem, 1, fAccessedDirty, ~(uint64_t)fAccessedDirty); + AssertRC(rc2); + /** @todo Nested VMX: Accessed/dirty bit currently not supported, asserted below. */ + Assert(!(CPUMGetGuestIa32VmxEptVpidCap(pVCpu) & VMX_BF_EPT_VPID_CAP_ACCESS_DIRTY_MASK)); + pTlbe->fFlagsAndPhysRev &= ~((fNoWriteNoDirty & IEMTLBE_F_PT_NO_DIRTY) | IEMTLBE_F_PT_NO_ACCESSED); + } + + /* + * Check if the physical page info needs updating. + */ + if ((pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PHYS_REV) == pVCpu->iem.s.DataTlb.uTlbPhysRev) +# ifdef IN_RING3 + pbMem = pTlbe->pbMappingR3; +# else + pbMem = NULL; +# endif + else + { + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_WRITE == IEMTLBE_F_PG_NO_WRITE); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_READ == IEMTLBE_F_PG_NO_READ); + AssertCompile(PGMIEMGCPHYS2PTR_F_NO_MAPPINGR3 == IEMTLBE_F_NO_MAPPINGR3); + AssertCompile(PGMIEMGCPHYS2PTR_F_UNASSIGNED == IEMTLBE_F_PG_UNASSIGNED); + pTlbe->pbMappingR3 = NULL; + pTlbe->fFlagsAndPhysRev &= ~( IEMTLBE_F_PHYS_REV + | IEMTLBE_F_NO_MAPPINGR3 | IEMTLBE_F_PG_NO_READ | IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_UNASSIGNED); + int rc = PGMPhysIemGCPhys2PtrNoLock(pVCpu->CTX_SUFF(pVM), pVCpu, pTlbe->GCPhys, &pVCpu->iem.s.DataTlb.uTlbPhysRev, + &pbMem, &pTlbe->fFlagsAndPhysRev); + AssertRCStmt(rc, IEM_DO_LONGJMP(pVCpu, rc)); +# ifdef IN_RING3 + pTlbe->pbMappingR3 = pbMem; +# endif + } + + /* + * Check the physical page level access and mapping. + */ + if (!(pTlbe->fFlagsAndPhysRev & ((fNoWriteNoDirty | fNoRead) & (IEMTLBE_F_PG_NO_WRITE | IEMTLBE_F_PG_NO_READ)))) + { /* probably likely */ } + else + { + rcStrict = iemMemBounceBufferMapPhys(pVCpu, iMemMap, (void **)&pbMem, cbMem, + pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), fAccess, + pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PG_UNASSIGNED ? VERR_PGM_PHYS_TLB_UNASSIGNED + : pTlbe->fFlagsAndPhysRev & IEMTLBE_F_PG_NO_READ ? VERR_PGM_PHYS_TLB_CATCH_ALL + : VERR_PGM_PHYS_TLB_CATCH_WRITE); + if (rcStrict == VINF_SUCCESS) + return pbMem; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + } + Assert(!(pTlbe->fFlagsAndPhysRev & IEMTLBE_F_NO_MAPPINGR3)); /* ASSUMPTIONS about PGMPhysIemGCPhys2PtrNoLock behaviour. */ + + if (pbMem) + { + Assert(!((uintptr_t)pbMem & GUEST_PAGE_OFFSET_MASK)); + pbMem = pbMem + (GCPtrMem & GUEST_PAGE_OFFSET_MASK); + fAccess |= IEM_ACCESS_NOT_LOCKED; + } + else + { + Assert(!(fAccess & IEM_ACCESS_NOT_LOCKED)); + RTGCPHYS const GCPhysFirst = pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK); + rcStrict = iemMemPageMap(pVCpu, GCPhysFirst, fAccess, (void **)&pbMem, &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + if (rcStrict == VINF_SUCCESS) + return pbMem; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + + void * const pvMem = pbMem; + + if (fAccess & IEM_ACCESS_TYPE_WRITE) + Log8(("IEM WR %RGv (%RGp) LB %#zx\n", GCPtrMem, pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), cbMem)); + if (fAccess & IEM_ACCESS_TYPE_READ) + Log9(("IEM RD %RGv (%RGp) LB %#zx\n", GCPtrMem, pTlbe->GCPhys | (GCPtrMem & GUEST_PAGE_OFFSET_MASK), cbMem)); + +#else /* !IEM_WITH_DATA_TLB */ + + + RTGCPHYS GCPhysFirst; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, GCPtrMem, (uint32_t)cbMem, fAccess, &GCPhysFirst); + if (rcStrict == VINF_SUCCESS) { /*likely*/ } + else IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + + if (fAccess & IEM_ACCESS_TYPE_WRITE) + Log8(("IEM WR %RGv (%RGp) LB %#zx\n", GCPtrMem, GCPhysFirst, cbMem)); + if (fAccess & IEM_ACCESS_TYPE_READ) + Log9(("IEM RD %RGv (%RGp) LB %#zx\n", GCPtrMem, GCPhysFirst, cbMem)); + + void *pvMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysFirst, fAccess, &pvMem, &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + if (rcStrict == VINF_SUCCESS) + { /* likely */ } + else + { + rcStrict = iemMemBounceBufferMapPhys(pVCpu, iMemMap, &pvMem, cbMem, GCPhysFirst, fAccess, rcStrict); + if (rcStrict == VINF_SUCCESS) + return pvMem; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + +#endif /* !IEM_WITH_DATA_TLB */ + + /* + * Fill in the mapping table entry. + */ + pVCpu->iem.s.aMemMappings[iMemMap].pv = pvMem; + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = fAccess; + pVCpu->iem.s.iNextMapping = iMemMap + 1; + pVCpu->iem.s.cActiveMappings++; + + iemMemUpdateWrittenCounter(pVCpu, fAccess, cbMem); + return pvMem; +} + + +/** + * Commits the guest memory if bounce buffered and unmaps it, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The mapping. + * @param fAccess The kind of access. + */ +void iemMemCommitAndUnmapJmp(PVMCPUCC pVCpu, void *pvMem, uint32_t fAccess) IEM_NOEXCEPT_MAY_LONGJMP +{ + int iMemMap = iemMapLookup(pVCpu, pvMem, fAccess); + AssertStmt(iMemMap >= 0, IEM_DO_LONGJMP(pVCpu, iMemMap)); + + /* If it's bounce buffered, we may need to write back the buffer. */ + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_BOUNCE_BUFFERED) + { + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_TYPE_WRITE) + { + VBOXSTRICTRC rcStrict = iemMemBounceBufferCommitAndUnmap(pVCpu, iMemMap, false /*fPostponeFail*/); + if (rcStrict == VINF_SUCCESS) + return; + IEM_DO_LONGJMP(pVCpu, VBOXSTRICTRC_VAL(rcStrict)); + } + } + /* Otherwise unlock it. */ + else if (!(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_NOT_LOCKED)) + PGMPhysReleasePageMappingLock(pVCpu->CTX_SUFF(pVM), &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + + /* Free the entry. */ + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + Assert(pVCpu->iem.s.cActiveMappings != 0); + pVCpu->iem.s.cActiveMappings--; +} + +#endif /* IEM_WITH_SETJMP */ + +#ifndef IN_RING3 +/** + * Commits the guest memory if bounce buffered and unmaps it, if any bounce + * buffer part shows trouble it will be postponed to ring-3 (sets FF and stuff). + * + * Allows the instruction to be completed and retired, while the IEM user will + * return to ring-3 immediately afterwards and do the postponed writes there. + * + * @returns VBox status code (no strict statuses). Caller must check + * VMCPU_FF_IEM before repeating string instructions and similar stuff. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The mapping. + * @param fAccess The kind of access. + */ +VBOXSTRICTRC iemMemCommitAndUnmapPostponeTroubleToR3(PVMCPUCC pVCpu, void *pvMem, uint32_t fAccess) RT_NOEXCEPT +{ + int iMemMap = iemMapLookup(pVCpu, pvMem, fAccess); + AssertReturn(iMemMap >= 0, iMemMap); + + /* If it's bounce buffered, we may need to write back the buffer. */ + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_BOUNCE_BUFFERED) + { + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_TYPE_WRITE) + return iemMemBounceBufferCommitAndUnmap(pVCpu, iMemMap, true /*fPostponeFail*/); + } + /* Otherwise unlock it. */ + else if (!(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_NOT_LOCKED)) + PGMPhysReleasePageMappingLock(pVCpu->CTX_SUFF(pVM), &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + + /* Free the entry. */ + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + Assert(pVCpu->iem.s.cActiveMappings != 0); + pVCpu->iem.s.cActiveMappings--; + return VINF_SUCCESS; +} +#endif + + +/** + * Rollbacks mappings, releasing page locks and such. + * + * The caller shall only call this after checking cActiveMappings. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + */ +void iemMemRollback(PVMCPUCC pVCpu) RT_NOEXCEPT +{ + Assert(pVCpu->iem.s.cActiveMappings > 0); + + uint32_t iMemMap = RT_ELEMENTS(pVCpu->iem.s.aMemMappings); + while (iMemMap-- > 0) + { + uint32_t const fAccess = pVCpu->iem.s.aMemMappings[iMemMap].fAccess; + if (fAccess != IEM_ACCESS_INVALID) + { + AssertMsg(!(fAccess & ~IEM_ACCESS_VALID_MASK) && fAccess != 0, ("%#x\n", fAccess)); + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + if (!(fAccess & (IEM_ACCESS_BOUNCE_BUFFERED | IEM_ACCESS_NOT_LOCKED))) + PGMPhysReleasePageMappingLock(pVCpu->CTX_SUFF(pVM), &pVCpu->iem.s.aMemMappingLocks[iMemMap].Lock); + AssertMsg(pVCpu->iem.s.cActiveMappings > 0, + ("iMemMap=%u fAccess=%#x pv=%p GCPhysFirst=%RGp GCPhysSecond=%RGp\n", + iMemMap, fAccess, pVCpu->iem.s.aMemMappings[iMemMap].pv, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond)); + pVCpu->iem.s.cActiveMappings--; + } + } +} + + +/** + * Fetches a data byte. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu8Dst Where to return the byte. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU8(PVMCPUCC pVCpu, uint8_t *pu8Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint8_t const *pu8Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu8Src, sizeof(*pu8Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, 0); + if (rc == VINF_SUCCESS) + { + *pu8Dst = *pu8Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu8Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data byte, longjmp on error. + * + * @returns The byte. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint8_t iemMemFetchDataU8Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint8_t const *pu8Src = (uint8_t const *)iemMemMapJmp(pVCpu, sizeof(*pu8Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, 0); + uint8_t const bRet = *pu8Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu8Src, IEM_ACCESS_DATA_R); + return bRet; +} +#endif /* IEM_WITH_SETJMP */ + + +/** + * Fetches a data word. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16Dst Where to return the word. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU16(PVMCPUCC pVCpu, uint16_t *pu16Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint16_t const *pu16Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Src, sizeof(*pu16Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu16Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Dst = *pu16Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu16Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data word, longjmp on error. + * + * @returns The word + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint16_t iemMemFetchDataU16Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint16_t const *pu16Src = (uint16_t const *)iemMemMapJmp(pVCpu, sizeof(*pu16Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, + sizeof(*pu16Src) - 1); + uint16_t const u16Ret = *pu16Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu16Src, IEM_ACCESS_DATA_R); + return u16Ret; +} +#endif + + +/** + * Fetches a data dword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32Dst Where to return the dword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU32(PVMCPUCC pVCpu, uint32_t *pu32Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint32_t const *pu32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Src, sizeof(*pu32Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu32Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Dst = *pu32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu32Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +/** + * Fetches a data dword and zero extends it to a qword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU32_ZX_U64(PVMCPUCC pVCpu, uint64_t *pu64Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint32_t const *pu32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Src, sizeof(*pu32Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu32Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = *pu32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu32Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP + +/** + * Fetches a data dword, longjmp on error, fallback/safe version. + * + * @returns The dword + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint32_t iemMemFetchDataU32SafeJmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + uint32_t const *pu32Src = (uint32_t const *)iemMemMapJmp(pVCpu, sizeof(*pu32Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, + sizeof(*pu32Src) - 1); + uint32_t const u32Ret = *pu32Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu32Src, IEM_ACCESS_DATA_R); + return u32Ret; +} + + +/** + * Fetches a data dword, longjmp on error. + * + * @returns The dword + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint32_t iemMemFetchDataU32Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ +# if defined(IEM_WITH_DATA_TLB) && defined(IN_RING3) + /* + * Convert from segmented to flat address and check that it doesn't cross a page boundrary. + */ + RTGCPTR GCPtrEff = iemMemApplySegmentToReadJmp(pVCpu, iSegReg, sizeof(uint32_t), GCPtrMem); + if (RT_LIKELY((GCPtrEff & GUEST_PAGE_OFFSET_MASK) <= GUEST_PAGE_SIZE - sizeof(uint32_t))) + { + /* + * TLB lookup. + */ + uint64_t const uTag = IEMTLB_CALC_TAG( &pVCpu->iem.s.DataTlb, GCPtrEff); + PIEMTLBENTRY pTlbe = IEMTLB_TAG_TO_ENTRY(&pVCpu->iem.s.DataTlb, uTag); + if (pTlbe->uTag == uTag) + { + /* + * Check TLB page table level access flags. + */ + uint64_t const fNoUser = pVCpu->iem.s.uCpl == 3 ? IEMTLBE_F_PT_NO_USER : 0; + if ( (pTlbe->fFlagsAndPhysRev & ( IEMTLBE_F_PHYS_REV | IEMTLBE_F_PG_UNASSIGNED | IEMTLBE_F_PG_NO_READ + | IEMTLBE_F_PT_NO_ACCESSED | IEMTLBE_F_NO_MAPPINGR3 | fNoUser)) + == pVCpu->iem.s.DataTlb.uTlbPhysRev) + { + STAM_STATS({pVCpu->iem.s.DataTlb.cTlbHits++;}); + + /* + * Alignment check: + */ + /** @todo check priority \#AC vs \#PF */ + if ( !(GCPtrEff & (sizeof(uint32_t) - 1)) + || !(pVCpu->cpum.GstCtx.cr0 & X86_CR0_AM) + || !pVCpu->cpum.GstCtx.eflags.Bits.u1AC + || pVCpu->iem.s.uCpl != 3) + { + /* + * Fetch and return the dword + */ + Assert(pTlbe->pbMappingR3); /* (Only ever cleared by the owning EMT.) */ + Assert(!((uintptr_t)pTlbe->pbMappingR3 & GUEST_PAGE_OFFSET_MASK)); + return *(uint32_t const *)&pTlbe->pbMappingR3[GCPtrEff & GUEST_PAGE_OFFSET_MASK]; + } + Log10(("iemMemFetchDataU32Jmp: Raising #AC for %RGv\n", GCPtrEff)); + iemRaiseAlignmentCheckExceptionJmp(pVCpu); + } + } + } + + /* Fall back on the slow careful approach in case of TLB miss, MMIO, exception + outdated page pointer, or other troubles. */ + Log10(("iemMemFetchDataU32Jmp: %u:%RGv fallback\n", iSegReg, GCPtrMem)); + return iemMemFetchDataU32SafeJmp(pVCpu, iSegReg, GCPtrMem); + +# else + uint32_t const *pu32Src = (uint32_t const *)iemMemMapJmp(pVCpu, sizeof(*pu32Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu32Src) - 1); + uint32_t const u32Ret = *pu32Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu32Src, IEM_ACCESS_DATA_R); + return u32Ret; +# endif +} +#endif + + +#ifdef SOME_UNUSED_FUNCTION +/** + * Fetches a data dword and sign extends it to a qword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Dst Where to return the sign extended value. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataS32SxU64(PVMCPUCC pVCpu, uint64_t *pu64Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + int32_t const *pi32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pi32Src, sizeof(*pi32Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pi32Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = *pi32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pi32Src, IEM_ACCESS_DATA_R); + } +#ifdef __GNUC__ /* warning: GCC may be a royal pain */ + else + *pu64Dst = 0; +#endif + return rc; +} +#endif + + +/** + * Fetches a data qword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU64(PVMCPUCC pVCpu, uint64_t *pu64Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint64_t const *pu64Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Src, sizeof(*pu64Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu64Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = *pu64Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu64Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data qword, longjmp on error. + * + * @returns The qword. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint64_t iemMemFetchDataU64Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint64_t const *pu64Src = (uint64_t const *)iemMemMapJmp(pVCpu, sizeof(*pu64Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, sizeof(*pu64Src) - 1); + uint64_t const u64Ret = *pu64Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu64Src, IEM_ACCESS_DATA_R); + return u64Ret; +} +#endif + + +/** + * Fetches a data qword, aligned at a 16 byte boundrary (for SSE). + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU64AlignedU128(PVMCPUCC pVCpu, uint64_t *pu64Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint64_t const *pu64Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Src, sizeof(*pu64Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 15 | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + if (rc == VINF_SUCCESS) + { + *pu64Dst = *pu64Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu64Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data qword, longjmp on error. + * + * @returns The qword. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +uint64_t iemMemFetchDataU64AlignedU128Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint64_t const *pu64Src = (uint64_t const *)iemMemMapJmp(pVCpu, sizeof(*pu64Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, + 15 | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + uint64_t const u64Ret = *pu64Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu64Src, IEM_ACCESS_DATA_R); + return u64Ret; +} +#endif + + +/** + * Fetches a data tword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pr80Dst Where to return the tword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataR80(PVMCPUCC pVCpu, PRTFLOAT80U pr80Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTFLOAT80U pr80Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pr80Src, sizeof(*pr80Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, 7); + if (rc == VINF_SUCCESS) + { + *pr80Dst = *pr80Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pr80Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data tword, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pr80Dst Where to return the tword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataR80Jmp(PVMCPUCC pVCpu, PRTFLOAT80U pr80Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTFLOAT80U pr80Src = (PCRTFLOAT80U)iemMemMapJmp(pVCpu, sizeof(*pr80Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, 7); + *pr80Dst = *pr80Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pr80Src, IEM_ACCESS_DATA_R); +} +#endif + + +/** + * Fetches a data decimal tword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pd80Dst Where to return the tword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataD80(PVMCPUCC pVCpu, PRTPBCD80U pd80Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTPBCD80U pd80Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pd80Src, sizeof(*pd80Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 7 /** @todo FBLD alignment check */); + if (rc == VINF_SUCCESS) + { + *pd80Dst = *pd80Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pd80Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data decimal tword, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pd80Dst Where to return the tword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataD80Jmp(PVMCPUCC pVCpu, PRTPBCD80U pd80Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTPBCD80U pd80Src = (PCRTPBCD80U)iemMemMapJmp(pVCpu, sizeof(*pd80Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 7 /** @todo FBSTP alignment check */); + *pd80Dst = *pd80Src; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pd80Src, IEM_ACCESS_DATA_R); +} +#endif + + +/** + * Fetches a data dqword (double qword), generally SSE related. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu128Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU128(PVMCPUCC pVCpu, PRTUINT128U pu128Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTUINT128U pu128Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu128Src, sizeof(*pu128Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 0 /* NO_AC variant */); + if (rc == VINF_SUCCESS) + { + pu128Dst->au64[0] = pu128Src->au64[0]; + pu128Dst->au64[1] = pu128Src->au64[1]; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu128Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data dqword (double qword), generally SSE related. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu128Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataU128Jmp(PVMCPUCC pVCpu, PRTUINT128U pu128Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTUINT128U pu128Src = (PCRTUINT128U)iemMemMapJmp(pVCpu, sizeof(*pu128Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 0 /* NO_AC variant */); + pu128Dst->au64[0] = pu128Src->au64[0]; + pu128Dst->au64[1] = pu128Src->au64[1]; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu128Src, IEM_ACCESS_DATA_R); +} +#endif + + +/** + * Fetches a data dqword (double qword) at an aligned address, generally SSE + * related. + * + * Raises \#GP(0) if not aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu128Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU128AlignedSse(PVMCPUCC pVCpu, PRTUINT128U pu128Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTUINT128U pu128Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu128Src, sizeof(*pu128Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, (sizeof(*pu128Src) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + if (rc == VINF_SUCCESS) + { + pu128Dst->au64[0] = pu128Src->au64[0]; + pu128Dst->au64[1] = pu128Src->au64[1]; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu128Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data dqword (double qword) at an aligned address, generally SSE + * related, longjmp on error. + * + * Raises \#GP(0) if not aligned. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu128Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataU128AlignedSseJmp(PVMCPUCC pVCpu, PRTUINT128U pu128Dst, uint8_t iSegReg, + RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTUINT128U pu128Src = (PCRTUINT128U)iemMemMapJmp(pVCpu, sizeof(*pu128Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, + (sizeof(*pu128Src) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + pu128Dst->au64[0] = pu128Src->au64[0]; + pu128Dst->au64[1] = pu128Src->au64[1]; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu128Src, IEM_ACCESS_DATA_R); +} +#endif + + +/** + * Fetches a data oword (octo word), generally AVX related. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu256Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU256(PVMCPUCC pVCpu, PRTUINT256U pu256Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTUINT256U pu256Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu256Src, sizeof(*pu256Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 0 /* NO_AC variant */); + if (rc == VINF_SUCCESS) + { + pu256Dst->au64[0] = pu256Src->au64[0]; + pu256Dst->au64[1] = pu256Src->au64[1]; + pu256Dst->au64[2] = pu256Src->au64[2]; + pu256Dst->au64[3] = pu256Src->au64[3]; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu256Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data oword (octo word), generally AVX related. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu256Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataU256Jmp(PVMCPUCC pVCpu, PRTUINT256U pu256Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTUINT256U pu256Src = (PCRTUINT256U)iemMemMapJmp(pVCpu, sizeof(*pu256Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, 0 /* NO_AC variant */); + pu256Dst->au64[0] = pu256Src->au64[0]; + pu256Dst->au64[1] = pu256Src->au64[1]; + pu256Dst->au64[2] = pu256Src->au64[2]; + pu256Dst->au64[3] = pu256Src->au64[3]; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu256Src, IEM_ACCESS_DATA_R); +} +#endif + + +/** + * Fetches a data oword (octo word) at an aligned address, generally AVX + * related. + * + * Raises \#GP(0) if not aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu256Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchDataU256AlignedSse(PVMCPUCC pVCpu, PRTUINT256U pu256Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PCRTUINT256U pu256Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu256Src, sizeof(*pu256Src), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_R, (sizeof(*pu256Src) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + if (rc == VINF_SUCCESS) + { + pu256Dst->au64[0] = pu256Src->au64[0]; + pu256Dst->au64[1] = pu256Src->au64[1]; + pu256Dst->au64[2] = pu256Src->au64[2]; + pu256Dst->au64[3] = pu256Src->au64[3]; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu256Src, IEM_ACCESS_DATA_R); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Fetches a data oword (octo word) at an aligned address, generally AVX + * related, longjmp on error. + * + * Raises \#GP(0) if not aligned. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu256Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +void iemMemFetchDataU256AlignedSseJmp(PVMCPUCC pVCpu, PRTUINT256U pu256Dst, uint8_t iSegReg, + RTGCPTR GCPtrMem) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PCRTUINT256U pu256Src = (PCRTUINT256U)iemMemMapJmp(pVCpu, sizeof(*pu256Src), iSegReg, GCPtrMem, IEM_ACCESS_DATA_R, + (sizeof(*pu256Src) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + pu256Dst->au64[0] = pu256Src->au64[0]; + pu256Dst->au64[1] = pu256Src->au64[1]; + pu256Dst->au64[2] = pu256Src->au64[2]; + pu256Dst->au64[3] = pu256Src->au64[3]; + iemMemCommitAndUnmapJmp(pVCpu, (void *)pu256Src, IEM_ACCESS_DATA_R); +} +#endif + + + +/** + * Fetches a descriptor register (lgdt, lidt). + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pcbLimit Where to return the limit. + * @param pGCPtrBase Where to return the base. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param enmOpSize The effective operand size. + */ +VBOXSTRICTRC iemMemFetchDataXdtr(PVMCPUCC pVCpu, uint16_t *pcbLimit, PRTGCPTR pGCPtrBase, uint8_t iSegReg, + RTGCPTR GCPtrMem, IEMMODE enmOpSize) RT_NOEXCEPT +{ + /* + * Just like SIDT and SGDT, the LIDT and LGDT instructions are a + * little special: + * - The two reads are done separately. + * - Operand size override works in 16-bit and 32-bit code, but 64-bit. + * - We suspect the 386 to actually commit the limit before the base in + * some cases (search for 386 in bs3CpuBasic2_lidt_lgdt_One). We + * don't try emulate this eccentric behavior, because it's not well + * enough understood and rather hard to trigger. + * - The 486 seems to do a dword limit read when the operand size is 32-bit. + */ + VBOXSTRICTRC rcStrict; + if (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT) + { + rcStrict = iemMemFetchDataU16(pVCpu, pcbLimit, iSegReg, GCPtrMem); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemMemFetchDataU64(pVCpu, pGCPtrBase, iSegReg, GCPtrMem + 2); + } + else + { + uint32_t uTmp = 0; /* (Visual C++ maybe used uninitialized) */ + if (enmOpSize == IEMMODE_32BIT) + { + if (IEM_GET_TARGET_CPU(pVCpu) != IEMTARGETCPU_486) + { + rcStrict = iemMemFetchDataU16(pVCpu, pcbLimit, iSegReg, GCPtrMem); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemMemFetchDataU32(pVCpu, &uTmp, iSegReg, GCPtrMem + 2); + } + else + { + rcStrict = iemMemFetchDataU32(pVCpu, &uTmp, iSegReg, GCPtrMem); + if (rcStrict == VINF_SUCCESS) + { + *pcbLimit = (uint16_t)uTmp; + rcStrict = iemMemFetchDataU32(pVCpu, &uTmp, iSegReg, GCPtrMem + 2); + } + } + if (rcStrict == VINF_SUCCESS) + *pGCPtrBase = uTmp; + } + else + { + rcStrict = iemMemFetchDataU16(pVCpu, pcbLimit, iSegReg, GCPtrMem); + if (rcStrict == VINF_SUCCESS) + { + rcStrict = iemMemFetchDataU32(pVCpu, &uTmp, iSegReg, GCPtrMem + 2); + if (rcStrict == VINF_SUCCESS) + *pGCPtrBase = uTmp & UINT32_C(0x00ffffff); + } + } + } + return rcStrict; +} + + + +/** + * Stores a data byte. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u8Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU8(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint8_t u8Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint8_t *pu8Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu8Dst, sizeof(*pu8Dst), iSegReg, GCPtrMem, IEM_ACCESS_DATA_W, 0); + if (rc == VINF_SUCCESS) + { + *pu8Dst = u8Value; + rc = iemMemCommitAndUnmap(pVCpu, pu8Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data byte, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u8Value The value to store. + */ +void iemMemStoreDataU8Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint8_t u8Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint8_t *pu8Dst = (uint8_t *)iemMemMapJmp(pVCpu, sizeof(*pu8Dst), iSegReg, GCPtrMem, IEM_ACCESS_DATA_W, 0); + *pu8Dst = u8Value; + iemMemCommitAndUnmapJmp(pVCpu, pu8Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data word. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u16Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU16(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint16_t u16Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint16_t *pu16Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Dst, sizeof(*pu16Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu16Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Dst = u16Value; + rc = iemMemCommitAndUnmap(pVCpu, pu16Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data word, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u16Value The value to store. + */ +void iemMemStoreDataU16Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint16_t u16Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint16_t *pu16Dst = (uint16_t *)iemMemMapJmp(pVCpu, sizeof(*pu16Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu16Dst) - 1); + *pu16Dst = u16Value; + iemMemCommitAndUnmapJmp(pVCpu, pu16Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data dword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u32Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU32(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint32_t u32Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint32_t *pu32Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Dst, sizeof(*pu32Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu32Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Dst = u32Value; + rc = iemMemCommitAndUnmap(pVCpu, pu32Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data dword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u32Value The value to store. + */ +void iemMemStoreDataU32Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint32_t u32Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint32_t *pu32Dst = (uint32_t *)iemMemMapJmp(pVCpu, sizeof(*pu32Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu32Dst) - 1); + *pu32Dst = u32Value; + iemMemCommitAndUnmapJmp(pVCpu, pu32Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data qword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u64Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU64(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint64_t u64Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint64_t *pu64Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Dst, sizeof(*pu64Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu64Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = u64Value; + rc = iemMemCommitAndUnmap(pVCpu, pu64Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data qword, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u64Value The value to store. + */ +void iemMemStoreDataU64Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, uint64_t u64Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + uint64_t *pu64Dst = (uint64_t *)iemMemMapJmp(pVCpu, sizeof(*pu64Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, sizeof(*pu64Dst) - 1); + *pu64Dst = u64Value; + iemMemCommitAndUnmapJmp(pVCpu, pu64Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data dqword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u128Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU128(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, RTUINT128U u128Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PRTUINT128U pu128Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu128Dst, sizeof(*pu128Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, 0 /* NO_AC variant */); + if (rc == VINF_SUCCESS) + { + pu128Dst->au64[0] = u128Value.au64[0]; + pu128Dst->au64[1] = u128Value.au64[1]; + rc = iemMemCommitAndUnmap(pVCpu, pu128Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data dqword, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u128Value The value to store. + */ +void iemMemStoreDataU128Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, RTUINT128U u128Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PRTUINT128U pu128Dst = (PRTUINT128U)iemMemMapJmp(pVCpu, sizeof(*pu128Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, 0 /* NO_AC variant */); + pu128Dst->au64[0] = u128Value.au64[0]; + pu128Dst->au64[1] = u128Value.au64[1]; + iemMemCommitAndUnmapJmp(pVCpu, pu128Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data dqword, SSE aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u128Value The value to store. + */ +VBOXSTRICTRC iemMemStoreDataU128AlignedSse(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, RTUINT128U u128Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PRTUINT128U pu128Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu128Dst, sizeof(*pu128Dst), iSegReg, GCPtrMem, IEM_ACCESS_DATA_W, + (sizeof(*pu128Dst) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + if (rc == VINF_SUCCESS) + { + pu128Dst->au64[0] = u128Value.au64[0]; + pu128Dst->au64[1] = u128Value.au64[1]; + rc = iemMemCommitAndUnmap(pVCpu, pu128Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data dqword, SSE aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param u128Value The value to store. + */ +void iemMemStoreDataU128AlignedSseJmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, + RTUINT128U u128Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PRTUINT128U pu128Dst = (PRTUINT128U)iemMemMapJmp(pVCpu, sizeof(*pu128Dst), iSegReg, GCPtrMem, IEM_ACCESS_DATA_W, + (sizeof(*pu128Dst) - 1) | IEM_MEMMAP_F_ALIGN_GP | IEM_MEMMAP_F_ALIGN_SSE); + pu128Dst->au64[0] = u128Value.au64[0]; + pu128Dst->au64[1] = u128Value.au64[1]; + iemMemCommitAndUnmapJmp(pVCpu, pu128Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data dqword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param pu256Value Pointer to the value to store. + */ +VBOXSTRICTRC iemMemStoreDataU256(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, PCRTUINT256U pu256Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PRTUINT256U pu256Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu256Dst, sizeof(*pu256Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, 0 /* NO_AC variant */); + if (rc == VINF_SUCCESS) + { + pu256Dst->au64[0] = pu256Value->au64[0]; + pu256Dst->au64[1] = pu256Value->au64[1]; + pu256Dst->au64[2] = pu256Value->au64[2]; + pu256Dst->au64[3] = pu256Value->au64[3]; + rc = iemMemCommitAndUnmap(pVCpu, pu256Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data dqword, longjmp on error. + * + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param pu256Value Pointer to the value to store. + */ +void iemMemStoreDataU256Jmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, PCRTUINT256U pu256Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PRTUINT256U pu256Dst = (PRTUINT256U)iemMemMapJmp(pVCpu, sizeof(*pu256Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, 0 /* NO_AC variant */); + pu256Dst->au64[0] = pu256Value->au64[0]; + pu256Dst->au64[1] = pu256Value->au64[1]; + pu256Dst->au64[2] = pu256Value->au64[2]; + pu256Dst->au64[3] = pu256Value->au64[3]; + iemMemCommitAndUnmapJmp(pVCpu, pu256Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a data dqword, AVX \#GP(0) aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param pu256Value Pointer to the value to store. + */ +VBOXSTRICTRC iemMemStoreDataU256AlignedAvx(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, PCRTUINT256U pu256Value) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + PRTUINT256U pu256Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu256Dst, sizeof(*pu256Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, (sizeof(*pu256Dst) - 1) | IEM_MEMMAP_F_ALIGN_GP); + if (rc == VINF_SUCCESS) + { + pu256Dst->au64[0] = pu256Value->au64[0]; + pu256Dst->au64[1] = pu256Value->au64[1]; + pu256Dst->au64[2] = pu256Value->au64[2]; + pu256Dst->au64[3] = pu256Value->au64[3]; + rc = iemMemCommitAndUnmap(pVCpu, pu256Dst, IEM_ACCESS_DATA_W); + } + return rc; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Stores a data dqword, AVX aligned. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + * @param pu256Value Pointer to the value to store. + */ +void iemMemStoreDataU256AlignedAvxJmp(PVMCPUCC pVCpu, uint8_t iSegReg, RTGCPTR GCPtrMem, + PCRTUINT256U pu256Value) IEM_NOEXCEPT_MAY_LONGJMP +{ + /* The lazy approach for now... */ + PRTUINT256U pu256Dst = (PRTUINT256U)iemMemMapJmp(pVCpu, sizeof(*pu256Dst), iSegReg, GCPtrMem, + IEM_ACCESS_DATA_W, (sizeof(*pu256Dst) - 1) | IEM_MEMMAP_F_ALIGN_GP); + pu256Dst->au64[0] = pu256Value->au64[0]; + pu256Dst->au64[1] = pu256Value->au64[1]; + pu256Dst->au64[2] = pu256Value->au64[2]; + pu256Dst->au64[3] = pu256Value->au64[3]; + iemMemCommitAndUnmapJmp(pVCpu, pu256Dst, IEM_ACCESS_DATA_W); +} +#endif + + +/** + * Stores a descriptor register (sgdt, sidt). + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbLimit The limit. + * @param GCPtrBase The base address. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemStoreDataXdtr(PVMCPUCC pVCpu, uint16_t cbLimit, RTGCPTR GCPtrBase, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* + * The SIDT and SGDT instructions actually stores the data using two + * independent writes (see bs3CpuBasic2_sidt_sgdt_One). The instructions + * does not respond to opsize prefixes. + */ + VBOXSTRICTRC rcStrict = iemMemStoreDataU16(pVCpu, iSegReg, GCPtrMem, cbLimit); + if (rcStrict == VINF_SUCCESS) + { + if (pVCpu->iem.s.enmCpuMode == IEMMODE_16BIT) + rcStrict = iemMemStoreDataU32(pVCpu, iSegReg, GCPtrMem + 2, + IEM_GET_TARGET_CPU(pVCpu) <= IEMTARGETCPU_286 + ? (uint32_t)GCPtrBase | UINT32_C(0xff000000) : (uint32_t)GCPtrBase); + else if (pVCpu->iem.s.enmCpuMode == IEMMODE_32BIT) + rcStrict = iemMemStoreDataU32(pVCpu, iSegReg, GCPtrMem + 2, (uint32_t)GCPtrBase); + else + rcStrict = iemMemStoreDataU64(pVCpu, iSegReg, GCPtrMem + 2, GCPtrBase); + } + return rcStrict; +} + + +/** + * Pushes a word onto the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16Value The value to push. + */ +VBOXSTRICTRC iemMemStackPushU16(PVMCPUCC pVCpu, uint16_t u16Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPush(pVCpu, 2, &uNewRsp); + + /* Write the word the lazy way. */ + uint16_t *pu16Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Dst, sizeof(*pu16Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu16Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Dst = u16Value; + rc = iemMemCommitAndUnmap(pVCpu, pu16Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + + return rc; +} + + +/** + * Pushes a dword onto the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u32Value The value to push. + */ +VBOXSTRICTRC iemMemStackPushU32(PVMCPUCC pVCpu, uint32_t u32Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPush(pVCpu, 4, &uNewRsp); + + /* Write the dword the lazy way. */ + uint32_t *pu32Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Dst, sizeof(*pu32Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu32Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Dst = u32Value; + rc = iemMemCommitAndUnmap(pVCpu, pu32Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + + return rc; +} + + +/** + * Pushes a dword segment register value onto the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u32Value The value to push. + */ +VBOXSTRICTRC iemMemStackPushU32SReg(PVMCPUCC pVCpu, uint32_t u32Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPush(pVCpu, 4, &uNewRsp); + + /* The intel docs talks about zero extending the selector register + value. My actual intel CPU here might be zero extending the value + but it still only writes the lower word... */ + /** @todo Test this on new HW and on AMD and in 64-bit mode. Also test what + * happens when crossing an electric page boundrary, is the high word checked + * for write accessibility or not? Probably it is. What about segment limits? + * It appears this behavior is also shared with trap error codes. + * + * Docs indicate the behavior changed maybe in Pentium or Pentium Pro. Check + * ancient hardware when it actually did change. */ + uint16_t *pu16Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Dst, sizeof(uint32_t), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_RW, sizeof(*pu16Dst) - 1); /** @todo 2 or 4 alignment check for PUSH SS? */ + if (rc == VINF_SUCCESS) + { + *pu16Dst = (uint16_t)u32Value; + rc = iemMemCommitAndUnmap(pVCpu, pu16Dst, IEM_ACCESS_STACK_RW); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + + return rc; +} + + +/** + * Pushes a qword onto the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u64Value The value to push. + */ +VBOXSTRICTRC iemMemStackPushU64(PVMCPUCC pVCpu, uint64_t u64Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPush(pVCpu, 8, &uNewRsp); + + /* Write the word the lazy way. */ + uint64_t *pu64Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Dst, sizeof(*pu64Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu64Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = u64Value; + rc = iemMemCommitAndUnmap(pVCpu, pu64Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + + return rc; +} + + +/** + * Pops a word from the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16Value Where to store the popped value. + */ +VBOXSTRICTRC iemMemStackPopU16(PVMCPUCC pVCpu, uint16_t *pu16Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPop(pVCpu, 2, &uNewRsp); + + /* Write the word the lazy way. */ + uint16_t const *pu16Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Src, sizeof(*pu16Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu16Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Value = *pu16Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu16Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + } + + return rc; +} + + +/** + * Pops a dword from the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32Value Where to store the popped value. + */ +VBOXSTRICTRC iemMemStackPopU32(PVMCPUCC pVCpu, uint32_t *pu32Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPop(pVCpu, 4, &uNewRsp); + + /* Write the word the lazy way. */ + uint32_t const *pu32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Src, sizeof(*pu32Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu32Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Value = *pu32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu32Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + } + + return rc; +} + + +/** + * Pops a qword from the stack. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Value Where to store the popped value. + */ +VBOXSTRICTRC iemMemStackPopU64(PVMCPUCC pVCpu, uint64_t *pu64Value) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + uint64_t uNewRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPop(pVCpu, 8, &uNewRsp); + + /* Write the word the lazy way. */ + uint64_t const *pu64Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Src, sizeof(*pu64Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu64Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Value = *pu64Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu64Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rc == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + } + + return rc; +} + + +/** + * Pushes a word onto the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u16Value The value to push. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPushU16Ex(PVMCPUCC pVCpu, uint16_t u16Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPushEx(pVCpu, &NewRsp, 2); + + /* Write the word the lazy way. */ + uint16_t *pu16Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Dst, sizeof(*pu16Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu16Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Dst = u16Value; + rc = iemMemCommitAndUnmap(pVCpu, pu16Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + *pTmpRsp = NewRsp; + + return rc; +} + + +/** + * Pushes a dword onto the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u32Value The value to push. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPushU32Ex(PVMCPUCC pVCpu, uint32_t u32Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPushEx(pVCpu, &NewRsp, 4); + + /* Write the word the lazy way. */ + uint32_t *pu32Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Dst, sizeof(*pu32Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu32Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Dst = u32Value; + rc = iemMemCommitAndUnmap(pVCpu, pu32Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + *pTmpRsp = NewRsp; + + return rc; +} + + +/** + * Pushes a dword onto the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param u64Value The value to push. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPushU64Ex(PVMCPUCC pVCpu, uint64_t u64Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPushEx(pVCpu, &NewRsp, 8); + + /* Write the word the lazy way. */ + uint64_t *pu64Dst; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Dst, sizeof(*pu64Dst), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, sizeof(*pu64Dst) - 1); + if (rc == VINF_SUCCESS) + { + *pu64Dst = u64Value; + rc = iemMemCommitAndUnmap(pVCpu, pu64Dst, IEM_ACCESS_STACK_W); + } + + /* Commit the new RSP value unless we an access handler made trouble. */ + if (rc == VINF_SUCCESS) + *pTmpRsp = NewRsp; + + return rc; +} + + +/** + * Pops a word from the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16Value Where to store the popped value. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPopU16Ex(PVMCPUCC pVCpu, uint16_t *pu16Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPopEx(pVCpu, &NewRsp, 2); + + /* Write the word the lazy way. */ + uint16_t const *pu16Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Src, sizeof(*pu16Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu16Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu16Value = *pu16Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu16Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rc == VINF_SUCCESS) + *pTmpRsp = NewRsp; + } + + return rc; +} + + +/** + * Pops a dword from the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32Value Where to store the popped value. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPopU32Ex(PVMCPUCC pVCpu, uint32_t *pu32Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPopEx(pVCpu, &NewRsp, 4); + + /* Write the word the lazy way. */ + uint32_t const *pu32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Src, sizeof(*pu32Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu32Src) - 1); + if (rc == VINF_SUCCESS) + { + *pu32Value = *pu32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu32Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rc == VINF_SUCCESS) + *pTmpRsp = NewRsp; + } + + return rc; +} + + +/** + * Pops a qword from the stack, using a temporary stack pointer. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Value Where to store the popped value. + * @param pTmpRsp Pointer to the temporary stack pointer. + */ +VBOXSTRICTRC iemMemStackPopU64Ex(PVMCPUCC pVCpu, uint64_t *pu64Value, PRTUINT64U pTmpRsp) RT_NOEXCEPT +{ + /* Increment the stack pointer. */ + RTUINT64U NewRsp = *pTmpRsp; + RTGCPTR GCPtrTop = iemRegGetRspForPopEx(pVCpu, &NewRsp, 8); + + /* Write the word the lazy way. */ + uint64_t const *pu64Src; + VBOXSTRICTRC rcStrict = iemMemMap(pVCpu, (void **)&pu64Src, sizeof(*pu64Src), X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_R, sizeof(*pu64Src) - 1); + if (rcStrict == VINF_SUCCESS) + { + *pu64Value = *pu64Src; + rcStrict = iemMemCommitAndUnmap(pVCpu, (void *)pu64Src, IEM_ACCESS_STACK_R); + + /* Commit the new RSP value. */ + if (rcStrict == VINF_SUCCESS) + *pTmpRsp = NewRsp; + } + + return rcStrict; +} + + +/** + * Begin a special stack push (used by interrupt, exceptions and such). + * + * This will raise \#SS or \#PF if appropriate. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbMem The number of bytes to push onto the stack. + * @param cbAlign The alignment mask (7, 3, 1). + * @param ppvMem Where to return the pointer to the stack memory. + * As with the other memory functions this could be + * direct access or bounce buffered access, so + * don't commit register until the commit call + * succeeds. + * @param puNewRsp Where to return the new RSP value. This must be + * passed unchanged to + * iemMemStackPushCommitSpecial(). + */ +VBOXSTRICTRC iemMemStackPushBeginSpecial(PVMCPUCC pVCpu, size_t cbMem, uint32_t cbAlign, + void **ppvMem, uint64_t *puNewRsp) RT_NOEXCEPT +{ + Assert(cbMem < UINT8_MAX); + RTGCPTR GCPtrTop = iemRegGetRspForPush(pVCpu, (uint8_t)cbMem, puNewRsp); + return iemMemMap(pVCpu, ppvMem, cbMem, X86_SREG_SS, GCPtrTop, + IEM_ACCESS_STACK_W, cbAlign); +} + + +/** + * Commits a special stack push (started by iemMemStackPushBeginSpecial). + * + * This will update the rSP. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The pointer returned by + * iemMemStackPushBeginSpecial(). + * @param uNewRsp The new RSP value returned by + * iemMemStackPushBeginSpecial(). + */ +VBOXSTRICTRC iemMemStackPushCommitSpecial(PVMCPUCC pVCpu, void *pvMem, uint64_t uNewRsp) RT_NOEXCEPT +{ + VBOXSTRICTRC rcStrict = iemMemCommitAndUnmap(pVCpu, pvMem, IEM_ACCESS_STACK_W); + if (rcStrict == VINF_SUCCESS) + pVCpu->cpum.GstCtx.rsp = uNewRsp; + return rcStrict; +} + + +/** + * Begin a special stack pop (used by iret, retf and such). + * + * This will raise \#SS or \#PF if appropriate. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param cbMem The number of bytes to pop from the stack. + * @param cbAlign The alignment mask (7, 3, 1). + * @param ppvMem Where to return the pointer to the stack memory. + * @param puNewRsp Where to return the new RSP value. This must be + * assigned to CPUMCTX::rsp manually some time + * after iemMemStackPopDoneSpecial() has been + * called. + */ +VBOXSTRICTRC iemMemStackPopBeginSpecial(PVMCPUCC pVCpu, size_t cbMem, uint32_t cbAlign, + void const **ppvMem, uint64_t *puNewRsp) RT_NOEXCEPT +{ + Assert(cbMem < UINT8_MAX); + RTGCPTR GCPtrTop = iemRegGetRspForPop(pVCpu, (uint8_t)cbMem, puNewRsp); + return iemMemMap(pVCpu, (void **)ppvMem, cbMem, X86_SREG_SS, GCPtrTop, IEM_ACCESS_STACK_R, cbAlign); +} + + +/** + * Continue a special stack pop (used by iret and retf), for the purpose of + * retrieving a new stack pointer. + * + * This will raise \#SS or \#PF if appropriate. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param off Offset from the top of the stack. This is zero + * except in the retf case. + * @param cbMem The number of bytes to pop from the stack. + * @param ppvMem Where to return the pointer to the stack memory. + * @param uCurNewRsp The current uncommitted RSP value. (No need to + * return this because all use of this function is + * to retrieve a new value and anything we return + * here would be discarded.) + */ +VBOXSTRICTRC iemMemStackPopContinueSpecial(PVMCPUCC pVCpu, size_t off, size_t cbMem, + void const **ppvMem, uint64_t uCurNewRsp) RT_NOEXCEPT +{ + Assert(cbMem < UINT8_MAX); + + /* The essense of iemRegGetRspForPopEx and friends: */ /** @todo put this into a inlined function? */ + RTGCPTR GCPtrTop; + if (pVCpu->iem.s.enmCpuMode == IEMMODE_64BIT) + GCPtrTop = uCurNewRsp; + else if (pVCpu->cpum.GstCtx.ss.Attr.n.u1DefBig) + GCPtrTop = (uint32_t)uCurNewRsp; + else + GCPtrTop = (uint16_t)uCurNewRsp; + + return iemMemMap(pVCpu, (void **)ppvMem, cbMem, X86_SREG_SS, GCPtrTop + off, IEM_ACCESS_STACK_R, + 0 /* checked in iemMemStackPopBeginSpecial */); +} + + +/** + * Done with a special stack pop (started by iemMemStackPopBeginSpecial or + * iemMemStackPopContinueSpecial). + * + * The caller will manually commit the rSP. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pvMem The pointer returned by + * iemMemStackPopBeginSpecial() or + * iemMemStackPopContinueSpecial(). + */ +VBOXSTRICTRC iemMemStackPopDoneSpecial(PVMCPUCC pVCpu, void const *pvMem) RT_NOEXCEPT +{ + return iemMemCommitAndUnmap(pVCpu, (void *)pvMem, IEM_ACCESS_STACK_R); +} + + +/** + * Fetches a system table byte. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pbDst Where to return the byte. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchSysU8(PVMCPUCC pVCpu, uint8_t *pbDst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint8_t const *pbSrc; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pbSrc, sizeof(*pbSrc), iSegReg, GCPtrMem, IEM_ACCESS_SYS_R, 0); + if (rc == VINF_SUCCESS) + { + *pbDst = *pbSrc; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pbSrc, IEM_ACCESS_SYS_R); + } + return rc; +} + + +/** + * Fetches a system table word. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu16Dst Where to return the word. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchSysU16(PVMCPUCC pVCpu, uint16_t *pu16Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint16_t const *pu16Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu16Src, sizeof(*pu16Src), iSegReg, GCPtrMem, IEM_ACCESS_SYS_R, 0); + if (rc == VINF_SUCCESS) + { + *pu16Dst = *pu16Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu16Src, IEM_ACCESS_SYS_R); + } + return rc; +} + + +/** + * Fetches a system table dword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu32Dst Where to return the dword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchSysU32(PVMCPUCC pVCpu, uint32_t *pu32Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint32_t const *pu32Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu32Src, sizeof(*pu32Src), iSegReg, GCPtrMem, IEM_ACCESS_SYS_R, 0); + if (rc == VINF_SUCCESS) + { + *pu32Dst = *pu32Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu32Src, IEM_ACCESS_SYS_R); + } + return rc; +} + + +/** + * Fetches a system table qword. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pu64Dst Where to return the qword. + * @param iSegReg The index of the segment register to use for + * this access. The base and limits are checked. + * @param GCPtrMem The address of the guest memory. + */ +VBOXSTRICTRC iemMemFetchSysU64(PVMCPUCC pVCpu, uint64_t *pu64Dst, uint8_t iSegReg, RTGCPTR GCPtrMem) RT_NOEXCEPT +{ + /* The lazy approach for now... */ + uint64_t const *pu64Src; + VBOXSTRICTRC rc = iemMemMap(pVCpu, (void **)&pu64Src, sizeof(*pu64Src), iSegReg, GCPtrMem, IEM_ACCESS_SYS_R, 0); + if (rc == VINF_SUCCESS) + { + *pu64Dst = *pu64Src; + rc = iemMemCommitAndUnmap(pVCpu, (void *)pu64Src, IEM_ACCESS_SYS_R); + } + return rc; +} + + +/** + * Fetches a descriptor table entry with caller specified error code. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pDesc Where to return the descriptor table entry. + * @param uSel The selector which table entry to fetch. + * @param uXcpt The exception to raise on table lookup error. + * @param uErrorCode The error code associated with the exception. + */ +static VBOXSTRICTRC iemMemFetchSelDescWithErr(PVMCPUCC pVCpu, PIEMSELDESC pDesc, uint16_t uSel, + uint8_t uXcpt, uint16_t uErrorCode) RT_NOEXCEPT +{ + AssertPtr(pDesc); + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_GDTR | CPUMCTX_EXTRN_LDTR); + + /** @todo did the 286 require all 8 bytes to be accessible? */ + /* + * Get the selector table base and check bounds. + */ + RTGCPTR GCPtrBase; + if (uSel & X86_SEL_LDT) + { + if ( !pVCpu->cpum.GstCtx.ldtr.Attr.n.u1Present + || (uSel | X86_SEL_RPL_LDT) > pVCpu->cpum.GstCtx.ldtr.u32Limit ) + { + Log(("iemMemFetchSelDesc: LDT selector %#x is out of bounds (%3x) or ldtr is NP (%#x)\n", + uSel, pVCpu->cpum.GstCtx.ldtr.u32Limit, pVCpu->cpum.GstCtx.ldtr.Sel)); + return iemRaiseXcptOrInt(pVCpu, 0, uXcpt, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + uErrorCode, 0); + } + + Assert(pVCpu->cpum.GstCtx.ldtr.Attr.n.u1Present); + GCPtrBase = pVCpu->cpum.GstCtx.ldtr.u64Base; + } + else + { + if ((uSel | X86_SEL_RPL_LDT) > pVCpu->cpum.GstCtx.gdtr.cbGdt) + { + Log(("iemMemFetchSelDesc: GDT selector %#x is out of bounds (%3x)\n", uSel, pVCpu->cpum.GstCtx.gdtr.cbGdt)); + return iemRaiseXcptOrInt(pVCpu, 0, uXcpt, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, + uErrorCode, 0); + } + GCPtrBase = pVCpu->cpum.GstCtx.gdtr.pGdt; + } + + /* + * Read the legacy descriptor and maybe the long mode extensions if + * required. + */ + VBOXSTRICTRC rcStrict; + if (IEM_GET_TARGET_CPU(pVCpu) > IEMTARGETCPU_286) + rcStrict = iemMemFetchSysU64(pVCpu, &pDesc->Legacy.u, UINT8_MAX, GCPtrBase + (uSel & X86_SEL_MASK)); + else + { + rcStrict = iemMemFetchSysU16(pVCpu, &pDesc->Legacy.au16[0], UINT8_MAX, GCPtrBase + (uSel & X86_SEL_MASK) + 0); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemMemFetchSysU16(pVCpu, &pDesc->Legacy.au16[1], UINT8_MAX, GCPtrBase + (uSel & X86_SEL_MASK) + 2); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemMemFetchSysU16(pVCpu, &pDesc->Legacy.au16[2], UINT8_MAX, GCPtrBase + (uSel & X86_SEL_MASK) + 4); + if (rcStrict == VINF_SUCCESS) + pDesc->Legacy.au16[3] = 0; + else + return rcStrict; + } + + if (rcStrict == VINF_SUCCESS) + { + if ( !IEM_IS_LONG_MODE(pVCpu) + || pDesc->Legacy.Gen.u1DescType) + pDesc->Long.au64[1] = 0; + else if ( (uint32_t)(uSel | X86_SEL_RPL_LDT) + 8 + <= (uSel & X86_SEL_LDT ? pVCpu->cpum.GstCtx.ldtr.u32Limit : pVCpu->cpum.GstCtx.gdtr.cbGdt)) + rcStrict = iemMemFetchSysU64(pVCpu, &pDesc->Long.au64[1], UINT8_MAX, GCPtrBase + (uSel | X86_SEL_RPL_LDT) + 1); + else + { + Log(("iemMemFetchSelDesc: system selector %#x is out of bounds\n", uSel)); + /** @todo is this the right exception? */ + return iemRaiseXcptOrInt(pVCpu, 0, uXcpt, IEM_XCPT_FLAGS_T_CPU_XCPT | IEM_XCPT_FLAGS_ERR, uErrorCode, 0); + } + } + return rcStrict; +} + + +/** + * Fetches a descriptor table entry. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param pDesc Where to return the descriptor table entry. + * @param uSel The selector which table entry to fetch. + * @param uXcpt The exception to raise on table lookup error. + */ +VBOXSTRICTRC iemMemFetchSelDesc(PVMCPUCC pVCpu, PIEMSELDESC pDesc, uint16_t uSel, uint8_t uXcpt) RT_NOEXCEPT +{ + return iemMemFetchSelDescWithErr(pVCpu, pDesc, uSel, uXcpt, uSel & X86_SEL_MASK_OFF_RPL); +} + + +/** + * Marks the selector descriptor as accessed (only non-system descriptors). + * + * This function ASSUMES that iemMemFetchSelDesc has be called previously and + * will therefore skip the limit checks. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param uSel The selector. + */ +VBOXSTRICTRC iemMemMarkSelDescAccessed(PVMCPUCC pVCpu, uint16_t uSel) RT_NOEXCEPT +{ + /* + * Get the selector table base and calculate the entry address. + */ + RTGCPTR GCPtr = uSel & X86_SEL_LDT + ? pVCpu->cpum.GstCtx.ldtr.u64Base + : pVCpu->cpum.GstCtx.gdtr.pGdt; + GCPtr += uSel & X86_SEL_MASK; + + /* + * ASMAtomicBitSet will assert if the address is misaligned, so do some + * ugly stuff to avoid this. This will make sure it's an atomic access + * as well more or less remove any question about 8-bit or 32-bit accesss. + */ + VBOXSTRICTRC rcStrict; + uint32_t volatile *pu32; + if ((GCPtr & 3) == 0) + { + /* The normal case, map the 32-bit bits around the accessed bit (40). */ + GCPtr += 2 + 2; + rcStrict = iemMemMap(pVCpu, (void **)&pu32, 4, UINT8_MAX, GCPtr, IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + ASMAtomicBitSet(pu32, 8); /* X86_SEL_TYPE_ACCESSED is 1, but it is preceeded by u8BaseHigh1. */ + } + else + { + /* The misaligned GDT/LDT case, map the whole thing. */ + rcStrict = iemMemMap(pVCpu, (void **)&pu32, 8, UINT8_MAX, GCPtr, IEM_ACCESS_SYS_RW, 0); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + switch ((uintptr_t)pu32 & 3) + { + case 0: ASMAtomicBitSet(pu32, 40 + 0 - 0); break; + case 1: ASMAtomicBitSet((uint8_t volatile *)pu32 + 3, 40 + 0 - 24); break; + case 2: ASMAtomicBitSet((uint8_t volatile *)pu32 + 2, 40 + 0 - 16); break; + case 3: ASMAtomicBitSet((uint8_t volatile *)pu32 + 1, 40 + 0 - 8); break; + } + } + + return iemMemCommitAndUnmap(pVCpu, (void *)pu32, IEM_ACCESS_SYS_RW); +} + +/** @} */ + +/** @name Opcode Helpers. + * @{ + */ + +/** + * Calculates the effective address of a ModR/M memory operand. + * + * Meant to be used via IEM_MC_CALC_RM_EFF_ADDR. + * + * @return Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param bRm The ModRM byte. + * @param cbImm The size of any immediate following the + * effective address opcode bytes. Important for + * RIP relative addressing. + * @param pGCPtrEff Where to return the effective address. + */ +VBOXSTRICTRC iemOpHlpCalcRmEffAddr(PVMCPUCC pVCpu, uint8_t bRm, uint8_t cbImm, PRTGCPTR pGCPtrEff) RT_NOEXCEPT +{ + Log5(("iemOpHlpCalcRmEffAddr: bRm=%#x\n", bRm)); +# define SET_SS_DEF() \ + do \ + { \ + if (!(pVCpu->iem.s.fPrefixes & IEM_OP_PRF_SEG_MASK)) \ + pVCpu->iem.s.iEffSeg = X86_SREG_SS; \ + } while (0) + + if (pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT) + { +/** @todo Check the effective address size crap! */ + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT) + { + uint16_t u16EffAddr; + + /* Handle the disp16 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 6) + IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); + else + { + /* Get the displacment. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: u16EffAddr = 0; break; + case 1: IEM_OPCODE_GET_NEXT_S8_SX_U16(&u16EffAddr); break; + case 2: IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); break; + default: AssertFailedReturn(VERR_IEM_IPE_1); /* (caller checked for these) */ + } + + /* Add the base and index registers to the disp. */ + switch (bRm & X86_MODRM_RM_MASK) + { + case 0: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.si; break; + case 1: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.di; break; + case 2: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.si; SET_SS_DEF(); break; + case 3: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.di; SET_SS_DEF(); break; + case 4: u16EffAddr += pVCpu->cpum.GstCtx.si; break; + case 5: u16EffAddr += pVCpu->cpum.GstCtx.di; break; + case 6: u16EffAddr += pVCpu->cpum.GstCtx.bp; SET_SS_DEF(); break; + case 7: u16EffAddr += pVCpu->cpum.GstCtx.bx; break; + } + } + + *pGCPtrEff = u16EffAddr; + } + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + uint32_t u32EffAddr; + + /* Handle the disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + IEM_OPCODE_GET_NEXT_U32(&u32EffAddr); + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK)) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: /* SIB */ + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch ((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: u32EffAddr = 0; /*none */ break; + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + u32EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch (bSib & X86_SIB_BASE_MASK) + { + case 0: u32EffAddr += pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr += pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr += pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr += pVCpu->cpum.GstCtx.ebx; break; + case 4: u32EffAddr += pVCpu->cpum.GstCtx.esp; SET_SS_DEF(); break; + case 5: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + u32EffAddr += pVCpu->cpum.GstCtx.ebp; + SET_SS_DEF(); + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + } + break; + case 6: u32EffAddr += pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr += pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + break; + } + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; SET_SS_DEF(); break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u32EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + break; + } + default: + AssertFailedReturn(VERR_IEM_IPE_2); /* (caller checked for these) */ + } + + } + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT) + *pGCPtrEff = u32EffAddr; + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT); + *pGCPtrEff = u32EffAddr & UINT16_MAX; + } + } + } + else + { + uint64_t u64EffAddr; + + /* Handle the rip+disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + { + IEM_OPCODE_GET_NEXT_S32_SX_U64(&u64EffAddr); + u64EffAddr += pVCpu->cpum.GstCtx.rip + IEM_GET_INSTR_LEN(pVCpu) + cbImm; + } + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; SET_SS_DEF(); break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + /* SIB */ + case 4: + case 12: + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch (((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) | pVCpu->iem.s.uRexIndex) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr = 0; /*none */ break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr = pVCpu->cpum.GstCtx.r12; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + u64EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch ((bSib & X86_SIB_BASE_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr += pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr += pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr += pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr += pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr += pVCpu->cpum.GstCtx.rsp; SET_SS_DEF(); break; + case 6: u64EffAddr += pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr += pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr += pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr += pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr += pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr += pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr += pVCpu->cpum.GstCtx.r12; break; + case 14: u64EffAddr += pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr += pVCpu->cpum.GstCtx.r15; break; + /* complicated encodings */ + case 5: + case 13: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + if (!pVCpu->iem.s.uRexB) + { + u64EffAddr += pVCpu->cpum.GstCtx.rbp; + SET_SS_DEF(); + } + else + u64EffAddr += pVCpu->cpum.GstCtx.r13; + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + } + break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; + IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u64EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET(); /* (caller checked for these) */ + } + + } + + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_64BIT) + *pGCPtrEff = u64EffAddr; + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + *pGCPtrEff = u64EffAddr & UINT32_MAX; + } + } + + Log5(("iemOpHlpCalcRmEffAddr: EffAddr=%#010RGv\n", *pGCPtrEff)); + return VINF_SUCCESS; +} + + +/** + * Calculates the effective address of a ModR/M memory operand. + * + * Meant to be used via IEM_MC_CALC_RM_EFF_ADDR. + * + * @return Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param bRm The ModRM byte. + * @param cbImm The size of any immediate following the + * effective address opcode bytes. Important for + * RIP relative addressing. + * @param pGCPtrEff Where to return the effective address. + * @param offRsp RSP displacement. + */ +VBOXSTRICTRC iemOpHlpCalcRmEffAddrEx(PVMCPUCC pVCpu, uint8_t bRm, uint8_t cbImm, PRTGCPTR pGCPtrEff, int8_t offRsp) RT_NOEXCEPT +{ + Log5(("iemOpHlpCalcRmEffAddr: bRm=%#x\n", bRm)); +# define SET_SS_DEF() \ + do \ + { \ + if (!(pVCpu->iem.s.fPrefixes & IEM_OP_PRF_SEG_MASK)) \ + pVCpu->iem.s.iEffSeg = X86_SREG_SS; \ + } while (0) + + if (pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT) + { +/** @todo Check the effective address size crap! */ + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT) + { + uint16_t u16EffAddr; + + /* Handle the disp16 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 6) + IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); + else + { + /* Get the displacment. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: u16EffAddr = 0; break; + case 1: IEM_OPCODE_GET_NEXT_S8_SX_U16(&u16EffAddr); break; + case 2: IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); break; + default: AssertFailedReturn(VERR_IEM_IPE_1); /* (caller checked for these) */ + } + + /* Add the base and index registers to the disp. */ + switch (bRm & X86_MODRM_RM_MASK) + { + case 0: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.si; break; + case 1: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.di; break; + case 2: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.si; SET_SS_DEF(); break; + case 3: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.di; SET_SS_DEF(); break; + case 4: u16EffAddr += pVCpu->cpum.GstCtx.si; break; + case 5: u16EffAddr += pVCpu->cpum.GstCtx.di; break; + case 6: u16EffAddr += pVCpu->cpum.GstCtx.bp; SET_SS_DEF(); break; + case 7: u16EffAddr += pVCpu->cpum.GstCtx.bx; break; + } + } + + *pGCPtrEff = u16EffAddr; + } + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + uint32_t u32EffAddr; + + /* Handle the disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + IEM_OPCODE_GET_NEXT_U32(&u32EffAddr); + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK)) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: /* SIB */ + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch ((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: u32EffAddr = 0; /*none */ break; + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + u32EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch (bSib & X86_SIB_BASE_MASK) + { + case 0: u32EffAddr += pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr += pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr += pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr += pVCpu->cpum.GstCtx.ebx; break; + case 4: + u32EffAddr += pVCpu->cpum.GstCtx.esp + offRsp; + SET_SS_DEF(); + break; + case 5: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + u32EffAddr += pVCpu->cpum.GstCtx.ebp; + SET_SS_DEF(); + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + } + break; + case 6: u32EffAddr += pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr += pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + break; + } + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; SET_SS_DEF(); break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u32EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + break; + } + default: + AssertFailedReturn(VERR_IEM_IPE_2); /* (caller checked for these) */ + } + + } + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT) + *pGCPtrEff = u32EffAddr; + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT); + *pGCPtrEff = u32EffAddr & UINT16_MAX; + } + } + } + else + { + uint64_t u64EffAddr; + + /* Handle the rip+disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + { + IEM_OPCODE_GET_NEXT_S32_SX_U64(&u64EffAddr); + u64EffAddr += pVCpu->cpum.GstCtx.rip + IEM_GET_INSTR_LEN(pVCpu) + cbImm; + } + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; SET_SS_DEF(); break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + /* SIB */ + case 4: + case 12: + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch (((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) | pVCpu->iem.s.uRexIndex) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr = 0; /*none */ break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr = pVCpu->cpum.GstCtx.r12; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + u64EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch ((bSib & X86_SIB_BASE_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr += pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr += pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr += pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr += pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr += pVCpu->cpum.GstCtx.rsp + offRsp; SET_SS_DEF(); break; + case 6: u64EffAddr += pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr += pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr += pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr += pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr += pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr += pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr += pVCpu->cpum.GstCtx.r12; break; + case 14: u64EffAddr += pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr += pVCpu->cpum.GstCtx.r15; break; + /* complicated encodings */ + case 5: + case 13: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + if (!pVCpu->iem.s.uRexB) + { + u64EffAddr += pVCpu->cpum.GstCtx.rbp; + SET_SS_DEF(); + } + else + u64EffAddr += pVCpu->cpum.GstCtx.r13; + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + } + break; + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; + IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u64EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET(); /* (caller checked for these) */ + } + + } + + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_64BIT) + *pGCPtrEff = u64EffAddr; + else + { + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + *pGCPtrEff = u64EffAddr & UINT32_MAX; + } + } + + Log5(("iemOpHlpCalcRmEffAddr: EffAddr=%#010RGv\n", *pGCPtrEff)); + return VINF_SUCCESS; +} + + +#ifdef IEM_WITH_SETJMP +/** + * Calculates the effective address of a ModR/M memory operand. + * + * Meant to be used via IEM_MC_CALC_RM_EFF_ADDR. + * + * May longjmp on internal error. + * + * @return The effective address. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param bRm The ModRM byte. + * @param cbImm The size of any immediate following the + * effective address opcode bytes. Important for + * RIP relative addressing. + */ +RTGCPTR iemOpHlpCalcRmEffAddrJmp(PVMCPUCC pVCpu, uint8_t bRm, uint8_t cbImm) IEM_NOEXCEPT_MAY_LONGJMP +{ + Log5(("iemOpHlpCalcRmEffAddrJmp: bRm=%#x\n", bRm)); +# define SET_SS_DEF() \ + do \ + { \ + if (!(pVCpu->iem.s.fPrefixes & IEM_OP_PRF_SEG_MASK)) \ + pVCpu->iem.s.iEffSeg = X86_SREG_SS; \ + } while (0) + + if (pVCpu->iem.s.enmCpuMode != IEMMODE_64BIT) + { +/** @todo Check the effective address size crap! */ + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT) + { + uint16_t u16EffAddr; + + /* Handle the disp16 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 6) + IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); + else + { + /* Get the displacment. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: u16EffAddr = 0; break; + case 1: IEM_OPCODE_GET_NEXT_S8_SX_U16(&u16EffAddr); break; + case 2: IEM_OPCODE_GET_NEXT_U16(&u16EffAddr); break; + default: AssertFailedStmt(IEM_DO_LONGJMP(pVCpu, VERR_IEM_IPE_1)); /* (caller checked for these) */ + } + + /* Add the base and index registers to the disp. */ + switch (bRm & X86_MODRM_RM_MASK) + { + case 0: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.si; break; + case 1: u16EffAddr += pVCpu->cpum.GstCtx.bx + pVCpu->cpum.GstCtx.di; break; + case 2: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.si; SET_SS_DEF(); break; + case 3: u16EffAddr += pVCpu->cpum.GstCtx.bp + pVCpu->cpum.GstCtx.di; SET_SS_DEF(); break; + case 4: u16EffAddr += pVCpu->cpum.GstCtx.si; break; + case 5: u16EffAddr += pVCpu->cpum.GstCtx.di; break; + case 6: u16EffAddr += pVCpu->cpum.GstCtx.bp; SET_SS_DEF(); break; + case 7: u16EffAddr += pVCpu->cpum.GstCtx.bx; break; + } + } + + Log5(("iemOpHlpCalcRmEffAddrJmp: EffAddr=%#06RX16\n", u16EffAddr)); + return u16EffAddr; + } + + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + uint32_t u32EffAddr; + + /* Handle the disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + IEM_OPCODE_GET_NEXT_U32(&u32EffAddr); + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK)) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: /* SIB */ + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch ((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) + { + case 0: u32EffAddr = pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr = pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr = pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr = pVCpu->cpum.GstCtx.ebx; break; + case 4: u32EffAddr = 0; /*none */ break; + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + u32EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch (bSib & X86_SIB_BASE_MASK) + { + case 0: u32EffAddr += pVCpu->cpum.GstCtx.eax; break; + case 1: u32EffAddr += pVCpu->cpum.GstCtx.ecx; break; + case 2: u32EffAddr += pVCpu->cpum.GstCtx.edx; break; + case 3: u32EffAddr += pVCpu->cpum.GstCtx.ebx; break; + case 4: u32EffAddr += pVCpu->cpum.GstCtx.esp; SET_SS_DEF(); break; + case 5: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + u32EffAddr += pVCpu->cpum.GstCtx.ebp; + SET_SS_DEF(); + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + } + break; + case 6: u32EffAddr += pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr += pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + break; + } + case 5: u32EffAddr = pVCpu->cpum.GstCtx.ebp; SET_SS_DEF(); break; + case 6: u32EffAddr = pVCpu->cpum.GstCtx.esi; break; + case 7: u32EffAddr = pVCpu->cpum.GstCtx.edi; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u32EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u32EffAddr += u32Disp; + break; + } + default: + AssertFailedStmt(IEM_DO_LONGJMP(pVCpu, VERR_IEM_IPE_2)); /* (caller checked for these) */ + } + } + + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT) + { + Log5(("iemOpHlpCalcRmEffAddrJmp: EffAddr=%#010RX32\n", u32EffAddr)); + return u32EffAddr; + } + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_16BIT); + Log5(("iemOpHlpCalcRmEffAddrJmp: EffAddr=%#06RX32\n", u32EffAddr & UINT16_MAX)); + return u32EffAddr & UINT16_MAX; + } + + uint64_t u64EffAddr; + + /* Handle the rip+disp32 form with no registers first. */ + if ((bRm & (X86_MODRM_MOD_MASK | X86_MODRM_RM_MASK)) == 5) + { + IEM_OPCODE_GET_NEXT_S32_SX_U64(&u64EffAddr); + u64EffAddr += pVCpu->cpum.GstCtx.rip + IEM_GET_INSTR_LEN(pVCpu) + cbImm; + } + else + { + /* Get the register (or SIB) value. */ + switch ((bRm & X86_MODRM_RM_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; SET_SS_DEF(); break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + /* SIB */ + case 4: + case 12: + { + uint8_t bSib; IEM_OPCODE_GET_NEXT_U8(&bSib); + + /* Get the index and scale it. */ + switch (((bSib >> X86_SIB_INDEX_SHIFT) & X86_SIB_INDEX_SMASK) | pVCpu->iem.s.uRexIndex) + { + case 0: u64EffAddr = pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr = pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr = pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr = pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr = 0; /*none */ break; + case 5: u64EffAddr = pVCpu->cpum.GstCtx.rbp; break; + case 6: u64EffAddr = pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr = pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr = pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr = pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr = pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr = pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr = pVCpu->cpum.GstCtx.r12; break; + case 13: u64EffAddr = pVCpu->cpum.GstCtx.r13; break; + case 14: u64EffAddr = pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr = pVCpu->cpum.GstCtx.r15; break; + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + u64EffAddr <<= (bSib >> X86_SIB_SCALE_SHIFT) & X86_SIB_SCALE_SMASK; + + /* add base */ + switch ((bSib & X86_SIB_BASE_MASK) | pVCpu->iem.s.uRexB) + { + case 0: u64EffAddr += pVCpu->cpum.GstCtx.rax; break; + case 1: u64EffAddr += pVCpu->cpum.GstCtx.rcx; break; + case 2: u64EffAddr += pVCpu->cpum.GstCtx.rdx; break; + case 3: u64EffAddr += pVCpu->cpum.GstCtx.rbx; break; + case 4: u64EffAddr += pVCpu->cpum.GstCtx.rsp; SET_SS_DEF(); break; + case 6: u64EffAddr += pVCpu->cpum.GstCtx.rsi; break; + case 7: u64EffAddr += pVCpu->cpum.GstCtx.rdi; break; + case 8: u64EffAddr += pVCpu->cpum.GstCtx.r8; break; + case 9: u64EffAddr += pVCpu->cpum.GstCtx.r9; break; + case 10: u64EffAddr += pVCpu->cpum.GstCtx.r10; break; + case 11: u64EffAddr += pVCpu->cpum.GstCtx.r11; break; + case 12: u64EffAddr += pVCpu->cpum.GstCtx.r12; break; + case 14: u64EffAddr += pVCpu->cpum.GstCtx.r14; break; + case 15: u64EffAddr += pVCpu->cpum.GstCtx.r15; break; + /* complicated encodings */ + case 5: + case 13: + if ((bRm & X86_MODRM_MOD_MASK) != 0) + { + if (!pVCpu->iem.s.uRexB) + { + u64EffAddr += pVCpu->cpum.GstCtx.rbp; + SET_SS_DEF(); + } + else + u64EffAddr += pVCpu->cpum.GstCtx.r13; + } + else + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + } + break; + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); + } + + /* Get and add the displacement. */ + switch ((bRm >> X86_MODRM_MOD_SHIFT) & X86_MODRM_MOD_SMASK) + { + case 0: + break; + case 1: + { + int8_t i8Disp; + IEM_OPCODE_GET_NEXT_S8(&i8Disp); + u64EffAddr += i8Disp; + break; + } + case 2: + { + uint32_t u32Disp; + IEM_OPCODE_GET_NEXT_U32(&u32Disp); + u64EffAddr += (int32_t)u32Disp; + break; + } + IEM_NOT_REACHED_DEFAULT_CASE_RET2(RTGCPTR_MAX); /* (caller checked for these) */ + } + + } + + if (pVCpu->iem.s.enmEffAddrMode == IEMMODE_64BIT) + { + Log5(("iemOpHlpCalcRmEffAddrJmp: EffAddr=%#010RGv\n", u64EffAddr)); + return u64EffAddr; + } + Assert(pVCpu->iem.s.enmEffAddrMode == IEMMODE_32BIT); + Log5(("iemOpHlpCalcRmEffAddrJmp: EffAddr=%#010RGv\n", u64EffAddr & UINT32_MAX)); + return u64EffAddr & UINT32_MAX; +} +#endif /* IEM_WITH_SETJMP */ + +/** @} */ + + +#ifdef LOG_ENABLED +/** + * Logs the current instruction. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + * @param fSameCtx Set if we have the same context information as the VMM, + * clear if we may have already executed an instruction in + * our debug context. When clear, we assume IEMCPU holds + * valid CPU mode info. + * + * The @a fSameCtx parameter is now misleading and obsolete. + * @param pszFunction The IEM function doing the execution. + */ +static void iemLogCurInstr(PVMCPUCC pVCpu, bool fSameCtx, const char *pszFunction) RT_NOEXCEPT +{ +# ifdef IN_RING3 + if (LogIs2Enabled()) + { + char szInstr[256]; + uint32_t cbInstr = 0; + if (fSameCtx) + DBGFR3DisasInstrEx(pVCpu->pVMR3->pUVM, pVCpu->idCpu, 0, 0, + DBGF_DISAS_FLAGS_CURRENT_GUEST | DBGF_DISAS_FLAGS_DEFAULT_MODE, + szInstr, sizeof(szInstr), &cbInstr); + else + { + uint32_t fFlags = 0; + switch (pVCpu->iem.s.enmCpuMode) + { + case IEMMODE_64BIT: fFlags |= DBGF_DISAS_FLAGS_64BIT_MODE; break; + case IEMMODE_32BIT: fFlags |= DBGF_DISAS_FLAGS_32BIT_MODE; break; + case IEMMODE_16BIT: + if (!(pVCpu->cpum.GstCtx.cr0 & X86_CR0_PE) || pVCpu->cpum.GstCtx.eflags.Bits.u1VM) + fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE; + else + fFlags |= DBGF_DISAS_FLAGS_16BIT_MODE; + break; + } + DBGFR3DisasInstrEx(pVCpu->pVMR3->pUVM, pVCpu->idCpu, pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, fFlags, + szInstr, sizeof(szInstr), &cbInstr); + } + + PCX86FXSTATE pFpuCtx = &pVCpu->cpum.GstCtx.XState.x87; + Log2(("**** %s\n" + " eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n" + " eip=%08x esp=%08x ebp=%08x iopl=%d tr=%04x\n" + " cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x efl=%08x\n" + " fsw=%04x fcw=%04x ftw=%02x mxcsr=%04x/%04x\n" + " %s\n" + , pszFunction, + pVCpu->cpum.GstCtx.eax, pVCpu->cpum.GstCtx.ebx, pVCpu->cpum.GstCtx.ecx, pVCpu->cpum.GstCtx.edx, pVCpu->cpum.GstCtx.esi, pVCpu->cpum.GstCtx.edi, + pVCpu->cpum.GstCtx.eip, pVCpu->cpum.GstCtx.esp, pVCpu->cpum.GstCtx.ebp, pVCpu->cpum.GstCtx.eflags.Bits.u2IOPL, pVCpu->cpum.GstCtx.tr.Sel, + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.ds.Sel, pVCpu->cpum.GstCtx.es.Sel, + pVCpu->cpum.GstCtx.fs.Sel, pVCpu->cpum.GstCtx.gs.Sel, pVCpu->cpum.GstCtx.eflags.u, + pFpuCtx->FSW, pFpuCtx->FCW, pFpuCtx->FTW, pFpuCtx->MXCSR, pFpuCtx->MXCSR_MASK, + szInstr)); + + if (LogIs3Enabled()) + DBGFR3InfoEx(pVCpu->pVMR3->pUVM, pVCpu->idCpu, "cpumguest", "verbose", NULL); + } + else +# endif + LogFlow(("%s: cs:rip=%04x:%08RX64 ss:rsp=%04x:%08RX64 EFL=%06x\n", pszFunction, pVCpu->cpum.GstCtx.cs.Sel, + pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp, pVCpu->cpum.GstCtx.eflags.u)); + RT_NOREF_PV(pVCpu); RT_NOREF_PV(fSameCtx); +} +#endif /* LOG_ENABLED */ + + +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX +/** + * Deals with VMCPU_FF_VMX_APIC_WRITE, VMCPU_FF_VMX_MTF, VMCPU_FF_VMX_NMI_WINDOW, + * VMCPU_FF_VMX_PREEMPT_TIMER and VMCPU_FF_VMX_INT_WINDOW. + * + * @returns Modified rcStrict. + * @param pVCpu The cross context virtual CPU structure of the calling thread. + * @param rcStrict The instruction execution status. + */ +static VBOXSTRICTRC iemHandleNestedInstructionBoundaryFFs(PVMCPUCC pVCpu, VBOXSTRICTRC rcStrict) RT_NOEXCEPT +{ + Assert(CPUMIsGuestInVmxNonRootMode(IEM_GET_CTX(pVCpu))); + if (!VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_VMX_APIC_WRITE | VMCPU_FF_VMX_MTF)) + { + /* VMX preemption timer takes priority over NMI-window exits. */ + if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_PREEMPT_TIMER)) + { + rcStrict = iemVmxVmexitPreemptTimer(pVCpu); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_PREEMPT_TIMER)); + } + /* + * Check remaining intercepts. + * + * NMI-window and Interrupt-window VM-exits. + * Interrupt shadow (block-by-STI and Mov SS) inhibits interrupts and may also block NMIs. + * Event injection during VM-entry takes priority over NMI-window and interrupt-window VM-exits. + * + * See Intel spec. 26.7.6 "NMI-Window Exiting". + * See Intel spec. 26.7.5 "Interrupt-Window Exiting and Virtual-Interrupt Delivery". + */ + else if ( VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_VMX_NMI_WINDOW | VMCPU_FF_VMX_INT_WINDOW) + && !CPUMIsInInterruptShadow(&pVCpu->cpum.GstCtx) + && !TRPMHasTrap(pVCpu)) + { + Assert(CPUMIsGuestVmxInterceptEvents(&pVCpu->cpum.GstCtx)); + if ( VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_NMI_WINDOW) + && CPUMIsGuestVmxVirtNmiBlocking(&pVCpu->cpum.GstCtx)) + { + rcStrict = iemVmxVmexit(pVCpu, VMX_EXIT_NMI_WINDOW, 0 /* u64ExitQual */); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_NMI_WINDOW)); + } + else if ( VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_INT_WINDOW) + && CPUMIsGuestVmxVirtIntrEnabled(&pVCpu->cpum.GstCtx)) + { + rcStrict = iemVmxVmexit(pVCpu, VMX_EXIT_INT_WINDOW, 0 /* u64ExitQual */); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_INT_WINDOW)); + } + } + } + /* TPR-below threshold/APIC write has the highest priority. */ + else if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_APIC_WRITE)) + { + rcStrict = iemVmxApicWriteEmulation(pVCpu); + Assert(!CPUMIsInInterruptShadow(&pVCpu->cpum.GstCtx)); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_APIC_WRITE)); + } + /* MTF takes priority over VMX-preemption timer. */ + else + { + rcStrict = iemVmxVmexit(pVCpu, VMX_EXIT_MTF, 0 /* u64ExitQual */); + Assert(!CPUMIsInInterruptShadow(&pVCpu->cpum.GstCtx)); + Assert(!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_VMX_MTF)); + } + return rcStrict; +} +#endif /* VBOX_WITH_NESTED_HWVIRT_VMX */ + + +/** @def IEM_TRY_SETJMP + * Wrapper around setjmp / try, hiding all the ugly differences. + * + * @note Use with extreme care as this is a fragile macro. + * @param a_pVCpu The cross context virtual CPU structure of the calling EMT. + * @param a_rcTarget The variable that should receive the status code in case + * of a longjmp/throw. + */ +/** @def IEM_TRY_SETJMP_AGAIN + * For when setjmp / try is used again in the same variable scope as a previous + * IEM_TRY_SETJMP invocation. + */ +/** @def IEM_CATCH_LONGJMP_BEGIN + * Start wrapper for catch / setjmp-else. + * + * This will set up a scope. + * + * @note Use with extreme care as this is a fragile macro. + * @param a_pVCpu The cross context virtual CPU structure of the calling EMT. + * @param a_rcTarget The variable that should receive the status code in case + * of a longjmp/throw. + */ +/** @def IEM_CATCH_LONGJMP_END + * End wrapper for catch / setjmp-else. + * + * This will close the scope set up by IEM_CATCH_LONGJMP_BEGIN and clean up the + * state. + * + * @note Use with extreme care as this is a fragile macro. + * @param a_pVCpu The cross context virtual CPU structure of the calling EMT. + */ +#if defined(IEM_WITH_SETJMP) || defined(DOXYGEN_RUNNING) +# ifdef IEM_WITH_THROW_CATCH +# define IEM_TRY_SETJMP(a_pVCpu, a_rcTarget) \ + a_rcTarget = VINF_SUCCESS; \ + try +# define IEM_TRY_SETJMP_AGAIN(a_pVCpu, a_rcTarget) \ + IEM_TRY_SETJMP(a_pVCpu, a_rcTarget) +# define IEM_CATCH_LONGJMP_BEGIN(a_pVCpu, a_rcTarget) \ + catch (int rcThrown) \ + { \ + a_rcTarget = rcThrown +# define IEM_CATCH_LONGJMP_END(a_pVCpu) \ + } \ + ((void)0) +# else /* !IEM_WITH_THROW_CATCH */ +# define IEM_TRY_SETJMP(a_pVCpu, a_rcTarget) \ + jmp_buf JmpBuf; \ + jmp_buf * volatile pSavedJmpBuf = pVCpu->iem.s.CTX_SUFF(pJmpBuf); \ + pVCpu->iem.s.CTX_SUFF(pJmpBuf) = &JmpBuf; \ + if ((rcStrict = setjmp(JmpBuf)) == 0) +# define IEM_TRY_SETJMP_AGAIN(a_pVCpu, a_rcTarget) \ + pSavedJmpBuf = pVCpu->iem.s.CTX_SUFF(pJmpBuf); \ + pVCpu->iem.s.CTX_SUFF(pJmpBuf) = &JmpBuf; \ + if ((rcStrict = setjmp(JmpBuf)) == 0) +# define IEM_CATCH_LONGJMP_BEGIN(a_pVCpu, a_rcTarget) \ + else \ + { \ + ((void)0) +# define IEM_CATCH_LONGJMP_END(a_pVCpu) \ + } \ + (a_pVCpu)->iem.s.CTX_SUFF(pJmpBuf) = pSavedJmpBuf +# endif /* !IEM_WITH_THROW_CATCH */ +#endif /* IEM_WITH_SETJMP */ + + +/** + * The actual code execution bits of IEMExecOne, IEMExecOneEx, and + * IEMExecOneWithPrefetchedByPC. + * + * Similar code is found in IEMExecLots. + * + * @return Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + * @param fExecuteInhibit If set, execute the instruction following CLI, + * POP SS and MOV SS,GR. + * @param pszFunction The calling function name. + */ +DECLINLINE(VBOXSTRICTRC) iemExecOneInner(PVMCPUCC pVCpu, bool fExecuteInhibit, const char *pszFunction) +{ + AssertMsg(pVCpu->iem.s.aMemMappings[0].fAccess == IEM_ACCESS_INVALID, ("0: %#x %RGp\n", pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemBbMappings[0].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[1].fAccess == IEM_ACCESS_INVALID, ("1: %#x %RGp\n", pVCpu->iem.s.aMemMappings[1].fAccess, pVCpu->iem.s.aMemBbMappings[1].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[2].fAccess == IEM_ACCESS_INVALID, ("2: %#x %RGp\n", pVCpu->iem.s.aMemMappings[2].fAccess, pVCpu->iem.s.aMemBbMappings[2].GCPhysFirst)); + RT_NOREF_PV(pszFunction); + +#ifdef IEM_WITH_SETJMP + VBOXSTRICTRC rcStrict; + IEM_TRY_SETJMP(pVCpu, rcStrict) + { + uint8_t b; IEM_OPCODE_GET_FIRST_U8(&b); + rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); + } + IEM_CATCH_LONGJMP_BEGIN(pVCpu, rcStrict); + { + pVCpu->iem.s.cLongJumps++; + } + IEM_CATCH_LONGJMP_END(pVCpu); +#else + uint8_t b; IEM_OPCODE_GET_FIRST_U8(&b); + VBOXSTRICTRC rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); +#endif + if (rcStrict == VINF_SUCCESS) + pVCpu->iem.s.cInstructions++; + if (pVCpu->iem.s.cActiveMappings > 0) + { + Assert(rcStrict != VINF_SUCCESS); + iemMemRollback(pVCpu); + } + AssertMsg(pVCpu->iem.s.aMemMappings[0].fAccess == IEM_ACCESS_INVALID, ("0: %#x %RGp\n", pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemBbMappings[0].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[1].fAccess == IEM_ACCESS_INVALID, ("1: %#x %RGp\n", pVCpu->iem.s.aMemMappings[1].fAccess, pVCpu->iem.s.aMemBbMappings[1].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[2].fAccess == IEM_ACCESS_INVALID, ("2: %#x %RGp\n", pVCpu->iem.s.aMemMappings[2].fAccess, pVCpu->iem.s.aMemBbMappings[2].GCPhysFirst)); + +//#ifdef DEBUG +// AssertMsg(IEM_GET_INSTR_LEN(pVCpu) == cbInstr || rcStrict != VINF_SUCCESS, ("%u %u\n", IEM_GET_INSTR_LEN(pVCpu), cbInstr)); +//#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + /* + * Perform any VMX nested-guest instruction boundary actions. + * + * If any of these causes a VM-exit, we must skip executing the next + * instruction (would run into stale page tables). A VM-exit makes sure + * there is no interrupt-inhibition, so that should ensure we don't go + * to try execute the next instruction. Clearing fExecuteInhibit is + * problematic because of the setjmp/longjmp clobbering above. + */ + if ( !VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_VMX_APIC_WRITE | VMCPU_FF_VMX_MTF | VMCPU_FF_VMX_PREEMPT_TIMER + | VMCPU_FF_VMX_INT_WINDOW | VMCPU_FF_VMX_NMI_WINDOW) + || rcStrict != VINF_SUCCESS) + { /* likely */ } + else + rcStrict = iemHandleNestedInstructionBoundaryFFs(pVCpu, rcStrict); +#endif + + /* Execute the next instruction as well if a cli, pop ss or + mov ss, Gr has just completed successfully. */ + if ( fExecuteInhibit + && rcStrict == VINF_SUCCESS + && CPUMIsInInterruptShadow(&pVCpu->cpum.GstCtx)) + { + rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, pVCpu->iem.s.fBypassHandlers, pVCpu->iem.s.fDisregardLock); + if (rcStrict == VINF_SUCCESS) + { +#ifdef LOG_ENABLED + iemLogCurInstr(pVCpu, false, pszFunction); +#endif +#ifdef IEM_WITH_SETJMP + IEM_TRY_SETJMP_AGAIN(pVCpu, rcStrict) + { + uint8_t b; IEM_OPCODE_GET_FIRST_U8(&b); + rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); + } + IEM_CATCH_LONGJMP_BEGIN(pVCpu, rcStrict); + { + pVCpu->iem.s.cLongJumps++; + } + IEM_CATCH_LONGJMP_END(pVCpu); +#else + IEM_OPCODE_GET_FIRST_U8(&b); + rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); +#endif + if (rcStrict == VINF_SUCCESS) + { + pVCpu->iem.s.cInstructions++; +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (!VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_VMX_APIC_WRITE | VMCPU_FF_VMX_MTF | VMCPU_FF_VMX_PREEMPT_TIMER + | VMCPU_FF_VMX_INT_WINDOW | VMCPU_FF_VMX_NMI_WINDOW)) + { /* likely */ } + else + rcStrict = iemHandleNestedInstructionBoundaryFFs(pVCpu, rcStrict); +#endif + } + if (pVCpu->iem.s.cActiveMappings > 0) + { + Assert(rcStrict != VINF_SUCCESS); + iemMemRollback(pVCpu); + } + AssertMsg(pVCpu->iem.s.aMemMappings[0].fAccess == IEM_ACCESS_INVALID, ("0: %#x %RGp\n", pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemBbMappings[0].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[1].fAccess == IEM_ACCESS_INVALID, ("1: %#x %RGp\n", pVCpu->iem.s.aMemMappings[1].fAccess, pVCpu->iem.s.aMemBbMappings[1].GCPhysFirst)); + AssertMsg(pVCpu->iem.s.aMemMappings[2].fAccess == IEM_ACCESS_INVALID, ("2: %#x %RGp\n", pVCpu->iem.s.aMemMappings[2].fAccess, pVCpu->iem.s.aMemBbMappings[2].GCPhysFirst)); + } + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + /** @todo drop this after we bake this change into RIP advancing. */ + CPUMClearInterruptShadow(&pVCpu->cpum.GstCtx); /* hope this is correct for all exceptional cases... */ + } + + /* + * Return value fiddling, statistics and sanity assertions. + */ + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); + + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + return rcStrict; +} + + +/** + * Execute one instruction. + * + * @return Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + */ +VMMDECL(VBOXSTRICTRC) IEMExecOne(PVMCPUCC pVCpu) +{ + AssertCompile(sizeof(pVCpu->iem.s) <= sizeof(pVCpu->iem.padding)); /* (tstVMStruct can't do it's job w/o instruction stats) */ +#ifdef LOG_ENABLED + iemLogCurInstr(pVCpu, true, "IEMExecOne"); +#endif + + /* + * Do the decoding and emulation. + */ + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, false); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemExecOneInner(pVCpu, true, "IEMExecOne"); + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + if (rcStrict != VINF_SUCCESS) + LogFlow(("IEMExecOne: cs:rip=%04x:%08RX64 ss:rsp=%04x:%08RX64 EFL=%06x - rcStrict=%Rrc\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp, pVCpu->cpum.GstCtx.eflags.u, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; +} + + +VMMDECL(VBOXSTRICTRC) IEMExecOneEx(PVMCPUCC pVCpu, uint32_t *pcbWritten) +{ + uint32_t const cbOldWritten = pVCpu->iem.s.cbWritten; + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, false); + if (rcStrict == VINF_SUCCESS) + { + rcStrict = iemExecOneInner(pVCpu, true, "IEMExecOneEx"); + if (pcbWritten) + *pcbWritten = pVCpu->iem.s.cbWritten - cbOldWritten; + } + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + return rcStrict; +} + + +VMMDECL(VBOXSTRICTRC) IEMExecOneWithPrefetchedByPC(PVMCPUCC pVCpu, uint64_t OpcodeBytesPC, + const void *pvOpcodeBytes, size_t cbOpcodeBytes) +{ + VBOXSTRICTRC rcStrict; + if ( cbOpcodeBytes + && pVCpu->cpum.GstCtx.rip == OpcodeBytesPC) + { + iemInitDecoder(pVCpu, false, false); +#ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.uInstrBufPc = OpcodeBytesPC; + pVCpu->iem.s.pbInstrBuf = (uint8_t const *)pvOpcodeBytes; + pVCpu->iem.s.cbInstrBufTotal = (uint16_t)RT_MIN(X86_PAGE_SIZE, cbOpcodeBytes); + pVCpu->iem.s.offCurInstrStart = 0; + pVCpu->iem.s.offInstrNextByte = 0; +#else + pVCpu->iem.s.cbOpcode = (uint8_t)RT_MIN(cbOpcodeBytes, sizeof(pVCpu->iem.s.abOpcode)); + memcpy(pVCpu->iem.s.abOpcode, pvOpcodeBytes, pVCpu->iem.s.cbOpcode); +#endif + rcStrict = VINF_SUCCESS; + } + else + rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, false); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemExecOneInner(pVCpu, true, "IEMExecOneWithPrefetchedByPC"); + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + return rcStrict; +} + + +VMMDECL(VBOXSTRICTRC) IEMExecOneBypassEx(PVMCPUCC pVCpu, uint32_t *pcbWritten) +{ + uint32_t const cbOldWritten = pVCpu->iem.s.cbWritten; + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, true, false); + if (rcStrict == VINF_SUCCESS) + { + rcStrict = iemExecOneInner(pVCpu, false, "IEMExecOneBypassEx"); + if (pcbWritten) + *pcbWritten = pVCpu->iem.s.cbWritten - cbOldWritten; + } + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + return rcStrict; +} + + +VMMDECL(VBOXSTRICTRC) IEMExecOneBypassWithPrefetchedByPC(PVMCPUCC pVCpu, uint64_t OpcodeBytesPC, + const void *pvOpcodeBytes, size_t cbOpcodeBytes) +{ + VBOXSTRICTRC rcStrict; + if ( cbOpcodeBytes + && pVCpu->cpum.GstCtx.rip == OpcodeBytesPC) + { + iemInitDecoder(pVCpu, true, false); +#ifdef IEM_WITH_CODE_TLB + pVCpu->iem.s.uInstrBufPc = OpcodeBytesPC; + pVCpu->iem.s.pbInstrBuf = (uint8_t const *)pvOpcodeBytes; + pVCpu->iem.s.cbInstrBufTotal = (uint16_t)RT_MIN(X86_PAGE_SIZE, cbOpcodeBytes); + pVCpu->iem.s.offCurInstrStart = 0; + pVCpu->iem.s.offInstrNextByte = 0; +#else + pVCpu->iem.s.cbOpcode = (uint8_t)RT_MIN(cbOpcodeBytes, sizeof(pVCpu->iem.s.abOpcode)); + memcpy(pVCpu->iem.s.abOpcode, pvOpcodeBytes, pVCpu->iem.s.cbOpcode); +#endif + rcStrict = VINF_SUCCESS; + } + else + rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, true, false); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemExecOneInner(pVCpu, false, "IEMExecOneBypassWithPrefetchedByPC"); + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + return rcStrict; +} + + +/** + * For handling split cacheline lock operations when the host has split-lock + * detection enabled. + * + * This will cause the interpreter to disregard the lock prefix and implicit + * locking (xchg). + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + */ +VMMDECL(VBOXSTRICTRC) IEMExecOneIgnoreLock(PVMCPUCC pVCpu) +{ + /* + * Do the decoding and emulation. + */ + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, true /*fDisregardLock*/); + if (rcStrict == VINF_SUCCESS) + rcStrict = iemExecOneInner(pVCpu, true, "IEMExecOneIgnoreLock"); + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + if (rcStrict != VINF_SUCCESS) + LogFlow(("IEMExecOneIgnoreLock: cs:rip=%04x:%08RX64 ss:rsp=%04x:%08RX64 EFL=%06x - rcStrict=%Rrc\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp, pVCpu->cpum.GstCtx.eflags.u, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; +} + + +VMMDECL(VBOXSTRICTRC) IEMExecLots(PVMCPUCC pVCpu, uint32_t cMaxInstructions, uint32_t cPollRate, uint32_t *pcInstructions) +{ + uint32_t const cInstructionsAtStart = pVCpu->iem.s.cInstructions; + AssertMsg(RT_IS_POWER_OF_TWO(cPollRate + 1), ("%#x\n", cPollRate)); + + /* + * See if there is an interrupt pending in TRPM, inject it if we can. + */ + /** @todo What if we are injecting an exception and not an interrupt? Is that + * possible here? For now we assert it is indeed only an interrupt. */ + if (!TRPMHasTrap(pVCpu)) + { /* likely */ } + else + { + if ( !CPUMIsInInterruptShadow(&pVCpu->cpum.GstCtx) + && !CPUMAreInterruptsInhibitedByNmi(&pVCpu->cpum.GstCtx)) + { + /** @todo Can we centralize this under CPUMCanInjectInterrupt()? */ +#if defined(VBOX_WITH_NESTED_HWVIRT_SVM) || defined(VBOX_WITH_NESTED_HWVIRT_VMX) + bool fIntrEnabled = CPUMGetGuestGif(&pVCpu->cpum.GstCtx); + if (fIntrEnabled) + { + if (!CPUMIsGuestInNestedHwvirtMode(IEM_GET_CTX(pVCpu))) + fIntrEnabled = pVCpu->cpum.GstCtx.eflags.Bits.u1IF; + else if (CPUMIsGuestInVmxNonRootMode(IEM_GET_CTX(pVCpu))) + fIntrEnabled = CPUMIsGuestVmxPhysIntrEnabled(IEM_GET_CTX(pVCpu)); + else + { + Assert(CPUMIsGuestInSvmNestedHwVirtMode(IEM_GET_CTX(pVCpu))); + fIntrEnabled = CPUMIsGuestSvmPhysIntrEnabled(pVCpu, IEM_GET_CTX(pVCpu)); + } + } +#else + bool fIntrEnabled = pVCpu->cpum.GstCtx.eflags.Bits.u1IF; +#endif + if (fIntrEnabled) + { + uint8_t u8TrapNo; + TRPMEVENT enmType; + uint32_t uErrCode; + RTGCPTR uCr2; + int rc2 = TRPMQueryTrapAll(pVCpu, &u8TrapNo, &enmType, &uErrCode, &uCr2, NULL /*pu8InstLen*/, NULL /*fIcebp*/); + AssertRC(rc2); + Assert(enmType == TRPM_HARDWARE_INT); + VBOXSTRICTRC rcStrict = IEMInjectTrap(pVCpu, u8TrapNo, enmType, (uint16_t)uErrCode, uCr2, 0 /*cbInstr*/); + + TRPMResetTrap(pVCpu); + +#if defined(VBOX_WITH_NESTED_HWVIRT_SVM) || defined(VBOX_WITH_NESTED_HWVIRT_VMX) + /* Injecting an event may cause a VM-exit. */ + if ( rcStrict != VINF_SUCCESS + && rcStrict != VINF_IEM_RAISED_XCPT) + return iemExecStatusCodeFiddling(pVCpu, rcStrict); +#else + NOREF(rcStrict); +#endif + } + } + } + + /* + * Initial decoder init w/ prefetch, then setup setjmp. + */ + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, false); + if (rcStrict == VINF_SUCCESS) + { +#ifdef IEM_WITH_SETJMP + pVCpu->iem.s.cActiveMappings = 0; /** @todo wtf? */ + IEM_TRY_SETJMP(pVCpu, rcStrict) +#endif + { + /* + * The run loop. We limit ourselves to 4096 instructions right now. + */ + uint32_t cMaxInstructionsGccStupidity = cMaxInstructions; + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + for (;;) + { + /* + * Log the state. + */ +#ifdef LOG_ENABLED + iemLogCurInstr(pVCpu, true, "IEMExecLots"); +#endif + + /* + * Do the decoding and emulation. + */ + uint8_t b; IEM_OPCODE_GET_FIRST_U8(&b); + rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); +#ifdef VBOX_STRICT + CPUMAssertGuestRFlagsCookie(pVM, pVCpu); +#endif + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { + Assert(pVCpu->iem.s.cActiveMappings == 0); + pVCpu->iem.s.cInstructions++; + +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + /* Perform any VMX nested-guest instruction boundary actions. */ + uint64_t fCpu = pVCpu->fLocalForcedActions; + if (!(fCpu & ( VMCPU_FF_VMX_APIC_WRITE | VMCPU_FF_VMX_MTF | VMCPU_FF_VMX_PREEMPT_TIMER + | VMCPU_FF_VMX_INT_WINDOW | VMCPU_FF_VMX_NMI_WINDOW))) + { /* likely */ } + else + { + rcStrict = iemHandleNestedInstructionBoundaryFFs(pVCpu, rcStrict); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + fCpu = pVCpu->fLocalForcedActions; + else + { + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); + break; + } + } +#endif + if (RT_LIKELY(pVCpu->iem.s.rcPassUp == VINF_SUCCESS)) + { +#ifndef VBOX_WITH_NESTED_HWVIRT_VMX + uint64_t fCpu = pVCpu->fLocalForcedActions; +#endif + fCpu &= VMCPU_FF_ALL_MASK & ~( VMCPU_FF_PGM_SYNC_CR3 + | VMCPU_FF_PGM_SYNC_CR3_NON_GLOBAL + | VMCPU_FF_TLB_FLUSH + | VMCPU_FF_UNHALT ); + + if (RT_LIKELY( ( !fCpu + || ( !(fCpu & ~(VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC)) + && !pVCpu->cpum.GstCtx.rflags.Bits.u1IF) ) + && !VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_MASK) )) + { + if (cMaxInstructionsGccStupidity-- > 0) + { + /* Poll timers every now an then according to the caller's specs. */ + if ( (cMaxInstructionsGccStupidity & cPollRate) != 0 + || !TMTimerPollBool(pVM, pVCpu)) + { + Assert(pVCpu->iem.s.cActiveMappings == 0); + iemReInitDecoder(pVCpu); + continue; + } + } + } + } + Assert(pVCpu->iem.s.cActiveMappings == 0); + } + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); + break; + } + } +#ifdef IEM_WITH_SETJMP + IEM_CATCH_LONGJMP_BEGIN(pVCpu, rcStrict); + { + if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); +# if defined(VBOX_WITH_NESTED_HWVIRT_SVM) || defined(VBOX_WITH_NESTED_HWVIRT_VMX) + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); +# endif + pVCpu->iem.s.cLongJumps++; + } + IEM_CATCH_LONGJMP_END(pVCpu); +#endif + + /* + * Assert hidden register sanity (also done in iemInitDecoder and iemReInitDecoder). + */ + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + } + else + { + if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + +#if defined(VBOX_WITH_NESTED_HWVIRT_SVM) || defined(VBOX_WITH_NESTED_HWVIRT_VMX) + /* + * When a nested-guest causes an exception intercept (e.g. #PF) when fetching + * code as part of instruction execution, we need this to fix-up VINF_SVM_VMEXIT. + */ + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); +#endif + } + + /* + * Maybe re-enter raw-mode and log. + */ + if (rcStrict != VINF_SUCCESS) + LogFlow(("IEMExecLots: cs:rip=%04x:%08RX64 ss:rsp=%04x:%08RX64 EFL=%06x - rcStrict=%Rrc\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp, pVCpu->cpum.GstCtx.eflags.u, VBOXSTRICTRC_VAL(rcStrict))); + if (pcInstructions) + *pcInstructions = pVCpu->iem.s.cInstructions - cInstructionsAtStart; + return rcStrict; +} + + +/** + * Interface used by EMExecuteExec, does exit statistics and limits. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param fWillExit To be defined. + * @param cMinInstructions Minimum number of instructions to execute before checking for FFs. + * @param cMaxInstructions Maximum number of instructions to execute. + * @param cMaxInstructionsWithoutExits + * The max number of instructions without exits. + * @param pStats Where to return statistics. + */ +VMMDECL(VBOXSTRICTRC) IEMExecForExits(PVMCPUCC pVCpu, uint32_t fWillExit, uint32_t cMinInstructions, uint32_t cMaxInstructions, + uint32_t cMaxInstructionsWithoutExits, PIEMEXECFOREXITSTATS pStats) +{ + NOREF(fWillExit); /** @todo define flexible exit crits */ + + /* + * Initialize return stats. + */ + pStats->cInstructions = 0; + pStats->cExits = 0; + pStats->cMaxExitDistance = 0; + pStats->cReserved = 0; + + /* + * Initial decoder init w/ prefetch, then setup setjmp. + */ + VBOXSTRICTRC rcStrict = iemInitDecoderAndPrefetchOpcodes(pVCpu, false, false); + if (rcStrict == VINF_SUCCESS) + { +#ifdef IEM_WITH_SETJMP + pVCpu->iem.s.cActiveMappings = 0; /** @todo wtf?!? */ + IEM_TRY_SETJMP(pVCpu, rcStrict) +#endif + { +#ifdef IN_RING0 + bool const fCheckPreemptionPending = !RTThreadPreemptIsPossible() || !RTThreadPreemptIsEnabled(NIL_RTTHREAD); +#endif + uint32_t cInstructionSinceLastExit = 0; + + /* + * The run loop. We limit ourselves to 4096 instructions right now. + */ + PVM pVM = pVCpu->CTX_SUFF(pVM); + for (;;) + { + /* + * Log the state. + */ +#ifdef LOG_ENABLED + iemLogCurInstr(pVCpu, true, "IEMExecForExits"); +#endif + + /* + * Do the decoding and emulation. + */ + uint32_t const cPotentialExits = pVCpu->iem.s.cPotentialExits; + + uint8_t b; IEM_OPCODE_GET_FIRST_U8(&b); + rcStrict = FNIEMOP_CALL(g_apfnOneByteMap[b]); + + if ( cPotentialExits != pVCpu->iem.s.cPotentialExits + && cInstructionSinceLastExit > 0 /* don't count the first */ ) + { + pStats->cExits += 1; + if (cInstructionSinceLastExit > pStats->cMaxExitDistance) + pStats->cMaxExitDistance = cInstructionSinceLastExit; + cInstructionSinceLastExit = 0; + } + + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + { + Assert(pVCpu->iem.s.cActiveMappings == 0); + pVCpu->iem.s.cInstructions++; + pStats->cInstructions++; + cInstructionSinceLastExit++; + +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + /* Perform any VMX nested-guest instruction boundary actions. */ + uint64_t fCpu = pVCpu->fLocalForcedActions; + if (!(fCpu & ( VMCPU_FF_VMX_APIC_WRITE | VMCPU_FF_VMX_MTF | VMCPU_FF_VMX_PREEMPT_TIMER + | VMCPU_FF_VMX_INT_WINDOW | VMCPU_FF_VMX_NMI_WINDOW))) + { /* likely */ } + else + { + rcStrict = iemHandleNestedInstructionBoundaryFFs(pVCpu, rcStrict); + if (RT_LIKELY(rcStrict == VINF_SUCCESS)) + fCpu = pVCpu->fLocalForcedActions; + else + { + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); + break; + } + } +#endif + if (RT_LIKELY(pVCpu->iem.s.rcPassUp == VINF_SUCCESS)) + { +#ifndef VBOX_WITH_NESTED_HWVIRT_VMX + uint64_t fCpu = pVCpu->fLocalForcedActions; +#endif + fCpu &= VMCPU_FF_ALL_MASK & ~( VMCPU_FF_PGM_SYNC_CR3 + | VMCPU_FF_PGM_SYNC_CR3_NON_GLOBAL + | VMCPU_FF_TLB_FLUSH + | VMCPU_FF_UNHALT ); + if (RT_LIKELY( ( ( !fCpu + || ( !(fCpu & ~(VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC)) + && !pVCpu->cpum.GstCtx.rflags.Bits.u1IF)) + && !VM_FF_IS_ANY_SET(pVM, VM_FF_ALL_MASK) ) + || pStats->cInstructions < cMinInstructions)) + { + if (pStats->cInstructions < cMaxInstructions) + { + if (cInstructionSinceLastExit <= cMaxInstructionsWithoutExits) + { +#ifdef IN_RING0 + if ( !fCheckPreemptionPending + || !RTThreadPreemptIsPending(NIL_RTTHREAD)) +#endif + { + Assert(pVCpu->iem.s.cActiveMappings == 0); + iemReInitDecoder(pVCpu); + continue; + } +#ifdef IN_RING0 + rcStrict = VINF_EM_RAW_INTERRUPT; + break; +#endif + } + } + } + Assert(!(fCpu & VMCPU_FF_IEM)); + } + Assert(pVCpu->iem.s.cActiveMappings == 0); + } + else if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); + break; + } + } +#ifdef IEM_WITH_SETJMP + IEM_CATCH_LONGJMP_BEGIN(pVCpu, rcStrict); + { + if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + pVCpu->iem.s.cLongJumps++; + } + IEM_CATCH_LONGJMP_END(pVCpu); +#endif + + /* + * Assert hidden register sanity (also done in iemInitDecoder and iemReInitDecoder). + */ + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.cs)); + Assert(CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &pVCpu->cpum.GstCtx.ss)); + } + else + { + if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + +#if defined(VBOX_WITH_NESTED_HWVIRT_SVM) || defined(VBOX_WITH_NESTED_HWVIRT_VMX) + /* + * When a nested-guest causes an exception intercept (e.g. #PF) when fetching + * code as part of instruction execution, we need this to fix-up VINF_SVM_VMEXIT. + */ + rcStrict = iemExecStatusCodeFiddling(pVCpu, rcStrict); +#endif + } + + /* + * Maybe re-enter raw-mode and log. + */ + if (rcStrict != VINF_SUCCESS) + LogFlow(("IEMExecForExits: cs:rip=%04x:%08RX64 ss:rsp=%04x:%08RX64 EFL=%06x - rcStrict=%Rrc; ins=%u exits=%u maxdist=%u\n", + pVCpu->cpum.GstCtx.cs.Sel, pVCpu->cpum.GstCtx.rip, pVCpu->cpum.GstCtx.ss.Sel, pVCpu->cpum.GstCtx.rsp, + pVCpu->cpum.GstCtx.eflags.u, VBOXSTRICTRC_VAL(rcStrict), pStats->cInstructions, pStats->cExits, pStats->cMaxExitDistance)); + return rcStrict; +} + + +/** + * Injects a trap, fault, abort, software interrupt or external interrupt. + * + * The parameter list matches TRPMQueryTrapAll pretty closely. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + * @param u8TrapNo The trap number. + * @param enmType What type is it (trap/fault/abort), software + * interrupt or hardware interrupt. + * @param uErrCode The error code if applicable. + * @param uCr2 The CR2 value if applicable. + * @param cbInstr The instruction length (only relevant for + * software interrupts). + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMInjectTrap(PVMCPUCC pVCpu, uint8_t u8TrapNo, TRPMEVENT enmType, uint16_t uErrCode, RTGCPTR uCr2, + uint8_t cbInstr) +{ + iemInitDecoder(pVCpu, false, false); +#ifdef DBGFTRACE_ENABLED + RTTraceBufAddMsgF(pVCpu->CTX_SUFF(pVM)->CTX_SUFF(hTraceBuf), "IEMInjectTrap: %x %d %x %llx", + u8TrapNo, enmType, uErrCode, uCr2); +#endif + + uint32_t fFlags; + switch (enmType) + { + case TRPM_HARDWARE_INT: + Log(("IEMInjectTrap: %#4x ext\n", u8TrapNo)); + fFlags = IEM_XCPT_FLAGS_T_EXT_INT; + uErrCode = uCr2 = 0; + break; + + case TRPM_SOFTWARE_INT: + Log(("IEMInjectTrap: %#4x soft\n", u8TrapNo)); + fFlags = IEM_XCPT_FLAGS_T_SOFT_INT; + uErrCode = uCr2 = 0; + break; + + case TRPM_TRAP: + Log(("IEMInjectTrap: %#4x trap err=%#x cr2=%#RGv\n", u8TrapNo, uErrCode, uCr2)); + fFlags = IEM_XCPT_FLAGS_T_CPU_XCPT; + if (u8TrapNo == X86_XCPT_PF) + fFlags |= IEM_XCPT_FLAGS_CR2; + switch (u8TrapNo) + { + case X86_XCPT_DF: + case X86_XCPT_TS: + case X86_XCPT_NP: + case X86_XCPT_SS: + case X86_XCPT_PF: + case X86_XCPT_AC: + case X86_XCPT_GP: + fFlags |= IEM_XCPT_FLAGS_ERR; + break; + } + break; + + IEM_NOT_REACHED_DEFAULT_CASE_RET(); + } + + VBOXSTRICTRC rcStrict = iemRaiseXcptOrInt(pVCpu, cbInstr, u8TrapNo, fFlags, uErrCode, uCr2); + + if (pVCpu->iem.s.cActiveMappings > 0) + iemMemRollback(pVCpu); + + return rcStrict; +} + + +/** + * Injects the active TRPM event. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + */ +VMMDECL(VBOXSTRICTRC) IEMInjectTrpmEvent(PVMCPUCC pVCpu) +{ +#ifndef IEM_IMPLEMENTS_TASKSWITCH + IEM_RETURN_ASPECT_NOT_IMPLEMENTED_LOG(("Event injection\n")); +#else + uint8_t u8TrapNo; + TRPMEVENT enmType; + uint32_t uErrCode; + RTGCUINTPTR uCr2; + uint8_t cbInstr; + int rc = TRPMQueryTrapAll(pVCpu, &u8TrapNo, &enmType, &uErrCode, &uCr2, &cbInstr, NULL /* fIcebp */); + if (RT_FAILURE(rc)) + return rc; + + /** @todo r=ramshankar: Pass ICEBP info. to IEMInjectTrap() below and handle + * ICEBP \#DB injection as a special case. */ + VBOXSTRICTRC rcStrict = IEMInjectTrap(pVCpu, u8TrapNo, enmType, uErrCode, uCr2, cbInstr); +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (rcStrict == VINF_SVM_VMEXIT) + rcStrict = VINF_SUCCESS; +#endif +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (rcStrict == VINF_VMX_VMEXIT) + rcStrict = VINF_SUCCESS; +#endif + /** @todo Are there any other codes that imply the event was successfully + * delivered to the guest? See @bugref{6607}. */ + if ( rcStrict == VINF_SUCCESS + || rcStrict == VINF_IEM_RAISED_XCPT) + TRPMResetTrap(pVCpu); + + return rcStrict; +#endif +} + + +VMM_INT_DECL(int) IEMBreakpointSet(PVM pVM, RTGCPTR GCPtrBp) +{ + RT_NOREF_PV(pVM); RT_NOREF_PV(GCPtrBp); + return VERR_NOT_IMPLEMENTED; +} + + +VMM_INT_DECL(int) IEMBreakpointClear(PVM pVM, RTGCPTR GCPtrBp) +{ + RT_NOREF_PV(pVM); RT_NOREF_PV(GCPtrBp); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * Interface for HM and EM for executing string I/O OUT (write) instructions. + * + * This API ASSUMES that the caller has already verified that the guest code is + * allowed to access the I/O port. (The I/O port is in the DX register in the + * guest state.) + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbValue The size of the I/O port access (1, 2, or 4). + * @param enmAddrMode The addressing mode. + * @param fRepPrefix Indicates whether a repeat prefix is used + * (doesn't matter which for this instruction). + * @param cbInstr The instruction length in bytes. + * @param iEffSeg The effective segment address. + * @param fIoChecked Whether the access to the I/O port has been + * checked or not. It's typically checked in the + * HM scenario. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecStringIoWrite(PVMCPUCC pVCpu, uint8_t cbValue, IEMMODE enmAddrMode, + bool fRepPrefix, uint8_t cbInstr, uint8_t iEffSeg, bool fIoChecked) +{ + AssertMsgReturn(iEffSeg < X86_SREG_COUNT, ("%#x\n", iEffSeg), VERR_IEM_INVALID_EFF_SEG); + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 1); + + /* + * State init. + */ + iemInitExec(pVCpu, false /*fBypassHandlers*/); + + /* + * Switch orgy for getting to the right handler. + */ + VBOXSTRICTRC rcStrict; + if (fRepPrefix) + { + switch (enmAddrMode) + { + case IEMMODE_16BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_outs_op8_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_outs_op16_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_outs_op32_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_32BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_outs_op8_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_outs_op16_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_outs_op32_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_64BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_outs_op8_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_outs_op16_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_outs_op32_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + default: + AssertMsgFailedReturn(("enmAddrMode=%d\n", enmAddrMode), VERR_IEM_INVALID_ADDRESS_MODE); + } + } + else + { + switch (enmAddrMode) + { + case IEMMODE_16BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_outs_op8_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_outs_op16_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_outs_op32_addr16(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_32BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_outs_op8_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_outs_op16_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_outs_op32_addr32(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_64BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_outs_op8_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 2: rcStrict = iemCImpl_outs_op16_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + case 4: rcStrict = iemCImpl_outs_op32_addr64(pVCpu, cbInstr, iEffSeg, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + default: + AssertMsgFailedReturn(("enmAddrMode=%d\n", enmAddrMode), VERR_IEM_INVALID_ADDRESS_MODE); + } + } + + if (pVCpu->iem.s.cActiveMappings) + iemMemRollback(pVCpu); + + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM for executing string I/O IN (read) instructions. + * + * This API ASSUMES that the caller has already verified that the guest code is + * allowed to access the I/O port. (The I/O port is in the DX register in the + * guest state.) + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbValue The size of the I/O port access (1, 2, or 4). + * @param enmAddrMode The addressing mode. + * @param fRepPrefix Indicates whether a repeat prefix is used + * (doesn't matter which for this instruction). + * @param cbInstr The instruction length in bytes. + * @param fIoChecked Whether the access to the I/O port has been + * checked or not. It's typically checked in the + * HM scenario. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecStringIoRead(PVMCPUCC pVCpu, uint8_t cbValue, IEMMODE enmAddrMode, + bool fRepPrefix, uint8_t cbInstr, bool fIoChecked) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 1); + + /* + * State init. + */ + iemInitExec(pVCpu, false /*fBypassHandlers*/); + + /* + * Switch orgy for getting to the right handler. + */ + VBOXSTRICTRC rcStrict; + if (fRepPrefix) + { + switch (enmAddrMode) + { + case IEMMODE_16BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_ins_op8_addr16(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_ins_op16_addr16(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_ins_op32_addr16(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_32BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_ins_op8_addr32(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_ins_op16_addr32(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_ins_op32_addr32(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_64BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_rep_ins_op8_addr64(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_rep_ins_op16_addr64(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_rep_ins_op32_addr64(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + default: + AssertMsgFailedReturn(("enmAddrMode=%d\n", enmAddrMode), VERR_IEM_INVALID_ADDRESS_MODE); + } + } + else + { + switch (enmAddrMode) + { + case IEMMODE_16BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_ins_op8_addr16(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_ins_op16_addr16(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_ins_op32_addr16(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_32BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_ins_op8_addr32(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_ins_op16_addr32(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_ins_op32_addr32(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + case IEMMODE_64BIT: + switch (cbValue) + { + case 1: rcStrict = iemCImpl_ins_op8_addr64(pVCpu, cbInstr, fIoChecked); break; + case 2: rcStrict = iemCImpl_ins_op16_addr64(pVCpu, cbInstr, fIoChecked); break; + case 4: rcStrict = iemCImpl_ins_op32_addr64(pVCpu, cbInstr, fIoChecked); break; + default: + AssertMsgFailedReturn(("cbValue=%#x\n", cbValue), VERR_IEM_INVALID_OPERAND_SIZE); + } + break; + + default: + AssertMsgFailedReturn(("enmAddrMode=%d\n", enmAddrMode), VERR_IEM_INVALID_ADDRESS_MODE); + } + } + + if ( pVCpu->iem.s.cActiveMappings == 0 + || VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_IEM)) + { /* likely */ } + else + { + AssertMsg(!IOM_SUCCESS(rcStrict), ("%#x\n", VBOXSTRICTRC_VAL(rcStrict))); + iemMemRollback(pVCpu); + } + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for rawmode to write execute an OUT instruction. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param u16Port The port to read. + * @param fImm Whether the port is specified using an immediate operand or + * using the implicit DX register. + * @param cbReg The register size. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedOut(PVMCPUCC pVCpu, uint8_t cbInstr, uint16_t u16Port, bool fImm, uint8_t cbReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 1); + Assert(cbReg <= 4 && cbReg != 3); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_3(iemCImpl_out, u16Port, fImm, cbReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for rawmode to write execute an IN instruction. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param u16Port The port to read. + * @param fImm Whether the port is specified using an immediate operand or + * using the implicit DX. + * @param cbReg The register size. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedIn(PVMCPUCC pVCpu, uint8_t cbInstr, uint16_t u16Port, bool fImm, uint8_t cbReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 1); + Assert(cbReg <= 4 && cbReg != 3); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_3(iemCImpl_in, u16Port, fImm, cbReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to write to a CRx register. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param iCrReg The control register number (destination). + * @param iGReg The general purpose register number (source). + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMovCRxWrite(PVMCPUCC pVCpu, uint8_t cbInstr, uint8_t iCrReg, uint8_t iGReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + Assert(iCrReg < 16); + Assert(iGReg < 16); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_2(iemCImpl_mov_Cd_Rd, iCrReg, iGReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to read from a CRx register. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param iGReg The general purpose register number (destination). + * @param iCrReg The control register number (source). + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMovCRxRead(PVMCPUCC pVCpu, uint8_t cbInstr, uint8_t iGReg, uint8_t iCrReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_CR3 | CPUMCTX_EXTRN_CR4 + | CPUMCTX_EXTRN_APIC_TPR); + Assert(iCrReg < 16); + Assert(iGReg < 16); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_2(iemCImpl_mov_Rd_Cd, iGReg, iCrReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to write to a DRx register. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param iDrReg The debug register number (destination). + * @param iGReg The general purpose register number (source). + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMovDRxWrite(PVMCPUCC pVCpu, uint8_t cbInstr, uint8_t iDrReg, uint8_t iGReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_DR7); + Assert(iDrReg < 8); + Assert(iGReg < 16); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_2(iemCImpl_mov_Dd_Rd, iDrReg, iGReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to read from a DRx register. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param iGReg The general purpose register number (destination). + * @param iDrReg The debug register number (source). + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMovDRxRead(PVMCPUCC pVCpu, uint8_t cbInstr, uint8_t iGReg, uint8_t iDrReg) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_DR7); + Assert(iDrReg < 8); + Assert(iGReg < 16); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_2(iemCImpl_mov_Rd_Dd, iGReg, iDrReg); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to clear the CR0[TS] bit. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedClts(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_clts); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the LMSW instruction (loads CR0). + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param uValue The value to load into CR0. + * @param GCPtrEffDst The guest-linear address if the LMSW instruction has a + * memory operand. Otherwise pass NIL_RTGCPTR. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedLmsw(PVMCPUCC pVCpu, uint8_t cbInstr, uint16_t uValue, RTGCPTR GCPtrEffDst) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_2(iemCImpl_lmsw, uValue, GCPtrEffDst); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the XSETBV instruction (loads XCRx). + * + * Takes input values in ecx and edx:eax of the CPU context of the calling EMT. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + * @param cbInstr The instruction length in bytes. + * @remarks In ring-0 not all of the state needs to be synced in. + * @thread EMT(pVCpu) + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedXsetbv(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_xsetbv); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the WBINVD instruction. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedWbinvd(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_wbinvd); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the INVD instruction. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedInvd(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_invd); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the INVLPG instruction. + * + * @returns Strict VBox status code. + * @retval VINF_PGM_SYNC_CR3 + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param GCPtrPage The effective address of the page to invalidate. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedInvlpg(PVMCPUCC pVCpu, uint8_t cbInstr, RTGCPTR GCPtrPage) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_1(iemCImpl_invlpg, GCPtrPage); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the INVPCID instruction. + * + * @returns Strict VBox status code. + * @retval VINF_PGM_SYNC_CR3 + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * @param iEffSeg The effective segment register. + * @param GCPtrDesc The effective address of the INVPCID descriptor. + * @param uType The invalidation type. + * + * @remarks In ring-0 not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedInvpcid(PVMCPUCC pVCpu, uint8_t cbInstr, uint8_t iEffSeg, RTGCPTR GCPtrDesc, + uint64_t uType) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 4); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_3(iemCImpl_invpcid, iEffSeg, GCPtrDesc, uType); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the CPUID instruction. + * + * @returns Strict VBox status code. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in, the usual pluss RAX and RCX. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedCpuid(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_RAX | CPUMCTX_EXTRN_RCX); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_cpuid); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the RDPMC instruction. + * + * @returns Strict VBox status code. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedRdpmc(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_CR4); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_rdpmc); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the RDTSC instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedRdtsc(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_CR4); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_rdtsc); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the RDTSCP instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. Recommended + * to include CPUMCTX_EXTRN_TSC_AUX, to avoid extra fetch call. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedRdtscp(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_CR4 | CPUMCTX_EXTRN_TSC_AUX); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_rdtscp); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the RDMSR instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. Requires RCX and + * (currently) all MSRs. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedRdmsr(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_RCX | CPUMCTX_EXTRN_ALL_MSRS); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_rdmsr); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the WRMSR instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. Requires RCX, RAX, RDX, + * and (currently) all MSRs. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedWrmsr(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 2); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK + | CPUMCTX_EXTRN_RCX | CPUMCTX_EXTRN_RAX | CPUMCTX_EXTRN_RDX | CPUMCTX_EXTRN_ALL_MSRS); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_wrmsr); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the MONITOR instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. + * @remarks ASSUMES the default segment of DS and no segment override prefixes + * are used. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMonitor(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_MEM_MASK | CPUMCTX_EXTRN_DS); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_1(iemCImpl_monitor, X86_SREG_DS); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the MWAIT instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedMwait(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 3); + IEM_CTX_ASSERT(pVCpu, IEM_CPUMCTX_EXTRN_EXEC_DECODED_NO_MEM_MASK | CPUMCTX_EXTRN_RCX | CPUMCTX_EXTRN_RAX); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_mwait); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Interface for HM and EM to emulate the HLT instruction. + * + * @returns Strict VBox status code. + * @retval VINF_IEM_RAISED_XCPT (VINF_EM_RESCHEDULE) if exception is raised. + * + * @param pVCpu The cross context virtual CPU structure. + * @param cbInstr The instruction length in bytes. + * + * @remarks Not all of the state needs to be synced in. + */ +VMM_INT_DECL(VBOXSTRICTRC) IEMExecDecodedHlt(PVMCPUCC pVCpu, uint8_t cbInstr) +{ + IEMEXEC_ASSERT_INSTR_LEN_RETURN(cbInstr, 1); + + iemInitExec(pVCpu, false /*fBypassHandlers*/); + VBOXSTRICTRC rcStrict = IEM_CIMPL_CALL_0(iemCImpl_hlt); + Assert(!pVCpu->iem.s.cActiveMappings); + return iemUninitExecAndFiddleStatusAndMaybeReenter(pVCpu, rcStrict); +} + + +/** + * Checks if IEM is in the process of delivering an event (interrupt or + * exception). + * + * @returns true if we're in the process of raising an interrupt or exception, + * false otherwise. + * @param pVCpu The cross context virtual CPU structure. + * @param puVector Where to store the vector associated with the + * currently delivered event, optional. + * @param pfFlags Where to store th event delivery flags (see + * IEM_XCPT_FLAGS_XXX), optional. + * @param puErr Where to store the error code associated with the + * event, optional. + * @param puCr2 Where to store the CR2 associated with the event, + * optional. + * @remarks The caller should check the flags to determine if the error code and + * CR2 are valid for the event. + */ +VMM_INT_DECL(bool) IEMGetCurrentXcpt(PVMCPUCC pVCpu, uint8_t *puVector, uint32_t *pfFlags, uint32_t *puErr, uint64_t *puCr2) +{ + bool const fRaisingXcpt = pVCpu->iem.s.cXcptRecursions > 0; + if (fRaisingXcpt) + { + if (puVector) + *puVector = pVCpu->iem.s.uCurXcpt; + if (pfFlags) + *pfFlags = pVCpu->iem.s.fCurXcpt; + if (puErr) + *puErr = pVCpu->iem.s.uCurXcptErr; + if (puCr2) + *puCr2 = pVCpu->iem.s.uCurXcptCr2; + } + return fRaisingXcpt; +} + +#ifdef IN_RING3 + +/** + * Handles the unlikely and probably fatal merge cases. + * + * @returns Merged status code. + * @param rcStrict Current EM status code. + * @param rcStrictCommit The IOM I/O or MMIO write commit status to merge + * with @a rcStrict. + * @param iMemMap The memory mapping index. For error reporting only. + * @param pVCpu The cross context virtual CPU structure of the calling + * thread, for error reporting only. + */ +DECL_NO_INLINE(static, VBOXSTRICTRC) iemR3MergeStatusSlow(VBOXSTRICTRC rcStrict, VBOXSTRICTRC rcStrictCommit, + unsigned iMemMap, PVMCPUCC pVCpu) +{ + if (RT_FAILURE_NP(rcStrict)) + return rcStrict; + + if (RT_FAILURE_NP(rcStrictCommit)) + return rcStrictCommit; + + if (rcStrict == rcStrictCommit) + return rcStrictCommit; + + AssertLogRelMsgFailed(("rcStrictCommit=%Rrc rcStrict=%Rrc iMemMap=%u fAccess=%#x FirstPg=%RGp LB %u SecondPg=%RGp LB %u\n", + VBOXSTRICTRC_VAL(rcStrictCommit), VBOXSTRICTRC_VAL(rcStrict), iMemMap, + pVCpu->iem.s.aMemMappings[iMemMap].fAccess, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond)); + return VERR_IOM_FF_STATUS_IPE; +} + + +/** + * Helper for IOMR3ProcessForceFlag. + * + * @returns Merged status code. + * @param rcStrict Current EM status code. + * @param rcStrictCommit The IOM I/O or MMIO write commit status to merge + * with @a rcStrict. + * @param iMemMap The memory mapping index. For error reporting only. + * @param pVCpu The cross context virtual CPU structure of the calling + * thread, for error reporting only. + */ +DECLINLINE(VBOXSTRICTRC) iemR3MergeStatus(VBOXSTRICTRC rcStrict, VBOXSTRICTRC rcStrictCommit, unsigned iMemMap, PVMCPUCC pVCpu) +{ + /* Simple. */ + if (RT_LIKELY(rcStrict == VINF_SUCCESS || rcStrict == VINF_EM_RAW_TO_R3)) + return rcStrictCommit; + + if (RT_LIKELY(rcStrictCommit == VINF_SUCCESS)) + return rcStrict; + + /* EM scheduling status codes. */ + if (RT_LIKELY( rcStrict >= VINF_EM_FIRST + && rcStrict <= VINF_EM_LAST)) + { + if (RT_LIKELY( rcStrictCommit >= VINF_EM_FIRST + && rcStrictCommit <= VINF_EM_LAST)) + return rcStrict < rcStrictCommit ? rcStrict : rcStrictCommit; + } + + /* Unlikely */ + return iemR3MergeStatusSlow(rcStrict, rcStrictCommit, iMemMap, pVCpu); +} + + +/** + * Called by force-flag handling code when VMCPU_FF_IEM is set. + * + * @returns Merge between @a rcStrict and what the commit operation returned. + * @param pVM The cross context VM structure. + * @param pVCpu The cross context virtual CPU structure of the calling EMT. + * @param rcStrict The status code returned by ring-0 or raw-mode. + */ +VMMR3_INT_DECL(VBOXSTRICTRC) IEMR3ProcessForceFlag(PVM pVM, PVMCPUCC pVCpu, VBOXSTRICTRC rcStrict) +{ + /* + * Reset the pending commit. + */ + AssertMsg( (pVCpu->iem.s.aMemMappings[0].fAccess | pVCpu->iem.s.aMemMappings[1].fAccess | pVCpu->iem.s.aMemMappings[2].fAccess) + & (IEM_ACCESS_PENDING_R3_WRITE_1ST | IEM_ACCESS_PENDING_R3_WRITE_2ND), + ("%#x %#x %#x\n", + pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemMappings[1].fAccess, pVCpu->iem.s.aMemMappings[2].fAccess)); + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_IEM); + + /* + * Commit the pending bounce buffers (usually just one). + */ + unsigned cBufs = 0; + unsigned iMemMap = RT_ELEMENTS(pVCpu->iem.s.aMemMappings); + while (iMemMap-- > 0) + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & (IEM_ACCESS_PENDING_R3_WRITE_1ST | IEM_ACCESS_PENDING_R3_WRITE_2ND)) + { + Assert(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_TYPE_WRITE); + Assert(pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_BOUNCE_BUFFERED); + Assert(!pVCpu->iem.s.aMemBbMappings[iMemMap].fUnassigned); + + uint16_t const cbFirst = pVCpu->iem.s.aMemBbMappings[iMemMap].cbFirst; + uint16_t const cbSecond = pVCpu->iem.s.aMemBbMappings[iMemMap].cbSecond; + uint8_t const *pbBuf = &pVCpu->iem.s.aBounceBuffers[iMemMap].ab[0]; + + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_PENDING_R3_WRITE_1ST) + { + VBOXSTRICTRC rcStrictCommit1 = PGMPhysWrite(pVM, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, + pbBuf, + cbFirst, + PGMACCESSORIGIN_IEM); + rcStrict = iemR3MergeStatus(rcStrict, rcStrictCommit1, iMemMap, pVCpu); + Log(("IEMR3ProcessForceFlag: iMemMap=%u GCPhysFirst=%RGp LB %#x %Rrc => %Rrc\n", + iMemMap, pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysFirst, cbFirst, + VBOXSTRICTRC_VAL(rcStrictCommit1), VBOXSTRICTRC_VAL(rcStrict))); + } + + if (pVCpu->iem.s.aMemMappings[iMemMap].fAccess & IEM_ACCESS_PENDING_R3_WRITE_2ND) + { + VBOXSTRICTRC rcStrictCommit2 = PGMPhysWrite(pVM, + pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, + pbBuf + cbFirst, + cbSecond, + PGMACCESSORIGIN_IEM); + rcStrict = iemR3MergeStatus(rcStrict, rcStrictCommit2, iMemMap, pVCpu); + Log(("IEMR3ProcessForceFlag: iMemMap=%u GCPhysSecond=%RGp LB %#x %Rrc => %Rrc\n", + iMemMap, pVCpu->iem.s.aMemBbMappings[iMemMap].GCPhysSecond, cbSecond, + VBOXSTRICTRC_VAL(rcStrictCommit2), VBOXSTRICTRC_VAL(rcStrict))); + } + cBufs++; + pVCpu->iem.s.aMemMappings[iMemMap].fAccess = IEM_ACCESS_INVALID; + } + + AssertMsg(cBufs > 0 && cBufs == pVCpu->iem.s.cActiveMappings, + ("cBufs=%u cActiveMappings=%u - %#x %#x %#x\n", cBufs, pVCpu->iem.s.cActiveMappings, + pVCpu->iem.s.aMemMappings[0].fAccess, pVCpu->iem.s.aMemMappings[1].fAccess, pVCpu->iem.s.aMemMappings[2].fAccess)); + pVCpu->iem.s.cActiveMappings = 0; + return rcStrict; +} + +#endif /* IN_RING3 */ + |