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

使用XSETBV写入XCR0会在支持MPX的硬件上的VM中创建一般保护故障,c,linux-kernel,x86,virtual-machine,vmware,C,Linux Kernel,X86,Virtual Machine,Vmware,我正在尝试写入x86_64 Debian v7虚拟机上的扩展控制寄存器0(xcr0)。我这样做的方法是通过一个内核模块(soCPL=0)和一些内联程序集。但是,当我尝试执行xsetbv指令时,我总是遇到一个常规保护故障(#GP) 模块的init功能首先检查控制寄存器4(cr4)中是否设置了osxsave位。如果不是,它就会设置它。然后,我使用xgetbv读取xcr0寄存器。这很好,并且(在我所做的有限测试中)具有值0b111。我想设置bndreg和bndcsr位,它们是第3位和第4位(0索引),

我正在尝试写入x86_64 Debian v7虚拟机上的扩展控制寄存器0(
xcr0
)。我这样做的方法是通过一个内核模块(so
CPL=0
)和一些内联程序集。但是,当我尝试执行
xsetbv
指令时,我总是遇到一个常规保护故障(
#GP

模块的
init
功能首先检查控制寄存器4(
cr4
)中是否设置了
osxsave
位。如果不是,它就会设置它。然后,我使用
xgetbv
读取
xcr0
寄存器。这很好,并且(在我所做的有限测试中)具有值
0b111
。我想设置
bndreg
bndcsr
位,它们是第3位和第4位(0索引),因此我会执行一些
操作,并使用
xsetbv
0b11111
写回
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      \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
,只读取它的值并将其写回,事情似乎运行得很好。查看英特尔手册,我发现了产生
#GP
的各种原因,但没有一个与我的情况相符。原因如下,我解释了为什么它们很可能不适用

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

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

  • 如果
    edx:eax
    中的值设置了由
    ecx
    -->根据英特尔手册指定的
    xcr
    中保留的位,并且我正在设置的位没有保留

  • 如果试图清除
    xcr0
    -->的位0,我在设置它之前打印出
    xcr0
    ,它是
    0b11111

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

提前感谢您提供的任何帮助,以了解发生这种情况的原因

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

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

根据虚拟机本身的虚拟化CPUID,虚拟机运行的虚拟x86 CPU没有,因此出现这种故障一点也不奇怪。虚拟机监控程序可能在VMEXIT中手动执行此操作,模拟
xsetbv
,并检查对虚拟化xcr0的更改

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

请注意,MPX引入了新的体系结构状态(bnd
寄存器),这些状态必须在上下文开关上保存/恢复。如果您的虚拟机监控程序不想这样做,那将是禁用MPX的一个原因。(我认为它可以作为
xsave
的一部分进行保存/恢复,但它确实会使保存稍微大一些。)我对MPX没有太多的了解;这可能是虚拟机监控程序必须在vmexits中处理的事情,以使边界检查不应用于虚拟机监控程序。。。如果是这样,那将是一个很大的不便

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

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

根据虚拟机本身的虚拟化CPUID,虚拟机运行的虚拟x86 CPU没有,因此出现这种故障一点也不奇怪。虚拟机监控程序可能在VMEXIT中手动执行此操作,模拟
xsetbv
,并检查对虚拟化xcr0的更改

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


请注意,MPX引入了新的体系结构状态(bnd
寄存器),这些状态必须在上下文开关上保存/恢复。如果您的虚拟机监控程序不想这样做,那将是禁用MPX的一个原因。(我认为它可以作为
xsave
的一部分进行保存/恢复,但它确实会使保存稍微大一些。)我对MPX没有太多的了解;这可能是虚拟机监控程序必须在vmexits中处理的事情,以使边界检查不应用于虚拟机监控程序。。。如果是这样,那将是一个很大的不便。

彼得·科尔德斯是对的,这是我的虚拟机监控程序的问题。我正在使用VMWare Fusion进行虚拟化,在互联网上进行了大量挖掘之后,我找到了VMWare的以下引用:

内存保护扩展(MPX)已引入英特尔Skylake第二代CPU,并为绑定c提供了硬件支持
cpuid.enableMPX = "TRUE"
cpuid.<leaf>.<register> = "<value>"
cpuid.7.ebx = "----:----:---1:----:----:----:----:----"