/** * \#PF Virtual Handler callback for Guest write access to the Guest's own GDT. * * @returns VBox status code (appropriate for trap handling and GC return). * @param pVM VM Handle. * @param uErrorCode CPU Error code. * @param pRegFrame Trap register frame. * @param pvFault The fault address (cr2). * @param pvRange The base address of the handled virtual range. * @param offRange The offset of the access into this range. * (If it's a EIP range this is the EIP, if not it's pvFault.) */ VMMRCDECL(int) selmRCGuestGDTWriteHandler(PVM pVM, RTGCUINT uErrorCode, PCPUMCTXCORE pRegFrame, RTGCPTR pvFault, RTGCPTR pvRange, uintptr_t offRange) { PVMCPU pVCpu = VMMGetCpu0(pVM); LogFlow(("selmRCGuestGDTWriteHandler errcode=%x fault=%RGv offRange=%08x\n", (uint32_t)uErrorCode, pvFault, offRange)); /* * First check if this is the LDT entry. * LDT updates are problems since an invalid LDT entry will cause trouble during worldswitch. */ int rc; if (CPUMGetGuestLDTR(pVCpu) / sizeof(X86DESC) == offRange / sizeof(X86DESC)) { Log(("LDTR selector change -> fall back to HC!!\n")); rc = VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; /** @todo We're not handling changed to the selectors in LDTR and TR correctly at all. * We should ignore any changes to those and sync them only when they are loaded by the guest! */ } else { /* * Attempt to emulate the instruction and sync the affected entries. */ /** @todo should check if any affected selectors are loaded. */ uint32_t cb; rc = EMInterpretInstruction(pVM, pVCpu, pRegFrame, (RTGCPTR)(RTRCUINTPTR)pvFault, &cb); if (RT_SUCCESS(rc) && cb) { unsigned iGDTE1 = offRange / sizeof(X86DESC); int rc2 = selmGCSyncGDTEntry(pVM, pRegFrame, iGDTE1); if (rc2 == VINF_SUCCESS) { Assert(cb); unsigned iGDTE2 = (offRange + cb - 1) / sizeof(X86DESC); if (iGDTE1 != iGDTE2) rc2 = selmGCSyncGDTEntry(pVM, pRegFrame, iGDTE2); if (rc2 == VINF_SUCCESS) { STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestGDTHandled); return rc; } } if (rc == VINF_SUCCESS || RT_FAILURE(rc2)) rc = rc2; } else { Assert(RT_FAILURE(rc)); if (rc == VERR_EM_INTERPRETER) rc = VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; } } if ( rc != VINF_EM_RAW_EMULATE_INSTR_LDT_FAULT && rc != VINF_EM_RAW_EMULATE_INSTR_TSS_FAULT) { /* Not necessary when we need to go back to the host context to sync the LDT or TSS. */ VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_GDT); } STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestGDTUnhandled); return rc; }
/** * Synchronizes one GDT entry (guest -> shadow). * * @returns VBox status code (appropriate for trap handling and GC return). * @param pVM VM Handle. * @param pRegFrame Trap register frame. * @param iGDTEntry The GDT entry to sync. */ static int selmGCSyncGDTEntry(PVM pVM, PCPUMCTXCORE pRegFrame, unsigned iGDTEntry) { PVMCPU pVCpu = VMMGetCpu0(pVM); Log2(("GDT %04X LDTR=%04X\n", iGDTEntry, CPUMGetGuestLDTR(pVCpu))); /* * Validate the offset. */ VBOXGDTR GdtrGuest; CPUMGetGuestGDTR(pVCpu, &GdtrGuest); unsigned offEntry = iGDTEntry * sizeof(X86DESC); if ( iGDTEntry >= SELM_GDT_ELEMENTS || offEntry > GdtrGuest.cbGdt) return VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; /* * Read the guest descriptor. */ X86DESC Desc; int rc = MMGCRamRead(pVM, &Desc, (uint8_t *)(uintptr_t)GdtrGuest.pGdt + offEntry, sizeof(X86DESC)); if (RT_FAILURE(rc)) return VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; /* * Check for conflicts. */ RTSEL Sel = iGDTEntry << X86_SEL_SHIFT; Assert( !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS] & ~X86_SEL_MASK) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_DS] & ~X86_SEL_MASK) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS64] & ~X86_SEL_MASK) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS] & ~X86_SEL_MASK) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS_TRAP08] & ~X86_SEL_MASK)); if ( pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_DS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS64] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS_TRAP08] == Sel) { if (Desc.Gen.u1Present) { Log(("selmGCSyncGDTEntry: Sel=%d Desc=%.8Rhxs: detected conflict!!\n", Sel, &Desc)); return VINF_SELM_SYNC_GDT; } Log(("selmGCSyncGDTEntry: Sel=%d Desc=%.8Rhxs: potential conflict (still not present)!\n", Sel, &Desc)); /* Note: we can't continue below or else we'll change the shadow descriptor!! */ /* When the guest makes the selector present, then we'll do a GDT sync. */ return VINF_SUCCESS; } /* * Code and data selectors are generally 1:1, with the * 'little' adjustment we do for DPL 0 selectors. */ PX86DESC pShadowDescr = &pVM->selm.s.paGdtRC[iGDTEntry]; if (Desc.Gen.u1DescType) { /* * Hack for A-bit against Trap E on read-only GDT. */ /** @todo Fix this by loading ds and cs before turning off WP. */ Desc.Gen.u4Type |= X86_SEL_TYPE_ACCESSED; /* * All DPL 0 code and data segments are squeezed into DPL 1. * * We're skipping conforming segments here because those * cannot give us any trouble. */ if ( Desc.Gen.u2Dpl == 0 && (Desc.Gen.u4Type & (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF)) != (X86_SEL_TYPE_CODE | X86_SEL_TYPE_CONF) ) Desc.Gen.u2Dpl = 1; } else { /* * System type selectors are marked not present. * Recompiler or special handling is required for these. */ /** @todo what about interrupt gates and rawr0? */ Desc.Gen.u1Present = 0; } //Log(("O: base=%08X limit=%08X attr=%04X\n", X86DESC_BASE(*pShadowDescr)), X86DESC_LIMIT(*pShadowDescr), (pShadowDescr->au32[1] >> 8) & 0xFFFF )); //Log(("N: base=%08X limit=%08X attr=%04X\n", X86DESC_BASE(Desc)), X86DESC_LIMIT(Desc), (Desc.au32[1] >> 8) & 0xFFFF )); *pShadowDescr = Desc; /* Check if we change the LDT selector */ if (Sel == CPUMGetGuestLDTR(pVCpu)) /** @todo this isn't correct in two(+) ways! 1. It shouldn't be done until the LDTR is reloaded. 2. It caused the next instruction to be emulated. */ { VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_LDT); return VINF_EM_RAW_EMULATE_INSTR_LDT_FAULT; } #ifdef LOG_ENABLED if (Sel == (pRegFrame->cs & X86_SEL_MASK)) Log(("GDT write to selector in CS register %04X\n", pRegFrame->cs)); else if (Sel == (pRegFrame->ds & X86_SEL_MASK)) Log(("GDT write to selector in DS register %04X\n", pRegFrame->ds)); else if (Sel == (pRegFrame->es & X86_SEL_MASK)) Log(("GDT write to selector in ES register %04X\n", pRegFrame->es)); else if (Sel == (pRegFrame->fs & X86_SEL_MASK)) Log(("GDT write to selector in FS register %04X\n", pRegFrame->fs)); else if (Sel == (pRegFrame->gs & X86_SEL_MASK)) Log(("GDT write to selector in GS register %04X\n", pRegFrame->gs)); else if (Sel == (pRegFrame->ss & X86_SEL_MASK)) Log(("GDT write to selector in SS register %04X\n", pRegFrame->ss)); #endif return VINF_SUCCESS; }
/** * Synchronizes one GDT entry (guest -> shadow). * * @returns VBox strict status code (appropriate for trap handling and GC * return). * @retval VINF_SUCCESS * @retval VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT * @retval VINF_SELM_SYNC_GDT * * @param pVM Pointer to the VM. * @param pVCpu The current virtual CPU. * @param pCtx CPU context for the current CPU. * @param iGDTEntry The GDT entry to sync. * * @remarks Caller checks that this isn't the LDT entry! */ static VBOXSTRICTRC selmRCSyncGDTEntry(PVM pVM, PVMCPU pVCpu, PCPUMCTX pCtx, unsigned iGDTEntry) { Log2(("GDT %04X LDTR=%04X\n", iGDTEntry, CPUMGetGuestLDTR(pVCpu))); /* * Validate the offset. */ VBOXGDTR GdtrGuest; CPUMGetGuestGDTR(pVCpu, &GdtrGuest); unsigned offEntry = iGDTEntry * sizeof(X86DESC); if ( iGDTEntry >= SELM_GDT_ELEMENTS || offEntry > GdtrGuest.cbGdt) return VINF_SUCCESS; /* ignore */ /* * Read the guest descriptor. */ X86DESC Desc; int rc = MMGCRamRead(pVM, &Desc, (uint8_t *)(uintptr_t)GdtrGuest.pGdt + offEntry, sizeof(X86DESC)); if (RT_FAILURE(rc)) { rc = PGMPhysSimpleReadGCPtr(pVCpu, &Desc, (uintptr_t)GdtrGuest.pGdt + offEntry, sizeof(X86DESC)); if (RT_FAILURE(rc)) { VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_GDT); VMCPU_FF_SET(pVCpu, VMCPU_FF_TO_R3); /* paranoia */ /* return VINF_EM_RESCHEDULE_REM; - bad idea if we're in a patch. */ return VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; } } /* * Check for conflicts. */ RTSEL Sel = iGDTEntry << X86_SEL_SHIFT; Assert( !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS] & ~X86_SEL_MASK_OFF_RPL) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_DS] & ~X86_SEL_MASK_OFF_RPL) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS64] & ~X86_SEL_MASK_OFF_RPL) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS] & ~X86_SEL_MASK_OFF_RPL) && !(pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS_TRAP08] & ~X86_SEL_MASK_OFF_RPL)); if ( pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_DS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_CS64] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS] == Sel || pVM->selm.s.aHyperSel[SELM_HYPER_SEL_TSS_TRAP08] == Sel) { if (Desc.Gen.u1Present) { Log(("selmRCSyncGDTEntry: Sel=%d Desc=%.8Rhxs: detected conflict!!\n", Sel, &Desc)); VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_GDT); VMCPU_FF_SET(pVCpu, VMCPU_FF_TO_R3); return VINF_SELM_SYNC_GDT; /** @todo this status code is ignored, unfortunately. */ } Log(("selmRCSyncGDTEntry: Sel=%d Desc=%.8Rhxs: potential conflict (still not present)!\n", Sel, &Desc)); /* Note: we can't continue below or else we'll change the shadow descriptor!! */ /* When the guest makes the selector present, then we'll do a GDT sync. */ return VINF_SUCCESS; } /* * Convert the guest selector to a shadow selector and update the shadow GDT. */ selmGuestToShadowDesc(pVM, &Desc); PX86DESC pShwDescr = &pVM->selm.s.paGdtRC[iGDTEntry]; //Log(("O: base=%08X limit=%08X attr=%04X\n", X86DESC_BASE(*pShwDescr)), X86DESC_LIMIT(*pShwDescr), (pShwDescr->au32[1] >> 8) & 0xFFFF )); //Log(("N: base=%08X limit=%08X attr=%04X\n", X86DESC_BASE(Desc)), X86DESC_LIMIT(Desc), (Desc.au32[1] >> 8) & 0xFFFF )); *pShwDescr = Desc; /* * Detect and mark stale registers. */ VBOXSTRICTRC rcStrict = VINF_SUCCESS; PCPUMSELREG paSReg = CPUMCTX_FIRST_SREG(pCtx); for (unsigned iSReg = 0; iSReg <= X86_SREG_COUNT; iSReg++) { if (Sel == (paSReg[iSReg].Sel & X86_SEL_MASK_OFF_RPL)) { if (CPUMSELREG_ARE_HIDDEN_PARTS_VALID(pVCpu, &paSReg[iSReg])) { if (selmIsSRegStale32(&paSReg[iSReg], &Desc, iSReg)) { Log(("GDT write to selector in %s register %04X (now stale)\n", g_aszSRegNms[iSReg], paSReg[iSReg].Sel)); paSReg[iSReg].fFlags |= CPUMSELREG_FLAGS_STALE; VMCPU_FF_SET(pVCpu, VMCPU_FF_TO_R3); /* paranoia */ /* rcStrict = VINF_EM_RESCHEDULE_REM; - bad idea if we're in a patch. */ rcStrict = VINF_EM_RAW_EMULATE_INSTR_GDT_FAULT; } else if (paSReg[iSReg].fFlags & CPUMSELREG_FLAGS_STALE) { Log(("GDT write to selector in %s register %04X (no longer stale)\n", g_aszSRegNms[iSReg], paSReg[iSReg].Sel)); paSReg[iSReg].fFlags &= ~CPUMSELREG_FLAGS_STALE; } else Log(("GDT write to selector in %s register %04X (no important change)\n", g_aszSRegNms[iSReg], paSReg[iSReg].Sel)); } else Log(("GDT write to selector in %s register %04X (out of sync)\n", g_aszSRegNms[iSReg], paSReg[iSReg].Sel)); } } /** @todo Detect stale LDTR as well? */ return rcStrict; }