VirtualBox

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

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