C语言 使用 XSETBV 写入 XCR0 会在支持 MPX 的硬件上的 VM 中产生常规保护错误



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

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

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 $0, %%ecx      tn" // %ecx selects the xcr to write
"xor %%rdx, %%rdx   tn" // set %rdx to zero
"xsetbv             tn" // write from edx:eax into xcr0
:
: "a" (new_xcr0)        /* input    */
: "ecx", "rdx"          /* clobbered    */
);

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

  • 如果当前权限级别不是 0 -->我使用内核模块来实现CPL=0

  • 如果在%ecx--中指定了无效xcr>则 0 在%ecx中有效且适用于xgetbv

  • 如果edx:eax中的值设置了在ecx指定的xcr中保留的位 --> 根据英特尔手册和维基百科,则我正在设置的位不会被保留

  • 如果尝试清除xcr0的第 0 位 -->我在设置之前打印了xcr0,它被0b11111

  • 如果尝试将xcr0[2:1]设置为0b10-->我在设置之前打印了xcr0,它被0b11111

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

Peter Cordes 是对的,这是我的虚拟机管理程序的问题。我正在使用VMWare Fusion进行虚拟化,在互联网上进行大量挖掘后,我从VMWare中找到了以下引述:

内存保护扩展 (MPX) 在英特尔 Skylake 一代 CPU 中引入,为绑定检查提供硬件支持。从 Ice Lake 一代开始的英特尔 CPU 将不支持此功能。

从 ESXi 6.7 P02 和 ESXi 7.0 GA 开始,为了最大程度地减少未来升级期间的中断,VMware 将不再在打开电源时向虚拟机公开 MPX。虚拟机配置选项可用于继续公开 MPX。

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

cpuid.enableMPX = "TRUE"

在我这样做之后,事情进展顺利,我能够使用xsetbv来启用bndreg位并bndcsrxcr0


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

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

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

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

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

虚拟机

上的/proc/cpuinfo不会在标志中列出mpx(但它确实列出了xsave)。不过,我的主机确实支持 MPX。我正在运行支持 MPX 的 Linux 内核版本 3.19,并且我已经有一个使用 MPX 编译的二进制文件(当我 objdump 时,bnd 指令等都在那里)。问题是指令被视为NOP。我认为我上面描述的过程可以解决这个问题并使 CPU 能够识别 MPX。

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

根据 VM 自己的虚拟化 CPUID,运行虚拟机的虚拟 x86 CPU 不会,因此出现此错误也就不足为奇了。 虚拟机管理程序可能在 VMEXIT 中手动执行此操作,模拟xsetbv并检查对虚拟化 xcr0 的更改。

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

请注意,MPX 引入了新的架构状态(bnd寄存器),这些状态必须在上下文切换上保存/恢复。 如果您的虚拟机管理程序不想这样做,这将是禁用 MPX 的原因之一。 (我认为它可以作为xsave的一部分保存/恢复,但它确实使保存略大。 我没怎么看过 MPX;这可能是虚拟机管理程序在 VMexits 中必须处理的问题,以便不将边界检查应用于虚拟机管理程序... 如果是这样,那将是一个很大的不便。

最新更新