我正在尝试写入x86_64 Debian v7 虚拟机上的扩展控制寄存器 0 (xcr0
)。我这样做的方法是通过带有一些内联程序集的内核模块(所以CPL=0
)。但是,当我尝试执行xsetbv
指令时,我不断遇到一般保护错误(#GP
)。
我的模块的init
功能首先检查控制寄存器4(cr4
)中是否设置了osxsave
位。如果不是,它会设置它。然后,我使用xgetbv
读取xcr0
寄存器。这工作正常,并且(在我所做的有限测试中)具有0b111
的值。我想设置第 3 位和第 4 位(0 索引)的bndreg
位和bndcsr
位,所以我做了一些OR
并使用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 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
位并bndcsr
位xcr0
。
在更正常的情况下(即该功能不受弃用的困扰)使用 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 中必须处理的问题,以便不将边界检查应用于虚拟机管理程序... 如果是这样,那将是一个很大的不便。