VirtualBox

source: vbox/trunk/src/VBox/VMM/testcase/NemRawBench-1.cpp@ 92626

Last change on this file since 92626 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.2 KB
Line 
1/* $Id: NemRawBench-1.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * NEM Benchmark.
4 */
5
6/*
7 * Copyright (C) 2018-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#ifdef RT_OS_WINDOWS
23# include <iprt/win/windows.h>
24# include <WinHvPlatform.h>
25# if !defined(_INTPTR) && defined(_M_AMD64) /* void pedantic stdint.h warnings */
26# define _INTPTR 2
27# endif
28
29#elif defined(RT_OS_LINUX)
30# include <linux/kvm.h>
31# include <errno.h>
32# include <sys/fcntl.h>
33# include <sys/ioctl.h>
34# include <sys/mman.h>
35# include <unistd.h>
36# include <time.h>
37
38#elif defined(RT_OS_DARWIN)
39# include <Availability.h>
40# if 1 /* header mix hack */
41# undef __OSX_AVAILABLE_STARTING
42# define __OSX_AVAILABLE_STARTING(_osx, _ios)
43# endif
44# include <Hypervisor/hv.h>
45# include <Hypervisor/hv_arch_x86.h>
46# include <Hypervisor/hv_arch_vmx.h>
47# include <Hypervisor/hv_vmx.h>
48# include <mach/mach_time.h>
49# include <mach/kern_return.h>
50# include <sys/time.h>
51# include <time.h>
52# include <sys/mman.h>
53# include <errno.h>
54
55#else
56# error "port me"
57#endif
58
59#include <stdarg.h>
60#include <stdint.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64
65
66/*********************************************************************************************************************************
67* Defined Constants And Macros *
68*********************************************************************************************************************************/
69/** The base mapping address of the g_pbMem. */
70#define MY_MEM_BASE 0x1000
71/** No-op MMIO access address. */
72#define MY_NOP_MMIO 0x0808
73/** The RIP which the testcode starts. */
74#define MY_TEST_RIP 0x2000
75
76/** The test termination port number. */
77#define MY_TERM_PORT 0x01
78/** The no-op test port number. */
79#define MY_NOP_PORT 0x7f
80
81#define MY_TEST_F_NOP_IO (1U<<0)
82#define MY_TEST_F_CPUID (1U<<1)
83#define MY_TEST_F_NOP_MMIO (1U<<2)
84
85
86
87/*********************************************************************************************************************************
88* Global Variables *
89*********************************************************************************************************************************/
90/** Chunk of memory mapped at address 0x1000 (MY_MEM_BASE). */
91static unsigned char *g_pbMem;
92/** Amount of RAM at address 0x1000 (MY_MEM_BASE). */
93static size_t g_cbMem;
94#ifdef RT_OS_WINDOWS
95static WHV_PARTITION_HANDLE g_hPartition = NULL;
96
97/** @name APIs imported from WinHvPlatform.dll
98 * @{ */
99static decltype(WHvCreatePartition) *g_pfnWHvCreatePartition;
100static decltype(WHvSetupPartition) *g_pfnWHvSetupPartition;
101static decltype(WHvGetPartitionProperty) *g_pfnWHvGetPartitionProperty;
102static decltype(WHvSetPartitionProperty) *g_pfnWHvSetPartitionProperty;
103static decltype(WHvMapGpaRange) *g_pfnWHvMapGpaRange;
104static decltype(WHvCreateVirtualProcessor) *g_pfnWHvCreateVirtualProcessor;
105static decltype(WHvRunVirtualProcessor) *g_pfnWHvRunVirtualProcessor;
106static decltype(WHvGetVirtualProcessorRegisters) *g_pfnWHvGetVirtualProcessorRegisters;
107static decltype(WHvSetVirtualProcessorRegisters) *g_pfnWHvSetVirtualProcessorRegisters;
108/** @} */
109static uint64_t (WINAPI *g_pfnRtlGetSystemTimePrecise)(void);
110
111#elif defined(RT_OS_LINUX)
112/** The VM handle. */
113static int g_fdVm;
114/** The VCPU handle. */
115static int g_fdVCpu;
116/** The kvm_run structure for the VCpu. */
117static struct kvm_run *g_pVCpuRun;
118/** The size of the g_pVCpuRun mapping. */
119static ssize_t g_cbVCpuRun;
120
121#elif defined(RT_OS_DARWIN)
122/** The VCpu ID. */
123static hv_vcpuid_t g_idVCpu;
124#endif
125
126
127static int error(const char *pszFormat, ...)
128{
129 fprintf(stderr, "error: ");
130 va_list va;
131 va_start(va, pszFormat);
132 vfprintf(stderr, pszFormat, va);
133 va_end(va);
134 return 1;
135}
136
137
138static uint64_t getNanoTS(void)
139{
140#ifdef RT_OS_WINDOWS
141 return g_pfnRtlGetSystemTimePrecise() * 100;
142
143#elif defined(RT_OS_LINUX)
144 struct timespec ts;
145 clock_gettime(CLOCK_MONOTONIC, &ts);
146 return (uint64_t)ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec;
147
148#elif defined(RT_OS_DARWIN)
149 static struct mach_timebase_info s_Info = { 0, 0 };
150 static double s_rdFactor = 0.0;
151 /* Lazy init. */
152 if (s_Info.denom != 0)
153 { /* likely */ }
154 else if (mach_timebase_info(&s_Info) == KERN_SUCCESS)
155 s_rdFactor = (double)s_Info.numer / (double)s_Info.denom;
156 else
157 {
158 error("mach_timebase_info(&Info) failed\n");
159 exit(1);
160 }
161 if (s_Info.denom == 1 && s_Info.numer == 1) /* special case: absolute time is in nanoseconds */
162 return mach_absolute_time();
163 return mach_absolute_time() * s_rdFactor;
164#else
165 struct timeval tv;
166 gettimeofday(&tv, NULL);
167 return (uint64_t)tv.tv_sec * UINT64_C(1000000000)
168 + (tv.tv_usec * UINT32_C(1000));
169#endif
170}
171
172
173char *formatNum(uint64_t uNum, unsigned cchWidth, char *pszDst, size_t cbDst)
174{
175 char szTmp[64 + 22];
176#ifdef _MSC_VER
177 size_t cchTmp = _snprintf(szTmp, sizeof(szTmp) - 22, "%I64u", uNum);
178#else
179 size_t cchTmp = snprintf(szTmp, sizeof(szTmp) - 22, "%llu", (unsigned long long)uNum);
180#endif
181 size_t cSeps = (cchTmp - 1) / 3;
182 size_t const cchTotal = cchTmp + cSeps;
183 if (cSeps)
184 {
185 szTmp[cchTotal] = '\0';
186 for (size_t iSrc = cchTmp, iDst = cchTotal; cSeps > 0; cSeps--)
187 {
188 szTmp[--iDst] = szTmp[--iSrc];
189 szTmp[--iDst] = szTmp[--iSrc];
190 szTmp[--iDst] = szTmp[--iSrc];
191 szTmp[--iDst] = ' ';
192 }
193 }
194
195 size_t offDst = 0;
196 while (cchWidth-- > cchTotal && offDst < cbDst)
197 pszDst[offDst++] = ' ';
198 size_t offSrc = 0;
199 while (offSrc < cchTotal && offDst < cbDst)
200 pszDst[offDst++] = szTmp[offSrc++];
201 pszDst[offDst] = '\0';
202 return pszDst;
203}
204
205
206int reportResult(const char *pszInstruction, uint32_t cInstructions, uint64_t nsElapsed, uint32_t cExits)
207{
208 uint64_t const cInstrPerSec = nsElapsed ? (uint64_t)cInstructions * 1000000000 / nsElapsed : 0;
209 char szTmp1[64], szTmp2[64], szTmp3[64];
210 printf("%s %7s instructions per second (%s exits in %s ns)\n",
211 formatNum(cInstrPerSec, 10, szTmp1, sizeof(szTmp1)), pszInstruction,
212 formatNum(cExits, 0, szTmp2, sizeof(szTmp2)),
213 formatNum(nsElapsed, 0, szTmp3, sizeof(szTmp3)));
214 return 0;
215}
216
217
218
219#ifdef RT_OS_WINDOWS
220
221/*
222 * Windows - Hyper-V Platform API.
223 */
224
225static int createVM(void)
226{
227 /*
228 * Resolve APIs.
229 */
230 HMODULE hmod = LoadLibraryW(L"WinHvPlatform.dll");
231 if (hmod == NULL)
232 return error("Error loading WinHvPlatform.dll: %u\n", GetLastError());
233 static struct { const char *pszFunction; FARPROC *ppfn; } const s_aImports[] =
234 {
235# define IMPORT_ENTRY(a_Name) { #a_Name, (FARPROC *)&g_pfn##a_Name }
236 IMPORT_ENTRY(WHvCreatePartition),
237 IMPORT_ENTRY(WHvSetupPartition),
238 IMPORT_ENTRY(WHvGetPartitionProperty),
239 IMPORT_ENTRY(WHvSetPartitionProperty),
240 IMPORT_ENTRY(WHvMapGpaRange),
241 IMPORT_ENTRY(WHvCreateVirtualProcessor),
242 IMPORT_ENTRY(WHvRunVirtualProcessor),
243 IMPORT_ENTRY(WHvGetVirtualProcessorRegisters),
244 IMPORT_ENTRY(WHvSetVirtualProcessorRegisters),
245# undef IMPORT_ENTRY
246 };
247 FARPROC pfn;
248 for (size_t i = 0; i < sizeof(s_aImports) / sizeof(s_aImports[0]); i++)
249 {
250 *s_aImports[i].ppfn = pfn = GetProcAddress(hmod, s_aImports[i].pszFunction);
251 if (!pfn)
252 return error("Error resolving WinHvPlatform.dll!%s: %u\n", s_aImports[i].pszFunction, GetLastError());
253 }
254# ifndef IN_SLICKEDIT
255# define WHvCreatePartition g_pfnWHvCreatePartition
256# define WHvSetupPartition g_pfnWHvSetupPartition
257# define WHvGetPartitionProperty g_pfnWHvGetPartitionProperty
258# define WHvSetPartitionProperty g_pfnWHvSetPartitionProperty
259# define WHvMapGpaRange g_pfnWHvMapGpaRange
260# define WHvCreateVirtualProcessor g_pfnWHvCreateVirtualProcessor
261# define WHvRunVirtualProcessor g_pfnWHvRunVirtualProcessor
262# define WHvGetVirtualProcessorRegisters g_pfnWHvGetVirtualProcessorRegisters
263# define WHvSetVirtualProcessorRegisters g_pfnWHvSetVirtualProcessorRegisters
264# endif
265 /* Need a precise time function. */
266 *(FARPROC *)&g_pfnRtlGetSystemTimePrecise = pfn = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetSystemTimePrecise");
267 if (pfn == NULL)
268 return error("Error resolving ntdll.dll!RtlGetSystemTimePrecise: %u\n", GetLastError());
269
270 /*
271 * Create the partition with 1 CPU and the specfied amount of memory.
272 */
273 WHV_PARTITION_HANDLE hPartition;
274 HRESULT hrc = WHvCreatePartition(&hPartition);
275 if (!SUCCEEDED(hrc))
276 return error("WHvCreatePartition failed: %#x\n", hrc);
277 g_hPartition = hPartition;
278
279 WHV_PARTITION_PROPERTY Property;
280 memset(&Property, 0, sizeof(Property));
281 Property.ProcessorCount = 1;
282 hrc = WHvSetPartitionProperty(hPartition, WHvPartitionPropertyCodeProcessorCount, &Property, sizeof(Property));
283 if (!SUCCEEDED(hrc))
284 return error("WHvSetPartitionProperty/WHvPartitionPropertyCodeProcessorCount failed: %#x\n", hrc);
285
286 memset(&Property, 0, sizeof(Property));
287 Property.ExtendedVmExits.X64CpuidExit = 1;
288 Property.ExtendedVmExits.X64MsrExit = 1;
289 hrc = WHvSetPartitionProperty(hPartition, WHvPartitionPropertyCodeExtendedVmExits, &Property, sizeof(Property));
290 if (!SUCCEEDED(hrc))
291 return error("WHvSetPartitionProperty/WHvPartitionPropertyCodeExtendedVmExits failed: %#x\n", hrc);
292
293 hrc = WHvSetupPartition(hPartition);
294 if (!SUCCEEDED(hrc))
295 return error("WHvSetupPartition failed: %#x\n", hrc);
296
297 hrc = WHvCreateVirtualProcessor(hPartition, 0 /*idVCpu*/, 0 /*fFlags*/);
298 if (!SUCCEEDED(hrc))
299 return error("WHvCreateVirtualProcessor failed: %#x\n", hrc);
300
301 g_pbMem = (unsigned char *)VirtualAlloc(NULL, g_cbMem, MEM_COMMIT, PAGE_READWRITE);
302 if (!g_pbMem)
303 return error("VirtualAlloc failed: %u\n", GetLastError());
304 memset(g_pbMem, 0xcc, g_cbMem);
305
306 hrc = WHvMapGpaRange(hPartition, g_pbMem, MY_MEM_BASE /*GCPhys*/, g_cbMem,
307 WHvMapGpaRangeFlagRead | WHvMapGpaRangeFlagWrite | WHvMapGpaRangeFlagExecute);
308 if (!SUCCEEDED(hrc))
309 return error("WHvMapGpaRange failed: %#x\n", hrc);
310
311 WHV_RUN_VP_EXIT_CONTEXT ExitInfo;
312 memset(&ExitInfo, 0, sizeof(ExitInfo));
313 WHvRunVirtualProcessor(g_hPartition, 0 /*idCpu*/, &ExitInfo, sizeof(ExitInfo));
314
315 return 0;
316}
317
318
319static int runtimeError(const char *pszFormat, ...)
320{
321 fprintf(stderr, "runtime error: ");
322 va_list va;
323 va_start(va, pszFormat);
324 vfprintf(stderr, pszFormat, va);
325 va_end(va);
326
327 static struct { const char *pszName; WHV_REGISTER_NAME enmName; unsigned uType; } const s_aRegs[] =
328 {
329 { "rip", WHvX64RegisterRip, 64 },
330 { "cs", WHvX64RegisterCs, 1 },
331 { "rflags", WHvX64RegisterRflags, 32 },
332 { "rax", WHvX64RegisterRax, 64 },
333 { "rcx", WHvX64RegisterRcx, 64 },
334 { "rdx", WHvX64RegisterRdx, 64 },
335 { "rbx", WHvX64RegisterRbx, 64 },
336 { "rsp", WHvX64RegisterRsp, 64 },
337 { "ss", WHvX64RegisterSs, 1 },
338 { "rbp", WHvX64RegisterRbp, 64 },
339 { "rsi", WHvX64RegisterRsi, 64 },
340 { "rdi", WHvX64RegisterRdi, 64 },
341 { "ds", WHvX64RegisterDs, 1 },
342 { "es", WHvX64RegisterEs, 1 },
343 { "fs", WHvX64RegisterFs, 1 },
344 { "gs", WHvX64RegisterGs, 1 },
345 { "cr0", WHvX64RegisterCr0, 64 },
346 { "cr2", WHvX64RegisterCr2, 64 },
347 { "cr3", WHvX64RegisterCr3, 64 },
348 { "cr4", WHvX64RegisterCr4, 64 },
349 };
350 for (unsigned i = 0; i < sizeof(s_aRegs) / sizeof(s_aRegs[0]); i++)
351 {
352 WHV_REGISTER_VALUE Value;
353 WHV_REGISTER_NAME enmName = s_aRegs[i].enmName;
354 HRESULT hrc = WHvGetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, &enmName, 1, &Value);
355 if (SUCCEEDED(hrc))
356 {
357 if (s_aRegs[i].uType == 32)
358 fprintf(stderr, "%8s=%08x\n", s_aRegs[i].pszName, Value.Reg32);
359 else if (s_aRegs[i].uType == 64)
360 fprintf(stderr, "%8s=%08x'%08x\n", s_aRegs[i].pszName, (unsigned)(Value.Reg64 >> 32), Value.Reg32);
361 else if (s_aRegs[i].uType == 1)
362 fprintf(stderr, "%8s=%04x base=%08x'%08x limit=%08x attr=%04x\n", s_aRegs[i].pszName,
363 Value.Segment.Selector, (unsigned)(Value.Segment.Base >> 32), (unsigned)Value.Segment.Base,
364 Value.Segment.Limit, Value.Segment.Attributes);
365 }
366 else
367 fprintf(stderr, "%8s=<WHvGetVirtualProcessorRegisters failed %#x>\n", s_aRegs[i].pszName, hrc);
368 }
369
370 return 1;
371}
372
373
374static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
375 unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
376 unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
377{
378 (void)fTest;
379
380 /*
381 * Initialize the real mode context.
382 */
383# define ADD_REG64(a_enmName, a_uValue) do { \
384 aenmNames[iReg] = (a_enmName); \
385 aValues[iReg].Reg128.High64 = 0; \
386 aValues[iReg].Reg64 = (a_uValue); \
387 iReg++; \
388 } while (0)
389# define ADD_SEG(a_enmName, a_Base, a_Limit, a_Sel, a_fCode) \
390 do { \
391 aenmNames[iReg] = a_enmName; \
392 aValues[iReg].Segment.Base = (a_Base); \
393 aValues[iReg].Segment.Limit = (a_Limit); \
394 aValues[iReg].Segment.Selector = (a_Sel); \
395 aValues[iReg].Segment.Attributes = a_fCode ? 0x9b : 0x93; \
396 iReg++; \
397 } while (0)
398 WHV_REGISTER_NAME aenmNames[80];
399 WHV_REGISTER_VALUE aValues[80];
400 unsigned iReg = 0;
401 ADD_REG64(WHvX64RegisterRax, uEax);
402 ADD_REG64(WHvX64RegisterRcx, uEcx);
403 ADD_REG64(WHvX64RegisterRdx, uEdx);
404 ADD_REG64(WHvX64RegisterRbx, uEbx);
405 ADD_REG64(WHvX64RegisterRsp, uEsp);
406 ADD_REG64(WHvX64RegisterRbp, uEbp);
407 ADD_REG64(WHvX64RegisterRsi, uEsi);
408 ADD_REG64(WHvX64RegisterRdi, uEdi);
409 ADD_REG64(WHvX64RegisterRip, MY_TEST_RIP);
410 ADD_REG64(WHvX64RegisterRflags, 2);
411 ADD_SEG(WHvX64RegisterEs, 0x00000, 0xffff, 0x0000, 0);
412 ADD_SEG(WHvX64RegisterCs, 0x00000, 0xffff, 0x0000, 1);
413 ADD_SEG(WHvX64RegisterSs, 0x00000, 0xffff, 0x0000, 0);
414 ADD_SEG(WHvX64RegisterDs, 0x00000, 0xffff, 0x0000, 0);
415 ADD_SEG(WHvX64RegisterFs, 0x00000, 0xffff, 0x0000, 0);
416 ADD_SEG(WHvX64RegisterGs, 0x00000, 0xffff, 0x0000, 0);
417 ADD_REG64(WHvX64RegisterCr0, 0x10010 /*WP+ET*/);
418 ADD_REG64(WHvX64RegisterCr2, 0);
419 ADD_REG64(WHvX64RegisterCr3, 0);
420 ADD_REG64(WHvX64RegisterCr4, 0);
421 HRESULT hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, iReg, aValues);
422 if (!SUCCEEDED(hrc))
423 return error("WHvSetVirtualProcessorRegisters failed (for %s): %#x\n", pszInstruction, hrc);
424# undef ADD_REG64
425# undef ADD_SEG
426
427 /*
428 * Run the test.
429 */
430 uint32_t cExits = 0;
431 uint64_t const nsStart = getNanoTS();
432 for (;;)
433 {
434 WHV_RUN_VP_EXIT_CONTEXT ExitInfo;
435 memset(&ExitInfo, 0, sizeof(ExitInfo));
436 hrc = WHvRunVirtualProcessor(g_hPartition, 0 /*idCpu*/, &ExitInfo, sizeof(ExitInfo));
437 if (SUCCEEDED(hrc))
438 {
439 cExits++;
440 if (ExitInfo.ExitReason == WHvRunVpExitReasonX64IoPortAccess)
441 {
442 if (ExitInfo.IoPortAccess.PortNumber == MY_NOP_PORT)
443 { /* likely: nop instruction */ }
444 else if (ExitInfo.IoPortAccess.PortNumber == MY_TERM_PORT)
445 break;
446 else
447 return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, ExitInfo.IoPortAccess.PortNumber);
448
449 /* Advance. */
450 if (ExitInfo.VpContext.InstructionLength)
451 {
452 aenmNames[0] = WHvX64RegisterRip;
453 aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
454 hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 1, aValues);
455 if (SUCCEEDED(hrc))
456 { /* likely */ }
457 else
458 return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
459 }
460 else
461 return runtimeError("VpContext.InstructionLength is zero (for %s)\n", pszInstruction);
462 }
463 else if (ExitInfo.ExitReason == WHvRunVpExitReasonX64Cpuid)
464 {
465 /* Advance RIP and set default results. */
466 if (ExitInfo.VpContext.InstructionLength)
467 {
468 aenmNames[0] = WHvX64RegisterRip;
469 aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
470 aenmNames[1] = WHvX64RegisterRax;
471 aValues[1].Reg64 = ExitInfo.CpuidAccess.DefaultResultRax;
472 aenmNames[2] = WHvX64RegisterRcx;
473 aValues[2].Reg64 = ExitInfo.CpuidAccess.DefaultResultRcx;
474 aenmNames[3] = WHvX64RegisterRdx;
475 aValues[3].Reg64 = ExitInfo.CpuidAccess.DefaultResultRdx;
476 aenmNames[4] = WHvX64RegisterRbx;
477 aValues[4].Reg64 = ExitInfo.CpuidAccess.DefaultResultRbx;
478 hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 5, aValues);
479 if (SUCCEEDED(hrc))
480 { /* likely */ }
481 else
482 return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
483 }
484 else
485 return runtimeError("VpContext.InstructionLength is zero (for %s)\n", pszInstruction);
486 }
487 else if (ExitInfo.ExitReason == WHvRunVpExitReasonMemoryAccess)
488 {
489 if (ExitInfo.MemoryAccess.Gpa == MY_NOP_MMIO)
490 { /* likely: nop address */ }
491 else
492 return runtimeError("Unexpected memory access (for %s): %#x\n", pszInstruction, ExitInfo.MemoryAccess.Gpa);
493
494 /* Advance and set return register (assuming RAX and two byte instruction). */
495 aenmNames[0] = WHvX64RegisterRip;
496 if (ExitInfo.VpContext.InstructionLength)
497 aValues[0].Reg64 = ExitInfo.VpContext.Rip + ExitInfo.VpContext.InstructionLength;
498 else
499 aValues[0].Reg64 = ExitInfo.VpContext.Rip + 2;
500 aenmNames[1] = WHvX64RegisterRax;
501 aValues[1].Reg64 = 42;
502 hrc = WHvSetVirtualProcessorRegisters(g_hPartition, 0 /*idCpu*/, aenmNames, 2, aValues);
503 if (SUCCEEDED(hrc))
504 { /* likely */ }
505 else
506 return runtimeError("Error advancing RIP (for %s): %#x\n", pszInstruction, hrc);
507 }
508 else
509 return runtimeError("Unexpected exit (for %s): %#x\n", pszInstruction, ExitInfo.ExitReason);
510 }
511 else
512 return runtimeError("WHvRunVirtualProcessor failed (for %s): %#x\n", pszInstruction, hrc);
513 }
514 uint64_t const nsElapsed = getNanoTS() - nsStart;
515 return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
516}
517
518
519
520#elif defined(RT_OS_LINUX)
521
522/*
523 * GNU/linux - KVM
524 */
525
526static int createVM(void)
527{
528 int fd = open("/dev/kvm", O_RDWR);
529 if (fd < 0)
530 return error("Error opening /dev/kvm: %d\n", errno);
531
532 g_fdVm = ioctl(fd, KVM_CREATE_VM, (uintptr_t)0);
533 if (g_fdVm < 0)
534 return error("KVM_CREATE_VM failed: %d\n", errno);
535
536 /* Create the VCpu. */
537 g_cbVCpuRun = ioctl(fd, KVM_GET_VCPU_MMAP_SIZE, (uintptr_t)0);
538 if (g_cbVCpuRun <= 0x1000 || (g_cbVCpuRun & 0xfff))
539 return error("Failed to get KVM_GET_VCPU_MMAP_SIZE: %#xz errno=%d\n", g_cbVCpuRun, errno);
540
541 g_fdVCpu = ioctl(g_fdVm, KVM_CREATE_VCPU, (uintptr_t)0);
542 if (g_fdVCpu < 0)
543 return error("KVM_CREATE_VCPU failed: %d\n", errno);
544
545 g_pVCpuRun = (struct kvm_run *)mmap(NULL, g_cbVCpuRun, PROT_READ | PROT_WRITE, MAP_PRIVATE, g_fdVCpu, 0);
546 if ((void *)g_pVCpuRun == MAP_FAILED)
547 return error("mmap kvm_run failed: %d\n", errno);
548
549 /* Memory. */
550 g_pbMem = (unsigned char *)mmap(NULL, g_cbMem, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
551 if ((void *)g_pbMem == MAP_FAILED)
552 return error("mmap RAM failed: %d\n", errno);
553
554 struct kvm_userspace_memory_region MemReg;
555 MemReg.slot = 0;
556 MemReg.flags = 0;
557 MemReg.guest_phys_addr = MY_MEM_BASE;
558 MemReg.memory_size = g_cbMem;
559 MemReg.userspace_addr = (uintptr_t)g_pbMem;
560 int rc = ioctl(g_fdVm, KVM_SET_USER_MEMORY_REGION, &MemReg);
561 if (rc != 0)
562 return error("KVM_SET_USER_MEMORY_REGION failed: %d (%d)\n", errno, rc);
563
564 close(fd);
565 return 0;
566}
567
568
569static void printSReg(const char *pszName, struct kvm_segment const *pSReg)
570{
571 fprintf(stderr, " %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",
572 pszName, pSReg->selector, pSReg->base, pSReg->limit, pSReg->type, pSReg->present, pSReg->dpl,
573 pSReg->db, pSReg->s, pSReg->l, pSReg->g, pSReg->avl, pSReg->unusable);
574}
575
576
577static int runtimeError(const char *pszFormat, ...)
578{
579 fprintf(stderr, "runtime error: ");
580 va_list va;
581 va_start(va, pszFormat);
582 vfprintf(stderr, pszFormat, va);
583 va_end(va);
584
585 fprintf(stderr, " exit_reason=%#010x\n", g_pVCpuRun->exit_reason);
586 fprintf(stderr, "ready_for_interrupt_injection=%#x\n", g_pVCpuRun->ready_for_interrupt_injection);
587 fprintf(stderr, " if_flag=%#x\n", g_pVCpuRun->if_flag);
588 fprintf(stderr, " flags=%#x\n", g_pVCpuRun->flags);
589 fprintf(stderr, " kvm_valid_regs=%#018llx\n", g_pVCpuRun->kvm_valid_regs);
590 fprintf(stderr, " kvm_dirty_regs=%#018llx\n", g_pVCpuRun->kvm_dirty_regs);
591
592 struct kvm_regs Regs;
593 memset(&Regs, 0, sizeof(Regs));
594 struct kvm_sregs SRegs;
595 memset(&SRegs, 0, sizeof(SRegs));
596 if ( ioctl(g_fdVCpu, KVM_GET_REGS, &Regs) != -1
597 && ioctl(g_fdVCpu, KVM_GET_SREGS, &SRegs) != -1)
598 {
599 fprintf(stderr, " rip=%016llx\n", Regs.rip);
600 printSReg("cs", &SRegs.cs);
601 fprintf(stderr, " rflags=%08llx\n", Regs.rflags);
602 fprintf(stderr, " rax=%016llx\n", Regs.rax);
603 fprintf(stderr, " rbx=%016llx\n", Regs.rcx);
604 fprintf(stderr, " rdx=%016llx\n", Regs.rdx);
605 fprintf(stderr, " rcx=%016llx\n", Regs.rbx);
606 fprintf(stderr, " rsp=%016llx\n", Regs.rsp);
607 fprintf(stderr, " rbp=%016llx\n", Regs.rbp);
608 fprintf(stderr, " rsi=%016llx\n", Regs.rsi);
609 fprintf(stderr, " rdi=%016llx\n", Regs.rdi);
610 printSReg("ss", &SRegs.ss);
611 printSReg("ds", &SRegs.ds);
612 printSReg("es", &SRegs.es);
613 printSReg("fs", &SRegs.fs);
614 printSReg("gs", &SRegs.gs);
615 printSReg("tr", &SRegs.tr);
616 printSReg("ldtr", &SRegs.ldt);
617
618 uint64_t const offMem = Regs.rip + SRegs.cs.base - MY_MEM_BASE;
619 if (offMem < g_cbMem - 10)
620 fprintf(stderr, " bytes at PC (%#zx): %02x %02x %02x %02x %02x %02x %02x %02x\n", (size_t)(offMem + MY_MEM_BASE),
621 g_pbMem[offMem ], g_pbMem[offMem + 1], g_pbMem[offMem + 2], g_pbMem[offMem + 3],
622 g_pbMem[offMem + 4], g_pbMem[offMem + 5], g_pbMem[offMem + 6], g_pbMem[offMem + 7]);
623 }
624
625 return 1;
626}
627
628static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
629 unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
630 unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
631{
632 (void)fTest;
633
634 /*
635 * Setup real mode context.
636 */
637#define SET_SEG(a_SReg, a_Base, a_Limit, a_Sel, a_fCode) \
638 do { \
639 a_SReg.base = (a_Base); \
640 a_SReg.limit = (a_Limit); \
641 a_SReg.selector = (a_Sel); \
642 a_SReg.type = (a_fCode) ? 10 : 3; \
643 a_SReg.present = 1; \
644 a_SReg.dpl = 0; \
645 a_SReg.db = 0; \
646 a_SReg.s = 1; \
647 a_SReg.l = 0; \
648 a_SReg.g = 0; \
649 a_SReg.avl = 0; \
650 a_SReg.unusable = 0; \
651 a_SReg.padding = 0; \
652 } while (0)
653 struct kvm_regs Regs;
654 memset(&Regs, 0, sizeof(Regs));
655 Regs.rax = uEax;
656 Regs.rcx = uEcx;
657 Regs.rdx = uEdx;
658 Regs.rbx = uEbx;
659 Regs.rsp = uEsp;
660 Regs.rbp = uEbp;
661 Regs.rsi = uEsi;
662 Regs.rdi = uEdi;
663 Regs.rip = MY_TEST_RIP;
664 Regs.rflags = 2;
665 int rc = ioctl(g_fdVCpu, KVM_SET_REGS, &Regs);
666 if (rc != 0)
667 return error("KVM_SET_REGS failed: %d (rc=%d)\n", errno, rc);
668
669 struct kvm_sregs SRegs;
670 memset(&SRegs, 0, sizeof(SRegs));
671 rc = ioctl(g_fdVCpu, KVM_GET_SREGS, &SRegs);
672 if (rc != 0)
673 return error("KVM_GET_SREGS failed: %d (rc=%d)\n", errno, rc);
674 SET_SEG(SRegs.es, 0x00000, 0xffff, 0x0000, 0);
675 SET_SEG(SRegs.cs, 0x00000, 0xffff, 0x0000, 1);
676 SET_SEG(SRegs.ss, 0x00000, 0xffff, 0x0000, 0);
677 SET_SEG(SRegs.ds, 0x00000, 0xffff, 0x0000, 0);
678 SET_SEG(SRegs.fs, 0x00000, 0xffff, 0x0000, 0);
679 SET_SEG(SRegs.gs, 0x00000, 0xffff, 0x0000, 0);
680 //SRegs.cr0 = 0x10010 /*WP+ET*/;
681 SRegs.cr2 = 0;
682 //SRegs.cr3 = 0;
683 //SRegs.cr4 = 0;
684 rc = ioctl(g_fdVCpu, KVM_SET_SREGS, &SRegs);
685 if (rc != 0)
686 return error("KVM_SET_SREGS failed: %d (rc=%d)\n", errno, rc);
687
688 /*
689 * Run the test.
690 */
691 uint32_t cExits = 0;
692 uint64_t const nsStart = getNanoTS();
693 for (;;)
694 {
695 rc = ioctl(g_fdVCpu, KVM_RUN, (uintptr_t)0);
696 if (rc == 0)
697 {
698 cExits++;
699 if (g_pVCpuRun->exit_reason == KVM_EXIT_IO)
700 {
701 if (g_pVCpuRun->io.port == MY_NOP_PORT)
702 { /* likely: nop instruction */ }
703 else if (g_pVCpuRun->io.port == MY_TERM_PORT)
704 break;
705 else
706 return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, g_pVCpuRun->io.port);
707 }
708 else if (g_pVCpuRun->exit_reason == KVM_EXIT_MMIO)
709 {
710 if (g_pVCpuRun->mmio.phys_addr == MY_NOP_MMIO)
711 { /* likely: nop address */ }
712 else
713 return runtimeError("Unexpected memory access (for %s): %#llx\n", pszInstruction, g_pVCpuRun->mmio.phys_addr);
714 }
715 else
716 return runtimeError("Unexpected exit (for %s): %d\n", pszInstruction, g_pVCpuRun->exit_reason);
717 }
718 else
719 return runtimeError("KVM_RUN failed (for %s): %#x (ret %d)\n", pszInstruction, errno, rc);
720 }
721 uint64_t const nsElapsed = getNanoTS() - nsStart;
722 return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
723}
724
725
726#elif defined(RT_OS_DARWIN)
727
728/*
729 * Mac OS X - Hypervisor API.
730 */
731
732static int createVM(void)
733{
734 /* VM and VCpu */
735 hv_return_t rcHv = hv_vm_create(HV_VM_DEFAULT);
736 if (rcHv != HV_SUCCESS)
737 return error("hv_vm_create failed: %#x\n", rcHv);
738
739 g_idVCpu = -1;
740 rcHv = hv_vcpu_create(&g_idVCpu, HV_VCPU_DEFAULT);
741 if (rcHv != HV_SUCCESS)
742 return error("hv_vcpu_create failed: %#x\n", rcHv);
743
744 /* Memory. */
745 g_pbMem = (unsigned char *)mmap(NULL, g_cbMem, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
746 if ((void *)g_pbMem == MAP_FAILED)
747 return error("mmap RAM failed: %d\n", errno);
748 memset(g_pbMem, 0xf4, g_cbMem);
749
750 rcHv = hv_vm_map(g_pbMem, MY_MEM_BASE, g_cbMem, HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
751 if (rcHv != HV_SUCCESS)
752 return error("hv_vm_map failed: %#x\n", rcHv);
753
754 rcHv = hv_vm_protect(0x2000, 0x1000, HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
755 if (rcHv != HV_SUCCESS)
756 return error("hv_vm_protect failed: %#x\n", rcHv);
757 return 0;
758}
759
760
761static int runtimeError(const char *pszFormat, ...)
762{
763 fprintf(stderr, "runtime error: ");
764 va_list va;
765 va_start(va, pszFormat);
766 vfprintf(stderr, pszFormat, va);
767 va_end(va);
768
769 static struct { const char *pszName; uint32_t uField; uint32_t uFmt : 31; uint32_t fIsReg : 1; } const s_aFields[] =
770 {
771 { "VMCS_RO_EXIT_REASON", VMCS_RO_EXIT_REASON, 64, 0 },
772 { "VMCS_RO_EXIT_QUALIFIC", VMCS_RO_EXIT_QUALIFIC, 64, 0 },
773 { "VMCS_RO_INSTR_ERROR", VMCS_RO_INSTR_ERROR, 64, 0 },
774 { "VMCS_RO_VMEXIT_IRQ_INFO", VMCS_RO_VMEXIT_IRQ_INFO, 64, 0 },
775 { "VMCS_RO_VMEXIT_IRQ_ERROR", VMCS_RO_VMEXIT_IRQ_ERROR, 64, 0 },
776 { "VMCS_RO_VMEXIT_INSTR_LEN", VMCS_RO_VMEXIT_INSTR_LEN, 64, 0 },
777 { "VMCS_RO_VMX_INSTR_INFO", VMCS_RO_VMX_INSTR_INFO, 64, 0 },
778 { "VMCS_RO_GUEST_LIN_ADDR", VMCS_RO_GUEST_LIN_ADDR, 64, 0 },
779 { "VMCS_GUEST_PHYSICAL_ADDRESS",VMCS_GUEST_PHYSICAL_ADDRESS,64, 0 },
780 { "VMCS_RO_IO_RCX", VMCS_RO_IO_RCX, 64, 0 },
781 { "VMCS_RO_IO_RSI", VMCS_RO_IO_RSI, 64, 0 },
782 { "VMCS_RO_IO_RDI", VMCS_RO_IO_RDI, 64, 0 },
783 { "VMCS_RO_IO_RIP", VMCS_RO_IO_RIP, 64, 0 },
784 { "rip", HV_X86_RIP, 64, 1 },
785 { "rip (vmcs)", VMCS_GUEST_RIP, 64, 0 },
786 { "cs", HV_X86_CS, 16, 1 },
787 { "cs (vmcs)", VMCS_GUEST_CS, 16, 0 },
788 { "cs.base", VMCS_GUEST_CS_BASE, 64, 0 },
789 { "cs.limit", VMCS_GUEST_CS_LIMIT, 32, 0 },
790 { "cs.attr", VMCS_GUEST_CS_AR, 32, 0 },
791 { "rflags", HV_X86_RFLAGS, 32, 1 },
792 { "rax", HV_X86_RAX, 64, 1 },
793 { "rcx", HV_X86_RCX, 64, 1 },
794 { "rdx", HV_X86_RDX, 64, 1 },
795 { "rbx", HV_X86_RBX, 64, 1 },
796 { "rsp", HV_X86_RSP, 64, 1 },
797 { "rsp (vmcs)", VMCS_GUEST_RSP, 64, 0 },
798 { "ss", HV_X86_SS, 16, 1 },
799 { "ss (vmcs)", VMCS_GUEST_SS, 16, 0 },
800 { "ss.base", VMCS_GUEST_SS_BASE, 64, 0 },
801 { "ss.limit", VMCS_GUEST_SS_LIMIT, 32, 0 },
802 { "ss.attr", VMCS_GUEST_SS_AR, 32, 0 },
803 { "rbp", HV_X86_RBP, 64, 1 },
804 { "rsi", HV_X86_RSI, 64, 1 },
805 { "rdi", HV_X86_RDI, 64, 1 },
806 { "ds", HV_X86_DS, 16, 1 },
807 { "ds (vmcs)", VMCS_GUEST_DS, 16, 0 },
808 { "ds.base", VMCS_GUEST_DS_BASE, 64, 0 },
809 { "ds.limit", VMCS_GUEST_DS_LIMIT, 32, 0 },
810 { "ds.attr", VMCS_GUEST_DS_AR, 32, 0 },
811 { "es", HV_X86_ES, 16, 1 },
812 { "es (vmcs)", VMCS_GUEST_ES, 16, 0 },
813 { "es.base", VMCS_GUEST_ES_BASE, 64, 0 },
814 { "es.limit", VMCS_GUEST_ES_LIMIT, 32, 0 },
815 { "es.attr", VMCS_GUEST_ES_AR, 32, 0 },
816 { "fs", HV_X86_FS, 16, 1 },
817 { "fs (vmcs)", VMCS_GUEST_FS, 16, 0 },
818 { "fs.base", VMCS_GUEST_FS_BASE, 64, 0 },
819 { "fs.limit", VMCS_GUEST_FS_LIMIT, 32, 0 },
820 { "fs.attr", VMCS_GUEST_FS_AR, 32, 0 },
821 { "gs", HV_X86_GS, 16, 1 },
822 { "gs (vmcs)", VMCS_GUEST_GS, 16, 0 },
823 { "gs.base", VMCS_GUEST_GS_BASE, 64, 0 },
824 { "gs.limit", VMCS_GUEST_GS_LIMIT, 32, 0 },
825 { "gs.attr", VMCS_GUEST_GS_AR, 32, 0 },
826 { "cr0", HV_X86_CR0, 64, 1 },
827 { "cr0 (vmcs)", VMCS_GUEST_CR0, 64, 0 },
828 { "cr2", HV_X86_CR2, 64, 1 },
829 { "cr3", HV_X86_CR3, 64, 1 },
830 { "cr3 (vmcs)", VMCS_GUEST_CR3, 64, 0 },
831 { "cr4", HV_X86_CR4, 64, 1 },
832 { "cr4 (vmcs)", VMCS_GUEST_CR4, 64, 0 },
833 { "idtr.base", VMCS_GUEST_IDTR_BASE, 64, 0 },
834 { "idtr.limit", VMCS_GUEST_IDTR_LIMIT, 32, 0 },
835 { "gdtr.base", VMCS_GUEST_GDTR_BASE, 64, 0 },
836 { "gdtr.limit", VMCS_GUEST_GDTR_LIMIT, 32, 0 },
837
838 { "VMCS_CTRL_PIN_BASED", VMCS_CTRL_PIN_BASED, 64, 0 },
839 { "VMCS_CTRL_CPU_BASED", VMCS_CTRL_CPU_BASED, 64, 0 },
840 { "VMCS_CTRL_CPU_BASED2", VMCS_CTRL_CPU_BASED2, 64, 0 },
841 { "VMCS_CTRL_VMENTRY_CONTROLS", VMCS_CTRL_VMENTRY_CONTROLS, 64, 0 },
842 { "VMCS_CTRL_VMEXIT_CONTROLS", VMCS_CTRL_VMEXIT_CONTROLS, 64, 0 },
843 { "VMCS_CTRL_EXC_BITMAP", VMCS_CTRL_EXC_BITMAP, 64, 0 },
844 { "VMCS_CTRL_CR0_MASK", VMCS_CTRL_CR0_MASK, 64, 0 },
845 { "VMCS_CTRL_CR0_SHADOW", VMCS_CTRL_CR0_SHADOW, 64, 0 },
846 { "VMCS_CTRL_CR4_MASK", VMCS_CTRL_CR4_MASK, 64, 0 },
847 { "VMCS_CTRL_CR4_SHADOW", VMCS_CTRL_CR4_SHADOW, 64, 0 },
848 };
849 for (unsigned i = 0; i < sizeof(s_aFields) / sizeof(s_aFields[0]); i++)
850 {
851 uint64_t uValue = UINT64_MAX;
852 hv_return_t rcHv;
853 if (s_aFields[i].fIsReg)
854 rcHv = hv_vcpu_read_register(g_idVCpu, (hv_x86_reg_t)s_aFields[i].uField, &uValue);
855 else
856 rcHv = hv_vmx_vcpu_read_vmcs(g_idVCpu, s_aFields[i].uField, &uValue);
857 if (rcHv == HV_SUCCESS)
858 {
859 if (s_aFields[i].uFmt == 16)
860 fprintf(stderr, "%28s=%04llx\n", s_aFields[i].pszName, uValue);
861 else if (s_aFields[i].uFmt == 32)
862 fprintf(stderr, "%28s=%08llx\n", s_aFields[i].pszName, uValue);
863 else
864 fprintf(stderr, "%28s=%08x'%08x\n", s_aFields[i].pszName, (uint32_t)(uValue >> 32), (uint32_t)uValue);
865 }
866 else
867 fprintf(stderr, "%28s=<%s failed %#x>\n", s_aFields[i].pszName,
868 s_aFields[i].fIsReg ? "hv_vcpu_read_register" : "hv_vmx_vcpu_read_vmcs", rcHv);
869 }
870 return 1;
871}
872
873
874static int runRealModeTest(unsigned cInstructions, const char *pszInstruction, unsigned fTest,
875 unsigned uEax, unsigned uEcx, unsigned uEdx, unsigned uEbx,
876 unsigned uEsp, unsigned uEbp, unsigned uEsi, unsigned uEdi)
877{
878 /*
879 * Setup real mode context.
880 */
881#define WRITE_REG_RET(a_enmReg, a_uValue) \
882 do { \
883 hv_return_t rcHvX = hv_vcpu_write_register(g_idVCpu, a_enmReg, a_uValue); \
884 if (rcHvX == HV_SUCCESS) { /* likely */ } \
885 else return error("hv_vcpu_write_register(%#x, %s, %#llx) -> %#x\n", g_idVCpu, #a_enmReg, (uint64_t)(a_uValue), rcHvX); \
886 } while (0)
887#define READ_REG_RET(a_enmReg, a_puValue) \
888 do { \
889 hv_return_t rcHvX = hv_vcpu_read_register(g_idVCpu, a_enmReg, a_puValue); \
890 if (rcHvX == HV_SUCCESS) { /* likely */ } \
891 else return error("hv_vcpu_read_register(%#x, %s,) -> %#x\n", g_idVCpu, #a_enmReg, rcHvX); \
892 } while (0)
893#define WRITE_VMCS_RET(a_enmField, a_uValue) \
894 do { \
895 hv_return_t rcHvX = hv_vmx_vcpu_write_vmcs(g_idVCpu, a_enmField, a_uValue); \
896 if (rcHvX == HV_SUCCESS) { /* likely */ } \
897 else return error("hv_vmx_vcpu_write_vmcs(%#x, %s, %#llx) -> %#x\n", g_idVCpu, #a_enmField, (uint64_t)(a_uValue), rcHvX); \
898 } while (0)
899#define READ_VMCS_RET(a_enmField, a_puValue) \
900 do { \
901 hv_return_t rcHvX = hv_vmx_vcpu_read_vmcs(g_idVCpu, a_enmField, a_puValue); \
902 if (rcHvX == HV_SUCCESS) { /* likely */ } \
903 else return error("hv_vmx_vcpu_read_vmcs(%#x, %s,) -> %#x\n", g_idVCpu, #a_enmField, rcHvX); \
904 } while (0)
905#define READ_CAP_RET(a_enmCap, a_puValue) \
906 do { \
907 hv_return_t rcHvX = hv_vmx_read_capability(a_enmCap, a_puValue); \
908 if (rcHvX == HV_SUCCESS) { /* likely */ } \
909 else return error("hv_vmx_read_capability(%s) -> %#x\n", #a_enmCap); \
910 } while (0)
911#define CAP_2_CTRL(a_uCap, a_fWanted) ( ((a_fWanted) | (uint32_t)(a_uCap)) & (uint32_t)((a_uCap) >> 32) )
912#if 1
913 uint64_t uCap;
914 READ_CAP_RET(HV_VMX_CAP_PINBASED, &uCap);
915 WRITE_VMCS_RET(VMCS_CTRL_PIN_BASED, CAP_2_CTRL(uCap, PIN_BASED_INTR | PIN_BASED_NMI | PIN_BASED_VIRTUAL_NMI));
916 READ_CAP_RET(HV_VMX_CAP_PROCBASED, &uCap);
917 WRITE_VMCS_RET(VMCS_CTRL_CPU_BASED, CAP_2_CTRL(uCap, CPU_BASED_HLT
918 | CPU_BASED_INVLPG
919 | CPU_BASED_MWAIT
920 | CPU_BASED_RDPMC
921 | CPU_BASED_RDTSC
922 | CPU_BASED_CR3_LOAD
923 | CPU_BASED_CR3_STORE
924 | CPU_BASED_CR8_LOAD
925 | CPU_BASED_CR8_STORE
926 | CPU_BASED_MOV_DR
927 | CPU_BASED_UNCOND_IO
928 | CPU_BASED_MONITOR
929 | CPU_BASED_PAUSE
930 ));
931 READ_CAP_RET(HV_VMX_CAP_PROCBASED2, &uCap);
932 WRITE_VMCS_RET(VMCS_CTRL_CPU_BASED2, CAP_2_CTRL(uCap, 0));
933 READ_CAP_RET(HV_VMX_CAP_ENTRY, &uCap);
934 WRITE_VMCS_RET(VMCS_CTRL_VMENTRY_CONTROLS, CAP_2_CTRL(uCap, 0));
935#endif
936 WRITE_VMCS_RET(VMCS_CTRL_EXC_BITMAP, UINT32_MAX);
937 WRITE_VMCS_RET(VMCS_CTRL_CR0_MASK, 0x60000000);
938 WRITE_VMCS_RET(VMCS_CTRL_CR0_SHADOW, 0x00000000);
939 WRITE_VMCS_RET(VMCS_CTRL_CR4_MASK, 0x00000000);
940 WRITE_VMCS_RET(VMCS_CTRL_CR4_SHADOW, 0x00000000);
941
942 WRITE_REG_RET(HV_X86_RAX, uEax);
943 WRITE_REG_RET(HV_X86_RCX, uEcx);
944 WRITE_REG_RET(HV_X86_RDX, uEdx);
945 WRITE_REG_RET(HV_X86_RBX, uEbx);
946 WRITE_REG_RET(HV_X86_RSP, uEsp);
947 WRITE_REG_RET(HV_X86_RBP, uEbp);
948 WRITE_REG_RET(HV_X86_RSI, uEsi);
949 WRITE_REG_RET(HV_X86_RDI, uEdi);
950 WRITE_REG_RET(HV_X86_RIP, MY_TEST_RIP);
951 WRITE_REG_RET(HV_X86_RFLAGS, 2);
952 WRITE_REG_RET(HV_X86_ES, 0x0000);
953 WRITE_VMCS_RET(VMCS_GUEST_ES_BASE, 0x0000000);
954 WRITE_VMCS_RET(VMCS_GUEST_ES_LIMIT, 0xffff);
955 WRITE_VMCS_RET(VMCS_GUEST_ES_AR, 0x93);
956 WRITE_REG_RET(HV_X86_CS, 0x0000);
957 WRITE_VMCS_RET(VMCS_GUEST_CS_BASE, 0x0000000);
958 WRITE_VMCS_RET(VMCS_GUEST_CS_LIMIT, 0xffff);
959 WRITE_VMCS_RET(VMCS_GUEST_CS_AR, 0x9b);
960 WRITE_REG_RET(HV_X86_SS, 0x0000);
961 WRITE_VMCS_RET(VMCS_GUEST_SS_BASE, 0x0000000);
962 WRITE_VMCS_RET(VMCS_GUEST_SS_LIMIT, 0xffff);
963 WRITE_VMCS_RET(VMCS_GUEST_SS_AR, 0x93);
964 WRITE_REG_RET(HV_X86_DS, 0x0000);
965 WRITE_VMCS_RET(VMCS_GUEST_DS_BASE, 0x0000000);
966 WRITE_VMCS_RET(VMCS_GUEST_DS_LIMIT, 0xffff);
967 WRITE_VMCS_RET(VMCS_GUEST_DS_AR, 0x93);
968 WRITE_REG_RET(HV_X86_FS, 0x0000);
969 WRITE_VMCS_RET(VMCS_GUEST_FS_BASE, 0x0000000);
970 WRITE_VMCS_RET(VMCS_GUEST_FS_LIMIT, 0xffff);
971 WRITE_VMCS_RET(VMCS_GUEST_FS_AR, 0x93);
972 WRITE_REG_RET(HV_X86_GS, 0x0000);
973 WRITE_VMCS_RET(VMCS_GUEST_GS_BASE, 0x0000000);
974 WRITE_VMCS_RET(VMCS_GUEST_GS_LIMIT, 0xffff);
975 WRITE_VMCS_RET(VMCS_GUEST_GS_AR, 0x93);
976 //WRITE_REG_RET(HV_X86_CR0, 0x10030 /*WP+NE+ET*/);
977 WRITE_VMCS_RET(VMCS_GUEST_CR0, 0x10030 /*WP+NE+ET*/);
978 //WRITE_REG_RET(HV_X86_CR2, 0);
979 //WRITE_REG_RET(HV_X86_CR3, 0);
980 WRITE_VMCS_RET(VMCS_GUEST_CR3, 0);
981 //WRITE_REG_RET(HV_X86_CR4, 0x2000);
982 WRITE_VMCS_RET(VMCS_GUEST_CR4, 0x2000);
983 WRITE_VMCS_RET(VMCS_GUEST_LDTR, 0x0000);
984 WRITE_VMCS_RET(VMCS_GUEST_LDTR_BASE, 0x00000000);
985 WRITE_VMCS_RET(VMCS_GUEST_LDTR_LIMIT, 0x0000);
986 WRITE_VMCS_RET(VMCS_GUEST_LDTR_AR, 0x10000);
987 WRITE_VMCS_RET(VMCS_GUEST_TR, 0x0000);
988 WRITE_VMCS_RET(VMCS_GUEST_TR_BASE, 0x00000000);
989 WRITE_VMCS_RET(VMCS_GUEST_TR_LIMIT, 0x0000);
990 WRITE_VMCS_RET(VMCS_GUEST_TR_AR, 0x00083);
991 hv_vcpu_flush(g_idVCpu);
992 hv_vcpu_invalidate_tlb(g_idVCpu);
993
994 /*
995 * Run the test.
996 */
997 uint32_t cExits = 0;
998 uint64_t const nsStart = getNanoTS();
999 for (;;)
1000 {
1001 hv_return_t rcHv = hv_vcpu_run(g_idVCpu);
1002 if (rcHv == HV_SUCCESS)
1003 {
1004 cExits++;
1005 uint64_t uExitReason = UINT64_MAX;
1006 READ_VMCS_RET(VMCS_RO_EXIT_REASON, &uExitReason);
1007 if (!(uExitReason & UINT64_C(0x80000000)))
1008 {
1009 if (uExitReason == VMX_REASON_IO)
1010 {
1011 uint64_t uIoQual = UINT64_MAX;
1012 READ_VMCS_RET(VMCS_RO_EXIT_QUALIFIC, &uIoQual);
1013 if ((uint16_t)(uIoQual >> 16) == MY_NOP_PORT && (fTest & MY_TEST_F_NOP_IO))
1014 { /* likely: nop instruction */ }
1015 else if ((uint16_t)(uIoQual >> 16) == MY_TERM_PORT)
1016 break;
1017 else
1018 return runtimeError("Unexpected I/O port access (for %s): %#x\n", pszInstruction, (uint16_t)(uIoQual >> 16));
1019
1020 /* Advance RIP. */
1021 uint64_t cbInstr = UINT64_MAX;
1022 READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
1023 if (cbInstr < 1 || cbInstr > 15)
1024 return runtimeError("Bad instr len: %#llx\n", cbInstr);
1025 uint64_t uRip = UINT64_MAX;
1026 READ_REG_RET(HV_X86_RIP, &uRip);
1027 WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
1028 }
1029 else if (uExitReason == VMX_REASON_CPUID && (fTest & MY_TEST_F_CPUID))
1030 {
1031 /* Set registers and advance RIP. */
1032 WRITE_REG_RET(HV_X86_RAX, 0x42424242);
1033 WRITE_REG_RET(HV_X86_RCX, 0x04242424);
1034 WRITE_REG_RET(HV_X86_RDX, 0x00424242);
1035 WRITE_REG_RET(HV_X86_RBX, 0x00024242);
1036
1037 uint64_t cbInstr = UINT64_MAX;
1038 READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
1039 if (cbInstr < 1 || cbInstr > 15)
1040 return runtimeError("Bad instr len: %#llx\n", cbInstr);
1041 uint64_t uRip = UINT64_MAX;
1042 READ_REG_RET(HV_X86_RIP, &uRip);
1043 WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
1044 }
1045 else if (uExitReason == VMX_REASON_EPT_VIOLATION)
1046 {
1047 uint64_t uEptQual = UINT64_MAX;
1048 READ_VMCS_RET(VMCS_RO_EXIT_QUALIFIC, &uEptQual);
1049 uint64_t GCPhys = UINT64_MAX;
1050 READ_VMCS_RET(VMCS_GUEST_PHYSICAL_ADDRESS, &GCPhys);
1051 if (GCPhys == MY_NOP_MMIO && (fTest & MY_TEST_F_NOP_MMIO))
1052 { /* likely */ }
1053 else if (GCPhys == MY_TEST_RIP)
1054 continue; /* dunno why we get this, but restarting it works */
1055 else
1056 return runtimeError("Unexpected EPT viotaion at %#llx\n", GCPhys);
1057
1058 /* Set RAX and advance RIP. */
1059 WRITE_REG_RET(HV_X86_RAX, 42);
1060
1061 uint64_t cbInstr = UINT64_MAX;
1062 READ_VMCS_RET(VMCS_RO_VMEXIT_INSTR_LEN, &cbInstr);
1063 if (cbInstr < 1 || cbInstr > 15)
1064 return runtimeError("Bad instr len: %#llx\n", cbInstr);
1065 uint64_t uRip = UINT64_MAX;
1066 READ_REG_RET(HV_X86_RIP, &uRip);
1067 WRITE_REG_RET(HV_X86_RIP, uRip + cbInstr);
1068 }
1069 else if (uExitReason == VMX_REASON_IRQ)
1070 { /* ignore */ }
1071 else
1072 return runtimeError("Unexpected exit reason: %#x\n", uExitReason);
1073 }
1074 else
1075 return runtimeError("VM entry failure: %#x\n", uExitReason);
1076 }
1077 else
1078 return runtimeError("hv_vcpu_run failed (for %s): %#x\n", pszInstruction, rcHv);
1079 }
1080 uint64_t const nsElapsed = getNanoTS() - nsStart;
1081 return reportResult(pszInstruction, cInstructions, nsElapsed, cExits);
1082}
1083
1084#else
1085# error "port me"
1086#endif
1087
1088void dumpCode(uint8_t const *pb, uint8_t *pbEnd)
1089{
1090 printf("testing:");
1091 for (; pb != pbEnd; pb++)
1092 printf(" %02x", *pb);
1093 printf("\n");
1094}
1095
1096
1097int ioportTest(unsigned cFactor)
1098{
1099 /*
1100 * Produce realmode code
1101 */
1102 unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
1103 unsigned char * const pbStart = pb;
1104 /* OUT DX, AL - 10 times */
1105 for (unsigned i = 0; i < 10; i++)
1106 *pb++ = 0xee;
1107 /* DEC ECX */
1108 *pb++ = 0x66;
1109 *pb++ = 0x48 + 1;
1110 /* JNZ MY_TEST_RIP */
1111 *pb++ = 0x75;
1112 *pb = (signed char)(pbStart - pb - 1);
1113 pb++;
1114 /* OUT 1, AL - Temination port call. */
1115 *pb++ = 0xe6;
1116 *pb++ = MY_TERM_PORT;
1117 /* JMP to previous instruction */
1118 *pb++ = 0xeb;
1119 *pb++ = 0xfc;
1120 dumpCode(pbStart, pb);
1121
1122 return runRealModeTest(100000 * cFactor, "OUT", MY_TEST_F_NOP_IO,
1123 42 /*eax*/, 10000 * cFactor /*ecx*/, MY_NOP_PORT /*edx*/, 0 /*ebx*/,
1124 0 /*esp*/, 0 /*ebp*/, 0 /*esi*/, 0 /*uEdi*/);
1125}
1126
1127
1128int cpuidTest(unsigned cFactor)
1129{
1130 /*
1131 * Produce realmode code
1132 */
1133 unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
1134 unsigned char * const pbStart = pb;
1135 for (unsigned i = 0; i < 10; i++)
1136 {
1137 /* XOR EAX,EAX */
1138 *pb++ = 0x66;
1139 *pb++ = 0x33;
1140 *pb++ = 0xc0;
1141
1142 /* CPUID */
1143 *pb++ = 0x0f;
1144 *pb++ = 0xa2;
1145 }
1146 /* DEC ESI */
1147 *pb++ = 0x66;
1148 *pb++ = 0x48 + 6;
1149 /* JNZ MY_TEST_RIP */
1150 *pb++ = 0x75;
1151 *pb = (signed char)(pbStart - pb - 1);
1152 pb++;
1153 /* OUT 1, AL - Temination port call. */
1154 *pb++ = 0xe6;
1155 *pb++ = MY_TERM_PORT;
1156 /* JMP to previous instruction */
1157 *pb++ = 0xeb;
1158 *pb++ = 0xfc;
1159 dumpCode(pbStart, pb);
1160
1161 return runRealModeTest(100000 * cFactor, "CPUID", MY_TEST_F_CPUID,
1162 0 /*eax*/, 0 /*ecx*/, 0 /*edx*/, 0 /*ebx*/,
1163 0 /*esp*/, 0 /*ebp*/, 10000 * cFactor /*esi*/, 0 /*uEdi*/);
1164}
1165
1166
1167int mmioTest(unsigned cFactor)
1168{
1169 /*
1170 * Produce realmode code accessing MY_MMIO_NOP address assuming it's low.
1171 */
1172 unsigned char *pb = &g_pbMem[MY_TEST_RIP - MY_MEM_BASE];
1173 unsigned char * const pbStart = pb;
1174 for (unsigned i = 0; i < 10; i++)
1175 {
1176 /* MOV AL,DS:[BX] */
1177 *pb++ = 0x8a;
1178 *pb++ = 0x07;
1179 }
1180 /* DEC ESI */
1181 *pb++ = 0x66;
1182 *pb++ = 0x48 + 6;
1183 /* JNZ MY_TEST_RIP */
1184 *pb++ = 0x75;
1185 *pb = (signed char)(pbStart - pb - 1);
1186 pb++;
1187 /* OUT 1, AL - Temination port call. */
1188 *pb++ = 0xe6;
1189 *pb++ = MY_TERM_PORT;
1190 /* JMP to previous instruction */
1191 *pb++ = 0xeb;
1192 *pb++ = 0xfc;
1193 dumpCode(pbStart, pb);
1194
1195 return runRealModeTest(100000 * cFactor, "MMIO/r1", MY_TEST_F_NOP_MMIO,
1196 0 /*eax*/, 0 /*ecx*/, 0 /*edx*/, MY_NOP_MMIO /*ebx*/,
1197 0 /*esp*/, 0 /*ebp*/, 10000 * cFactor /*esi*/, 0 /*uEdi*/);
1198}
1199
1200
1201
1202int main(int argc, char **argv)
1203{
1204 /*
1205 * Do some parameter parsing.
1206 */
1207#ifdef RT_OS_WINDOWS
1208 unsigned const cFactorDefault = 4;
1209#elif RT_OS_DARWIN
1210 unsigned const cFactorDefault = 32;
1211#else
1212 unsigned const cFactorDefault = 24;
1213#endif
1214 unsigned cFactor = cFactorDefault;
1215 for (int i = 1; i < argc; i++)
1216 {
1217 const char *pszArg = argv[i];
1218 if ( strcmp(pszArg, "--help") == 0
1219 || strcmp(pszArg, "/help") == 0
1220 || strcmp(pszArg, "-h") == 0
1221 || strcmp(pszArg, "-?") == 0
1222 || strcmp(pszArg, "/?") == 0)
1223 {
1224 printf("Does some benchmarking of the native NEM engine.\n"
1225 "\n"
1226 "Usage: NemRawBench-1 --factor <factor>\n"
1227 "\n"
1228 "Options\n"
1229 " --factor <factor>\n"
1230 " Iteration count factor. Default is %u.\n"
1231 " Lower it if execution is slow, increase if quick.\n",
1232 cFactorDefault);
1233 return 0;
1234 }
1235 if (strcmp(pszArg, "--factor") == 0)
1236 {
1237 i++;
1238 if (i < argc)
1239 cFactor = atoi(argv[i]);
1240 else
1241 {
1242 fprintf(stderr, "syntax error: Option %s is takes a value!\n", pszArg);
1243 return 2;
1244 }
1245 }
1246 else
1247 {
1248 fprintf(stderr, "syntax error: Unknown option: %s\n", pszArg);
1249 return 2;
1250 }
1251 }
1252
1253 /*
1254 * Create the VM
1255 */
1256 g_cbMem = 128*1024 - MY_MEM_BASE;
1257 int rcExit = createVM();
1258 if (rcExit == 0)
1259 {
1260 printf("tstNemBench-1: Successfully created test VM...\n");
1261
1262 /*
1263 * Do the benchmarking.
1264 */
1265 ioportTest(cFactor);
1266 cpuidTest(cFactor);
1267 mmioTest(cFactor);
1268
1269 printf("tstNemBench-1: done\n");
1270 }
1271 return rcExit;
1272}
1273
1274/*
1275 * Results:
1276 *
1277 * - Darwin/xnu 10.12.6/16.7.0; 3.1GHz Intel Core i7-7920HQ (Kaby Lake):
1278 * 925 845 OUT instructions per second (3 200 307 exits in 3 456 301 621 ns)
1279 * 949 278 CPUID instructions per second (3 200 222 exits in 3 370 980 173 ns)
1280 * 871 499 MMIO/r1 instructions per second (3 200 223 exits in 3 671 834 221 ns)
1281 *
1282 * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.1GHz Intel Core i7-7920HQ (Kaby Lake):
1283 * 829 775 OUT instructions per second (3 200 001 exits in 3 856 466 567 ns)
1284 * 2 212 038 CPUID instructions per second (1 exits in 1 446 629 591 ns) [1]
1285 * 477 962 MMIO/r1 instructions per second (3 200 001 exits in 6 695 090 600 ns)
1286 *
1287 * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.4GHz Core i5-3570 (Ivy Bridge):
1288 * 717 216 OUT instructions per second (2 400 001 exits in 3 346 271 640 ns)
1289 * 1 675 983 CPUID instructions per second (1 exits in 1 431 995 135 ns) [1]
1290 * 402 621 MMIO/r1 instructions per second (2 400 001 exits in 5 960 930 854 ns)
1291 *
1292 * - Linux 4.18.0-1-amd64 (debian); 3.4GHz AMD Threadripper 1950X:
1293 * 455 727 OUT instructions per second (2 400 001 exits in 5 266 300 471 ns)
1294 * 1 745 014 CPUID instructions per second (1 exits in 1 375 346 658 ns) [1]
1295 * 351 767 MMIO/r1 instructions per second (2 400 001 exits in 6 822 684 544 ns)
1296 *
1297 * - Windows 1803 updated as per 2018-10-01; 3.4GHz Core i5-3570 (Ivy Bridge):
1298 * 67 778 OUT instructions per second (400 001 exits in 5 901 560 700 ns)
1299 * 66 113 CPUID instructions per second (400 001 exits in 6 050 208 000 ns)
1300 * 62 939 MMIO/r1 instructions per second (400 001 exits in 6 355 302 900 ns)
1301 *
1302 * - Windows 1803 updated as per 2018-09-28; 3.4GHz AMD Threadripper 1950X:
1303 * 34 485 OUT instructions per second (400 001 exits in 11 598 918 200 ns)
1304 * 34 043 CPUID instructions per second (400 001 exits in 11 749 753 200 ns)
1305 * 33 124 MMIO/r1 instructions per second (400 001 exits in 12 075 617 000 ns)
1306 *
1307 * - Windows build 17763; 3.4GHz AMD Threadripper 1950X:
1308 * 65 633 OUT instructions per second (400 001 exits in 6 094 409 100 ns)
1309 * 65 245 CPUID instructions per second (400 001 exits in 6 130 720 600 ns)
1310 * 61 642 MMIO/r1 instructions per second (400 001 exits in 6 489 013 700 ns)
1311 *
1312 *
1313 * [1] CPUID causes no return to ring-3 with KVM.
1314 *
1315 *
1316 * For reference we can compare with similar tests in bs2-test1 running VirtualBox:
1317 *
1318 * - Linux 4.18.0-1-amd64 (debian); 3.4GHz AMD Threadripper 1950X; trunk/r125404:
1319 * real mode, 32-bit OUT : 1 338 471 ins/sec
1320 * real mode, 32-bit OUT-to-ring-3 : 500 337 ins/sec
1321 * real mode, CPUID : 1 566 343 ins/sec
1322 * real mode, 32-bit write : 870 671 ins/sec
1323 * real mode, 32-bit write-to-ring-3: 391 014 ins/sec
1324 *
1325 * - Darwin/xnu 10.12.6/16.7.0; 3.1GHz Intel Core i7-7920HQ (Kaby Lake); trunk/r125404:
1326 * real mode, 32-bit OUT : 790 117 ins/sec
1327 * real mode, 32-bit OUT-to-ring-3 : 157 205 ins/sec
1328 * real mode, CPUID : 1 001 087 ins/sec
1329 * real mode, 32-bit write : 651 257 ins/sec
1330 * real mode, 32-bit write-to-ring-3: 157 773 ins/sec
1331 *
1332 * - Linux 4.15.0 / ubuntu 18.04.1 Desktop LiveCD; 3.1GHz Intel Core i7-7920HQ (Kaby Lake); trunk/r125450:
1333 * real mode, 32-bit OUT : 1 229 245 ins/sec
1334 * real mode, 32-bit OUT-to-ring-3 : 284 848 ins/sec
1335 * real mode, CPUID : 1 429 760 ins/sec
1336 * real mode, 32-bit write : 820 679 ins/sec
1337 * real mode, 32-bit write-to-ring-3: 245 159 ins/sec
1338 *
1339 * - Windows 1803 updated as per 2018-10-01; 3.4GHz Core i5-3570 (Ivy Bridge); trunk/r15442:
1340 * real mode, 32-bit OUT : 961 939 ins/sec
1341 * real mode, 32-bit OUT-to-ring-3 : 189 458 ins/sec
1342 * real mode, CPUID : 1 060 582 ins/sec
1343 * real mode, 32-bit write : 637 967 ins/sec
1344 * real mode, 32-bit write-to-ring-3: 148 573 ins/sec
1345 *
1346 */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette