我试图在Linux内核空间中禁用/启用缓存。
我使用的代码是
__asm__ __volatile__(
"pushw %eax\n\t" /*line 646*/
"movl %cr0,%eax\n\t"
"orl $0x40000000,%eax\n\t"
"movl %eax,%cr0\n\t"
"wbinvd\n\t"
"pop %eax");
编译后,我得到如下错误信息:
memory.c: Assembler messages:
memory.c:645: Error: operand type mismatch for `push'
memory.c:646: Error: unsupported for `mov'
memory.c:648: Error: unsupported for `mov'
memory.c:650: Error: operand type mismatch for `pop'
make[4]: *** [memory.o] Error 1
我的机器是Intel(R)Xeon(R)CPU E5-1650 v2 @ 3.50GHz。 64位机器。
任何人都可以帮我指出哪个部分不正确以及如何修复它?
我猜它是因为指令和寄存器不匹配。但我对如何解决它感到困惑。 :(
提前致谢!
答案 0 :(得分:10)
尽管大多数32位寄存器仍然存在于64位架构中,但它们不再能够与堆栈交互。因此,尝试推送或弹出%eax
是非法操作。因此,如果您希望使用堆栈,则必须使用%rax
,这是相当于%eax
的64位架构。
答案 1 :(得分:4)
内联汇编语句存在一些问题,其中大部分都由错误消息指示。
第一条错误消息Error: operand type mismatch for `push'
对应于pushw %eax
指令。该错误是由于您使用的操作数大小后缀w
与操作数的实际大小%eax
不匹配的结果。您已经告诉它使用该指令在堆栈上推送一个16位值,但提供了一个32位寄存器作为操作数。您可以使用pushw %ax
来解决这个问题,但这不是您想要的。它只保留RAX寄存器的低16位,而不是整个寄存器。
另一个“显而易见”的修复方法是使用pushl %eax
,但这有两个问题。首先,为了解决修改整个RAX寄存器所需的其他问题,这意味着您需要保留它的所有64位,而不仅仅是低32位。第二个是64位模式下没有32位PUSH指令,因此无论如何都被迫使用pushq %rax
。
接下来的两条错误消息都是Error: unsupported for `mov'
。这些错误消息对应于movl %cr0,%eax
和movl %eax,%cr0
指令。两者都是同一问题的结果。在64位模式下,这些指令没有32位操作数大小的版本。您需要使用64位操作数,因此修复只是使用RAX而不是EAX。这就是整个64位RAX被破坏的地方,以及为什么我说你需要保留整个寄存器。
最后一条错误消息是Error: operand type mismatch for `pop'
。这是与第一个类似问题的结果。在这种情况下,您没有使用操作数大小后缀,这意味着汇编程序将尝试根据操作数确定操作数大小。由于您使用了32位操作数%eax
,因此它使用32位操作数大小。然而就像PUSH一样,在64位模式下有32位POP指令,所以你也不能使用%eax
。在任何情况下,由于PUSH指令需要为64位,因此POP指令需要64位才能匹配,因此修复是使用popq %rax
。
最后,错误消息未指出的一个问题是在64位模式下,CR0的大小扩展到64位。虽然额外的32位当前是保留的并且必须设置为零,但它们可以在将来的处理器中定义。因此orl $0x40000000,%eax
指令应该保留高64位。不幸的是,它不会,它将清除RAX的高32位,这意味着该指令也会无意中清除未来CPU可能赋予其意义的任何位。所以应该用orq $0x40000000,%rax
替换它。
所以固定的指令序列是:
pushq %rax
movq %cr0, %rax
orq $0x40000000, %rax
movq %rax, %cr0
wbinvd
popq %rax
然而,这不是我建议您在内联汇编中使用的内容。让GCC选择使用的寄存器可以简化它。这样就没有必要保留它了。以下是我建议的内容:
long long dummy;
asm volatile ("movq %%cr0, %0\n\t"
"orq $0x40000000, %0\n\t"
"movq %0, %%cr0\n\t"
"wbinvd"
: "=r" (dummy) : :);
答案 2 :(得分:3)
正确的方法是在%eax
上声明一个clobber,而不是自己保存/恢复。编译器可能比push / pop更有效,比如对任何想要保持活动的值使用不同的寄存器。这也意味着您不需要64位的不同代码来保存/恢复%rax
。
请注意pushq %rax
/ popq %rax
不在x86-64上的用户空间代码中是安全的。有no way to tell gcc that inline-asm clobbers the red-zone。它在内核代码中是安全的,其中ABI不使用红区,但同样,它仍然违背了GNU C内联asm语法的目的。
此处还有一个问题:mov %cr0, %eax
isn't a valid 64bit instruction。你必须使用64位寄存器。
让编译器为我们选择寄存器可以解决这个问题,并为编译器提供更多的自由,所以无论如何它都会更好。声明一个C变量,其类型在x86-64 ABI中为64位,在i386 ABI中为32位。 (例如long
,因为这是针对Linux内核ABI的,而不是long
总是32位的Windows。uintptr_t
是另一个可以在Linux内核中运行的选项。(但不是用户) -space:x32是具有32位指针的长模式。)
// is this enable or disable? I didn't check the manual
void set_caching_x86(void) {
long tmp; // mov to/from cr requires a 64bit reg in 64bit mode
asm volatile(
"mov %%cr0, %[tmp]\n\t" // Note the double-% when we want a literal % in the asm output
"or $0x40000000, %[tmp]\n\t"
"mov %[tmp], %%cr0\n\t"
"wbinvd\n\t"
: [tmp] "=r" (tmp) // outputs
: // no inputs
: // no clobbers. "memory" clobber isn't needed, this just affects performance, not contents
);
}
这个compiles and assembles to what we want,有或没有-m32
,你可以在Godbolt Compiler Explorer上看到。
当手动编写时,操作数大小更容易让操作数大小更明显,而不是总是在助记符上使用后缀。即push %eax
可以工作(在32位模式下),但仍然比让编译器处理它更糟糕。
即使在64位模式下,我们也可以使用%k[tmp]
来获取%eax
(或其他),但这会使高位32b归零。在or
指令的REX前缀上花费1个字节对于那些可能关心你写入控制寄存器的高32b的CPU的未来是值得的。
volatile
确保asm语句没有被优化掉,即使从未使用过输出值。
答案 3 :(得分:2)
根据intel - http://download.intel.com/products/processor/manual/325383.pdf一个字是16位,因此pushw期望一个16位操作数。寄存器eax是32位,必须使用pushl进行推送。 编辑: 你是组装32位还是64位?
答案 4 :(得分:1)
如果您从未弄清楚,如果编译 64位
,请使用pushq %rax