對《gcc中的內嵌組合語言》一文的補充說明

kendiv發表於2008-03-29
對《AT&T格式的彙編》一文的補充說明
 
 

初次 接觸到AT&T格式的彙編程式碼,看著那一堆莫名其妙的怪符號,真是有點痛不欲生的感覺,只好慢慢地去啃gcc文件,在似懂非懂的狀態下過了一段時 間。後來又在網上找到了靈溪寫的《gcc中的內嵌組合語言》一文,讀後自感大有裨益。幾個月下來,接觸的原始碼多了以後,慢慢有了一些經驗。為了使初次接 觸AT&T格式的彙編程式碼的同志不至於遭受我這樣的痛苦,就整理出該文來和大家共享.如有錯誤之處,歡迎大家指正,共同提高.

本文主要以舉例的方式對gcc中的內嵌組合語言進行進一步的解釋。

一、gcc對內嵌組合語言的處理方式

    gcc在編譯內嵌組合語言時,採取的步驟如下

  1. 變數輸入:   根據限定符的內容將輸入運算元放入合適的暫存器,如果限定符指定為立即數("i")或記憶體變數("m"),則該步被省略,如果限定符沒有具體指定輸入操作 數的型別(如常用的"g"),gcc會視需要決定是否將該運算元輸入到某個暫存器.這樣每個佔位符都與某個暫存器,記憶體變數,或立即數形成了一一對應的關 系.這就是對第二個冒號後內容的解釋.如::"a"(foo),"i"(100),"m"(bar)表示%0對應eax暫存器,%1對應100,%2對應 記憶體變數bar.
  2. 生成程式碼:  然後根據這種一一對應的關係(還應包括輸出操作符),用這些暫存器,記憶體變數,或立即數來取代彙編程式碼中的佔位符(則有點像巨集操作),注意,則一步驟並不檢查由這種取代操作所生成的彙編程式碼是否合法, 例如,如果有這樣一條指令asm("movl %0,%1"::"m"(foo),"m"(bar));如果你用gcc -c -S選項編譯該原始檔,那麼在生成的彙編檔案中,你將會看到生成了movl foo,bar這樣一條指令,這顯然是錯誤的.這個錯誤在稍後的編譯檢查中會被發現.
  3. 變數輸出:   按照輸出限定符的指定將暫存器的內容輸出到某個記憶體變數中,如果輸出運算元的限定符指定為記憶體變數("m"),則該步驟被省略.這就是對第一個冒號後內容的解釋,如:asm("mov %0,%1":"=m"(foo),"=a"(bar):);編譯後為
                 #APP
                     movl foo,eax
                 #NO_APP
                     movl eax,bar
    該語句雖然有點怪怪的,但它很好的體現了gcc的運作方式.          

再以arch/i386/kernel/apm.c中的一段程式碼為例,我們來比較一下它們編譯前後的情況

源程式

編譯後的彙編程式碼
__asm__ (
"pushl %%edi/n/t"
"pushl %%ebp/n/t"
"lcall %%cs:/n/t"
"setc %%al/n/t"
"addl %1,%2/n/t"
"popl %%ebp/n/t"
"popl %%edi/n/t"
:"=a"(ea),"=b"(eb),
  "=c"(ec),"=d"(ed),"=S"(es)
:"a"(eax_in),"b"(ebx_in),"c"(ecx_in)
:"memory","cc");

     movl eax_in,%eax
     movl ebx_in,%ebx
     movl ecx_in,%ecx
#APP
     pushl %edi
     pushl %ebp
     lcall %cs:
     setc %al
     addl eb,ec
     popl %ebp
     popl %edi
#NO_APP
     movl %eax,ea
     movl %ebx,eb
     movl %ecx,ec
     movl %edx,ed
     movl %esi,es

二.對第三個冒號後面內容的解釋

   第三個冒號後面內容主要針對gcc優化處理,它告訴gcc在本段彙編程式碼中對暫存器和記憶體的使用情況,以免gcc在優化處理時產生錯誤.

  1. 它可以是"eax","ebx","ecx"等暫存器名,表示本段彙編程式碼對該暫存器進行了顯式操作,如 asm ("mov %%eax,%0",:"=r"(foo)::"eax");這樣gcc在優化時會避免使用eax作臨時變數,或者避免cache到eax的記憶體變數通過 該段彙編碼.
    下面的程式碼均用gcc的-O2級優化,它顯示了嵌入彙編中第三個冒號後"eax"的作用
      源程式 編譯後的彙編程式碼
    正常情況下 int main()
    {int bar=1;
    bar=fun();
    bar++;
    return bar;
    }
    pushl %ebp
    movl %esp,%ebp
    call fun
    incl %eax #顯然,bar預設使用eax暫存器
    leave
    ret
    加了彙編後 int main()
    {int bar=1;
    bar=fun();
    asm volatile("" : : : "eax");
    bar++;
    return bar;
    }
    pushl %ebp
    movl %esp,%ebp #建立堆疊框架
    call fun

    #fun的返回值放入bar中,此時由於嵌入彙編
    #指明改變了eax的值,為了避免衝突,
    #bar改為使用edx暫存器

    movl %eax,%edx 

    #APP
    #NO_APP
    incl %edx
    movl %edx,%eax #放入main()的返回值
    leave
    ret
  2. "merory"是一個常用的限定,它表示彙編程式碼以不可預知的方式改變了記憶體,這樣gcc在優化時就不會讓cache到暫存器的記憶體變數使用該暫存器通過彙編程式碼,否則可能會發生同步出錯.有了上面的例子,這個問題就很好理解了

三.對"&"限定符的解釋

   這是一個較常見用於輸出的限定符.它告訴gcc輸出運算元使用的暫存器不可再讓輸入運算元使用.
   對於"g","r"等限定符,為了有效利用為數不多的幾個通用暫存器,gcc一般會讓輸入運算元和輸出運算元選用同一個暫存器.但如果程式碼沒編好,會引起 一些意想不到的錯誤:如 asm("call fun;mov ebx,%1":"=a"(foo):"r"(bar));gcc編譯的結果是foo和bar同時使用eax暫存器:
               movl bar,eax
        #APP
               call fun
               movl ebx,eax               
        #NO_APP
               movl eax,foo
本 來這段程式碼的意圖是將fun()函式的返回值放入foo變數,但半路殺出個程咬金,用ebx的值沖掉了返回值,所以這是一段錯誤的程式碼,解決的方法是加上 一個給輸出運算元加上一個"&"限定符:asm("call fun;mov ebx,%1":"=&a"(foo):"r"(bar));這樣gcc就會讓輸入運算元另尋高就,不再使用eax暫存器了

四.對%quot;&"限定符的解釋(老鐵補充)

%:說明指令中可與下一運算元交換的那個運算元,這意味著編譯可以交換這兩個運算元以使得能以代價更小的方法來滿足運算元約束,這常常用於真正只 有兩個運算元的加法指令的指令樣板中,這種加法指令的結果必須存放在兩個運算元之一中.

 原文地址 http://www.myfaq.com.cn/2005September/2005-09-13/203945.html
 

相關文章