2014-09-05 30 views
3

我正在試驗GCC的內聯彙編程序(我使用MinGW,我的操作系統是Win7)。 現在我只得到一些基本的C stdlib函數來工作。我一般不熟悉的Intel語法,但新AT & T.海灣合作委員會內聯 - 推地址,而不是其堆棧值

下面的代碼工作不錯:

char localmsg[] = "my local message"; 
asm("leal %0, %%eax" : "=m" (localmsg)); 
asm("push %eax"); 
asm("call %0" : : "m" (puts)); 
asm("add $4,%esp"); 

這LEA似乎是多餘的,但是,我可以只推值直接推到堆棧。好了,由於我相信這是一個AT &牛逼特點,這樣做:

asm("push %0" : "=m" (localmsg)); 

將產生最終的可執行下面的彙編代碼:

PUSH DWORD PTR SS:[ESP+1F] 

因此,而不是推的地址給我字符串,其內容被推,因爲「指針」是「取消引用」,用C語言。這顯然導致了崩潰。

我相信這只是GAS的正常行爲,但我無法找到任何有關如何解決這個問題的信息。我會很感激任何幫助。

P.S.我知道對於那些在這個問題上有經驗的人來說這是一個微不足道的問題。我希望被降低,但我花了45分鐘尋找解決方案,但什麼都沒發現。

P.P.S.我意識到正確的做法是在C代碼中調用puts()。這是純粹的教育/實驗的原因。

+0

'asm(「push%0」:「= m」(&localmsg));'work? – markgz 2014-09-05 19:24:01

+0

@markgz不,它抱怨說'asm聲明中需要左值'。無論如何,我認爲這不會有多大意義,因爲localmsg已經是一個指針了。 – szczurcio 2014-09-05 19:27:06

+0

爲什麼你將localmsg標記爲輸出操作數? – 2014-09-05 20:06:39

回答

3

雖然內聯asm總是有點棘手,但從它調用函數特別具有挑戰性。不是我會建議「開始知道inline asm」項目。如果你還沒有,我建議通過最新的內聯asm docs。已經做了很多工作來試圖解釋內聯asm是如何工作的。

也就是說,這裏有一些想法:

1)使用多個asm語句像這是一個壞主意。正如docs所說:不要指望在彙編之後,一系列asm語句保持完全連續。如果某些指令需要在輸出中保持連續,請將它們放在一條多指令asm語句中。

2)直接修改寄存器(就像你在使用eax一樣)而不讓gcc知道你這樣做也是一個壞主意。你應該使用寄存器約束(所以gcc可以選擇它自己的寄存器)或者clobbers讓gcc知道你在跺腳。 3)當一個函數(如puts)被調用時,雖然一些寄存器在返回之前必須恢復它們的值,但有些寄存器可以被調用函數視爲暫存寄存器(即在返回之前修改而不恢復)。正如我在#2中提到的那樣,讓你的asm在不通知gcc的情況下修改寄存器是一個非常糟糕的主意。如果您知道正在調用的函數的ABI,則可以將其臨時寄存器添加到asm的clobber列表中。

4)雖然在這個特定的例子中,你使用的是一個常量字符串,通常在將asm指針傳遞給字符串,結構體,數組等時,你可能需要使用「內存」clobber來確保任何在開始執行你的asm之前,等待寫入內存。

5)實際上,lea正在做一件非常重要的事情。 esp的值在編譯時是未知的,所以它不像你可以執行push $12345。有人需要計算(尤其是localmsg的偏移量),然後才能將其壓入堆棧。另請參見下面的第二個示例。 6)如果你更喜歡英特爾格式(以及什麼樣的思維不正確的人),你可以使用-masm = intel。

鑑於這一切,我在這段代碼中的第一次剪切看起來像這樣。請注意,這不會破壞寄存器。這是作爲一個練習...

#include <stdio.h> 

int main() 
{ 
    const char localmsg[] = "my local message"; 

    int result; 

    /* Use 'volatile' since 'result' is usually not going to get used, 
    which might tempt gcc to discard this asm statement as unneeded. */ 

    asm volatile ("push %[msg] \n\t" /* Push the address of the string. */ 
       "call %[puts] \n \t" /* Call the print function. */ 
       "add $4,%%esp"  /* Clean up the stack. */ 

       : "=a" (result) /* The result code from puts. */ 
       : [puts] "m" (puts), [msg] "r" (localmsg) 
       : "memory", "esp"); 

    printf("%d\n", result); 
} 

真這不避lea由於#5。但是,如果這是真的重要的,試試這個:

#include <stdio.h> 
const char localmsg[] = "my local message"; 

int main() 
{ 

    int result; 

    /* Use 'volatile' since 'result' is usually not going to get used. */ 

    asm volatile ("push %[msg] \n\t" /* Push the address of the string. */ 
       "call %[puts] \n \t" /* Call the print function. */ 
       "add $4,%%esp"  /* Clean up the stack. */ 

       : "=a" (result) /* The result code. */ 
       : [puts] "m" (puts), [msg] "i" (localmsg) 
       : "memory", "esp"); 

    printf("%d\n", result); 
} 

作爲一個全球性的,localmsg的地址現在是在編譯時(好吧,我簡化了一下)可知,在asm生產這個樣子的:

push $__ZL8localmsg 
call _puts 
add $4,%esp 

田田。

+0

優秀的答案。我非常喜歡英特爾的語法,但我不確定如何使它與擴展的語法和變量一起工作。 – szczurcio 2014-09-06 08:07:40

+0

我給你的內聯asm文檔的doc鏈接講述瞭如何使用變量(w /很多示例)。如果您是來自MS的編譯器,那麼您需要忘記在asm中使用符號名稱的想法。 (實際上)所有變量都需要被指定爲輸入或輸出。至於「讓英特爾工作」,沒有什麼。考慮這個(無用的)asm語句:'asm(「movl $ 0,%eax」);'。用'gcc foo.cpp'編譯,它會編譯時沒有錯誤。用'gcc -masm = intel foo.cpp'進行編譯,除非將其更改爲intel語法,否則將會出錯:'asm(「mov eax,0」);' – 2014-09-06 08:57:11

+0

是的,但我的意思是「參數」「傳遞」給內聯的ASM代碼,就像你幫助我的例子。 ATT中的[名稱]或%0,我應該怎麼做才能在英特爾工作? – szczurcio 2014-09-06 18:08:09

相關問題