summaryrefslogtreecommitdiffstats
path: root/src/VBox/VMM/testcase/NemRawBench-1.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/VMM/testcase/NemRawBench-1.cpp')
-rw-r--r--src/VBox/VMM/testcase/NemRawBench-1.cpp1351
1 files changed, 1351 insertions, 0 deletions
diff --git a/src/VBox/VMM/testcase/NemRawBench-1.cpp b/src/VBox/VMM/testcase/NemRawBench-1.cpp
new file mode 100644
index 00000000..22066501
--- /dev/null
+++ b/src/VBox/VMM/testcase/NemRawBench-1.cpp
@@ -0,0 +1,1351 @@
+/* $Id: NemRawBench-1.cpp $ */
+/** @file
+ * NEM Benchmark.
+ */
+
+/*
+ * Copyright (C) 2018-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <WinHvPlatform.h>
+# if !defined(_INTPTR) && defined(_M_AMD64) /* void pedantic stdint.h warnings */
+# define _INTPTR 2
+# endif
+
+#elif defined(RT_OS_LINUX)
+# include <linux/kvm.h>
+# include <errno.h>
+# include <sys/fcntl.h>
+# include <sys/ioctl.h>
+# include <sys/mman.h>
+# include <unistd.h>
+# include <time.h>
+
+#elif defined(RT_OS_DARWIN)
+# include <Availability.h>
+# if 1 /* header mix hack */
+# undef __OSX_AVAILABLE_STARTING
+# define __OSX_AVAILABLE_STARTING(_osx, _ios)
+# endif
+# include <Hypervisor/hv.h>
+# include <Hypervisor/hv_arch_x86.h>
+# include <Hypervisor/hv_arch_vmx.h>
+# include <Hypervisor/hv_vmx.h>
+# include <mach/mach_time.h>
+# include <mach/kern_return.h>
+# include <sys/time.h>
+# include <time.h>
+# include <sys/mman.h>
+# include <errno.h>
+
+#else
+# error "port me"
+#endif
+
+#include <iprt/stream.h>
+#include <iprt/stdarg.h>
+#include <iprt/types.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The base mapping address of the g_pbMem. */
+#define MY_MEM_BASE 0x1000
+/** No-op MMIO access address. */
+#define MY_NOP_MMIO 0x0808
+/** The RIP which the testcode starts. */
+#define MY_TEST_RIP 0x2000
+
+/** The test termination port number. */
+#define MY_TERM_PORT 0x01
+/** The no-op test port number. */
+#define MY_NOP_PORT 0x7f
+
+#define MY_TEST_F_NOP_IO (1U<<0)
+#define MY_TEST_F_CPUID (1U<<1)
+#define MY_TEST_F_NOP_MMIO (1U<<2)
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Chunk of memory mapped at address 0x1000 (MY_MEM_BASE). */
+static unsigned char *g_pbMem;
+/** Amount of RAM at address 0x1000 (MY_MEM_BASE). */
+static size_t g_cbMem;
+#ifdef RT_OS_WINDOWS
+static WHV_PARTITION_HANDLE g_hPartition = NULL;
+
+/** @name APIs imported from WinHvPlatform.dll
+ * @{ */
+static decltype(WHvCreatePartition) *g_pfnWHvCreatePartition;
+static decltype(WHvSetupPartition) *g_pfnWHvSetupPartition;
+static decltype(WHvGetPartitionProperty) *g_pfnWHvGetPartitionProperty;
+static decltype(WHvSetPartitionProperty) *g_pfnWHvSetPartitionProperty;
+static decltype(WHvMapGpaRange) *g_pfnWHvMapGpaRange;
+static decltype(WHvCreateVirtualProcessor) *g_pfnWHvCreateVirtualProcessor;
+static decltype(WHvRunVirtualProcessor) *g_pfnWHvRunVirtualProcessor;
+static decltype(WHvGetVirtualProcessorRegisters) *g_pfnWHvGetVirtualProcessorRegisters;
+static decltype(WHvSetVirtualProcessorRegisters) *g_pfnWHvSetVirtualProcessorRegisters;
+/** @} */
+static uint64_t (WINAPI *g_pfnRtlGetSystemTimePrecise)(void);
+
+#elif defined(RT_OS_LINUX)
+/** The VM handle. */
+static int g_fdVm;
+/** The VCPU handle. */
+static int g_fdVCpu;
+/** The kvm_run structure for the VCpu. */
+static struct kvm_run *g_pVCpuRun;
+/** The size of the g_pVCpuRun mapping. */
+static ssize_t g_cbVCpuRun;
+
+#elif defined(RT_OS_DARWIN)
+/** The VCpu ID. */
+static hv_vcpuid_t g_idVCpu;
+#endif
+
+
+static int error(const char *pszFormat, ...)
+{
+ RTStrmPrintf(g_pStdErr, "error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrmPrintfV(g_pStdErr, pszFormat, va);
+ va_end(va);
+ return 1;
+}
+
+
+static uint64_t getNanoTS(void)
+{
+#ifdef RT_OS_WINDOWS
+ return g_pfnRtlGetSystemTimePrecise() * 100;
+
+#elif defined(RT_OS_LINUX)
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
+
+#elif defined(RT_OS_DARWIN)
+ static struct mach_timebase_info s_Info = { 0, 0 };
+ static double s_rdFactor = 0.0;
+ /* Lazy init. */
+ if (s_Info.denom != 0)
+ { /* likely */ }
+ else if (mach_timebase_info(&s_Info) == KERN_SUCCESS)
+ s_rdFactor = (double)s_Info.numer / (double)s_Info.denom;
+ else
+ {
+ error("mach_timebase_info(&Info) failed\n");
+ exit(1);
+ }
+ if (s_Info.denom == 1 && s_Info.numer == 1) /* special case: absolute time is in nanoseconds */
+ return mach_absolute_time();
+ return mach_absolute_time() * s_rdFactor;
+#else
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (uint64_t)tv.tv_sec * UINT64_C(1000000000)
+ + (tv.tv_usec * UINT32_C(1000));
+#endif
+}
+
+
+char *formatNum(uint64_t uNum, unsigned cchWidth, char *pszDst, size_t cbDst)
+{
+ char szTmp[64 + 22];
+ size_t cchTmp = RTStrPrintf(szTmp, sizeof(szTmp) - 22, "%llu", (unsigned long long)uNum);
+ size_t cSeps = (cchTmp - 1) / 3;
+ size_t const cchTotal = cchTmp + cSeps;
+ if (cSeps)
+ {
+ szTmp[cchTotal] = '\0';
+ for (size_t iSrc = cchTmp, iDst = cchTotal; cSeps > 0; cSeps--)
+ {
+ szTmp[--iDst] = szTmp[--iSrc];
+ szTmp[--iDst] = szTmp[--iSrc];
+ szTmp[--iDst] = szTmp[--iSrc];
+ szTmp[--iDst] = ' ';
+ }
+ }
+
+ size_t offDst = 0;
+ while (cchWidth-- > cchTotal && offDst < cbDst)
+ pszDst[offDst++] = ' ';
+ size_t offSrc = 0;
+ while (offSrc < cchTotal && offDst < cbDst)
+ pszDst[offDst++] = szTmp[offSrc++];
+ pszDst[offDst] = '\0';
+ return pszDst;
+}
+
+
+int reportResult(const char *pszInstruction, uint32_t cInstructions, uint64_t nsElapsed, uint32_t cExits)
+{
+ uint64_t const cInstrPerSec = nsElapsed ? (uint64_t)cInstructions * 1000000000 / nsElapsed : 0;
+ char szTmp1[64], szTmp2[64], szTmp3[64];
+ RTPrintf("%s %7s instructions per second (%s exits in %s ns)\n",
+ formatNum(cInstrPerSec, 10, szTmp1, sizeof(szTmp1)), pszInstruction,
+ formatNum(cExits, 0, szTmp2, sizeof(szTmp2)),
+ formatNum(nsElapsed, 0, szTmp3, sizeof(szTmp3)));
+ return 0;
+}
+
+
+
+#ifdef RT_OS_WINDOWS
+
+/*
+ * Windows - Hyper-V Platform API.
+ */
+
+static int createVM(void)
+{
+ /*
+ * Resolve APIs.
+ */
+ HMODULE hmod = LoadLibraryW(L"WinHvPlatform.dll");
+ if (hmod == NULL)
+ return error("Error loading WinHvPlatform.dll: %u\n", GetLastError());
+ static struct { const char *pszFunction; FARPROC *ppfn; } const s_aImports[] =
+ {
+# define IMPORT_ENTRY(a_Name) { #a_Name, (FARPROC *)&g_pfn##a_Name }
+ IMPORT_ENTRY(WHvCreatePartition),
+ IMPORT_ENTRY(WHvSetupPartition),
+ IMPORT_ENTRY(WHvGetPartitionProperty),
+ IMPORT_ENTRY(WHvSetPartitionProperty),
+ IMPORT_ENTRY(WHvMapGpaRange),
+ IMPORT_ENTRY(WHvCreateVirtualProcessor),
+ IMPORT_ENTRY(WHvRunVirtualProcessor),
+ IMPORT_ENTRY(WHvGetVirtualProcessorRegisters),
+ IMPORT_ENTRY(WHvSetVirtualProcessorRegisters),
+# undef IMPORT_ENTRY
+ };
+ FARPROC pfn;
+ for (size_t i = 0; i < sizeof(s_aImports) / sizeof(s_aImports[0]); i++)
+ {
+ *s_aImports[i].ppfn = pfn = GetProcAddress(hmod, s_aImports[i].pszFunction);
+ if (!pfn)
+ return error("Error resolving WinHvPlatform.dll!%s: %u\n", s_aImports[i].pszFunction, GetLastError());
+ }
+# ifndef IN_SLICKEDIT
+# define WHvCreatePartition g_pfnWHvCreatePartition
+# define WHvSetupPartition g_pfnWHvSetupPartition
+# define WHvGetPartitionProperty g_pfnWHvGetPartitionProperty
+# define WHvSetPartitionProperty g_pfnWHvSetPartitionProperty
+# define WHvMapGpaRange g_pfnWHvMapGpaRange
+# define WHvCreateVirtualProcessor g_pfnWHvCreateVirtualProcessor
+# define WHvRunVirtualProcessor g_pfnWHvRunVirtualProcessor
+# define WHvGetVirtualProcessorRegisters g_pfnWHvGetVirtualProcessorRegisters
+# define WHvSetVirtualProcessorRegisters g_pfnWHvSetVirtualProcessorRegisters
+# endif
+ /* Need a precise time function. */
+ *(FARPROC *)&g_pfnRtlGetSystemTimePrecise = pfn = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetSystemTimePrecise");
+ if (pfn == NULL)
+ return error("Error resolving ntdll.dll!RtlGetSystemTimePrecise: %u\n", GetLastError());
+
+ /*
+ * Create the partition with 1 CPU and the specfied amount of memory.
+ */
+ WHV_PARTITION_HANDLE hPartition;
+ HRESULT hrc = WHvCreatePartition(&hPartition);
+ if (!SUCCEEDED(hrc))
+ return error("WHvCreatePartition failed: %#x\n", hrc);
+ g_hPartition = hPartition;
+
+ WHV_PARTITION_PROPERTY Property;
+ memset(&Property, 0, sizeof(Property));
+ Property.ProcessorCount = 1;
+ hrc = WHvSetPartitionProperty(hPartition, WHvPartitionPropertyCodeProcessorCount, &Property, sizeof(Property));
+ if (!SUCCEEDED(hrc))
+ return error("WHvSetPartitionProperty/WHvPartitionPropertyCodeProcessorCount failed: %#x\n", hrc);
+
+ memset(&Property, 0, sizeof(Property));
+ Property.ExtendedVmExits.X64CpuidExit = 1;
+ Property.ExtendedVmExits.X64MsrExit = 1;
+ hrc = WHvSetPartitionProperty(hPartition, WHvPartitionPropertyCodeExtendedVmExits, &Property, sizeof(Property));
+ if (!SUCCEEDED(hrc))
+ return error("WHvSetPartitionProperty/WHvPartitionPropertyCodeExtendedVmExits failed: %#x\n", hrc);
+
+ hrc = WHvSetupPartition(hPartition);
+ if (!SUCCEEDED(hrc))
+ return error("WHvSetupPartition failed: %#x\n", hrc);
+
+ hrc = WHvCreateVirtualProcessor(hPartition, 0 /*idVCpu*/, 0 /*fFlags*/);
+ if (!SUCCEEDED(hrc))
+ return error("WHvCreateVirtualProcessor failed: %#x\n", hrc);
+
+ g_pbMem = (unsigned char *)VirtualAlloc(NULL, g_cbMem, MEM_COMMIT, PAGE_READWRITE);
+ if (!g_pbMem)
+ return error("VirtualAlloc failed: %u\n", GetLastError());
+ memset(g_pbMem, 0xcc, g_cbMem);
+
+ hrc = WHvMapGpaRange(hPartition, g_pbMem, MY_MEM_BASE /*GCPhys*/, g_cbMem,
+ WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite | WHvMapGpaRangeFlagExecute);
+ if (!SUCCEEDED(hrc))
+ return error("WHvMapGpaRange failed: %#x\n", hrc);
+
+ WHV_RUN_VP_EXIT_CONTEXT ExitInfo;
+ memset(&ExitInfo, 0, sizeof(ExitInfo));
+ WHvRunVirtualProcessor(g_hPartition, 0 /*idCpu*/, &ExitInfo, sizeof(ExitInfo));
+
+ return 0;
+}
+
+
+static int runtimeError(const char *pszFormat, ...)
+{
+ RTStrmPrintf(g_pStdErr, "runtime error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrmPrintfV(g_pStdErr, pszFormat, va);
+ va_end(va);
+
+ static struct { const char *pszName; WHV_REGISTER_NAME enmName; unsigned uType; } const s_aRegs[] =
+ {
+ { "rip", WHvX64RegisterRip, 64 },
+ { "cs", WHvX64RegisterCs, 1 },
+ { "rflags", WHvX64RegisterRflags, 32 },
+ { "rax", WHvX64RegisterRax, 64 },
+ { "rcx", WHvX64RegisterRcx, 64 },
+ { "rdx", WHvX64RegisterRdx, 64 },
+ { "rbx", WHvX64RegisterRbx, 64 },
+ { "rsp", WHvX64RegisterRsp, 64 },
+ { "ss", WHvX64RegisterSs, 1 },
+ { "rbp", WHvX64RegisterRbp, 64 },
+ { "rsi", WHvX64RegisterRsi, 64 },
+ { "rdi", WHvX64RegisterRdi, 64 },
+ { "ds", WHvX64RegisterDs, 1 },
+ { "es", WHvX64RegisterEs, 1 },
+ { "fs", WHvX64RegisterFs, 1 },
+ { "gs", WHvX64RegisterGs, 1 },
+ { "cr0", WHvX64RegisterCr0, 64 },
+ { "cr2", WHvX64RegisterCr2, 64 },
+ { "cr3", WHvX64RegisterCr3, 64 },
+ { "cr4", WHvX64RegisterCr4, 64 },
+ };
+ for (unsigned i = 0; i < sizeof(s_aRegs) / sizeof(s_aRegs[0]); i++)
+ {
+ WHV_REGISTER_VALUE Value;
+ WHV_REGISTER_NAME enmName = s_aRegs[i].enmName;
+ HRESULT hrc = WHvGetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, &enmName, 1, &Value);
+ if (SUCCEEDED(hrc))
+ {
+ if (s_aRegs[i].uType == 32)
+ RTStrmPrintf(g_pStdErr, "%8s=%08x\n", s_aRegs[i].pszName, Value.Reg32);
+ else if (s_aRegs[i].uType == 64)
+ RTStrmPrintf(g_pStdErr, "%8s=%08x'%08x\n", s_aRegs[i].pszName, (unsigned)(Value.Reg64 >> 32), Value.Reg32);
+ else if (s_aRegs[i].uType == 1)
+ RTStrmPrintf(g_pStdErr, "%8s=%04x base=%08x'%08x limit=%08x attr=%04x\n", s_aRegs[i].pszName,
+ Value.Segment.Selector, (unsigned)(Value.Segment.Base >> 32), (unsigned)Value.Segment.Base,
+ Value.Segment.Limit, Value.Segment.Attributes);
+ }
+ else
+ RTStrmPrintf(g_pStdErr, "%8s=<WHvGetVirtualProcessorRegisters failed %#x>\n", s_aRegs[i].pszName, hrc);
+ }
+
+ return 1;
+}
+
+
+static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
+ unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
+ unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
+{
+ (void)fTest;
+
+ /*
+ * Initialize the real mode context.
+ */
+# define ADD_REG64(a_enmName, a_uValue) do { \
+ aenmNames[iReg] = (a_enmName); \
+ aValues[iReg].Reg128.High64 = 0; \
+ aValues[iReg].Reg64 = (a_uValue); \
+ iReg++; \
+ } while (0)
+# define ADD_SEG(a_enmName, a_Base, a_Limit, a_Sel, a_fCode) \
+ do { \
+ aenmNames[iReg] = a_enmName; \
+ aValues[iReg].Segment.Base = (a_Base); \
+ aValues[iReg].Segment.Limit = (a_Limit); \
+ aValues[iReg].Segment.Selector = (a_Sel); \
+ aValues[iReg].Segment.Attributes = a_fCode ? 0x9b : 0x93; \
+ iReg++; \
+ } while (0)
+ WHV_REGISTER_NAME aenmNames[80];
+ WHV_REGISTER_VALUE aValues[80];
+ unsigned iReg = 0;
+ ADD_REG64(WHvX64RegisterRax, uEax);
+ ADD_REG64(WHvX64RegisterRcx, uEcx);
+ ADD_REG64(WHvX64RegisterRdx, uEdx);
+ ADD_REG64(WHvX64RegisterRbx, uEbx);
+ ADD_REG64(WHvX64RegisterRsp, uEsp);
+ ADD_REG64(WHvX64RegisterRbp, uEbp);
+ ADD_REG64(WHvX64RegisterRsi, uEsi);
+ ADD_REG64(WHvX64RegisterRdi, uEdi);
+ ADD_REG64(WHvX64RegisterRip, MY_TEST_RIP);
+ ADD_REG64(WHvX64RegisterRflags, 2);
+ ADD_SEG(WHvX64RegisterEs, 0x00000, 0xffff, 0x0000, 0);
+ ADD_SEG(WHvX64RegisterCs, 0x00000, 0xffff, 0x0000, 1);
+ ADD_SEG(WHvX64RegisterSs, 0x00000, 0xffff, 0x0000, 0);
+ ADD_SEG(WHvX64RegisterDs, 0x00000, 0xffff, 0x0000, 0);
+ ADD_SEG(WHvX64RegisterFs, 0x00000, 0xffff, 0x0000, 0);
+ ADD_SEG(WHvX64RegisterGs, 0x00000, 0xffff, 0x0000, 0);
+ ADD_REG64(WHvX64RegisterCr0, 0x10010 /*WP+ET*/);
+ ADD_REG64(WHvX64RegisterCr2, 0);
+ ADD_REG64(WHvX64RegisterCr3, 0);
+ ADD_REG64(WHvX64RegisterCr4, 0);
+ HRESULT hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, iReg, aValues);
+ if (!SUCCEEDED(hrc))
+ return error("WHvSetVirtualProcessorRegisters failed (for %s): %#x\n", pszInstruction, hrc);
+# undef ADD_REG64
+# undef ADD_SEG
+
+ /*
+ * Run the test.
+ */
+ uint32_t cExits = 0;
+ uint64_t const nsStart = getNanoTS();
+ for (;;)
+ {
+ WHV_RUN_VP_EXIT_CONTEXT ExitInfo;
+ memset(&ExitInfo, 0, sizeof(ExitInfo));
+ hrc = WHvRunVirtualProcessor(g_hPartition, 0 /*idCpu*/, &ExitInfo, sizeof(ExitInfo));
+ if (SUCCEEDED(hrc))
+ {
+ cExits++;
+ if (ExitInfo.ExitReason == WHvRunVpExitReasonX64IoPortAccess)
+ {
+ if (ExitInfo.IoPortAccess.PortNumber == MY_NOP_PORT)
+ { /* likely: nop instruction */ }
+ else if (ExitInfo.IoPortAccess.PortNumber == MY_TERM_PORT)
+ break;
+ else
+ return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, ExitInfo.IoPortAccess.PortNumber);
+
+ /* Advance. */
+ if (ExitInfo.VpContext.InstructionLength)
+ {
+ aenmNames[0] = WHvX64RegisterRip;
+ aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
+ hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 1, aValues);
+ if (SUCCEEDED(hrc))
+ { /* likely */ }
+ else
+ return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
+ }
+ else
+ return runtimeError("VpContext.InstructionLength is zero (for %s)\n", pszInstruction);
+ }
+ else if (ExitInfo.ExitReason == WHvRunVpExitReasonX64Cpuid)
+ {
+ /* Advance RIP and set default results. */
+ if (ExitInfo.VpContext.InstructionLength)
+ {
+ aenmNames[0] = WHvX64RegisterRip;
+ aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
+ aenmNames[1] = WHvX64RegisterRax;
+ aValues[1].Reg64 = ExitInfo.CpuidAccess.DefaultResultRax;
+ aenmNames[2] = WHvX64RegisterRcx;
+ aValues[2].Reg64 = ExitInfo.CpuidAccess.DefaultResultRcx;
+ aenmNames[3] = WHvX64RegisterRdx;
+ aValues[3].Reg64 = ExitInfo.CpuidAccess.DefaultResultRdx;
+ aenmNames[4] = WHvX64RegisterRbx;
+ aValues[4].Reg64 = ExitInfo.CpuidAccess.DefaultResultRbx;
+ hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 5, aValues);
+ if (SUCCEEDED(hrc))
+ { /* likely */ }
+ else
+ return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
+ }
+ else
+ return runtimeError("VpContext.InstructionLength is zero (for %s)\n", pszInstruction);
+ }
+ else if (ExitInfo.ExitReason == WHvRunVpExitReasonMemoryAccess)
+ {
+ if (ExitInfo.MemoryAccess.Gpa == MY_NOP_MMIO)
+ { /* likely: nop address */ }
+ else
+ return runtimeError("Unexpected memory access (for %s): %#x\n", pszInstruction, ExitInfo.MemoryAccess.Gpa);
+
+ /* Advance and set return register (assuming RAX and two byte instruction). */
+ aenmNames[0] = WHvX64RegisterRip;
+ if (ExitInfo.VpContext.InstructionLength)
+ aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
+ else
+ aValues[0].Reg64 = ExitInfo.VpContext.Rip + 2;
+ aenmNames[1] = WHvX64RegisterRax;
+ aValues[1].Reg64 = 42;
+ hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 2, aValues);
+ if (SUCCEEDED(hrc))
+ { /* likely */ }
+ else
+ return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
+ }
+ else
+ return runtimeError("Unexpected exit (for %s): %#x\n", pszInstruction, ExitInfo.ExitReason);
+ }
+ else
+ return runtimeError("WHvRunVirtualProcessor failed (for %s): %#x\n", pszInstruction, hrc);
+ }
+ uint64_t const nsElapsed = getNanoTS() - nsStart;
+ return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
+}
+
+
+
+#elif defined(RT_OS_LINUX)
+
+/*
+ * GNU/linux - KVM
+ */
+
+static int createVM(void)
+{
+ int fd = open("/dev/kvm", O_RDWR);
+ if (fd < 0)
+ return error("Error opening /dev/kvm: %d\n", errno);
+
+ g_fdVm = ioctl(fd, KVM_CREATE_VM, (uintptr_t)0);
+ if (g_fdVm < 0)
+ return error("KVM_CREATE_VM failed: %d\n", errno);
+
+ /* Create the VCpu. */
+ g_cbVCpuRun = ioctl(fd, KVM_GET_VCPU_MMAP_SIZE, (uintptr_t)0);
+ if (g_cbVCpuRun <= 0x1000 || (g_cbVCpuRun & 0xfff))
+ return error("Failed to get KVM_GET_VCPU_MMAP_SIZE: %#xz errno=%d\n", g_cbVCpuRun, errno);
+
+ g_fdVCpu = ioctl(g_fdVm, KVM_CREATE_VCPU, (uintptr_t)0);
+ if (g_fdVCpu < 0)
+ return error("KVM_CREATE_VCPU failed: %d\n", errno);
+
+ g_pVCpuRun = (struct kvm_run *)mmap(NULL, g_cbVCpuRun, PROT_READ | PROT_WRITE, MAP_PRIVATE, g_fdVCpu, 0);
+ if ((void *)g_pVCpuRun == MAP_FAILED)
+ return error("mmap kvm_run failed: %d\n", errno);
+
+ /* Memory. */
+ g_pbMem = (unsigned char *)mmap(NULL, g_cbMem, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if ((void *)g_pbMem == MAP_FAILED)
+ return error("mmap RAM failed: %d\n", errno);
+
+ struct kvm_userspace_memory_region MemReg;
+ MemReg.slot = 0;
+ MemReg.flags = 0;
+ MemReg.guest_phys_addr = MY_MEM_BASE;
+ MemReg.memory_size = g_cbMem;
+ MemReg.userspace_addr = (uintptr_t)g_pbMem;
+ int rc = ioctl(g_fdVm, KVM_SET_USER_MEMORY_REGION, &MemReg);
+ if (rc != 0)
+ return error("KVM_SET_USER_MEMORY_REGION failed: %d (%d)\n", errno, rc);
+
+ close(fd);
+ return 0;
+}
+
+
+static void printSReg(const char *pszName, struct kvm_segment const *pSReg)
+{
+ RTStrmPrintf(g_pStdErr, " %5s=%04x base=%016llx limit=%08x type=%#x p=%d dpl=%d db=%d s=%d l=%d g=%d avl=%d un=%d\n",
+ pszName, pSReg->selector, pSReg->base, pSReg->limit, pSReg->type, pSReg->present, pSReg->dpl,
+ pSReg->db, pSReg->s, pSReg->l, pSReg->g, pSReg->avl, pSReg->unusable);
+}
+
+
+static int runtimeError(const char *pszFormat, ...)
+{
+ RTStrmPrintf(g_pStdErr, "runtime error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrmPrintfV(g_pStdErr, pszFormat, va);
+ va_end(va);
+
+ RTStrmPrintf(g_pStdErr, " exit_reason=%#010x\n", g_pVCpuRun->exit_reason);
+ RTStrmPrintf(g_pStdErr, "ready_for_interrupt_injection=%#x\n", g_pVCpuRun->ready_for_interrupt_injection);
+ RTStrmPrintf(g_pStdErr, " if_flag=%#x\n", g_pVCpuRun->if_flag);
+ RTStrmPrintf(g_pStdErr, " flags=%#x\n", g_pVCpuRun->flags);
+ RTStrmPrintf(g_pStdErr, " kvm_valid_regs=%#018llx\n", g_pVCpuRun->kvm_valid_regs);
+ RTStrmPrintf(g_pStdErr, " kvm_dirty_regs=%#018llx\n", g_pVCpuRun->kvm_dirty_regs);
+
+ struct kvm_regs Regs;
+ memset(&Regs, 0, sizeof(Regs));
+ struct kvm_sregs SRegs;
+ memset(&SRegs, 0, sizeof(SRegs));
+ if ( ioctl(g_fdVCpu, KVM_GET_REGS, &Regs) != -1
+ && ioctl(g_fdVCpu, KVM_GET_SREGS, &SRegs) != -1)
+ {
+ RTStrmPrintf(g_pStdErr, " rip=%016llx\n", Regs.rip);
+ printSReg("cs", &SRegs.cs);
+ RTStrmPrintf(g_pStdErr, " rflags=%08llx\n", Regs.rflags);
+ RTStrmPrintf(g_pStdErr, " rax=%016llx\n", Regs.rax);
+ RTStrmPrintf(g_pStdErr, " rbx=%016llx\n", Regs.rcx);
+ RTStrmPrintf(g_pStdErr, " rdx=%016llx\n", Regs.rdx);
+ RTStrmPrintf(g_pStdErr, " rcx=%016llx\n", Regs.rbx);
+ RTStrmPrintf(g_pStdErr, " rsp=%016llx\n", Regs.rsp);
+ RTStrmPrintf(g_pStdErr, " rbp=%016llx\n", Regs.rbp);
+ RTStrmPrintf(g_pStdErr, " rsi=%016llx\n", Regs.rsi);
+ RTStrmPrintf(g_pStdErr, " rdi=%016llx\n", Regs.rdi);
+ printSReg("ss", &SRegs.ss);
+ printSReg("ds", &SRegs.ds);
+ printSReg("es", &SRegs.es);
+ printSReg("fs", &SRegs.fs);
+ printSReg("gs", &SRegs.gs);
+ printSReg("tr", &SRegs.tr);
+ printSReg("ldtr", &SRegs.ldt);
+
+ uint64_t const offMem = Regs.rip + SRegs.cs.base - MY_MEM_BASE;
+ if (offMem < g_cbMem - 10)
+ RTStrmPrintf(g_pStdErr, " bytes at PC (%#zx): %02x %02x %02x %02x %02x %02x %02x %02x\n", (size_t)(offMem + MY_MEM_BASE),
+ g_pbMem[offMem ], g_pbMem[offMem + 1], g_pbMem[offMem + 2], g_pbMem[offMem + 3],
+ g_pbMem[offMem + 4], g_pbMem[offMem + 5], g_pbMem[offMem + 6], g_pbMem[offMem + 7]);
+ }
+
+ return 1;
+}
+
+static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
+ unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
+ unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
+{
+ (void)fTest;
+
+ /*
+ * Setup real mode context.
+ */
+#define SET_SEG(a_SReg, a_Base, a_Limit, a_Sel, a_fCode) \
+ do { \
+ a_SReg.base = (a_Base); \
+ a_SReg.limit = (a_Limit); \
+ a_SReg.selector = (a_Sel); \
+ a_SReg.type = (a_fCode) ? 10 : 3; \
+ a_SReg.present = 1; \
+ a_SReg.dpl = 0; \
+ a_SReg.db = 0; \
+ a_SReg.s = 1; \
+ a_SReg.l = 0; \
+ a_SReg.g = 0; \
+ a_SReg.avl = 0; \
+ a_SReg.unusable = 0; \
+ a_SReg.padding = 0; \
+ } while (0)
+ struct kvm_regs Regs;
+ memset(&Regs, 0, sizeof(Regs));
+ Regs.rax = uEax;
+ Regs.rcx = uEcx;
+ Regs.rdx = uEdx;
+ Regs.rbx = uEbx;
+ Regs.rsp = uEsp;
+ Regs.rbp = uEbp;
+ Regs.rsi = uEsi;
+ Regs.rdi = uEdi;
+ Regs.rip = MY_TEST_RIP;
+ Regs.rflags = 2;
+ int rc = ioctl(g_fdVCpu, KVM_SET_REGS, &Regs);
+ if (rc != 0)
+ return error("KVM_SET_REGS failed: %d (rc=%d)\n", errno, rc);
+
+ struct kvm_sregs SRegs;
+ memset(&SRegs, 0, sizeof(SRegs));
+ rc = ioctl(g_fdVCpu, KVM_GET_SREGS, &SRegs);
+ if (rc != 0)
+ return error("KVM_GET_SREGS failed: %d (rc=%d)\n", errno, rc);
+ SET_SEG(SRegs.es, 0x00000, 0xffff, 0x0000, 0);
+ SET_SEG(SRegs.cs, 0x00000, 0xffff, 0x0000, 1);
+ SET_SEG(SRegs.ss, 0x00000, 0xffff, 0x0000, 0);
+ SET_SEG(SRegs.ds, 0x00000, 0xffff, 0x0000, 0);
+ SET_SEG(SRegs.fs, 0x00000, 0xffff, 0x0000, 0);
+ SET_SEG(SRegs.gs, 0x00000, 0xffff, 0x0000, 0);
+ //SRegs.cr0 = 0x10010 /*WP+ET*/;
+ SRegs.cr2 = 0;
+ //SRegs.cr3 = 0;
+ //SRegs.cr4 = 0;
+ rc = ioctl(g_fdVCpu, KVM_SET_SREGS, &SRegs);
+ if (rc != 0)
+ return error("KVM_SET_SREGS failed: %d (rc=%d)\n", errno, rc);
+
+ /*
+ * Run the test.
+ */
+ uint32_t cExits = 0;
+ uint64_t const nsStart = getNanoTS();
+ for (;;)
+ {
+ rc = ioctl(g_fdVCpu, KVM_RUN, (uintptr_t)0);
+ if (rc == 0)
+ {
+ cExits++;
+ if (g_pVCpuRun->exit_reason == KVM_EXIT_IO)
+ {
+ if (g_pVCpuRun->io.port == MY_NOP_PORT)
+ { /* likely: nop instruction */ }
+ else if (g_pVCpuRun->io.port == MY_TERM_PORT)
+ break;
+ else
+ return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, g_pVCpuRun->io.port);
+ }
+ else if (g_pVCpuRun->exit_reason == KVM_EXIT_MMIO)
+ {
+ if (g_pVCpuRun->mmio.phys_addr == MY_NOP_MMIO)
+ { /* likely: nop address */ }
+ else
+ return runtimeError("Unexpected memory access (for %s): %#llx\n", pszInstruction, g_pVCpuRun->mmio.phys_addr);
+ }
+ else
+ return runtimeError("Unexpected exit (for %s): %d\n", pszInstruction, g_pVCpuRun->exit_reason);
+ }
+ else
+ return runtimeError("KVM_RUN failed (for %s): %#x (ret %d)\n", pszInstruction, errno, rc);
+ }
+ uint64_t const nsElapsed = getNanoTS() - nsStart;
+ return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
+}
+
+
+#elif defined(RT_OS_DARWIN)
+
+/*
+ * Mac OS X - Hypervisor API.
+ */
+
+static int createVM(void)
+{
+ /* VM and VCpu */
+ hv_return_t rcHv = hv_vm_create(HV_VM_DEFAULT);
+ if (rcHv != HV_SUCCESS)
+ return error("hv_vm_create failed: %#x\n", rcHv);
+
+ g_idVCpu = -1;
+ rcHv = hv_vcpu_create(&g_idVCpu, HV_VCPU_DEFAULT);
+ if (rcHv != HV_SUCCESS)
+ return error("hv_vcpu_create failed: %#x\n", rcHv);
+
+ /* Memory. */
+ g_pbMem = (unsigned char *)mmap(NULL, g_cbMem, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
+ if ((void *)g_pbMem == MAP_FAILED)
+ return error("mmap RAM failed: %d\n", errno);
+ memset(g_pbMem, 0xf4, g_cbMem);
+
+ rcHv = hv_vm_map(g_pbMem, MY_MEM_BASE, g_cbMem, HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
+ if (rcHv != HV_SUCCESS)
+ return error("hv_vm_map failed: %#x\n", rcHv);
+
+ rcHv = hv_vm_protect(0x2000, 0x1000, HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
+ if (rcHv != HV_SUCCESS)
+ return error("hv_vm_protect failed: %#x\n", rcHv);
+ return 0;
+}
+
+
+static int runtimeError(const char *pszFormat, ...)
+{
+ RTStrmPrintf(g_pStdErr, "runtime error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrmPrintfV(g_pStdErr, pszFormat, va);
+ va_end(va);
+
+ static struct { const char *pszName; uint32_t uField; uint32_t uFmt : 31; uint32_t fIsReg : 1; } const s_aFields[] =
+ {
+ { "VMCS_RO_EXIT_REASON", VMCS_RO_EXIT_REASON, 64, 0 },
+ { "VMCS_RO_EXIT_QUALIFIC", VMCS_RO_EXIT_QUALIFIC, 64, 0 },
+ { "VMCS_RO_INSTR_ERROR", VMCS_RO_INSTR_ERROR, 64, 0 },
+ { "VMCS_RO_VMEXIT_IRQ_INFO", VMCS_RO_VMEXIT_IRQ_INFO, 64, 0 },
+ { "VMCS_RO_VMEXIT_IRQ_ERROR", VMCS_RO_VMEXIT_IRQ_ERROR, 64, 0 },
+ { "VMCS_RO_VMEXIT_INSTR_LEN", VMCS_RO_VMEXIT_INSTR_LEN, 64, 0 },
+ { "VMCS_RO_VMX_INSTR_INFO", VMCS_RO_VMX_INSTR_INFO, 64, 0 },
+ { "VMCS_RO_GUEST_LIN_ADDR", VMCS_RO_GUEST_LIN_ADDR, 64, 0 },
+ { "VMCS_GUEST_PHYSICAL_ADDRESS",VMCS_GUEST_PHYSICAL_ADDRESS,64, 0 },
+ { "VMCS_RO_IO_RCX", VMCS_RO_IO_RCX, 64, 0 },
+ { "VMCS_RO_IO_RSI", VMCS_RO_IO_RSI, 64, 0 },
+ { "VMCS_RO_IO_RDI", VMCS_RO_IO_RDI, 64, 0 },
+ { "VMCS_RO_IO_RIP", VMCS_RO_IO_RIP, 64, 0 },
+ { "rip", HV_X86_RIP, 64, 1 },
+ { "rip (vmcs)", VMCS_GUEST_RIP, 64, 0 },
+ { "cs", HV_X86_CS, 16, 1 },
+ { "cs (vmcs)", VMCS_GUEST_CS, 16, 0 },
+ { "cs.base", VMCS_GUEST_CS_BASE, 64, 0 },
+ { "cs.limit", VMCS_GUEST_CS_LIMIT, 32, 0 },
+ { "cs.attr", VMCS_GUEST_CS_AR, 32, 0 },
+ { "rflags", HV_X86_RFLAGS, 32, 1 },
+ { "rax", HV_X86_RAX, 64, 1 },
+ { "rcx", HV_X86_RCX, 64, 1 },
+ { "rdx", HV_X86_RDX, 64, 1 },
+ { "rbx", HV_X86_RBX, 64, 1 },
+ { "rsp", HV_X86_RSP, 64, 1 },
+ { "rsp (vmcs)", VMCS_GUEST_RSP, 64, 0 },
+ { "ss", HV_X86_SS, 16, 1 },
+ { "ss (vmcs)", VMCS_GUEST_SS, 16, 0 },
+ { "ss.base", VMCS_GUEST_SS_BASE, 64, 0 },
+ { "ss.limit", VMCS_GUEST_SS_LIMIT, 32, 0 },
+ { "ss.attr", VMCS_GUEST_SS_AR, 32, 0 },
+ { "rbp", HV_X86_RBP, 64, 1 },
+ { "rsi", HV_X86_RSI, 64, 1 },
+ { "rdi", HV_X86_RDI, 64, 1 },
+ { "ds", HV_X86_DS, 16, 1 },
+ { "ds (vmcs)", VMCS_GUEST_DS, 16, 0 },
+ { "ds.base", VMCS_GUEST_DS_BASE, 64, 0 },
+ { "ds.limit", VMCS_GUEST_DS_LIMIT, 32, 0 },
+ { "ds.attr", VMCS_GUEST_DS_AR, 32, 0 },
+ { "es", HV_X86_ES, 16, 1 },
+ { "es (vmcs)", VMCS_GUEST_ES, 16, 0 },
+ { "es.base", VMCS_GUEST_ES_BASE, 64, 0 },
+ { "es.limit", VMCS_GUEST_ES_LIMIT, 32, 0 },
+ { "es.attr", VMCS_GUEST_ES_AR, 32, 0 },
+ { "fs", HV_X86_FS, 16, 1 },
+ { "fs (vmcs)", VMCS_GUEST_FS, 16, 0 },
+ { "fs.base", VMCS_GUEST_FS_BASE, 64, 0 },
+ { "fs.limit", VMCS_GUEST_FS_LIMIT, 32, 0 },
+ { "fs.attr", VMCS_GUEST_FS_AR, 32, 0 },
+ { "gs", HV_X86_GS, 16, 1 },
+ { "gs (vmcs)", VMCS_GUEST_GS, 16, 0 },
+ { "gs.base", VMCS_GUEST_GS_BASE, 64, 0 },
+ { "gs.limit", VMCS_GUEST_GS_LIMIT, 32, 0 },
+ { "gs.attr", VMCS_GUEST_GS_AR, 32, 0 },
+ { "cr0", HV_X86_CR0, 64, 1 },
+ { "cr0 (vmcs)", VMCS_GUEST_CR0, 64, 0 },
+ { "cr2", HV_X86_CR2, 64, 1 },
+ { "cr3", HV_X86_CR3, 64, 1 },
+ { "cr3 (vmcs)", VMCS_GUEST_CR3, 64, 0 },
+ { "cr4", HV_X86_CR4, 64, 1 },
+ { "cr4 (vmcs)", VMCS_GUEST_CR4, 64, 0 },
+ { "idtr.base", VMCS_GUEST_IDTR_BASE, 64, 0 },
+ { "idtr.limit", VMCS_GUEST_IDTR_LIMIT, 32, 0 },
+ { "gdtr.base", VMCS_GUEST_GDTR_BASE, 64, 0 },
+ { "gdtr.limit", VMCS_GUEST_GDTR_LIMIT, 32, 0 },
+
+ { "VMCS_CTRL_PIN_BASED", VMCS_CTRL_PIN_BASED, 64, 0 },
+ { "VMCS_CTRL_CPU_BASED", VMCS_CTRL_CPU_BASED, 64, 0 },
+ { "VMCS_CTRL_CPU_BASED2", VMCS_CTRL_CPU_BASED2, 64, 0 },
+ { "VMCS_CTRL_VMENTRY_CONTROLS", VMCS_CTRL_VMENTRY_CONTROLS, 64, 0 },
+ { "VMCS_CTRL_VMEXIT_CONTROLS", VMCS_CTRL_VMEXIT_CONTROLS, 64, 0 },
+ { "VMCS_CTRL_EXC_BITMAP", VMCS_CTRL_EXC_BITMAP, 64, 0 },
+ { "VMCS_CTRL_CR0_MASK", VMCS_CTRL_CR0_MASK, 64, 0 },
+ { "VMCS_CTRL_CR0_SHADOW", VMCS_CTRL_CR0_SHADOW, 64, 0 },
+ { "VMCS_CTRL_CR4_MASK", VMCS_CTRL_CR4_MASK, 64, 0 },
+ { "VMCS_CTRL_CR4_SHADOW", VMCS_CTRL_CR4_SHADOW, 64, 0 },
+ };
+ for (unsigned i = 0; i < sizeof(s_aFields) / sizeof(s_aFields[0]); i++)
+ {
+ uint64_t uValue = UINT64_MAX;
+ hv_return_t rcHv;
+ if (s_aFields[i].fIsReg)
+ rcHv = hv_vcpu_read_register(g_idVCpu, (hv_x86_reg_t)s_aFields[i].uField, &uValue);
+ else
+ rcHv = hv_vmx_vcpu_read_vmcs(g_idVCpu, s_aFields[i].uField, &uValue);
+ if (rcHv == HV_SUCCESS)
+ {
+ if (s_aFields[i].uFmt == 16)
+ RTStrmPrintf(g_pStdErr, "%28s=%04llx\n", s_aFields[i].pszName, uValue);
+ else if (s_aFields[i].uFmt == 32)
+ RTStrmPrintf(g_pStdErr, "%28s=%08llx\n", s_aFields[i].pszName, uValue);
+ else
+ RTStrmPrintf(g_pStdErr, "%28s=%08x'%08x\n", s_aFields[i].pszName, (uint32_t)(uValue >> 32), (uint32_t)uValue);
+ }
+ else
+ RTStrmPrintf(g_pStdErr, "%28s=<%s failed %#x>\n", s_aFields[i].pszName,
+ s_aFields[i].fIsReg ? "hv_vcpu_read_register" : "hv_vmx_vcpu_read_vmcs", rcHv);
+ }
+ return 1;
+}
+
+
+static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
+ unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
+ unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
+{
+ /*
+ * Setup real mode context.
+ */
+#define WRITE_REG_RET(a_enmReg, a_uValue) \
+ do { \
+ hv_return_t rcHvX = hv_vcpu_write_register(g_idVCpu, a_enmReg, a_uValue); \
+ if (rcHvX == HV_SUCCESS) { /* likely */ } \
+ else return error("hv_vcpu_write_register(%#x, %s, %#llx) -> %#x\n", g_idVCpu, #a_enmReg, (uint64_t)(a_uValue), rcHvX); \
+ } while (0)
+#define READ_REG_RET(a_enmReg, a_puValue) \
+ do { \
+ hv_return_t rcHvX = hv_vcpu_read_register(g_idVCpu, a_enmReg, a_puValue); \
+ if (rcHvX == HV_SUCCESS) { /* likely */ } \
+ else return error("hv_vcpu_read_register(%#x, %s,) -> %#x\n", g_idVCpu, #a_enmReg, rcHvX); \
+ } while (0)
+#define WRITE_VMCS_RET(a_enmField, a_uValue) \
+ do { \
+ hv_return_t rcHvX = hv_vmx_vcpu_write_vmcs(g_idVCpu, a_enmField, a_uValue); \
+ if (rcHvX == HV_SUCCESS) { /* likely */ } \
+ else return error("hv_vmx_vcpu_write_vmcs(%#x, %s, %#llx) -> %#x\n", g_idVCpu, #a_enmField, (uint64_t)(a_uValue), rcHvX); \
+ } while (0)
+#define READ_VMCS_RET(a_enmField, a_puValue) \
+ do { \
+ hv_return_t rcHvX = hv_vmx_vcpu_read_vmcs(g_idVCpu, a_enmField, a_puValue); \
+ if (rcHvX == HV_SUCCESS) { /* likely */ } \
+ else return error("hv_vmx_vcpu_read_vmcs(%#x, %s,) -> %#x\n", g_idVCpu, #a_enmField, rcHvX); \
+ } while (0)
+#define READ_CAP_RET(a_enmCap, a_puValue) \
+ do { \
+ hv_return_t rcHvX = hv_vmx_read_capability(a_enmCap, a_puValue); \
+ if (rcHvX == HV_SUCCESS) { /* likely */ } \
+ else return error("hv_vmx_read_capability(%s) -> %#x\n", #a_enmCap); \
+ } while (0)
+#define CAP_2_CTRL(a_uCap, a_fWanted) ( ((a_fWanted) | (uint32_t)(a_uCap)) & (uint32_t)((a_uCap) >> 32) )
+#if 1
+ uint64_t uCap;
+ READ_CAP_RET(HV_VMX_CAP_PINBASED, &uCap);
+ WRITE_VMCS_RET(VMCS_CTRL_PIN_BASED, CAP_2_CTRL(uCap, PIN_BASED_INTR | PIN_BASED_NMI | PIN_BASED_VIRTUAL_NMI));
+ READ_CAP_RET(HV_VMX_CAP_PROCBASED, &uCap);
+ WRITE_VMCS_RET(VMCS_CTRL_CPU_BASED, CAP_2_CTRL(uCap, CPU_BASED_HLT
+ | CPU_BASED_INVLPG
+ | CPU_BASED_MWAIT
+ | CPU_BASED_RDPMC
+ | CPU_BASED_RDTSC
+ | CPU_BASED_CR3_LOAD
+ | CPU_BASED_CR3_STORE
+ | CPU_BASED_CR8_LOAD
+ | CPU_BASED_CR8_STORE
+ | CPU_BASED_MOV_DR
+ | CPU_BASED_UNCOND_IO
+ | CPU_BASED_MONITOR
+ | CPU_BASED_PAUSE
+ ));
+ READ_CAP_RET(HV_VMX_CAP_PROCBASED2, &uCap);
+ WRITE_VMCS_RET(VMCS_CTRL_CPU_BASED2, CAP_2_CTRL(uCap, 0));
+ READ_CAP_RET(HV_VMX_CAP_ENTRY, &uCap);
+ WRITE_VMCS_RET(VMCS_CTRL_VMENTRY_CONTROLS, CAP_2_CTRL(uCap, 0));
+#endif
+ WRITE_VMCS_RET(VMCS_CTRL_EXC_BITMAP, UINT32_MAX);
+ WRITE_VMCS_RET(VMCS_CTRL_CR0_MASK, 0x60000000);
+ WRITE_VMCS_RET(VMCS_CTRL_CR0_SHADOW, 0x00000000);
+ WRITE_VMCS_RET(VMCS_CTRL_CR4_MASK, 0x00000000);
+ WRITE_VMCS_RET(VMCS_CTRL_CR4_SHADOW, 0x00000000);
+
+ WRITE_REG_RET(HV_X86_RAX, uEax);
+ WRITE_REG_RET(HV_X86_RCX, uEcx);
+ WRITE_REG_RET(HV_X86_RDX, uEdx);
+ WRITE_REG_RET(HV_X86_RBX, uEbx);
+ WRITE_REG_RET(HV_X86_RSP, uEsp);
+ WRITE_REG_RET(HV_X86_RBP, uEbp);
+ WRITE_REG_RET(HV_X86_RSI, uEsi);
+ WRITE_REG_RET(HV_X86_RDI, uEdi);
+ WRITE_REG_RET(HV_X86_RIP, MY_TEST_RIP);
+ WRITE_REG_RET(HV_X86_RFLAGS, 2);
+ WRITE_REG_RET(HV_X86_ES, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_ES_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_ES_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_ES_AR, 0x93);
+ WRITE_REG_RET(HV_X86_CS, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_CS_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_CS_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_CS_AR, 0x9b);
+ WRITE_REG_RET(HV_X86_SS, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_SS_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_SS_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_SS_AR, 0x93);
+ WRITE_REG_RET(HV_X86_DS, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_DS_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_DS_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_DS_AR, 0x93);
+ WRITE_REG_RET(HV_X86_FS, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_FS_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_FS_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_FS_AR, 0x93);
+ WRITE_REG_RET(HV_X86_GS, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_GS_BASE, 0x0000000);
+ WRITE_VMCS_RET(VMCS_GUEST_GS_LIMIT, 0xffff);
+ WRITE_VMCS_RET(VMCS_GUEST_GS_AR, 0x93);
+ //WRITE_REG_RET(HV_X86_CR0, 0x10030 /*WP+NE+ET*/);
+ WRITE_VMCS_RET(VMCS_GUEST_CR0, 0x10030 /*WP+NE+ET*/);
+ //WRITE_REG_RET(HV_X86_CR2, 0);
+ //WRITE_REG_RET(HV_X86_CR3, 0);
+ WRITE_VMCS_RET(VMCS_GUEST_CR3, 0);
+ //WRITE_REG_RET(HV_X86_CR4, 0x2000);
+ WRITE_VMCS_RET(VMCS_GUEST_CR4, 0x2000);
+ WRITE_VMCS_RET(VMCS_GUEST_LDTR, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_LDTR_BASE, 0x00000000);
+ WRITE_VMCS_RET(VMCS_GUEST_LDTR_LIMIT, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_LDTR_AR, 0x10000);
+ WRITE_VMCS_RET(VMCS_GUEST_TR, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_TR_BASE, 0x00000000);
+ WRITE_VMCS_RET(VMCS_GUEST_TR_LIMIT, 0x0000);
+ WRITE_VMCS_RET(VMCS_GUEST_TR_AR, 0x00083);
+ hv_vcpu_flush(g_idVCpu);
+ hv_vcpu_invalidate_tlb(g_idVCpu);
+
+ /*
+ * Run the test.
+ */
+ uint32_t cExits = 0;
+ uint64_t const nsStart = getNanoTS();
+ for (;;)
+ {
+ hv_return_t rcHv = hv_vcpu_run(g_idVCpu);
+ if (rcHv == HV_SUCCESS)
+ {
+ cExits++;
+ uint64_t uExitReason = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_EXIT_REASON, &uExitReason);
+ if (!(uExitReason & UINT64_C(0x80000000)))
+ {
+ if (uExitReason == VMX_REASON_IO)
+ {
+ uint64_t uIoQual = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_EXIT_QUALIFIC, &uIoQual);
+ if ((uint16_t)(uIoQual >> 16) == MY_NOP_PORT && (fTest & MY_TEST_F_NOP_IO))
+ { /* likely: nop instruction */ }
+ else if ((uint16_t)(uIoQual >> 16) == MY_TERM_PORT)
+ break;
+ else
+ return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, (uint16_t)(uIoQual >> 16));
+
+ /* Advance RIP. */
+ uint64_t cbInstr = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
+ if (cbInstr < 1 || cbInstr > 15)
+ return runtimeError("Bad instr len: %#llx\n", cbInstr);
+ uint64_t uRip = UINT64_MAX;
+ READ_REG_RET(HV_X86_RIP, &uRip);
+ WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
+ }
+ else if (uExitReason == VMX_REASON_CPUID && (fTest & MY_TEST_F_CPUID))
+ {
+ /* Set registers and advance RIP. */
+ WRITE_REG_RET(HV_X86_RAX, 0x42424242);
+ WRITE_REG_RET(HV_X86_RCX, 0x04242424);
+ WRITE_REG_RET(HV_X86_RDX, 0x00424242);
+ WRITE_REG_RET(HV_X86_RBX, 0x00024242);
+
+ uint64_t cbInstr = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
+ if (cbInstr < 1 || cbInstr > 15)
+ return runtimeError("Bad instr len: %#llx\n", cbInstr);
+ uint64_t uRip = UINT64_MAX;
+ READ_REG_RET(HV_X86_RIP, &uRip);
+ WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
+ }
+ else if (uExitReason == VMX_REASON_EPT_VIOLATION)
+ {
+ uint64_t uEptQual = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_EXIT_QUALIFIC, &uEptQual);
+ uint64_t GCPhys = UINT64_MAX;
+ READ_VMCS_RET(VMCS_GUEST_PHYSICAL_ADDRESS, &GCPhys);
+ if (GCPhys == MY_NOP_MMIO && (fTest & MY_TEST_F_NOP_MMIO))
+ { /* likely */ }
+ else if (GCPhys == MY_TEST_RIP)
+ continue; /* dunno why we get this, but restarting it works */
+ else
+ return runtimeError("Unexpected EPT viotaion at %#llx\n", GCPhys);
+
+ /* Set RAX and advance RIP. */
+ WRITE_REG_RET(HV_X86_RAX, 42);
+
+ uint64_t cbInstr = UINT64_MAX;
+ READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
+ if (cbInstr < 1 || cbInstr > 15)
+ return runtimeError("Bad instr len: %#llx\n", cbInstr);
+ uint64_t uRip = UINT64_MAX;
+ READ_REG_RET(HV_X86_RIP, &uRip);
+ WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
+ }
+ else if (uExitReason == VMX_REASON_IRQ)
+ { /* ignore */ }
+ else
+ return runtimeError("Unexpected exit reason: %#x\n", uExitReason);
+ }
+ else
+ return runtimeError("VM entry failure: %#x\n", uExitReason);
+ }
+ else
+ return runtimeError("hv_vcpu_run failed (for %s): %#x\n", pszInstruction, rcHv);
+ }
+ uint64_t const nsElapsed = getNanoTS() - nsStart;
+ return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
+}
+
+#else
+# error "port me"
+#endif
+
+void dumpCode(uint8_t const *pb, uint8_t *pbEnd)
+{
+ RTPrintf("testing:");
+ for (; pb != pbEnd; pb++)
+ RTPrintf(" %02x", *pb);
+ RTPrintf("\n");
+}
+
+
+int ioportTest(unsigned cFactor)
+{
+ /*
+ * Produce realmode code
+ */
+ unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
+ unsigned char * const pbStart = pb;
+ /* OUT DX, AL - 10 times */
+ for (unsigned i = 0; i < 10; i++)
+ *pb++ = 0xee;
+ /* DEC ECX */
+ *pb++ = 0x66;
+ *pb++ = 0x48 + 1;
+ /* JNZ MY_TEST_RIP */
+ *pb++ = 0x75;
+ *pb = (signed char)(pbStart - pb - 1);
+ pb++;
+ /* OUT 1, AL - Temination port call. */
+ *pb++ = 0xe6;
+ *pb++ = MY_TERM_PORT;
+ /* JMP to previous instruction */
+ *pb++ = 0xeb;
+ *pb++ = 0xfc;
+ dumpCode(pbStart, pb);
+
+ return runRealModeTest(100000 * cFactor, "OUT", MY_TEST_F_NOP_IO,
+ 42 /*eax*/, 10000 * cFactor /*ecx*/, MY_NOP_PORT /*edx*/, 0 /*ebx*/,
+ 0 /*esp*/, 0 /*ebp*/, 0 /*esi*/, 0 /*uEdi*/);
+}
+
+
+int cpuidTest(unsigned cFactor)
+{
+ /*
+ * Produce realmode code
+ */
+ unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
+ unsigned char * const pbStart = pb;
+ for (unsigned i = 0; i < 10; i++)
+ {
+ /* XOR EAX,EAX */
+ *pb++ = 0x66;
+ *pb++ = 0x33;
+ *pb++ = 0xc0;
+
+ /* CPUID */
+ *pb++ = 0x0f;
+ *pb++ = 0xa2;
+ }
+ /* DEC ESI */
+ *pb++ = 0x66;
+ *pb++ = 0x48 + 6;
+ /* JNZ MY_TEST_RIP */
+ *pb++ = 0x75;
+ *pb = (signed char)(pbStart - pb - 1);
+ pb++;
+ /* OUT 1, AL - Temination port call. */
+ *pb++ = 0xe6;
+ *pb++ = MY_TERM_PORT;
+ /* JMP to previous instruction */
+ *pb++ = 0xeb;
+ *pb++ = 0xfc;
+ dumpCode(pbStart, pb);
+
+ return runRealModeTest(100000 * cFactor, "CPUID", MY_TEST_F_CPUID,
+ 0 /*eax*/, 0 /*ecx*/, 0 /*edx*/, 0 /*ebx*/,
+ 0 /*esp*/, 0 /*ebp*/, 10000 * cFactor /*esi*/, 0 /*uEdi*/);
+}
+
+
+int mmioTest(unsigned cFactor)
+{
+ /*
+ * Produce realmode code accessing MY_MMIO_NOP address assuming it's low.
+ */
+ unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
+ unsigned char * const pbStart = pb;
+ for (unsigned i = 0; i < 10; i++)
+ {
+ /* MOV AL,DS:[BX] */
+ *pb++ = 0x8a;
+ *pb++ = 0x07;
+ }
+ /* DEC ESI */
+ *pb++ = 0x66;
+ *pb++ = 0x48 + 6;
+ /* JNZ MY_TEST_RIP */
+ *pb++ = 0x75;
+ *pb = (signed char)(pbStart - pb - 1);
+ pb++;
+ /* OUT 1, AL - Temination port call. */
+ *pb++ = 0xe6;
+ *pb++ = MY_TERM_PORT;
+ /* JMP to previous instruction */
+ *pb++ = 0xeb;
+ *pb++ = 0xfc;
+ dumpCode(pbStart, pb);
+
+ return runRealModeTest(100000 * cFactor, "MMIO/r1", MY_TEST_F_NOP_MMIO,
+ 0 /*eax*/, 0 /*ecx*/, 0 /*edx*/, MY_NOP_MMIO /*ebx*/,
+ 0 /*esp*/, 0 /*ebp*/, 10000 * cFactor /*esi*/, 0 /*uEdi*/);
+}
+
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Do some parameter parsing.
+ */
+#ifdef RT_OS_WINDOWS
+ unsigned const cFactorDefault = 4;
+#elif RT_OS_DARWIN
+ unsigned const cFactorDefault = 32;
+#else
+ unsigned const cFactorDefault = 24;
+#endif
+ unsigned cFactor = cFactorDefault;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *pszArg = argv[i];
+ if ( strcmp(pszArg, "--help") == 0
+ || strcmp(pszArg, "/help") == 0
+ || strcmp(pszArg, "-h") == 0
+ || strcmp(pszArg, "-?") == 0
+ || strcmp(pszArg, "/?") == 0)
+ {
+ RTPrintf("Does some benchmarking of the native NEM engine.\n"
+ "\n"
+ "Usage: NemRawBench-1 --factor <factor>\n"
+ "\n"
+ "Options\n"
+ " --factor <factor>\n"
+ " Iteration count factor. Default is %u.\n"
+ " Lower it if execution is slow, increase if quick.\n",
+ cFactorDefault);
+ return 0;
+ }
+ if (strcmp(pszArg, "--factor") == 0)
+ {
+ i++;
+ if (i < argc)
+ cFactor = RTStrToUInt32(argv[i]);
+ else
+ {
+ RTStrmPrintf(g_pStdErr, "syntax error: Option %s is takes a value!\n", pszArg);
+ return 2;
+ }
+ }
+ else
+ {
+ RTStrmPrintf(g_pStdErr, "syntax error: Unknown option: %s\n", pszArg);
+ return 2;
+ }
+ }
+
+ /*
+ * Create the VM
+ */
+ g_cbMem = 128*1024 - MY_MEM_BASE;
+ int rcExit = createVM();
+ if (rcExit == 0)
+ {
+ RTPrintf("tstNemBench-1: Successfully created test VM...\n");
+
+ /*
+ * Do the benchmarking.
+ */
+ ioportTest(cFactor);
+ cpuidTest(cFactor);
+ mmioTest(cFactor);
+
+ RTPrintf("tstNemBench-1: done\n");
+ }
+ return rcExit;
+}
+
+/*
+ * Results:
+ *
+ * - Darwin/xnu 10.12.6/16.7.0; 3.1GHz Intel Core i7-7920HQ (Kaby Lake):
+ * 925 845 OUT instructions per second (3 200 307 exits in 3 456 301 621 ns)
+ * 949 278 CPUID instructions per second (3 200 222 exits in 3 370 980 173 ns)
+ * 871 499 MMIO/r1 instructions per second (3 200 223 exits in 3 671 834 221 ns)
+ *
+ * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.1GHz Intel Core i7-7920HQ (Kaby Lake):
+ * 829 775 OUT instructions per second (3 200 001 exits in 3 856 466 567 ns)
+ * 2 212 038 CPUID instructions per second (1 exits in 1 446 629 591 ns) [1]
+ * 477 962 MMIO/r1 instructions per second (3 200 001 exits in 6 695 090 600 ns)
+ *
+ * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.4GHz Core i5-3570 (Ivy Bridge):
+ * 717 216 OUT instructions per second (2 400 001 exits in 3 346 271 640 ns)
+ * 1 675 983 CPUID instructions per second (1 exits in 1 431 995 135 ns) [1]
+ * 402 621 MMIO/r1 instructions per second (2 400 001 exits in 5 960 930 854 ns)
+ *
+ * - Linux 4.18.0-1-amd64 (debian); 3.4GHz AMD Threadripper 1950X:
+ * 455 727 OUT instructions per second (2 400 001 exits in 5 266 300 471 ns)
+ * 1 745 014 CPUID instructions per second (1 exits in 1 375 346 658 ns) [1]
+ * 351 767 MMIO/r1 instructions per second (2 400 001 exits in 6 822 684 544 ns)
+ *
+ * - Windows 1803 updated as per 2018-10-01; 3.4GHz Core i5-3570 (Ivy Bridge):
+ * 67 778 OUT instructions per second (400 001 exits in 5 901 560 700 ns)
+ * 66 113 CPUID instructions per second (400 001 exits in 6 050 208 000 ns)
+ * 62 939 MMIO/r1 instructions per second (400 001 exits in 6 355 302 900 ns)
+ *
+ * - Windows 1803 updated as per 2018-09-28; 3.4GHz AMD Threadripper 1950X:
+ * 34 485 OUT instructions per second (400 001 exits in 11 598 918 200 ns)
+ * 34 043 CPUID instructions per second (400 001 exits in 11 749 753 200 ns)
+ * 33 124 MMIO/r1 instructions per second (400 001 exits in 12 075 617 000 ns)
+ *
+ * - Windows build 17763; 3.4GHz AMD Threadripper 1950X:
+ * 65 633 OUT instructions per second (400 001 exits in 6 094 409 100 ns)
+ * 65 245 CPUID instructions per second (400 001 exits in 6 130 720 600 ns)
+ * 61 642 MMIO/r1 instructions per second (400 001 exits in 6 489 013 700 ns)
+ *
+ *
+ * [1] CPUID causes no return to ring-3 with KVM.
+ *
+ *
+ * For reference we can compare with similar tests in bs2-test1 running VirtualBox:
+ *
+ * - Linux 4.18.0-1-amd64 (debian); 3.4GHz AMD Threadripper 1950X; trunk/r125404:
+ * real mode, 32-bit OUT : 1 338 471 ins/sec
+ * real mode, 32-bit OUT-to-ring-3 : 500 337 ins/sec
+ * real mode, CPUID : 1 566 343 ins/sec
+ * real mode, 32-bit write : 870 671 ins/sec
+ * real mode, 32-bit write-to-ring-3: 391 014 ins/sec
+ *
+ * - Darwin/xnu 10.12.6/16.7.0; 3.1GHz Intel Core i7-7920HQ (Kaby Lake); trunk/r125404:
+ * real mode, 32-bit OUT : 790 117 ins/sec
+ * real mode, 32-bit OUT-to-ring-3 : 157 205 ins/sec
+ * real mode, CPUID : 1 001 087 ins/sec
+ * real mode, 32-bit write : 651 257 ins/sec
+ * real mode, 32-bit write-to-ring-3: 157 773 ins/sec
+ *
+ * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.1GHz Intel Core i7-7920HQ (Kaby Lake); trunk/r125450:
+ * real mode, 32-bit OUT : 1 229 245 ins/sec
+ * real mode, 32-bit OUT-to-ring-3 : 284 848 ins/sec
+ * real mode, CPUID : 1 429 760 ins/sec
+ * real mode, 32-bit write : 820 679 ins/sec
+ * real mode, 32-bit write-to-ring-3: 245 159 ins/sec
+ *
+ * - Windows 1803 updated as per 2018-10-01; 3.4GHz Core i5-3570 (Ivy Bridge); trunk/r15442:
+ * real mode, 32-bit OUT : 961 939 ins/sec
+ * real mode, 32-bit OUT-to-ring-3 : 189 458 ins/sec
+ * real mode, CPUID : 1 060 582 ins/sec
+ * real mode, 32-bit write : 637 967 ins/sec
+ * real mode, 32-bit write-to-ring-3: 148 573 ins/sec
+ *
+ */