C使用汇编:操作数类型不匹配进行推送

时间:2014-01-20 22:13:36

标签: c assembly linux-kernel

我试图在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位机器。

任何人都可以帮我指出哪个部分不正确以及如何修复它?

我猜它是因为指令和寄存器不匹配。但我对如何解决它感到困惑。 :(

提前致谢!

5 个答案:

答案 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,%eaxmovl %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