2017-09-02 192 views
-1

我想編譯一個簡單的C程序(Win7 32bit,Mingw32 Shell和GCC 5.3.0)。 C代碼是這樣的:奇怪的'asm'操作數有不可能的約束錯誤

#include <stdio.h> 
#include <stdlib.h> 

#define _set_tssldt_desc(n,addr,type) \ 
__asm__ ("movw $104,%1\n\t" \ 
    :\ 
    :"a" (addr),\ 
    "m" (*(n)),\ 
    "m" (*(n+2)),\ 
    "m" (*(n+4)),\ 
    "m" (*(n+5)),\ 
    "m" (*(n+6)),\ 
    "m" (*(n+7))\ 
    ) 

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") 


char *n; 
char *addr; 

int main(void) { 
    char *n = (char *)malloc(100*sizeof(int)); 
    char *addr = (char *)malloc(100*sizeof(int)); 
    set_tss_desc(n, addr); 
    free(n); 
    free(addr); 
    return 0; 
} 

_set_tssldt_desc(n,addr,type)是一個宏,其主體是彙編代碼。 set_tss_desc(n,addr)是另一個非常類似於_set_tssldt_desc(n,addr,type)的宏。主函數中調用了set_tss_desc(n,addr)宏。

當我試圖編譯這段代碼,編譯器的顯示我下面的錯誤:

$ gcc test.c 
    test.c: In function 'main': 
    test.c:5:1: error: 'asm' operand has impossible constraints 
    __asm__ ("movw $104,%1\n\t" \ 
    ^
    test.c:16:30: note: in expansion of macro '_set_tssldt_desc' 
    #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") 
           ^
    test.c:25:3: note: in expansion of macro 'set_tss_desc' 
     set_tss_desc(n, addr); 
    ^

奇怪的是,如果我的評論援引點出的主要功能,代碼編譯成功。或者,如果我刪除彙編代碼的輸出部分中的一些變量,它也編譯。

#include <stdio.h> 
#include <stdlib.h> 

#define _set_tssldt_desc(n,addr,type) \ 
__asm__ ("movw $104,%1\n\t" \ 
    :\ 
    :"a" (addr),\ 
    "m" (*(n)),\ 
    "m" (*(n+2)),\ 
    "m" (*(n+4)),\ 
    "m" (*(n+5)),\ 
    "m" (*(n+6))\ 
    ) 
//I DELETE "m" (*(n+7)) , code compiled 

#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") 


char *n; 
char *addr; 

int main(void) { 
    char *n = (char *)malloc(100*sizeof(int)); 
    char *addr = (char *)malloc(100*sizeof(int)); 
    set_tss_desc(n, addr); 
    free(n); 
    free(addr); 
    return 0; 
} 

有人可以向我解釋爲什麼是這樣以及如何解決這個問題?

+0

我猜不出寄存器。您正在使用* mingw *進行編譯,託管時使用某種調用約定。 –

+0

升級您的編譯器可能會有幫助。 GCC 5相當古老。 GCC 7.2是最新的。我推薦[Nuwen Mingw發行版](https://nuwen.net/mingw.html)作爲Windows的另一個小型開發發行版。 – tambre

+2

使用用戶定義的內聯彙編程序的任何代碼都不是「簡單的C程序」。 –

回答

2

As @MichealPetch says,你正在接近這個錯誤的方式。如果你試圖爲lgdt設置一個操作數,那麼在C語言中執行該操作數,並且僅對lgdt指令本身使用inline-asm。請參閱標記wiki以及標記wiki。

相關:一個C結構/聯合與英特爾描述符表:How to do computations with addresses at compile/linking time?。 (這個問題想要生成表格作爲靜態數據,因此要求在編譯時將地址分爲低/高兩半)。

另外:Implementing GDT with basic kernel對於一些C + asm GDT操作。或者可能不是,因爲那裏的答案只是說問題中的代碼是有問題的,沒有詳細的修復。

Linker error setting loading GDT register with LGDT instruction using Inline assembly有一個邁克爾佩奇的答案,有一些鏈接到更多的指南/教程。


它仍然回答的具體問題是有用的,即使正確的解決方法是https://gcc.gnu.org/wiki/DontUseInlineAsm

這個編譯罰款啓用優化。

對於-O0,gcc沒有注意到或利用這些操作數都是彼此之間的小恆定偏移量,並且可以使用具有偏移尋址模式的相同基址寄存器。它希望將一個指向每個輸入內存操作數的指針放入一個單獨的寄存器中,但會用盡寄存器。通過-O1或更高版本,CSE可以滿足您的期望。

您可以在最後3個內存操作數註釋的簡化示例中看到這一點,並將asm字符串更改爲包含所有操作數的asm註釋。從gcc5.3 -O0 -m32 on the Godbolt compiler explorer

#define _set_tssldt_desc(n,addr,type)  \ 
__asm__ ("movw $104,%1\n\t"    \ 
    "#operands: %0, %1, %2, %3\n"   \ 
    ... 

void simple_wrapper(char *n, char *addr) { 
    set_tss_desc(n, addr); 
} 


     pushl %ebp 
     movl %esp, %ebp 
     pushl %ebx 
     movl 8(%ebp), %eax 
     leal 2(%eax), %ecx 
     movl 8(%ebp), %eax 
     leal 4(%eax), %ebx 
     movl 12(%ebp), %eax 
     movl 8(%ebp), %edx 
#APP # your inline-asm code 
     movw $104,(%edx) 
     #operands: %eax, (%edx), (%ecx), (%ebx) 
#NO_APP 
     nop     # no idea why the compiler inserted a literal NOP here (not .p2align) 
     popl %ebx 
     popl %ebp 
     ret 

但已啓用優化,你

simple_wrapper: 
     movl 4(%esp), %edx 
     movl 8(%esp), %eax 
#APP 
     movw $104,(%edx) 
     #operands: %eax, (%edx), 2(%edx), 4(%edx) 
#NO_APP 
     ret 

通知後的操作數是如何使用基地+ DISP尋址模式。


您的約束是完全倒退的。你正在寫內存來告訴編譯器是一個輸入操作數。它會假設內存沒有被asm語句修改,因此如果您使用C加載它,它可能會將該加載移動到asm之前。和其他可能的破損。

如果您用過"=m"輸出操作數,這個代碼將是正確的(但相比讓編譯器仍然低效爲您代勞。)

你可以寫你的彙編做從單一的抵消本身內存輸入操作數,但是你需要做一些事情來告訴編譯器關於asm語句讀取的內存;例如"=m" (*(struct {char a; char x[];} *) n)告訴它你寫的整個對象從n開始。 (見this answer)。

AT & T語法的x86內存操作數總是抵扣,所以你可以使用2 + %[nbase],而不是一個單獨的操作數,如果你這樣做

asm("movw $104, %[nbase]\n\t" 
    "movw $123, 2 + %[nbase]\n\t" 
    : [nbase] "=m" (*(struct {char a; char x[];} *) n) 
    : [addr] "ri" (addr) 
); 

氣體會發出警告2 + (%ebx)或不管它最終被,但是這好。

爲每個你寫的地方使用一個單獨的存儲器輸出操作數可以避免任何關於告訴編譯器你編寫的內存的問題。但是你弄錯了:你告訴編譯器,當你使用movw $104來存儲從n開始的2個字節時,你的代碼不會使用n+1。所以這應該是一個uint16_t內存操作數。如果這聽起來很複雜,https://gcc.gnu.org/wiki/DontUseInlineAsm。就像邁克爾說的那樣,在C中使用struct來做這個部分,並且只對需要它的單個指令使用內聯asm。

使用更少的更寬的存儲指令顯然會更高效。 IDK下一步計劃做什麼,但是任何相鄰的常量應該合併到一個32位存儲中,如mov $(104 + 0x1234 << 16), %[n0]或其他。再次,https://gcc.gnu.org/wiki/DontUseInlineAsm

+0

我錯過了。如果你從Linus上拿走一個頁面,你可以在沒有警告的情況下做你想做的事情,並達到同樣的行爲。但是你可能已經知道Linus在內核中使用的訣竅。這個技巧將適用於clang和gcc。 –

+0

@MichaelPetch:不,我不知道,有什麼把戲? –