使用 XSETBV 写入 XCR0 在支持 MPX 的硬件上的 VM 中创建一般保护错误

Using XSETBV to write to XCR0 creates a general protection fault in a VM on hardware that supports MPX

我正在尝试写入 x86_64 Debian v7 虚拟机上的扩展控制寄存器 0 (xcr0)。我这样做的方法是通过一个带有一些内联汇编的内核模块(所以 CPL=0)。但是,当我尝试执行 xsetbv 指令时,我不断收到一般保护错误 (#GP)。

我的模块的 init 函数首先检查 osxsave 位是否设置在控制寄存器 4 (cr4) 中。如果不是,则设置它。然后,我使用 xgetbv 读取 xcr0 寄存器。这工作正常并且(在我所做的有限测试中)具有值 0b111。我想设置 bndregbndcsr 位,它们是第 3 位和第 4 位(0 索引),所以我做了一些 ORing 并将 0b11111 写回xcr0 使用 xsetbv。实现这最后一部分的代码如下。

unsigned long xcr0;             /* extended register    */
unsigned long bndreg = 0x8;     /* 3rd bit in xcr0      */
unsigned long bndcsr = 0x10;    /* 4th bit in xcr0      */

/* ... checking cr4 for osxsave and reading xcr0 ... */

if (!(xcr0 & bndreg))
    xcr0 |= bndreg;

if (!(xcr0 & bndcsr))
    xcr0 |= bndcsr;

/* ... xcr0 is now 0b11111 ... */

/*
 * write changes to xcr0; ignore high bits (set them =0) b/c they are reserved
 */
unsigned long new_xcr0 = ((xcr0) & 0xffffffff);
__asm__ volatile (
    "mov [=10=], %%ecx      \t\n" // %ecx selects the xcr to write
    "xor %%rdx, %%rdx   \t\n" // set %rdx to zero
    "xsetbv             \t\n" // write from edx:eax into xcr0
    :
    : "a" (new_xcr0)        /* input    */
    : "ecx", "rdx"          /* clobbered    */
);

通过查看一般保护故障的跟踪,我确定 xsetbv 指令是问题所在。但是,如果我不操纵 xcr0 并且只是读取它的值并将其写回,那么事情似乎工作正常。查看Intel手册和this site,我发现了#GP的各种原因,但其中none似乎符合我的情况。原因如下以及我对它们很可能不适用的原因的解释。

提前感谢您提供的任何帮助,以了解为什么会发生这种 #GP

/proc/cpuinfo on the VM doesn't list mpx in the flags (it does list xsave though). My host does have MPX support though. I am running Linux kernel version 3.19 which does support MPX and I already have a binary compiled with MPX (the bnd instructions etc. are all there when I objdump). The problem is that the instructions get treated as NOPs. I thought the process I described above would fix this and enable the CPU to recognize MPX.

如果您在支持 MPX 的机器上 运行 它将启用 MPX。 (假设您的代码是正确的。)

虚拟 x86 CPU 你的 VM 运行ning 上没有,根据它自己的虚拟化 CPUID,所以这个错误一点也不奇怪。管理程序可能在 VMEXIT 中手动执行此操作,模拟 xsetbv 并检查对虚拟化 xcr0 的更改。

如果您想使用您的硬件具有但您的 VM 不支持的功能,通常您必须 运行 在裸机上。或者找一个不同的 VM 向来宾公开该功能。

请注意,MPX 引入了新的架构状态(bnd 寄存器),在上下文切换时必须获得 saved/restored。如果您的管理程序不想这样做,那将是禁用 MPX 的原因之一。 (我认为它可以获得 saved/restored 作为 xsave 的一部分,但它确实使节省的空间稍微大一些。)我没怎么看过 MPX;这可能是管理程序必须在 vmexits 中处理的事情,以便不对管理程序应用边界检查...如果是这样,那将是一个很大的不便。

Peter Cordes 是对的,这是我的管理程序的问题。我正在使用 VMWare Fusion 进行虚拟化,在互联网上进行了大量挖掘之后,我发现了以下来自 VMWare 的引用:

Memory protection extensions (MPX) were introduced in Intel Skylake generation CPUs and provided hardware support for bound checking. This feature will not be supported in Intel CPUs beginning with the Ice Lake generation.

Starting with ESXi 6.7 P02 and ESXi 7.0 GA, in order to minimize disruptions during future upgrades, VMware will no longer expose MPX by default to VMs at power-on. A VM configuration option can be used to continue exposing MPX.

VMWare 提出的解决方案是使用以下指令编辑虚拟机的 .vmx 文件。

cpuid.enableMPX = "TRUE"

在我这样做之后,一切正常,我能够使用 xsetbv 启用 xcr0.

bndregbndcsr

当使用 VMWare 将 CPU 功能从主机公开到来宾时,在更正常的情况下(即该功能不受弃用的困扰)您可以屏蔽 cpuid 的位将以下内容添加到 VM 的 .vmx 文件中。

cpuid.<leaf>.<register> = "<value>"

因此,例如,如果我们假设 SMAP 可以通过这种方式公开,我们会想要设置 cpuid 叶子 7 的第 20 位。

cpuid.7.ebx = "----:----:---1:----:----:----:----:----"

冒号是可选的,以便于阅读字符串,1 和 0 覆盖任何默认设置,破折号用于保留默认设置。