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

C uses assemble: operand type mismatch for push

我正在尝试禁用/启用Linux内核空间中的缓存。

我使用的代码是

1
2
3
4
5
6
7
8
9
10
11
12
 __asm__ __volatile__(
     "pushw %eax
\t"
/*line 646*/
     "movl %cr0,%eax
\t"

     "orl $0x40000000,%eax
\t"

     "movl %eax,%cr0
\t"

     "wbinvd
\t"

     "pop  %eax");

编译后,出现如下错误信息:

1
2
3
4
5
6
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

我的机器是3.50GHz下的Intel?Xeon?CPU E5-1650 v2。 64位机器。

谁能帮助我指出哪个部分不正确以及如何解决?

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

提前致谢!


尽管大多数32位寄存器都保留在64位体系结构中,但它们不再能够与堆栈进行交互。因此,尝试压入或弹出%eax是非法操作。因此,如果您希望使用堆栈,则必须使用%rax,它是与%eax等效的64位体系结构。


内联汇编语句存在多个问题,其中大多数由错误消息指示。

第一条错误消息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。这就是破坏RAX的整个64位的地方,也是为什么我说您需要保留整个寄存器的原因。

最后一条错误消息是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

因此,固定的指令顺序为:

1
2
3
4
5
6
    pushq   %rax
    movq    %cr0, %rax
    orq     $0x40000000, %rax
    movq    %rax, %cr0
    wbinvd
    popq    %rax

但是,这不是我建议在您的内联程序集中使用的内容。让GCC选择使用的寄存器可以简化它。这样就无需保留它。这是我的建议:

1
2
3
4
5
6
7
8
9
long long dummy;
asm volatile ("movq %%cr0, %0
\t"

             "orq $0x40000000, %0
\t"

             "movq %0, %%cr0
\t"

             "wbinvd"
              :"=r" (dummy) : :);


正确的方法是在%eax上声明一个破坏者,而不是自己保存/恢复它。编译器可能会执行比push / pop更高效的操作,例如对要保留的任何值使用不同的寄存器。这也意味着您不需要其他用于64位的代码来保存/恢复%rax

请注意,在x86-64上的用户空间代码中,pushq %rax / popq %rax并不安全。无法告诉gcc内联汇编破坏了红色区域。这在内核代码中是安全的,在该代码中,ABI不使用红色区域,但是再次,它仍然违反了GNU C内联asm语法的目的。

这里还有一个额外的缺点:mov %cr0, %eax不是有效的64位指令。您必须使用64位寄存器。

让编译器为我们选择一个寄存器可以解决此问题,并为编译器提供更大的自由度,因此还是更好。声明一个C变量,其类型在x86-64 ABI中为64位,在i386 ABI中为32位。 (例如,long,因为这是针对Linux内核ABI的,而不是Windows,其中long始终为32位。uintptr_t是在Linux内核中可以使用的另一种选择。(但在用户空间中不存在:x32长模式(带有32位指针)。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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]
\t"
    // Note the double-% when we want a literal % in the asm output
     "or    $0x40000000, %[tmp]
\t"

     "mov   %[tmp], %%cr0
\t"

     "wbinvd
\t"

      : [tmp]"=r" (tmp) // outputs
      : // no inputs
      : // no clobbers. "memory" clobber isn't needed, this just affects performance, not contents
      );
}

不管有没有-m32,它都会编译并组装成我们想要的内容,就像您在Godbolt编译器资源管理器上看到的那样。

手工编写时,让操作数隐含操作数大小比较容易,而不是总是在助记符上使用后缀。即push %eax本来可以工作(在32位模式下),但比让编译器来处理它还要糟糕。

即使在64位模式下,我们本可以使用%k[tmp]来获取%eax(或其他值),但这会使上端32b零。在or指令的REX前缀上花1个字节值得,对于那些可能关心您写到控制寄存器的高32b内容的CPU来说,它具有更好的前瞻性。

volatile确保即使没有使用输出值,也不会对asm语句进行优化。


根据intel的说法-http://download.intel.com/products/processor/manual/325383.pdf一个字是16位,因此pushw期望使用16位操作数。寄存器eax为32位,必须使用pushl进行推送。
编辑:
您要装配32位还是64位?


如果您从未弄清楚,请在编译64位时使用pushq %rax