/** * \#PF Virtual Handler callback for Guest write access to the Guest's own current IDT. * * @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) trpmRCGuestIDTWriteHandler(PVM pVM, RTGCUINT uErrorCode, PCPUMCTXCORE pRegFrame, RTGCPTR pvFault, RTGCPTR pvRange, uintptr_t offRange) { PVMCPU pVCpu = VMMGetCpu0(pVM); uint16_t cbIDT; RTGCPTR GCPtrIDT = (RTGCPTR)CPUMGetGuestIDTR(pVCpu, &cbIDT); #ifdef VBOX_STRICT RTGCPTR GCPtrIDTEnd = (RTGCPTR)((RTGCUINTPTR)GCPtrIDT + cbIDT + 1); #endif uint32_t iGate = ((RTGCUINTPTR)pvFault - (RTGCUINTPTR)GCPtrIDT)/sizeof(VBOXIDTE); AssertMsg(offRange < (uint32_t)cbIDT+1, ("pvFault=%RGv GCPtrIDT=%RGv-%RGv pvRange=%RGv\n", pvFault, GCPtrIDT, GCPtrIDTEnd, pvRange)); Assert((RTGCPTR)(RTRCUINTPTR)pvRange == GCPtrIDT); #if 0 /* Note! this causes problems in Windows XP as instructions following the update can be dangerous (str eax has been seen) */ /* Note! not going back to ring 3 could make the code scanner miss them. */ /* Check if we can handle the write here. */ if ( iGate != 3 /* Gate 3 is handled differently; could do it here as well, but let ring 3 handle this case for now. */ && !ASMBitTest(&pVM->trpm.s.au32IdtPatched[0], iGate)) /* Passthru gates need special attention too. */ { uint32_t cb; int rc = EMInterpretInstruction(pVM, pVCpu, pRegFrame, pvFault, &cb); if (RT_SUCCESS(rc) && cb) { uint32_t iGate1 = (offRange + cb - 1)/sizeof(VBOXIDTE); Log(("trpmRCGuestIDTWriteHandler: write to gate %x (%x) offset %x cb=%d\n", iGate, iGate1, offRange, cb)); trpmClearGuestTrapHandler(pVM, iGate); if (iGate != iGate1) trpmClearGuestTrapHandler(pVM, iGate1); STAM_COUNTER_INC(&pVM->trpm.s.StatRCWriteGuestIDTHandled); return VINF_SUCCESS; } } #else NOREF(iGate); #endif Log(("trpmRCGuestIDTWriteHandler: eip=%RGv write to gate %x offset %x\n", pRegFrame->eip, iGate, offRange)); /** @todo Check which IDT entry and keep the update cost low in TRPMR3SyncIDT() and CSAMCheckGates(). */ VMCPU_FF_SET(pVCpu, VMCPU_FF_TRPM_SYNC_IDT); STAM_COUNTER_INC(&pVM->trpm.s.StatRCWriteGuestIDTFault); return VINF_EM_RAW_EMULATE_INSTR_IDT_FAULT; }
/** * \#PF Virtual Handler callback for Guest write access to the Guest's own current TSS. * * @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) selmRCGuestTSSWriteHandler(PVM pVM, RTGCUINT uErrorCode, PCPUMCTXCORE pRegFrame, RTGCPTR pvFault, RTGCPTR pvRange, uintptr_t offRange) { PVMCPU pVCpu = VMMGetCpu0(pVM); LogFlow(("selmRCGuestTSSWriteHandler errcode=%x fault=%RGv offRange=%08x\n", (uint32_t)uErrorCode, pvFault, offRange)); /* * Try emulate the access. */ uint32_t cb; int rc = EMInterpretInstruction(pVM, pVCpu, pRegFrame, (RTGCPTR)(RTRCUINTPTR)pvFault, &cb); if (RT_SUCCESS(rc) && cb) { rc = VINF_SUCCESS; /* * If it's on the same page as the esp0 and ss0 fields or actually one of them, * then check if any of these has changed. */ PCVBOXTSS pGuestTss = (PVBOXTSS)(uintptr_t)pVM->selm.s.GCPtrGuestTss; if ( PAGE_ADDRESS(&pGuestTss->esp0) == PAGE_ADDRESS(&pGuestTss->padding_ss0) && PAGE_ADDRESS(&pGuestTss->esp0) == PAGE_ADDRESS((uint8_t *)pGuestTss + offRange) && ( pGuestTss->esp0 != pVM->selm.s.Tss.esp1 || pGuestTss->ss0 != (pVM->selm.s.Tss.ss1 & ~1)) /* undo raw-r0 */ ) { Log(("selmRCGuestTSSWriteHandler: R0 stack: %RTsel:%RGv -> %RTsel:%RGv\n", (RTSEL)(pVM->selm.s.Tss.ss1 & ~1), (RTGCPTR)pVM->selm.s.Tss.esp1, (RTSEL)pGuestTss->ss0, (RTGCPTR)pGuestTss->esp0)); pVM->selm.s.Tss.esp1 = pGuestTss->esp0; pVM->selm.s.Tss.ss1 = pGuestTss->ss0 | 1; STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestTSSHandledChanged); } /* Handle misaligned TSS in a safe manner (just in case). */ else if ( offRange >= RT_UOFFSETOF(VBOXTSS, esp0) && offRange < RT_UOFFSETOF(VBOXTSS, padding_ss0)) { struct { uint32_t esp0; uint16_t ss0; uint16_t padding_ss0; } s; AssertCompileSize(s, 8); rc = selmRCReadTssBits(pVM, &s, &pGuestTss->esp0, sizeof(s)); if ( rc == VINF_SUCCESS && ( s.esp0 != pVM->selm.s.Tss.esp1 || s.ss0 != (pVM->selm.s.Tss.ss1 & ~1)) /* undo raw-r0 */ ) { Log(("selmRCGuestTSSWriteHandler: R0 stack: %RTsel:%RGv -> %RTsel:%RGv [x-page]\n", (RTSEL)(pVM->selm.s.Tss.ss1 & ~1), (RTGCPTR)pVM->selm.s.Tss.esp1, (RTSEL)s.ss0, (RTGCPTR)s.esp0)); pVM->selm.s.Tss.esp1 = s.esp0; pVM->selm.s.Tss.ss1 = s.ss0 | 1; STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestTSSHandledChanged); } } /* * If VME is enabled we need to check if the interrupt redirection bitmap * needs updating. */ if ( offRange >= RT_UOFFSETOF(VBOXTSS, offIoBitmap) && (CPUMGetGuestCR4(pVCpu) & X86_CR4_VME)) { if (offRange - RT_UOFFSETOF(VBOXTSS, offIoBitmap) < sizeof(pGuestTss->offIoBitmap)) { uint16_t offIoBitmap = pGuestTss->offIoBitmap; if (offIoBitmap != pVM->selm.s.offGuestIoBitmap) { Log(("TSS offIoBitmap changed: old=%#x new=%#x -> resync in ring-3\n", pVM->selm.s.offGuestIoBitmap, offIoBitmap)); VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_TSS); VMCPU_FF_SET(pVCpu, VMCPU_FF_TO_R3); } else Log(("TSS offIoBitmap: old=%#x new=%#x [unchanged]\n", pVM->selm.s.offGuestIoBitmap, offIoBitmap)); } else { /** @todo not sure how the partial case is handled; probably not allowed */ uint32_t offIntRedirBitmap = pVM->selm.s.offGuestIoBitmap - sizeof(pVM->selm.s.Tss.IntRedirBitmap); if ( offIntRedirBitmap <= offRange && offIntRedirBitmap + sizeof(pVM->selm.s.Tss.IntRedirBitmap) >= offRange + cb && offIntRedirBitmap + sizeof(pVM->selm.s.Tss.IntRedirBitmap) <= pVM->selm.s.cbGuestTss) { Log(("TSS IntRedirBitmap Changed: offIoBitmap=%x offIntRedirBitmap=%x cbTSS=%x offRange=%x cb=%x\n", pVM->selm.s.offGuestIoBitmap, offIntRedirBitmap, pVM->selm.s.cbGuestTss, offRange, cb)); /** @todo only update the changed part. */ for (uint32_t i = 0; i < sizeof(pVM->selm.s.Tss.IntRedirBitmap) / 8; i++) { rc = selmRCReadTssBits(pVM, &pVM->selm.s.Tss.IntRedirBitmap[i * 8], (uint8_t *)pGuestTss + offIntRedirBitmap + i * 8, 8); if (rc != VINF_SUCCESS) break; } STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestTSSRedir); } } } /* Return to ring-3 for a full resync if any of the above fails... (?) */ if (rc != VINF_SUCCESS) { VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_TSS); VMCPU_FF_SET(pVCpu, VMCPU_FF_TO_R3); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; } STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestTSSHandled); } else { Assert(RT_FAILURE(rc)); VMCPU_FF_SET(pVCpu, VMCPU_FF_SELM_SYNC_TSS); STAM_COUNTER_INC(&pVM->selm.s.StatRCWriteGuestTSSUnhandled); if (rc == VERR_EM_INTERPRETER) rc = VINF_EM_RAW_EMULATE_INSTR_TSS_FAULT; } return rc; }
/** * \#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; }