Beispiel #1
0
/**
 * Disassembles the instruction at RIP and if it's a hypercall
 * instruction, performs the hypercall.
 *
 * @param   pVCpu       The cross context virtual CPU structure.
 * @param   pCtx        Pointer to the guest-CPU context.
 * @param   pcbInstr    Where to store the disassembled instruction length.
 *                      Optional, can be NULL.
 *
 * @todo    This interface should disappear when IEM/REM execution engines
 *          handle VMCALL/VMMCALL instructions to call into GIM when
 *          required. See @bugref{7270#c168}.
 */
VMM_INT_DECL(VBOXSTRICTRC) GIMExecHypercallInstr(PVMCPU pVCpu, PCPUMCTX pCtx, uint8_t *pcbInstr)
{
    PVM pVM = pVCpu->CTX_SUFF(pVM);
    VMCPU_ASSERT_EMT(pVCpu);

    if (RT_UNLIKELY(!GIMIsEnabled(pVM)))
        return VERR_GIM_NOT_ENABLED;

    unsigned    cbInstr;
    DISCPUSTATE Dis;
    int rc = EMInterpretDisasCurrent(pVM, pVCpu, &Dis, &cbInstr);
    if (RT_SUCCESS(rc))
    {
        if (pcbInstr)
            *pcbInstr = (uint8_t)cbInstr;
        switch (pVM->gim.s.enmProviderId)
        {
        case GIMPROVIDERID_HYPERV:
            return gimHvExecHypercallInstr(pVCpu, pCtx, &Dis);

        case GIMPROVIDERID_KVM:
            return gimKvmExecHypercallInstr(pVCpu, pCtx, &Dis);

        default:
            AssertMsgFailed(("GIMExecHypercallInstr: for provider %u not available/implemented\n", pVM->gim.s.enmProviderId));
            return VERR_GIM_HYPERCALLS_NOT_AVAILABLE;
        }
    }

    Log(("GIM: GIMExecHypercallInstr: Failed to disassemble CS:RIP=%04x:%08RX64. rc=%Rrc\n", pCtx->cs.Sel, pCtx->rip, rc));
    return rc;
}
/**
 * \#PF Virtual Handler callback for Guest write access to the VBox shadow IDT.
 *
 * @returns VBox status code (appropriate for trap handling and GC return).
 * @param   pVM         Pointer to the VM.
 * @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) trpmRCShadowIDTWriteHandler(PVM pVM, RTGCUINT uErrorCode, PCPUMCTXCORE pRegFrame, RTGCPTR pvFault, RTGCPTR pvRange, uintptr_t offRange)
{
    PVMCPU pVCpu = VMMGetCpu0(pVM);
    LogRel(("FATAL ERROR: trpmRCShadowIDTWriteHandler: eip=%08X pvFault=%RGv pvRange=%08RGv\r\n", pRegFrame->eip, pvFault, pvRange));
    NOREF(uErrorCode); NOREF(offRange);

    /*
     * If we ever get here, then the guest has *probably* executed an SIDT
     * instruction that we failed to patch.  In theory this could be very bad,
     * but there are nasty applications out there that install device drivers
     * that mess with the guest's IDT.  In those cases, it's quite ok to simply
     * ignore the writes and pretend success.
     *
     * Another posibility is that the guest is touching some page memory and
     * it having nothing to do with our IDT or anything like that, just a
     * potential conflict that we didn't discover in time.
     */
    DISSTATE Dis;
    int rc = EMInterpretDisasCurrent(pVM, pVCpu, &Dis, NULL);
    if (rc == VINF_SUCCESS)
    {
        /* Just ignore the write. */
        pRegFrame->eip += Dis.cbInstr;
        return VINF_SUCCESS;
    }

    return VERR_TRPM_SHADOW_IDT_WRITE;
}
/**
 * Exception handler for #UD.
 *
 * @param   pVCpu       Pointer to the VMCPU.
 * @param   pCtx        Pointer to the guest-CPU context.
 * @param   pDis        Pointer to the disassembled instruction state at RIP.
 *                      Optional, can be NULL.
 */
VMM_INT_DECL(int) gimKvmXcptUD(PVMCPU pVCpu, PCPUMCTX pCtx, PDISCPUSTATE pDis)
{
    /*
     * If we didn't ask for #UD to be trapped, bail.
     */
    PVM     pVM  = pVCpu->CTX_SUFF(pVM);
    PGIMKVM pKvm = &pVM->gim.s.u.Kvm;
    if (RT_UNLIKELY(!pVM->gim.s.u.Kvm.fTrapXcptUD))
        return VERR_GIM_OPERATION_FAILED;

    /*
     * Make sure guest ring-0 is the one making the hypercall.
     */
    if (CPUMGetGuestCPL(pVCpu))
        return VERR_GIM_HYPERCALL_ACCESS_DENIED;

    int rc = VINF_SUCCESS;
    if (!pDis)
    {
        /*
         * Disassemble the instruction at RIP to figure out if it's the Intel VMCALL instruction
         * or the AMD VMMCALL instruction and if so, handle it as a hypercall.
         */
        DISCPUSTATE Dis;
        rc = EMInterpretDisasCurrent(pVM, pVCpu, &Dis, NULL /* pcbInstr */);
        pDis = &Dis;
    }

    if (RT_SUCCESS(rc))
    {
        /*
         * Patch the instruction to so we don't have to spend time disassembling it each time.
         * Makes sense only for HM as with raw-mode we will be getting a #UD regardless.
         */
        if (   pDis->pCurInstr->uOpcode == OP_VMCALL
            || pDis->pCurInstr->uOpcode == OP_VMMCALL)
        {
            if (   pDis->pCurInstr->uOpcode != pKvm->uOpCodeNative
                && HMIsEnabled(pVM))
            {
                uint8_t abHypercall[3];
                size_t  cbWritten = 0;
                rc = VMMPatchHypercall(pVM, &abHypercall, sizeof(abHypercall), &cbWritten);
                AssertRC(rc);
                Assert(sizeof(abHypercall) == pDis->cbInstr);
                Assert(sizeof(abHypercall) == cbWritten);

                rc = PGMPhysSimpleWriteGCPtr(pVCpu, pCtx->rip, &abHypercall, sizeof(abHypercall));
            }

            /*
             * Perform the hypercall and update RIP.
             *
             * For HM, we can simply resume guest execution without performing the hypercall now and
             * do it on the next VMCALL/VMMCALL exit handler on the patched instruction.
             *
             * For raw-mode we need to do this now anyway. So we do it here regardless with an added
             * advantage is that it saves one world-switch for the HM case.
             */
            if (RT_SUCCESS(rc))
            {
                int rc2 = gimKvmHypercall(pVCpu, pCtx);
                AssertRC(rc2);
                pCtx->rip += pDis->cbInstr;
            }
            return rc;
        }
    }

    return VERR_GIM_OPERATION_FAILED;
}