C語言的內嵌彙編

在內嵌彙編中,可以將C語言表達式指定爲彙編指令的操作數,而且不用去管如何將C語言表達式的值讀入哪個寄存器,以及如何將計算結果寫回C 變量,你只要告訴程序中C語言表達式與彙編指令操作數之間的對應關係即可, GCC會自動插入代碼完成必要的操作。

1、簡單的內嵌彙編
例:
     __asm__ __volatile__("hlt"); “__asm__”表示後面的代碼爲內嵌彙編,“asm”是“__asm__”的別名。“__volatile__”表示編譯器不要優化代碼,後面的指令 保留原樣,“volatile”是它的別名。括號裏面是彙編指令。

 在內嵌彙編中,可以將C語言表達式指定爲彙編指令的操作數,而且不用去管如何將C語言表達式的值讀入哪個寄存器,以及如何將計算結果寫回C 變量,你只要告訴程序中C語言表達式與彙編指令操作數之間的對應關係即可, GCC會自動插入代碼完成必要的操作。

1、簡單的內嵌彙編
例:
     __asm__ __volatile__("hlt"); “__asm__”表示後面的代碼爲內嵌彙編,“asm”是“__asm__”的別名。“__volatile__”表示編譯器不要優化代碼,後面的指令 保留原樣,“volatile”是它的別名。括號裏面是彙編指令。

2、內嵌彙編舉例
  使用內嵌彙編,要先編寫彙編指令模板,然後將C語言表達式與指令的操作數相關聯,並告訴GCC對這些操作有哪些限制條件。例如在下面的彙編語句:
   __asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));  

“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作數,稱爲佔位符,內嵌彙編靠它們將C 語言表達式與指令操作數相對應。指令模板後面用小括號括起來的是C語言表達式,本例中只有兩個:“result”和“input”,他們按照出現的順序分 別與指令操作數“%0”,“%1”對應;注意對應順序:第一個C 表達式對應“%0”;第二個表達式對應“%1”,依次類推,操作數至多有10 個,分別用“%0”,“%1”….“%9”表示。在每個操作數前面有一個用引號括起來的字符串,字符串的內容是對該操作數的限制或者說要求。 “result”前面的限制字符串是“=r”,其中“=”表示“result”是輸出操作數,“r”表示需要將“result”與某個通用寄存器相關聯, 先將操作數的值讀入寄存器,然後在指令中使用相應寄存器,而不是“result”本身,當然指令執行完後需要將寄存器中的值存入變量“result”,從 表面上看好像是指令直接對“result”進行操作,實際上GCC做了隱式處理,這樣我們可以少寫一些指令。“input”前面的“r”表示該表達式需要 先放入某個寄存器,然後在指令中使用該寄存器參加運算。
  C表達式或者變量與寄存器的關係由GCC自動處理,我們只需使用限制字符串指導 GCC如何處理即可。限制字符必須與指令對操作數的要求相匹配,否則產生的彙編代碼將會有錯,讀者可以將上例中的兩個“r”,都改爲“m”(m表示操作數 放在內存,而不是寄存器中),編譯後得到的結果是:
            movl input, result
很明顯這是一條非法指令,因此限制字符串必須與指令對操作數的要求匹配。例如指令movl允許寄存器到寄存器,立即數到寄存器等,但是不允許內存到內存的操作,因此兩個操作數不能同時使用“m”作爲限定字符。
內嵌彙編語法如下:
      __asm__(彙編語句模板: 輸出部分: 輸入部分: 破壞描述部分)
共四個部分:彙編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用“:”格開,彙編語句模板必不可少,其他三部分可選,如果使用了後面的部分,而前面部分爲空,也需要用“:”格開,相應部分內容爲空。例如:
            __asm__ __volatile__("cli": : :"memory")

1、彙編語句模板
   匯 編語句模板由彙編語句序列組成,語句之間使用“;”、“/n”或“/n/t”分開。指令中的操作數可以使用佔位符引用C語言變量,操作數佔位符最多10 個,名稱如下:%0,%1,…,%9。指令中使用佔位符表示的操作數,總被視爲long型(4個字節),但對其施加的操作根據指令可以是字或者字節,當把 操作數當作字或者字節使用時,默認爲低字或者低字節。對字節操作可以顯式的指明是低字節還是次字節。方法是在%和序號之間插入一個字母,“b”代表低字 節,“h”代表高字節,例如:%h1。

2、輸出部分
   輸出部分描述輸出操作數,不同的操作數描述符之間用逗號格開,每個操作數描述符由限定字符串和C 語言變量組成。每個輸出操作數的限定字符串必須包含“=”表示他是一個輸出操作數。
例:
         __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
描述符字符串表示對該變量的限制條件,這樣GCC 就可以根據這些條件決定如何分配寄存器,如何產生必要的代碼處理指令操作數與C表達式或C變量之間的聯繫。

3、輸入部分
輸入部分描述輸入操作數,不同的操作數描述符之間使用逗號格開,每個操作數描述符由限定字符串和C語言表達式或者C語言變量組成。
例1 :
           __asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
例二(bitops.h):
Static __inline__ void __set_bit(int nr, volatile void * addr)
{
       __asm__(
                        "btsl %1,%0"
                       :"=m" (ADDR)
                       :"Ir" (nr));
}

後 例功能是將(*addr)的第nr位設爲1。第一個佔位符%0與C 語言變量ADDR對應,第二個佔位符%1與C語言變量nr對應。因此上面的彙編語句代碼與下面的僞代碼等價:btsl nr, ADDR,該指令的兩個操作數不能全是內存變量,因此將nr的限定字符串指定爲“Ir”,將nr 與立即數或者寄存器相關聯,這樣兩個操作數中只有ADDR爲內存變量。

4、限制字符
  4.1、限制字符列表
  限制字符有很多種,有些是與特定體系結構相關,此處僅列出常用的限定字符和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其後的C語言變量與指令操作數之間的關係。

  分類            限定符                    描述
 通用寄存器       “a”               將輸入變量放入eax
                                             這裏有一個問題:假設eax已經被使用,那怎麼辦?
                                其實很簡單:因爲GCC 知道eax 已經被使用,它在這段彙編代碼
                                的起始處插入一條語句pushl %eax,將eax 內容保存到堆棧,然
                                後在這段代碼結束處再增加一條語句popl %eax,恢復eax的內容
                    “b”               將輸入變量放入ebx
                    “c”               將輸入變量放入ecx
                    “d”               將輸入變量放入edx
                     “s”              將輸入變量放入esi
                     “d”              將輸入變量放入edi
                     “q”              將輸入變量放入eax,ebx,ecx,edx中的一個
                     “r”               將輸入變量放入通用寄存器,也就是eax,ebx,ecx,
                                       edx,esi,edi中的一個
                    “A”              把eax和edx合成一個64 位的寄存器(use long longs)

      內存            “m”             內存變量
                    “o”             操作數爲內存變量,但是其尋址方式是偏移量類型,
                                      也即是基址尋址,或者是基址加變址尋址
                    “V”             操作數爲內存變量,但尋址方式不是偏移量類型
                    “ ”             操作數爲內存變量,但尋址方式爲自動增量
                    “p”             操作數是一個合法的內存地址(指針)

    寄存器或內存    “g”             將輸入變量放入eax,ebx,ecx,edx中的一個
                                      或者作爲內存變量
                     “X”            操作數可以是任何類型

    立即數
                    “I”             0-31之間的立即數(用於32位移位指令)
                     “J”            0-63之間的立即數(用於64位移位指令)
                    “N”             0-255之間的立即數(用於out指令)
                    “i”             立即數   
                    “n”            立即數,有些系統不支持除字以外的立即數,
                                      這些系統應該使用“n”而不是“i”

    匹配            “ 0 ”,         表示用它限制的操作數與某個指定的操作數匹配,
                    “1” ...               也即該操作數就是指定的那個操作數,例如“0”
                     “9”            去描述“%1”操作數,那麼“%1”引用的其實就
                                      是“%0”操作數,注意作爲限定符字母的0-9 與
                                      指令中的“%0”-“%9”的區別,前者描述操作數,
                                      後者代表操作數。
                      &                     該輸出操作數不能使用過和輸入操作數相同的寄存器

   操作數類型        “=”          操作數在指令中是隻寫的(輸出操作數)   
                      “+”          操作數在指令中是讀寫類型的(輸入輸出操作數)

    浮點數            “f”          浮點寄存器
                     “t”           第一個浮點寄存器
                     “u”          第二個浮點寄存器
                     “G”          標準的80387浮點常數
                      %                   該操作數可以和下一個操作數交換位置
                                      例如addl的兩個操作數可以交換順序
                                     (當然兩個操作數都不能是立即數)
                      #                   部分註釋,從該字符到其後的逗號之間所有字母被忽略
                      *                     表示如果選用寄存器,則其後的字母被忽略

5、破壞描述部分
  破壞描述符用於通知編譯器我們使用了哪些寄存器或內存,由逗號格開的字符串組成,每個字符串描述一種情況,一般是寄存器名;除寄存器外還有“memory”。例如:“%eax”,“%ebx”,“memory”等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章