/** * Enables the Hyper-V Hypercall page. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param GCPhysHypercallPage Where to map the hypercall page. */ VMMR3_INT_DECL(int) gimR3HvEnableHypercallPage(PVM pVM, RTGCPHYS GCPhysHypercallPage) { PPDMDEVINSR3 pDevIns = pVM->gim.s.pDevInsR3; PGIMMMIO2REGION pRegion = &pVM->gim.s.u.Hv.aMmio2Regions[GIM_HV_HYPERCALL_PAGE_REGION_IDX]; AssertPtrReturn(pDevIns, VERR_GIM_DEVICE_NOT_REGISTERED); if (pRegion->fMapped) { /* * Is it already enabled at the given guest-address? */ if (pRegion->GCPhysPage == GCPhysHypercallPage) return VINF_SUCCESS; /* * If it's mapped at a different address, unmap the previous address. */ int rc2 = gimR3HvDisableHypercallPage(pVM); AssertRC(rc2); } /* * Map the hypercall-page at the specified address. */ Assert(!pRegion->fMapped); int rc = GIMR3Mmio2Map(pVM, pRegion, GCPhysHypercallPage); if (RT_SUCCESS(rc)) { Assert(pRegion->GCPhysPage == GCPhysHypercallPage); /* * Patch the hypercall-page. */ size_t cbWritten = 0; rc = VMMPatchHypercall(pVM, pRegion->pvPageR3, PAGE_SIZE, &cbWritten); if ( RT_SUCCESS(rc) && cbWritten < PAGE_SIZE) { uint8_t *pbLast = (uint8_t *)pRegion->pvPageR3 + cbWritten; *pbLast = 0xc3; /* RET */ /* * Notify VMM that hypercalls are now enabled for all VCPUs. */ for (VMCPUID i = 0; i < pVM->cCpus; i++) VMMHypercallsEnable(&pVM->aCpus[i]); LogRel(("GIM: HyperV: Enabled hypercalls at %#RGp\n", GCPhysHypercallPage)); return VINF_SUCCESS; } else { if (rc == VINF_SUCCESS) rc = VERR_GIM_OPERATION_FAILED; LogRel(("GIM: HyperV: VMMPatchHypercall failed. rc=%Rrc cbWritten=%u\n", rc, cbWritten)); } GIMR3Mmio2Unmap(pVM, pRegion); } LogRel(("GIM: HyperV: GIMR3Mmio2Map failed. rc=%Rrc\n", rc)); return rc; }
/** * 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; }