diff options
Diffstat (limited to 'src/VBox/VMM/VMMAll/IEMAllCImplStrInstr.cpp.h')
-rw-r--r-- | src/VBox/VMM/VMMAll/IEMAllCImplStrInstr.cpp.h | 1773 |
1 files changed, 1773 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMAll/IEMAllCImplStrInstr.cpp.h b/src/VBox/VMM/VMMAll/IEMAllCImplStrInstr.cpp.h new file mode 100644 index 00000000..4bef3cfb --- /dev/null +++ b/src/VBox/VMM/VMMAll/IEMAllCImplStrInstr.cpp.h @@ -0,0 +1,1773 @@ +/* $Id: IEMAllCImplStrInstr.cpp.h $ */ +/** @file + * IEM - String Instruction Implementation Code Template. + */ + +/* + * 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 + */ + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#if OP_SIZE == 8 +# define OP_rAX al +#elif OP_SIZE == 16 +# define OP_rAX ax +#elif OP_SIZE == 32 +# define OP_rAX eax +#elif OP_SIZE == 64 +# define OP_rAX rax +#else +# error "Bad OP_SIZE." +#endif +#define OP_TYPE RT_CONCAT3(uint,OP_SIZE,_t) + +#if ADDR_SIZE == 16 +# define ADDR_rDI di +# define ADDR_rSI si +# define ADDR_rCX cx +# define ADDR2_TYPE uint32_t +# define ADDR_VMXSTRIO 0 +#elif ADDR_SIZE == 32 +# define ADDR_rDI edi +# define ADDR_rSI esi +# define ADDR_rCX ecx +# define ADDR2_TYPE uint32_t +# define ADDR_VMXSTRIO 1 +#elif ADDR_SIZE == 64 +# define ADDR_rDI rdi +# define ADDR_rSI rsi +# define ADDR_rCX rcx +# define ADDR2_TYPE uint64_t +# define ADDR_VMXSTRIO 2 +# define IS_64_BIT_CODE(a_pVCpu) (true) +#else +# error "Bad ADDR_SIZE." +#endif +#define ADDR_TYPE RT_CONCAT3(uint,ADDR_SIZE,_t) + +#if ADDR_SIZE == 64 || OP_SIZE == 64 +# define IS_64_BIT_CODE(a_pVCpu) (true) +#elif ADDR_SIZE == 32 +# define IS_64_BIT_CODE(a_pVCpu) ((a_pVCpu)->iem.s.enmCpuMode == IEMMODE_64BIT) +#else +# define IS_64_BIT_CODE(a_pVCpu) (false) +#endif + +/** @def IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN + * Used in the outer (page-by-page) loop to check for reasons for returnning + * before completing the instruction. In raw-mode we temporarily enable + * interrupts to let the host interrupt us. We cannot let big string operations + * hog the CPU, especially not in raw-mode. + */ +#define IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(a_pVM, a_pVCpu, a_fEflags) \ + do { \ + if (RT_LIKELY( !VMCPU_FF_IS_ANY_SET(a_pVCpu, (a_fEflags) & X86_EFL_IF ? VMCPU_FF_YIELD_REPSTR_MASK \ + : VMCPU_FF_YIELD_REPSTR_NOINT_MASK) \ + && !VM_FF_IS_ANY_SET(a_pVM, VM_FF_YIELD_REPSTR_MASK) \ + )) \ + { /* probable */ } \ + else \ + { \ + LogFlow(("%s: Leaving early (outer)! ffcpu=%#RX64 ffvm=%#x\n", \ + __FUNCTION__, (uint64_t)(a_pVCpu)->fLocalForcedActions, (a_pVM)->fGlobalForcedActions)); \ + return VINF_SUCCESS; \ + } \ + } while (0) + +/** @def IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN + * This is used in some of the inner loops to make sure we respond immediately + * to VMCPU_FF_IOM as well as outside requests. Use this for expensive + * instructions. Use IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN for + * ones that are typically cheap. */ +#define IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(a_pVM, a_pVCpu, a_fExitExpr) \ + do { \ + if (RT_LIKELY( ( !VMCPU_FF_IS_ANY_SET(a_pVCpu, VMCPU_FF_HIGH_PRIORITY_POST_REPSTR_MASK) \ + && !VM_FF_IS_ANY_SET(a_pVM, VM_FF_HIGH_PRIORITY_POST_REPSTR_MASK)) \ + || (a_fExitExpr) )) \ + { /* very likely */ } \ + else \ + { \ + LogFlow(("%s: Leaving early (inner)! ffcpu=%#RX64 ffvm=%#x\n", \ + __FUNCTION__, (uint64_t)(a_pVCpu)->fLocalForcedActions, (a_pVM)->fGlobalForcedActions)); \ + return VINF_SUCCESS; \ + } \ + } while (0) + + +/** @def IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN + * This is used in the inner loops where + * IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN isn't used. It only + * checks the CPU FFs so that we respond immediately to the pending IOM FF + * (status code is hidden in IEMCPU::rcPassUp by IEM memory commit code). + */ +#define IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(a_pVM, a_pVCpu, a_fExitExpr) \ + do { \ + if (RT_LIKELY( !VMCPU_FF_IS_ANY_SET(a_pVCpu, VMCPU_FF_HIGH_PRIORITY_POST_REPSTR_MASK) \ + || (a_fExitExpr) )) \ + { /* very likely */ } \ + else \ + { \ + LogFlow(("%s: Leaving early (inner)! ffcpu=%#RX64 (ffvm=%#x)\n", \ + __FUNCTION__, (uint64_t)(a_pVCpu)->fLocalForcedActions, (a_pVM)->fGlobalForcedActions)); \ + return VINF_SUCCESS; \ + } \ + } while (0) + + +/** + * Implements 'REPE CMPS'. + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_repe_cmps_op,OP_SIZE,_addr,ADDR_SIZE), uint8_t, iEffSeg) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_SREG_FROM_IDX(iEffSeg) | CPUMCTX_EXTRN_ES); + + PCCPUMSELREGHID pSrc1Hid = iemSRegGetHid(pVCpu, iEffSeg); + uint64_t uSrc1Base = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, pSrc1Hid, iEffSeg, &uSrc1Base); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint64_t uSrc2Base = 0; /* gcc may not be used uninitialized */ + rcStrict = iemMemSegCheckReadAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uSrc2Base); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uSrc1AddrReg = pVCpu->cpum.GstCtx.ADDR_rSI; + ADDR_TYPE uSrc2AddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + uint32_t uEFlags = pVCpu->cpum.GstCtx.eflags.u; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtSrc1Addr = uSrc1AddrReg + (ADDR2_TYPE)uSrc1Base; + ADDR2_TYPE uVirtSrc2Addr = uSrc2AddrReg + (ADDR2_TYPE)uSrc2Base; + uint32_t cLeftSrc1Page = (GUEST_PAGE_SIZE - (uVirtSrc1Addr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftSrc1Page > uCounterReg) + cLeftSrc1Page = uCounterReg; + uint32_t cLeftSrc2Page = (GUEST_PAGE_SIZE - (uVirtSrc2Addr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + uint32_t cLeftPage = RT_MIN(cLeftSrc1Page, cLeftSrc2Page); + + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Optimize reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uSrc1AddrReg < pSrc1Hid->u32Limit + && uSrc1AddrReg + (cLeftPage * (OP_SIZE / 8)) <= pSrc1Hid->u32Limit + && uSrc2AddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uSrc2AddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysSrc1Mem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtSrc1Addr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysSrc1Mem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + RTGCPHYS GCPhysSrc2Mem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtSrc2Addr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysSrc2Mem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + PGMPAGEMAPLOCK PgLockSrc2Mem; + OP_TYPE const *puSrc2Mem; + rcStrict = iemMemPageMap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, (void **)&puSrc2Mem, &PgLockSrc2Mem); + if (rcStrict == VINF_SUCCESS) + { + PGMPAGEMAPLOCK PgLockSrc1Mem; + OP_TYPE const *puSrc1Mem; + rcStrict = iemMemPageMap(pVCpu, GCPhysSrc1Mem, IEM_ACCESS_DATA_R, (void **)&puSrc1Mem, &PgLockSrc1Mem); + if (rcStrict == VINF_SUCCESS) + { + if (!memcmp(puSrc2Mem, puSrc1Mem, cLeftPage * (OP_SIZE / 8))) + { + /* All matches, only compare the last itme to get the right eflags. */ + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&puSrc1Mem[cLeftPage-1], puSrc2Mem[cLeftPage-1], &uEFlags); + uSrc1AddrReg += cLeftPage * cbIncr; + uSrc2AddrReg += cLeftPage * cbIncr; + uCounterReg -= cLeftPage; + } + else + { + /* Some mismatch, compare each item (and keep volatile + memory in mind). */ + uint32_t off = 0; + do + { + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&puSrc1Mem[off], puSrc2Mem[off], &uEFlags); + off++; + } while ( off < cLeftPage + && (uEFlags & X86_EFL_ZF)); + uSrc1AddrReg += cbIncr * off; + uSrc2AddrReg += cbIncr * off; + uCounterReg -= off; + } + + /* Update the registers before looping. */ + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg; + pVCpu->cpum.GstCtx.ADDR_rSI = uSrc1AddrReg; + pVCpu->cpum.GstCtx.ADDR_rDI = uSrc2AddrReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + + iemMemPageUnmap(pVCpu, GCPhysSrc1Mem, IEM_ACCESS_DATA_R, puSrc1Mem, &PgLockSrc1Mem); + iemMemPageUnmap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, puSrc2Mem, &PgLockSrc2Mem); + if ( uCounterReg == 0 + || !(uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + continue; + } + iemMemPageUnmap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, puSrc2Mem, &PgLockSrc2Mem); + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uValue1; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue1, iEffSeg, uSrc1AddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + OP_TYPE uValue2; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue2, X86_SREG_ES, uSrc2AddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)(&uValue1, uValue2, &uEFlags); + + pVCpu->cpum.GstCtx.ADDR_rSI = uSrc1AddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rDI = uSrc2AddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0 || !(uEFlags & X86_EFL_ZF)); + } while ( (int32_t)cLeftPage > 0 + && (uEFlags & X86_EFL_ZF)); + + /* + * Next page? Must check for interrupts and stuff here. + */ + if ( uCounterReg == 0 + || !(uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'REPNE CMPS'. + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_repne_cmps_op,OP_SIZE,_addr,ADDR_SIZE), uint8_t, iEffSeg) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_SREG_FROM_IDX(iEffSeg) | CPUMCTX_EXTRN_ES); + + PCCPUMSELREGHID pSrc1Hid = iemSRegGetHid(pVCpu, iEffSeg); + uint64_t uSrc1Base = 0; /* gcc may not be used uninitialized */; + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, pSrc1Hid, iEffSeg, &uSrc1Base); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint64_t uSrc2Base = 0; /* gcc may not be used uninitialized */ + rcStrict = iemMemSegCheckReadAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uSrc2Base); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uSrc1AddrReg = pVCpu->cpum.GstCtx.ADDR_rSI; + ADDR_TYPE uSrc2AddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + uint32_t uEFlags = pVCpu->cpum.GstCtx.eflags.u; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtSrc1Addr = uSrc1AddrReg + (ADDR2_TYPE)uSrc1Base; + ADDR2_TYPE uVirtSrc2Addr = uSrc2AddrReg + (ADDR2_TYPE)uSrc2Base; + uint32_t cLeftSrc1Page = (GUEST_PAGE_SIZE - (uVirtSrc1Addr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftSrc1Page > uCounterReg) + cLeftSrc1Page = uCounterReg; + uint32_t cLeftSrc2Page = (GUEST_PAGE_SIZE - (uVirtSrc2Addr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + uint32_t cLeftPage = RT_MIN(cLeftSrc1Page, cLeftSrc2Page); + + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Optimize reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uSrc1AddrReg < pSrc1Hid->u32Limit + && uSrc1AddrReg + (cLeftPage * (OP_SIZE / 8)) <= pSrc1Hid->u32Limit + && uSrc2AddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uSrc2AddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysSrc1Mem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtSrc1Addr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysSrc1Mem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + RTGCPHYS GCPhysSrc2Mem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtSrc2Addr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysSrc2Mem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + OP_TYPE const *puSrc2Mem; + PGMPAGEMAPLOCK PgLockSrc2Mem; + rcStrict = iemMemPageMap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, (void **)&puSrc2Mem, &PgLockSrc2Mem); + if (rcStrict == VINF_SUCCESS) + { + OP_TYPE const *puSrc1Mem; + PGMPAGEMAPLOCK PgLockSrc1Mem; + rcStrict = iemMemPageMap(pVCpu, GCPhysSrc1Mem, IEM_ACCESS_DATA_R, (void **)&puSrc1Mem, &PgLockSrc1Mem); + if (rcStrict == VINF_SUCCESS) + { + if (memcmp(puSrc2Mem, puSrc1Mem, cLeftPage * (OP_SIZE / 8))) + { + /* All matches, only compare the last item to get the right eflags. */ + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&puSrc1Mem[cLeftPage-1], puSrc2Mem[cLeftPage-1], &uEFlags); + uSrc1AddrReg += cLeftPage * cbIncr; + uSrc2AddrReg += cLeftPage * cbIncr; + uCounterReg -= cLeftPage; + } + else + { + /* Some mismatch, compare each item (and keep volatile + memory in mind). */ + uint32_t off = 0; + do + { + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&puSrc1Mem[off], puSrc2Mem[off], &uEFlags); + off++; + } while ( off < cLeftPage + && !(uEFlags & X86_EFL_ZF)); + uSrc1AddrReg += cbIncr * off; + uSrc2AddrReg += cbIncr * off; + uCounterReg -= off; + } + + /* Update the registers before looping. */ + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg; + pVCpu->cpum.GstCtx.ADDR_rSI = uSrc1AddrReg; + pVCpu->cpum.GstCtx.ADDR_rDI = uSrc2AddrReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + + iemMemPageUnmap(pVCpu, GCPhysSrc1Mem, IEM_ACCESS_DATA_R, puSrc1Mem, &PgLockSrc1Mem); + iemMemPageUnmap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, puSrc2Mem, &PgLockSrc2Mem); + if ( uCounterReg == 0 + || (uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + continue; + } + iemMemPageUnmap(pVCpu, GCPhysSrc2Mem, IEM_ACCESS_DATA_R, puSrc2Mem, &PgLockSrc2Mem); + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uValue1; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue1, iEffSeg, uSrc1AddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + OP_TYPE uValue2; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue2, X86_SREG_ES, uSrc2AddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)(&uValue1, uValue2, &uEFlags); + + pVCpu->cpum.GstCtx.ADDR_rSI = uSrc1AddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rDI = uSrc2AddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0 || (uEFlags & X86_EFL_ZF)); + } while ( (int32_t)cLeftPage > 0 + && !(uEFlags & X86_EFL_ZF)); + + /* + * Next page? Must check for interrupts and stuff here. + */ + if ( uCounterReg == 0 + || (uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'REPE SCAS'. + */ +IEM_CIMPL_DEF_0(RT_CONCAT4(iemCImpl_repe_scas_,OP_rAX,_m,ADDR_SIZE)) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_ES); + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + OP_TYPE const uValueReg = pVCpu->cpum.GstCtx.OP_rAX; + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + uint32_t uEFlags = pVCpu->cpum.GstCtx.eflags.u; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE const *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + /* Search till we find a mismatching item. */ + OP_TYPE uTmpValue; + bool fQuit; + uint32_t i = 0; + do + { + uTmpValue = puMem[i++]; + fQuit = uTmpValue != uValueReg; + } while (i < cLeftPage && !fQuit); + + /* Update the regs. */ + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&uValueReg, uTmpValue, &uEFlags); + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= i; + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += i * cbIncr; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + Assert(!(uEFlags & X86_EFL_ZF) == fQuit); + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, puMem, &PgLockMem); + if ( fQuit + || uCounterReg == 0) + break; + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + continue; + } + cLeftPage = 0; + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uTmpValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uTmpValue, X86_SREG_ES, uAddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&uValueReg, uTmpValue, &uEFlags); + + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0 || !(uEFlags & X86_EFL_ZF)); + } while ( (int32_t)cLeftPage > 0 + && (uEFlags & X86_EFL_ZF)); + + /* + * Next page? Must check for interrupts and stuff here. + */ + if ( uCounterReg == 0 + || !(uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'REPNE SCAS'. + */ +IEM_CIMPL_DEF_0(RT_CONCAT4(iemCImpl_repne_scas_,OP_rAX,_m,ADDR_SIZE)) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_ES); + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + OP_TYPE const uValueReg = pVCpu->cpum.GstCtx.OP_rAX; + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + uint32_t uEFlags = pVCpu->cpum.GstCtx.eflags.u; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE const *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + /* Search till we find a mismatching item. */ + OP_TYPE uTmpValue; + bool fQuit; + uint32_t i = 0; + do + { + uTmpValue = puMem[i++]; + fQuit = uTmpValue == uValueReg; + } while (i < cLeftPage && !fQuit); + + /* Update the regs. */ + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&uValueReg, uTmpValue, &uEFlags); + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= i; + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += i * cbIncr; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + Assert(!!(uEFlags & X86_EFL_ZF) == fQuit); + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, puMem, &PgLockMem); + if ( fQuit + || uCounterReg == 0) + break; + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + continue; + } + cLeftPage = 0; + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uTmpValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uTmpValue, X86_SREG_ES, uAddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + RT_CONCAT(iemAImpl_cmp_u,OP_SIZE)((OP_TYPE *)&uValueReg, uTmpValue, &uEFlags); + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + pVCpu->cpum.GstCtx.eflags.u = uEFlags; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0 || (uEFlags & X86_EFL_ZF)); + } while ( (int32_t)cLeftPage > 0 + && !(uEFlags & X86_EFL_ZF)); + + /* + * Next page? Must check for interrupts and stuff here. + */ + if ( uCounterReg == 0 + || (uEFlags & X86_EFL_ZF)) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, uEFlags); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + + + +/** + * Implements 'REP MOVS'. + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_rep_movs_op,OP_SIZE,_addr,ADDR_SIZE), uint8_t, iEffSeg) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_SREG_FROM_IDX(iEffSeg) | CPUMCTX_EXTRN_ES); + + PCCPUMSELREGHID pSrcHid = iemSRegGetHid(pVCpu, iEffSeg); + uint64_t uSrcBase = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, pSrcHid, iEffSeg, &uSrcBase); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint64_t uDstBase = 0; /* gcc may not be used uninitialized */ + rcStrict = iemMemSegCheckWriteAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uDstBase); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uSrcAddrReg = pVCpu->cpum.GstCtx.ADDR_rSI; + ADDR_TYPE uDstAddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + + /* + * Be careful with handle bypassing. + */ + if (pVCpu->iem.s.fBypassHandlers) + { + Log(("%s: declining because we're bypassing handlers\n", __FUNCTION__)); + return VERR_IEM_ASPECT_NOT_IMPLEMENTED; + } + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtSrcAddr = uSrcAddrReg + (ADDR2_TYPE)uSrcBase; + ADDR2_TYPE uVirtDstAddr = uDstAddrReg + (ADDR2_TYPE)uDstBase; + uint32_t cLeftSrcPage = (GUEST_PAGE_SIZE - (uVirtSrcAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftSrcPage > uCounterReg) + cLeftSrcPage = uCounterReg; + uint32_t cLeftDstPage = (GUEST_PAGE_SIZE - (uVirtDstAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + uint32_t cLeftPage = RT_MIN(cLeftSrcPage, cLeftDstPage); + + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uSrcAddrReg < pSrcHid->u32Limit + && uSrcAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pSrcHid->u32Limit + && uDstAddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uDstAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysSrcMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtSrcAddr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysSrcMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + RTGCPHYS GCPhysDstMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtDstAddr, OP_SIZE / 8, IEM_ACCESS_DATA_W, &GCPhysDstMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + PGMPAGEMAPLOCK PgLockDstMem; + OP_TYPE *puDstMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysDstMem, IEM_ACCESS_DATA_W, (void **)&puDstMem, &PgLockDstMem); + if (rcStrict == VINF_SUCCESS) + { + PGMPAGEMAPLOCK PgLockSrcMem; + OP_TYPE const *puSrcMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysSrcMem, IEM_ACCESS_DATA_R, (void **)&puSrcMem, &PgLockSrcMem); + if (rcStrict == VINF_SUCCESS) + { + Assert( (GCPhysSrcMem >> GUEST_PAGE_SHIFT) != (GCPhysDstMem >> GUEST_PAGE_SHIFT) + || ((uintptr_t)puSrcMem >> GUEST_PAGE_SHIFT) == ((uintptr_t)puDstMem >> GUEST_PAGE_SHIFT)); + + /* Perform the operation exactly (don't use memcpy to avoid + having to consider how its implementation would affect + any overlapping source and destination area). */ + OP_TYPE const *puSrcCur = puSrcMem; + OP_TYPE *puDstCur = puDstMem; + uint32_t cTodo = cLeftPage; + while (cTodo-- > 0) + *puDstCur++ = *puSrcCur++; + + /* Update the registers. */ + pVCpu->cpum.GstCtx.ADDR_rSI = uSrcAddrReg += cLeftPage * cbIncr; + pVCpu->cpum.GstCtx.ADDR_rDI = uDstAddrReg += cLeftPage * cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cLeftPage; + + iemMemPageUnmap(pVCpu, GCPhysSrcMem, IEM_ACCESS_DATA_R, puSrcMem, &PgLockSrcMem); + iemMemPageUnmap(pVCpu, GCPhysDstMem, IEM_ACCESS_DATA_W, puDstMem, &PgLockDstMem); + + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + iemMemPageUnmap(pVCpu, GCPhysDstMem, IEM_ACCESS_DATA_W, puDstMem, &PgLockDstMem); + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue, iEffSeg, uSrcAddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + rcStrict = RT_CONCAT(iemMemStoreDataU,OP_SIZE)(pVCpu, X86_SREG_ES, uDstAddrReg, uValue); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + pVCpu->cpum.GstCtx.ADDR_rSI = uSrcAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rDI = uDstAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + cLeftPage--; + IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0); + } while ((int32_t)cLeftPage > 0); + + /* + * Next page. Must check for interrupts and stuff here. + */ + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'REP STOS'. + */ +IEM_CIMPL_DEF_0(RT_CONCAT4(iemCImpl_stos_,OP_rAX,_m,ADDR_SIZE)) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_ES); + + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckWriteAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + OP_TYPE const uValue = pVCpu->cpum.GstCtx.OP_rAX; + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + + /* + * Be careful with handle bypassing. + */ + /** @todo Permit doing a page if correctly aligned. */ + if (pVCpu->iem.s.fBypassHandlers) + { + Log(("%s: declining because we're bypassing handlers\n", __FUNCTION__)); + return VERR_IEM_ASPECT_NOT_IMPLEMENTED; + } + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_W, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, do a block processing + * until the end of the current page. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_W, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + /* Update the regs first so we can loop on cLeftPage. */ + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cLeftPage; + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cLeftPage * cbIncr; + + /* Do the memsetting. */ +#if OP_SIZE == 8 + memset(puMem, uValue, cLeftPage); +/*#elif OP_SIZE == 32 + ASMMemFill32(puMem, cLeftPage * (OP_SIZE / 8), uValue);*/ +#else + while (cLeftPage-- > 0) + *puMem++ = uValue; +#endif + + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_W, puMem, &PgLockMem); + + if (uCounterReg == 0) + break; + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + cLeftPage = 0; + } + /* If we got an invalid physical address in the page table, just skip + ahead to the next page or the counter reaches zero. This crazy + optimization is for a buggy EFI firmware that's driving me nuts. */ + else if (rcStrict == VERR_PGM_PHYS_TLB_UNASSIGNED) + { + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cLeftPage; + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cLeftPage * cbIncr; + if (uCounterReg == 0) + break; + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + rcStrict = RT_CONCAT(iemMemStoreDataU,OP_SIZE)(pVCpu, X86_SREG_ES, uAddrReg, uValue); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0); + } while ((int32_t)cLeftPage > 0); + + /* + * Next page. Must check for interrupts and stuff here. + */ + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'REP LODS'. + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_lods_,OP_rAX,_m,ADDR_SIZE), int8_t, iEffSeg) +{ + PVM pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_SREG_FROM_IDX(iEffSeg)); + PCCPUMSELREGHID pSrcHid = iemSRegGetHid(pVCpu, iEffSeg); + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + VBOXSTRICTRC rcStrict = iemMemSegCheckReadAccessEx(pVCpu, pSrcHid, iEffSeg, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rSI; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pSrcHid->u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pSrcHid->u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, we can get away with + * just reading the last value on the page. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE const *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + /* Only get the last byte, the rest doesn't matter in direct access mode. */ +#if OP_SIZE == 32 + pVCpu->cpum.GstCtx.rax = puMem[cLeftPage - 1]; +#else + pVCpu->cpum.GstCtx.OP_rAX = puMem[cLeftPage - 1]; +#endif + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cLeftPage; + pVCpu->cpum.GstCtx.ADDR_rSI = uAddrReg += cLeftPage * cbIncr; + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, puMem, &PgLockMem); + + if (uCounterReg == 0) + break; + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + cLeftPage = 0; + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + */ + do + { + OP_TYPE uTmpValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uTmpValue, iEffSeg, uAddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; +#if OP_SIZE == 32 + pVCpu->cpum.GstCtx.rax = uTmpValue; +#else + pVCpu->cpum.GstCtx.OP_rAX = uTmpValue; +#endif + pVCpu->cpum.GstCtx.ADDR_rSI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + cLeftPage--; + IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0); + } while ((int32_t)cLeftPage > 0); + + if (rcStrict != VINF_SUCCESS) + break; + + /* + * Next page. Must check for interrupts and stuff here. + */ + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + } + + /* + * Done. + */ + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +#if OP_SIZE != 64 + +/** + * Implements 'INS' (no rep) + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_ins_op,OP_SIZE,_addr,ADDR_SIZE), bool, fIoChecked) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + VBOXSTRICTRC rcStrict; + + /* + * Be careful with handle bypassing. + */ + if (pVCpu->iem.s.fBypassHandlers) + { + Log(("%s: declining because we're bypassing handlers\n", __FUNCTION__)); + return VERR_IEM_ASPECT_NOT_IMPLEMENTED; + } + + /* + * ASSUMES the #GP for I/O permission is taken first, then any #GP for + * segmentation and finally any #PF due to virtual address translation. + * ASSUMES nothing is read from the I/O port before traps are taken. + */ + if (!fIoChecked) + { + rcStrict = iemHlpCheckPortIOPermission(pVCpu, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + } + + /* + * Check nested-guest I/O intercepts. + */ +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VMXEXITINSTRINFO ExitInstrInfo; + ExitInstrInfo.u = 0; + ExitInstrInfo.StrIo.u3AddrSize = ADDR_VMXSTRIO; + ExitInstrInfo.StrIo.iSegReg = X86_SREG_ES; + rcStrict = iemVmxVmexitInstrStrIo(pVCpu, VMXINSTRID_IO_INS, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8, false /* fRep */, + ExitInstrInfo, cbInstr); + if (rcStrict != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict; + } +#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_IOIO_PROT)) + { + rcStrict = iemSvmHandleIOIntercept(pVCpu, pVCpu->cpum.GstCtx.dx, SVMIOIOTYPE_IN, OP_SIZE / 8, ADDR_SIZE, X86_SREG_ES, + false /* fRep */, true /* fStrIo */, cbInstr); + if (rcStrict == VINF_SVM_VMEXIT) + return VINF_SUCCESS; + if (rcStrict != VINF_SVM_INTERCEPT_NOT_ACTIVE) + { + Log(("iemCImpl_ins_op: iemSvmHandleIOIntercept failed (u16Port=%#x, cbReg=%u) rc=%Rrc\n", pVCpu->cpum.GstCtx.dx, + OP_SIZE / 8, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } +#endif + + OP_TYPE *puMem; + rcStrict = iemMemMap(pVCpu, (void **)&puMem, OP_SIZE / 8, X86_SREG_ES, pVCpu->cpum.GstCtx.ADDR_rDI, + IEM_ACCESS_DATA_W, OP_SIZE / 8 - 1); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint32_t u32Value = 0; + rcStrict = IOMIOPortRead(pVM, pVCpu, pVCpu->cpum.GstCtx.dx, &u32Value, OP_SIZE / 8); + if (IOM_SUCCESS(rcStrict)) + { + /** + * @todo I/O breakpoint support for INS + */ + *puMem = (OP_TYPE)u32Value; +# ifdef IN_RING3 + VBOXSTRICTRC rcStrict2 = iemMemCommitAndUnmap(pVCpu, puMem, IEM_ACCESS_DATA_W); +# else + VBOXSTRICTRC rcStrict2 = iemMemCommitAndUnmapPostponeTroubleToR3(pVCpu, puMem, IEM_ACCESS_DATA_W); +# endif + if (RT_LIKELY(rcStrict2 == VINF_SUCCESS)) + { + if (!pVCpu->cpum.GstCtx.eflags.Bits.u1DF) + pVCpu->cpum.GstCtx.ADDR_rDI += OP_SIZE / 8; + else + pVCpu->cpum.GstCtx.ADDR_rDI -= OP_SIZE / 8; + + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + rcStrict2 = iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + if (rcStrict2 != VINF_SUCCESS) + { + iemSetPassUpStatus(pVCpu, rcStrict); + rcStrict = rcStrict2; + } + pVCpu->iem.s.cPotentialExits++; + } + else + AssertLogRelMsgFailedReturn(("rcStrict2=%Rrc\n", VBOXSTRICTRC_VAL(rcStrict2)), RT_FAILURE_NP(rcStrict2) ? rcStrict2 : VERR_IEM_IPE_1); + } + return rcStrict; +} + + +/** + * Implements 'REP INS'. + */ +IEM_CIMPL_DEF_1(RT_CONCAT4(iemCImpl_rep_ins_op,OP_SIZE,_addr,ADDR_SIZE), bool, fIoChecked) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + + IEM_CTX_IMPORT_RET(pVCpu, CPUMCTX_EXTRN_ES | CPUMCTX_EXTRN_TR); + + /* + * Setup. + */ + uint16_t const u16Port = pVCpu->cpum.GstCtx.dx; + VBOXSTRICTRC rcStrict; + if (!fIoChecked) + { +/** @todo check if this is too early for ecx=0. */ + rcStrict = iemHlpCheckPortIOPermission(pVCpu, u16Port, OP_SIZE / 8); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + } + + /* + * Check nested-guest I/O intercepts. + */ +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VMXEXITINSTRINFO ExitInstrInfo; + ExitInstrInfo.u = 0; + ExitInstrInfo.StrIo.u3AddrSize = ADDR_VMXSTRIO; + ExitInstrInfo.StrIo.iSegReg = X86_SREG_ES; + rcStrict = iemVmxVmexitInstrStrIo(pVCpu, VMXINSTRID_IO_INS, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8, true /* fRep */, + ExitInstrInfo, cbInstr); + if (rcStrict != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict; + } +#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_IOIO_PROT)) + { + rcStrict = iemSvmHandleIOIntercept(pVCpu, u16Port, SVMIOIOTYPE_IN, OP_SIZE / 8, ADDR_SIZE, X86_SREG_ES, true /* fRep */, + true /* fStrIo */, cbInstr); + if (rcStrict == VINF_SVM_VMEXIT) + return VINF_SUCCESS; + if (rcStrict != VINF_SVM_INTERCEPT_NOT_ACTIVE) + { + Log(("iemCImpl_rep_ins_op: iemSvmHandleIOIntercept failed (u16Port=%#x, cbReg=%u) rc=%Rrc\n", u16Port, OP_SIZE / 8, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } +#endif + + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + rcStrict = iemMemSegCheckWriteAccessEx(pVCpu, iemSRegUpdateHid(pVCpu, &pVCpu->cpum.GstCtx.es), X86_SREG_ES, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rDI; + + /* + * Be careful with handle bypassing. + */ + if (pVCpu->iem.s.fBypassHandlers) + { + Log(("%s: declining because we're bypassing handlers\n", __FUNCTION__)); + return VERR_IEM_ASPECT_NOT_IMPLEMENTED; + } + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pVCpu->cpum.GstCtx.es.u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pVCpu->cpum.GstCtx.es.u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_W, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, use the IOM + * string I/O interface to do the work. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_W, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + uint32_t cTransfers = cLeftPage; + rcStrict = IOMIOPortReadString(pVM, pVCpu, u16Port, puMem, &cTransfers, OP_SIZE / 8); + + uint32_t cActualTransfers = cLeftPage - cTransfers; + Assert(cActualTransfers <= cLeftPage); + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cbIncr * cActualTransfers; + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cActualTransfers; + puMem += cActualTransfers; + + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_W, puMem, &PgLockMem); + + if (rcStrict != VINF_SUCCESS) + { + if (IOM_SUCCESS(rcStrict)) + { + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + if (uCounterReg == 0) + rcStrict = iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + pVCpu->iem.s.cPotentialExits++; + } + return rcStrict; + } + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (uCounterReg == 0) + break; + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + cLeftPage = 0; + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + * + * Note! We ASSUME the CPU will raise #PF or #GP before access the + * I/O port, otherwise it wouldn't really be restartable. + */ + /** @todo investigate what the CPU actually does with \#PF/\#GP + * during INS. */ + do + { + OP_TYPE *puMem; + rcStrict = iemMemMap(pVCpu, (void **)&puMem, OP_SIZE / 8, X86_SREG_ES, uAddrReg, + IEM_ACCESS_DATA_W, OP_SIZE / 8 - 1); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + uint32_t u32Value = 0; + rcStrict = IOMIOPortRead(pVM, pVCpu, u16Port, &u32Value, OP_SIZE / 8); + if (!IOM_SUCCESS(rcStrict)) + { + iemMemRollback(pVCpu); + return rcStrict; + } + + *puMem = (OP_TYPE)u32Value; +# ifdef IN_RING3 + VBOXSTRICTRC rcStrict2 = iemMemCommitAndUnmap(pVCpu, puMem, IEM_ACCESS_DATA_W); +# else + VBOXSTRICTRC rcStrict2 = iemMemCommitAndUnmapPostponeTroubleToR3(pVCpu, puMem, IEM_ACCESS_DATA_W); +# endif + if (rcStrict2 == VINF_SUCCESS) + { /* likely */ } + else + AssertLogRelMsgFailedReturn(("rcStrict2=%Rrc\n", VBOXSTRICTRC_VAL(rcStrict2)), + RT_FAILURE(rcStrict2) ? rcStrict2 : VERR_IEM_IPE_1); + + pVCpu->cpum.GstCtx.ADDR_rDI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + + cLeftPage--; + if (rcStrict != VINF_SUCCESS) + { + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + if (uCounterReg == 0) + rcStrict = iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + pVCpu->iem.s.cPotentialExits++; + return rcStrict; + } + + IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0); + } while ((int32_t)cLeftPage > 0); + + + /* + * Next page. Must check for interrupts and stuff here. + */ + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + } + + /* + * Done. + */ + pVCpu->iem.s.cPotentialExits++; + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + + +/** + * Implements 'OUTS' (no rep) + */ +IEM_CIMPL_DEF_2(RT_CONCAT4(iemCImpl_outs_op,OP_SIZE,_addr,ADDR_SIZE), uint8_t, iEffSeg, bool, fIoChecked) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + VBOXSTRICTRC rcStrict; + + /* + * ASSUMES the #GP for I/O permission is taken first, then any #GP for + * segmentation and finally any #PF due to virtual address translation. + * ASSUMES nothing is read from the I/O port before traps are taken. + */ + if (!fIoChecked) + { + rcStrict = iemHlpCheckPortIOPermission(pVCpu, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + } + + /* + * Check nested-guest I/O intercepts. + */ +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VMXEXITINSTRINFO ExitInstrInfo; + ExitInstrInfo.u = 0; + ExitInstrInfo.StrIo.u3AddrSize = ADDR_VMXSTRIO; + ExitInstrInfo.StrIo.iSegReg = iEffSeg; + rcStrict = iemVmxVmexitInstrStrIo(pVCpu, VMXINSTRID_IO_OUTS, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8, false /* fRep */, + ExitInstrInfo, cbInstr); + if (rcStrict != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict; + } +#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_IOIO_PROT)) + { + rcStrict = iemSvmHandleIOIntercept(pVCpu, pVCpu->cpum.GstCtx.dx, SVMIOIOTYPE_OUT, OP_SIZE / 8, ADDR_SIZE, iEffSeg, + false /* fRep */, true /* fStrIo */, cbInstr); + if (rcStrict == VINF_SVM_VMEXIT) + return VINF_SUCCESS; + if (rcStrict != VINF_SVM_INTERCEPT_NOT_ACTIVE) + { + Log(("iemCImpl_outs_op: iemSvmHandleIOIntercept failed (u16Port=%#x, cbReg=%u) rc=%Rrc\n", pVCpu->cpum.GstCtx.dx, + OP_SIZE / 8, VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } +#endif + + OP_TYPE uValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue, iEffSeg, pVCpu->cpum.GstCtx.ADDR_rSI); + if (rcStrict == VINF_SUCCESS) + { + rcStrict = IOMIOPortWrite(pVM, pVCpu, pVCpu->cpum.GstCtx.dx, uValue, OP_SIZE / 8); + if (IOM_SUCCESS(rcStrict)) + { + if (!pVCpu->cpum.GstCtx.eflags.Bits.u1DF) + pVCpu->cpum.GstCtx.ADDR_rSI += OP_SIZE / 8; + else + pVCpu->cpum.GstCtx.ADDR_rSI -= OP_SIZE / 8; + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + if (rcStrict != VINF_SUCCESS) + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + rcStrict = iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + pVCpu->iem.s.cPotentialExits++; + } + } + return rcStrict; +} + + +/** + * Implements 'REP OUTS'. + */ +IEM_CIMPL_DEF_2(RT_CONCAT4(iemCImpl_rep_outs_op,OP_SIZE,_addr,ADDR_SIZE), uint8_t, iEffSeg, bool, fIoChecked) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + + /* + * Setup. + */ + uint16_t const u16Port = pVCpu->cpum.GstCtx.dx; + VBOXSTRICTRC rcStrict; + if (!fIoChecked) + { +/** @todo check if this is too early for ecx=0. */ + rcStrict = iemHlpCheckPortIOPermission(pVCpu, u16Port, OP_SIZE / 8); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + } + + /* + * Check nested-guest I/O intercepts. + */ +#ifdef VBOX_WITH_NESTED_HWVIRT_VMX + if (IEM_VMX_IS_NON_ROOT_MODE(pVCpu)) + { + VMXEXITINSTRINFO ExitInstrInfo; + ExitInstrInfo.u = 0; + ExitInstrInfo.StrIo.u3AddrSize = ADDR_VMXSTRIO; + ExitInstrInfo.StrIo.iSegReg = iEffSeg; + rcStrict = iemVmxVmexitInstrStrIo(pVCpu, VMXINSTRID_IO_OUTS, pVCpu->cpum.GstCtx.dx, OP_SIZE / 8, true /* fRep */, + ExitInstrInfo, cbInstr); + if (rcStrict != VINF_VMX_INTERCEPT_NOT_ACTIVE) + return rcStrict; + } +#endif + +#ifdef VBOX_WITH_NESTED_HWVIRT_SVM + if (IEM_SVM_IS_CTRL_INTERCEPT_SET(pVCpu, SVM_CTRL_INTERCEPT_IOIO_PROT)) + { + rcStrict = iemSvmHandleIOIntercept(pVCpu, u16Port, SVMIOIOTYPE_OUT, OP_SIZE / 8, ADDR_SIZE, iEffSeg, true /* fRep */, + true /* fStrIo */, cbInstr); + if (rcStrict == VINF_SVM_VMEXIT) + return VINF_SUCCESS; + if (rcStrict != VINF_SVM_INTERCEPT_NOT_ACTIVE) + { + Log(("iemCImpl_rep_outs_op: iemSvmHandleIOIntercept failed (u16Port=%#x, cbReg=%u) rc=%Rrc\n", u16Port, OP_SIZE / 8, + VBOXSTRICTRC_VAL(rcStrict))); + return rcStrict; + } + } +#endif + + ADDR_TYPE uCounterReg = pVCpu->cpum.GstCtx.ADDR_rCX; + if (uCounterReg == 0) + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + + PCCPUMSELREGHID pHid = iemSRegGetHid(pVCpu, iEffSeg); + uint64_t uBaseAddr = 0; /* gcc may not be used uninitialized */ + rcStrict = iemMemSegCheckReadAccessEx(pVCpu, pHid, iEffSeg, &uBaseAddr); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + int8_t const cbIncr = pVCpu->cpum.GstCtx.eflags.Bits.u1DF ? -(OP_SIZE / 8) : (OP_SIZE / 8); + ADDR_TYPE uAddrReg = pVCpu->cpum.GstCtx.ADDR_rSI; + + /* + * The loop. + */ + for (;;) + { + /* + * Do segmentation and virtual page stuff. + */ + ADDR2_TYPE uVirtAddr = uAddrReg + (ADDR2_TYPE)uBaseAddr; + uint32_t cLeftPage = (GUEST_PAGE_SIZE - (uVirtAddr & GUEST_PAGE_OFFSET_MASK)) / (OP_SIZE / 8); + if (cLeftPage > uCounterReg) + cLeftPage = uCounterReg; + if ( cLeftPage > 0 /* can be null if unaligned, do one fallback round. */ + && cbIncr > 0 /** @todo Implement reverse direction string ops. */ + && ( IS_64_BIT_CODE(pVCpu) + || ( uAddrReg < pHid->u32Limit + && uAddrReg + (cLeftPage * (OP_SIZE / 8)) <= pHid->u32Limit) + ) + ) + { + RTGCPHYS GCPhysMem; + rcStrict = iemMemPageTranslateAndCheckAccess(pVCpu, uVirtAddr, OP_SIZE / 8, IEM_ACCESS_DATA_R, &GCPhysMem); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + /* + * If we can map the page without trouble, we use the IOM + * string I/O interface to do the job. + */ + PGMPAGEMAPLOCK PgLockMem; + OP_TYPE const *puMem; + rcStrict = iemMemPageMap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, (void **)&puMem, &PgLockMem); + if (rcStrict == VINF_SUCCESS) + { + uint32_t cTransfers = cLeftPage; + rcStrict = IOMIOPortWriteString(pVM, pVCpu, u16Port, puMem, &cTransfers, OP_SIZE / 8); + + uint32_t cActualTransfers = cLeftPage - cTransfers; + Assert(cActualTransfers <= cLeftPage); + pVCpu->cpum.GstCtx.ADDR_rSI = uAddrReg += cbIncr * cActualTransfers; + pVCpu->cpum.GstCtx.ADDR_rCX = uCounterReg -= cActualTransfers; + puMem += cActualTransfers; + + iemMemPageUnmap(pVCpu, GCPhysMem, IEM_ACCESS_DATA_R, puMem, &PgLockMem); + + if (rcStrict != VINF_SUCCESS) + { + if (IOM_SUCCESS(rcStrict)) + { + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + if (uCounterReg == 0) + rcStrict = iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + pVCpu->iem.s.cPotentialExits++; + } + return rcStrict; + } + + if (uCounterReg == 0) + break; + + /* If unaligned, we drop thru and do the page crossing access + below. Otherwise, do the next page. */ + if (!(uVirtAddr & (OP_SIZE / 8 - 1))) + { + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + continue; + } + cLeftPage = 0; + } + } + + /* + * Fallback - slow processing till the end of the current page. + * In the cross page boundrary case we will end up here with cLeftPage + * as 0, we execute one loop then. + * + * Note! We ASSUME the CPU will raise #PF or #GP before access the + * I/O port, otherwise it wouldn't really be restartable. + */ + /** @todo investigate what the CPU actually does with \#PF/\#GP + * during INS. */ + do + { + OP_TYPE uValue; + rcStrict = RT_CONCAT(iemMemFetchDataU,OP_SIZE)(pVCpu, &uValue, iEffSeg, uAddrReg); + if (rcStrict != VINF_SUCCESS) + return rcStrict; + + rcStrict = IOMIOPortWrite(pVM, pVCpu, u16Port, uValue, OP_SIZE / 8); + if (IOM_SUCCESS(rcStrict)) + { + pVCpu->cpum.GstCtx.ADDR_rSI = uAddrReg += cbIncr; + pVCpu->cpum.GstCtx.ADDR_rCX = --uCounterReg; + cLeftPage--; + } + if (rcStrict != VINF_SUCCESS) + { + if (IOM_SUCCESS(rcStrict)) + { + /** @todo finish: work out how this should work wrt status codes. Not sure we + * can use iemSetPassUpStatus here, but it depends on what + * iemRegAddToRipAndFinishingClearingRF may eventually return (if anything)... */ + rcStrict = iemSetPassUpStatus(pVCpu, rcStrict); + if (uCounterReg == 0) + iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); + pVCpu->iem.s.cPotentialExits++; + } + return rcStrict; + } + IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN(pVM, pVCpu, uCounterReg == 0); + } while ((int32_t)cLeftPage > 0); + + + /* + * Next page. Must check for interrupts and stuff here. + */ + if (uCounterReg == 0) + break; + IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN(pVM, pVCpu, pVCpu->cpum.GstCtx.eflags.u); + } + + /* + * Done. + */ + pVCpu->iem.s.cPotentialExits++; + return iemRegAddToRipAndFinishingClearingRF(pVCpu, cbInstr); +} + +#endif /* OP_SIZE != 64-bit */ + + +#undef OP_rAX +#undef OP_SIZE +#undef ADDR_SIZE +#undef ADDR_rDI +#undef ADDR_rSI +#undef ADDR_rCX +#undef ADDR_rIP +#undef ADDR2_TYPE +#undef ADDR_TYPE +#undef ADDR2_TYPE +#undef ADDR_VMXSTRIO +#undef IS_64_BIT_CODE +#undef IEM_CHECK_FF_YIELD_REPSTR_MAYBE_RETURN +#undef IEM_CHECK_FF_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN +#undef IEM_CHECK_FF_CPU_HIGH_PRIORITY_POST_REPSTR_MAYBE_RETURN + |