2014-01-20 31 views
7

我試圖禁用/啓用緩存在Linux內核空間。C使用匯編:操作數類型不匹配推

我使用的代碼是

__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 

我的機器是英特爾(R)至強(R)CPU E5-1650 v2 @ 3.50GHz。 64位機器。

任何人都可以幫我指出哪一部分是不正確的,我該如何解決它?

我猜這是因爲指令和寄存器的不匹配。但我很困惑如何解決它。 :(

在此先感謝

+1

是不是'pushw'的字大小(16位)? EAX是32位,請嘗試'pushl' – Leeor

+0

Hi @Leeor,非常感謝您的評論!但pushl會報錯:memory.c:645:錯誤:'push'的指令後綴無效;我也嘗試過pushq,它也不工作 – Mike

回答

1

據英特爾 - http://download.intel.com/products/processor/manual/325383.pdf一個字是16位,這樣PUSHW期待一個16位指令寄存器EAX是32位,並且必須使用pushl 編輯推!: 您是否正在裝配32位或64位?

+0

我的機器是64位,所以我認爲eax是64位寄存器? – Mike

+1

這可能很有用 - http://www.x86-64.org/documentation/assembly.html它講述了圍繞第5段的推送指令。在x86寄存器長度上的 –

+0

永遠不會改變。當大小改變時,他們使用一個新的名字/前綴,所以你可以處理所有類型的寄存器從字節到8個字節。在編寫程序之前,你應該閱讀關於這個架構 –

9

儘管大多數32位寄存器都保留在64位體系結構中,但它們不再能夠與堆棧交互,因此試圖推入或彈出%eax是非法操作。你想玩棧,你必須使用%rax,這是64位體系結構相當於%eax

+0

更好的建議:使用clobber而不是保存/恢復自己。 –

1

如果你從來沒有想通了,使用pushq %rax如果編譯64位

2

正確的做法是聲明對%eax一撞,而不是保存/自己恢復它。編譯器可能會比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 inline asm語法的目的。


有一個額外的皺紋這裏:mov %cr0, %eaxisn't a valid 64bit instruction。你必須使用一個64位寄存器。

讓編譯器爲我們選擇一個寄存器解決了這個問題,並且給了編譯器更多的自由度,所以無論如何它更好。在x86-64 ABI中聲明一個64位類型的C變量,並在i386 ABI中聲明32位。 (例如long,因爲這是用於Linux內核ABI,而不是Windows,其中long總是32位。uintptr_t是另一種可在Linux內核中工作的選項(但不在用戶空間中: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編譯器瀏覽器看到的。

當用手寫入時,更容易讓操作數大小隱含在操作數中,而不是始終在助記符上使用後綴。即push %eax會工作(在32位模式下),但仍然比讓編譯器處理它更糟糕。

即使在64位模式下,我們也可以使用%k[tmp]來得到%eax(或其他),但是這會使上面的32b值爲零。對於or指令,在REX前綴上佔用1個字節對於可能關心您寫入控制寄存器上部32b的內容的CPU來說更值得將來驗證。

volatile確保asm語句沒有被優化掉,即使從不使用輸出值。

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) : :); 
+0

即使在32位模式下,long long也是64bit。我認爲這是行不通的,但顯然是這樣。用'-m32'編譯器可能爲它保留了兩個寄存器,但是'%0'只能擴展到其中的一個?也許它正在挑選'A'的禁忌:'edx:eax'?在我的回答中,我建議使用'long'。 (我已經使用了'tmp',直到我看到你的答案在幾分鐘內發佈,指出x86-64無法傳輸到32位寄存器。) –

+0

@PeterCordes我不會試圖編寫可以工作的東西在64位模式下。而且,嗯,我已經解決了ORL問題,在您更新文章之前我恰好注意到了它。 –

相關問題