C uses assembly: operand type mismatch for push

I am trying to disable / enable cache in Linux kernel space.

The code I use

__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"); 

After compilation, I received an error message as follows:

 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 

My machine is Intel (R) Xeon (R) CPU E5-1650 v2 @ 3.50 GHz. 64 bit machine.

Can someone help me point out which part is wrong and how can I fix it?

I assume that this is due to a mismatch of instruction and case. But I am confused about how to fix it. :(

Thanks in advance!

+7
c assembly linux-kernel
source share
5 answers

Although most 32-bit registers are stored in 64-bit architectures, they are no longer able to interact with glass. Therefore, trying to click or pop %eax is an illegal operation. Therefore, if you want to play with the stack, you must use %rax , which is equivalent to the 64-bit %eax architectures.

+9
source share

There are several problems in your built-in build instruction, most of which are indicated by error messages.

The first error message is Error: operand type mismatch for `push' corresponds to the pushw %eax instruction. The error occurs because the operand size suffix you used, w , does not match the actual operand size, %eax . You told him to use the instruction to push a 16-bit value on the stack, but on condition that the 32-bit register is an operand. You can fix this using pushw %ax , but that is not what you want. It would only store the lower 16 bits of the RAX register, not the entire register.

Another โ€œobviousโ€ fix would be to use pushl %eax , but there are two problems with this. First, to fix other problems, you need to change the entire RAX register, which means that you need to save all 64 bits, not just the lower 32 bits. Secondly, there is no 32-bit PUSH instruction in 64-bit mode, so you are forced to use pushq %rax independently.

The following two error messages: Error: unsupported for `mov' . These error messages follow the instructions movl %cr0,%eax and movl %eax,%cr0 . and both are the result of the same problem. In 64-bit mode, there is no 32-bit version of these operands. You need to use a 64-bit operand, so the fix is โ€‹โ€‹simply to use RAX instead of EAX. This is where all 64-bit RAXs go astray, and why I said you need to keep the whole register.

Last error message Error: operand type mismatch for `pop' . This is the result of a similar problem, such as the first. In this case, you did not use the operand size suffix, which means that the assembler will try to determine the operand size based on the operands. Since you used the 32-bit operand, %eax , it uses the 32-bit operand size. However, as with PUSH, there is a 32-bit POP command in 64-bit mode, so you cannot use %eax . In any case, since the PUSH command must be 64-bit, the POP command must be 64-bit to match, so the fix should use popq %rax .

Finally, one problem that is not indicated by the error message is that in 64-bit mode, the CR0 size expands to 64 bits. While the extra 32 bits are currently reserved and should be set to zero, they can be specified in future processors. Therefore, the command orl $0x40000000,%eax should preserve the upper 64-bit. Unfortunately, this does not happen, it will clear the upper 32-bit RAX bits, which means that this instruction will also inadvertently clear any of these bits that future processors may give. Therefore, it should be replaced by orq $0x40000000,%rax .

Thus, a fixed sequence of instructions will:

  pushq %rax movq %cr0, %rax orq $0x40000000, %rax movq %rax, %cr0 wbinvd popq %rax 

This is not what I'm going to suggest using in your inline assembly. This can be simplified by allowing GCC to select the register used. Thus, there is no need to save it. Here is what I would suggest instead:

 long long dummy; asm volatile ("movq %%cr0, %0\n\t" "orq $0x40000000, %0\n\t" "movq %0, %%cr0\n\t" "wbinvd" : "=r" (dummy) : :); 
+4
source share

The correct approach is to declare clobber on %eax instead of saving / restoring it yourself . The compiler can probably do something more efficient than push / pop, for example, use different registers for any values โ€‹โ€‹that it wants to stay alive. It also means that you do not need other code for 64-bit to save / restore %rax .

Please note that pushq %rax / popq %rax will not be safe in user space code on x86-64. There do not indicate gcc that inline-asm hides the red zone . This would be safe in kernel code where the ABI does not use the red zone, but then again, it still defeats the purpose of the inline asm GNU C syntax.


There is an additional wrinkle here: mov %cr0, %eax not a valid 64-bit instruction . You must use 64 bit register.

Giving the compiler the choice of case for us solves this problem and gives the compiler more freedom, so itโ€™s better anyway. Declare a C variable with type 64 bits in the x86-64 ABI and 32 bits in the i386 ABI. (for example, long , since this is for the Linux ABI kernel, and not for Windows, where long always 32 bits. uintptr_t is another option that will work in the Linux kernel. (But not in user space: x32 long mode with 32 -bit pointers).

 // 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 ); } 

This compiles and compiles what we want , with or without -m32 , as you can see in the Godbolt compiler explorer.

When recording manually, itโ€™s easier to specify the operand by the size of the operand, and not always use the suffix on the mnemonics. those. push %eax would work (in 32-bit mode), but it was worse than letting the compiler take care of it.

We could use %k[tmp] to get %eax (or something else) even in 64 bit mode, but this will result in the top 32b being zero. Spending 1 byte on the REX prefix for the or command is worth it to be more promising for processors that might be interested in what you write in upper case 32b of the control register.

volatile ensures that the asm statement is not optimized, even if the output value is never used.

+2
source share

According to intel - http://download.intel.com/products/processor/manual/325383.pdf The word is 16 bits, so pushw expects a 16-bit operand. The eax register is 32 bits and must be pressed using pushl. Edit: Are you going for 32-bit or 64-bit?

+1
source share

If you never understood this, use pushq %rax if you compile 64 bits

+1
source share

All Articles