使用XSETBV写入XCR0会在支持MPX的硬件上的VM中创建一般保护故障
我正在尝试写入x86_64 Debian v7虚拟机上的扩展控制寄存器0(使用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索引),
xcr0
)。我这样做的方法是通过一个内核模块(soCPL=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:----:----:----:----:----"