GCC 內聯彙編

2021-05-24 10:22:53 字數 4014 閱讀 2862

有時為了高效,有時為了直接控制硬體,有些模組我們不得不直接用組合語言來編寫,並且對外提供呼叫的介面,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?我們就以 gcc 為例,一窺其中奧秘!

一、關鍵字 

如何讓 gcc 知道**中內嵌的彙編呢? 借助關鍵字!來看下面的例子:

__asm__ __volatile__("hlt"); 

__asm__ 表示後面的**為內嵌彙編,asm 是 __asm__ 的別名。__volatile__ 表示編譯器不要優化**,後面的指令保留原樣,volatile 是它的別名。括號裡面是彙編指令。 

二、示例分析 

使用內嵌彙編,要先編寫彙編指令模板,然後將 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)); 

例 2: 

static __inline__ void __set_bit(int nr, volatile void * addr) 

後例功能是將 (*addr) 的第 nr 位設為 1。第乙個佔位符 %0 與 c  語言變數 addr 對應,第二個佔位符 %1 與 c 語言變數 nr 對應。因此上面的彙編語句**與下面的偽**等價:btsl nr, addr,該指令的兩個運算元不能全是記憶體變數,因此將 nr 的限定字串指定為「ir」,將 nr 與立即數或者暫存器相關聯,這樣兩個運算元中只有 addr 為記憶體變數。 

4、限制字元 

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

分類

限定符

描述

通用暫存器

「a」將輸入變數放入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」 等。

GCC內聯彙編

有時為了高效,有時為了直接控制硬體,有些模組我們不得不直接用組合語言來編寫,並且對外提供呼叫的介面,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?我們就以 gcc 為例,一窺其中奧秘!一 關鍵字 如何讓 gcc 知道 中內嵌的彙編呢?借助 關鍵字!來看下面的例子 asm volatile hlt ...

gcc內聯彙編

有時為了高效,有時為了直接控制硬體,有些模組我們不得不直接用組合語言來編寫,並且對外提供呼叫的介面,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?我們就以 gcc 為例,一窺其中奧秘!一 關鍵字 如何讓 gcc 知道 中內嵌的彙編呢?借助關鍵字!來看下面的例子 a volatile hlt a 表...

GCC 內聯彙編

最近研究powerpc的u boot源 不得不看看gcc的內聯彙編。extern inline unsigned in be32 const volatile unsigned iomem addr 這函式半天沒看懂,看到一篇文章的介紹,很有幫助,轉過來。至於具體的分析有空單獨寫個總結。有時為了高效...