GCC中的內嵌組合語言

2021-09-06 01:56:30 字數 3699 閱讀 5384

一.宣告

雖然linux的核心**大部分是用c語言編寫的,但是不可避免的其中還是有一部分是用組合語言寫成的。有些組合語言**是直接寫在彙編源程式中的,特別是linux的啟動**部分;還有一些則是利用gcc的內嵌組合語言嵌在c語言程式中的。這篇文章簡單介紹了gcc中的內嵌式組合語言,主要想幫助那些才開始閱讀linux核心**的朋友們能夠更快的入手。

寫這篇文章的主要資訊**是gnu的兩個info檔案:as.info和gcc.info,如果你覺得這篇文章中的介紹還不夠詳細的話,你可以查閱這兩個檔案。當然,直接查閱這兩個檔案可以獲得更加權威的資訊。如果你不想被這兩篇文件中的一大堆資訊搞迷糊的話,我建議你先閱讀一下這篇文章,然後在必要時再去查閱更權威的資訊。

二.簡介

在linux的核心**中,還是存在相當一部分的組合語言**。如果你想順利閱讀linux**的話,你不可能繞過這一部分**。在linux使用的組合語言**中,主要有兩種格式:一種是直接寫成組合語言源程式的形式,這一部分主要是一些linux的啟動**;另一部分則是利用gcc的內嵌式組合語言語句asm嵌在linux的c語言**中的。這篇文章主要是介紹第二種形式的組合語言**。

首先,我介紹一下as支援的組合語言的語法格式。大家知道,我們現在學習的組合語言的格式主要是intel風格的,而在linux的核心**中使用的則是at&t格式的組合語言**,應該說大部分人對這種格式的組合語言還不是很了解,所以我覺得有必要介紹一下。

接著,我主要介紹一下gcc的內嵌式組合語言的格式。gcc的內嵌式組合語言提供了一種在c語言源程式中直接嵌入彙編指令的很好的辦法,既能夠直接控制所形成的指令序列,又有著與c語言的良好介面,所以在linux**中很多地方都使用了這一語句。

三.gcc的內嵌組合語言語句asm 

利用gcc的asm語句,你可以在c語言**中直接嵌入組合語言指令,同時還可以使用c語言的表示式指定彙編指令所用到的運算元。這一特性提供了很大的方便。

要使用這一特性,首先要寫乙個彙編指令的模板(這種模板有點類似於機器描述檔案中的指令模板),然後要為每乙個運算元指定乙個限定字串。例如:

extern __inline__ void change_bit(int nr,volatile void *addr) 

上面的函式中:

lock_prefix:這是乙個巨集,如果定義了__smp__,擴充套件為"lock;",用於指定匯流排鎖定字首,否則擴充套件為"

"。 addr:這也是乙個巨集,定義為(*(volatile struct __dummy *) addr)

"btcl %1,%0":這就是嵌入的組合語言指令,btcl為指令操作碼,%1,%0是這條指令兩個運算元的佔位符。後面的兩個限定字串就用於描述這兩個運算元。

: "=m" (addr):第乙個冒號後的限定字串用於描述指令中的「輸出」運算元。刮號中的addr將運算元與c語言的變數聯絡起來。這個限定字串表示指令中的「%0」就是addr指標指向的記憶體運算元。這是乙個「輸出」型別的記憶體運算元。

: "ir" (nr):第二個冒號後的限定字串用於描述指令中的「輸入」運算元。這條限定字串表示指令中的「%1」就是變數nr,這個的運算元可以是乙個立即運算元或者是乙個暫存器運算元。

*注:限定字串與運算元佔位符之間的對應關係是這樣的:在所有限定字串中(包括第乙個冒號後的以及第二個冒號後的所有限定字串),最先出現的字串用於描述運算元「%0」,第二個出現的字串描述運算元「%1」,以此類推。

①彙編指令模板

asm語句中的彙編指令模板主要由彙編指令序列和限定字串組成。在乙個asm語句中可以包括多條彙編指令。彙編指令序列中使用運算元佔位符引用c語言中的變數。一條asm語句中最多可以包含十個運算元佔位符:%0,%1,...,%9。彙編指令序列後面是運算元限定字串,對指令序列中的佔位符進行限定。限定的內容包括:該佔位符與哪個c語言變數對應,可以是什麼型別的運算元等等。限定字串可以分為三個部分:輸出運算元限定字串(指令序列後第乙個冒號後的限定字串),輸入運算元限定字串(第乙個冒號與第二個冒號之間),還有第三種型別的限定字串在第二個冒號之後。同一種型別的限定字串之間用逗號間隔。asm語句中出現的第乙個限定字串用於描述佔位符「%0」,第二個用於描述佔位符「%1」,以此類推(不管該限定字串的型別)。如果指令序列中沒有任何輸出運算元,那麼在語句中出現的第乙個限定字串(該字串用於描述輸入運算元)之前應該有兩個冒號(這樣,編譯器就知道指令中沒有輸出運算元)。

指令中的輸出運算元對應的c語言變數應該具有左值型別,當然對於輸出運算元沒有這種左值限制。輸出運算元必須是只寫的,也就是說,asm對取出某個運算元,執行一定計算以後再將結果存回該運算元這種型別的彙編指令的支援不是直接的,而必須通過特定的格式的說明。如果彙編指令中包含了乙個輸入-輸出型別的運算元,那麼在模板中必須用兩個佔位符對該運算元的不同功能進行引用:乙個負責輸入,另乙個負責輸出。例如:

asm ("addl %2,%0":"=r"(foo):"0"(foo),"g"(bar));

在上面這條指令中,

"%0」是乙個輸入-輸出型別的運算元,

"=r"(foo)用於限定其輸出功能,該指令的輸出結果會存放到c語言變數foo中;

指令中沒有顯式的出現「%1」運算元,但是針對它有乙個限定字串"0"(foo),事實上指令中隱式的「%1」運算元用於描述「%0」運算元的輸入功能,它的限定字串中的"0"限定了「%1」運算元與「%0」 

具有相同的位址。可以這樣理解上述指令中的模板:該指令將「%1」和「%2」中的值相加,計算結果存放回「%0」中,指令中的「%1」與「%0」具有相同的位址。注意,用於描述「%1」的"0"限定字元足以保證「%1」與「%0」具有相同的位址。

但是,如果用下面的指令完成,這種輸入-輸出操作就不會正常工作: 

asm ("addl %2,%0":"=r"(foo):"r"(foo),"g"(bar)); 

雖然該指令中「%0」和「%1」同樣引用了c語言變數foo,但是gcc並不保證在生成的匯程式設計序中它們具有相同的位址。

還有一些彙編指令可能會改變某些暫存器的值,相應的彙編指令模板中必須將這種情況通知編譯器。所以在模板中還有第三種型別的限定字串,它們跟在輸入運算元限定字串的後面,之間用冒號間隔。這些字串是某些暫存器的名稱,代表該指令會改變這些暫存器中的內容。

在內嵌的彙編指令中可能會直接引用某些硬體暫存器,我們已經知道at&t格式的組合語言中,暫存器名以「%」作為字首,為了在生成的匯程式設計序中保留這個「%」號,在asm語句中對硬體暫存器的引用必須用「%%」作為暫存器名稱的字首。如果彙編指令改變了硬體暫存器的內容,不要忘記通知編譯器(在第三種型別的限定串中新增相應的字串)。還有一些指令可能會改變cpu標誌暫存器eflag的內容,那麼需要在第三種型別的限定字串中加入"cc"。

為了防止gcc在優化過程中對asm中的彙編指令進行改變,可以在"asm"關鍵字後加上"volatile"修飾符。

可以在一條asm語句中描述多條組合語言指令;各條彙編指令之間用「;」或者「/n」隔開。

②運算元限定字元

運算元限定字串中利用規定的限定字元來描述相應的運算元,一些常用的限定字元有:(還有一些沒有涉及的限定字元,參見gcc.info)

1."m":運算元是記憶體變數。

2."o":運算元是記憶體變數,但它的定址方式必須是「偏移量」型別的,也就是基址定址或者基址加變址定址。

3."v":運算元是記憶體變數,其定址方式非「偏移量」型別。 

4.""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"

GCC內嵌彙編

gcc內嵌彙編 限制字元 限制字元有很多種,有些是與特定體系結構相關,此處僅列出常用的限定字元和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其後的c語言變數與指令運算元之間的關係,例如是將變數放在暫存器中還是放在記憶體中等,下表列出了常用的限定字母。b 將輸入變數放入ebx ...

GCC內嵌彙編語法

內嵌彙編語法如下 asm 彙編語句模板 輸出部分 輸入部分 破壞描述部分 共四個部分 彙編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用 格開,彙編語句模板必不可少,其他三部分可選,如果使用了後面的部分,而前面部分為空,也需要用 格開,相應部分內容為空。例如 asm volatile cli...

Visual Basic內嵌組合語言解決方案

vb簡單易用,但功能有時候受限制。vc delphi都可以直接在程式中寫彙編 可惱的是,vb不行。我在網上也看過許多有關於vb嵌入彙編的方法,不過有些方法,過於複雜,而且也沒相應的介紹。我這裡提供一種方法,也許大家以後可能有用!基本思路 彙編 可以存在乙個byte型別的陣列中,然後通過某種手段,把系...