gcc中的内嵌汇编语言(Intel i386平台)
gcc中的內(nèi)嵌匯編語(yǔ)言(Inteli386平臺(tái))
一.聲明
雖然Linux的核心代碼大部分是用C語(yǔ)言編寫的,但是不可避免的其中還是有一部分是用匯編語(yǔ)言寫成的。有些匯編語(yǔ)言代碼是直接寫在匯編源程序中的,特別是Linux的啟動(dòng)代碼部分;還有一些則是利用gcc的內(nèi)嵌匯編語(yǔ)言嵌在C語(yǔ)言程序中的。這篇文章簡(jiǎn)單介紹了gcc中的內(nèi)嵌式匯編語(yǔ)言,主要想幫助那些才開(kāi)始閱讀Linux核心代碼的朋友們能夠更快的入手。
寫這篇文章的主要信息來(lái)源是GNU的兩個(gè)info文件:as.info和gcc.info,如果你覺(jué)得這篇文章中的介紹還不夠詳細(xì)的話,你可以查閱這兩個(gè)文件。當(dāng)然,直接查閱這兩個(gè)文件可以獲得更加權(quán)威的信息。如果你不想被這兩篇文檔中的一大堆信息搞迷糊的話,我建議你先閱讀一下這篇文章,然后在必要時(shí)再去查閱更權(quán)威的信息。
二.簡(jiǎn)介
在Linux的核心代碼中,還是存在相當(dāng)一部分的匯編語(yǔ)言代碼。如果你想順利閱讀Linux代碼的話,你不可能繞過(guò)這一部分代碼。在Linux使用的匯編語(yǔ)言代碼中,主要有兩種格式:一種是直接寫成匯編語(yǔ)言源程序的形式,這一部分主要是一些Linux的啟動(dòng)代碼;另一部分則是利用gcc的內(nèi)嵌式匯編語(yǔ)言語(yǔ)句asm嵌在Linux的C語(yǔ)言代碼中的。這篇文章主要是介紹第二種形式的匯編語(yǔ)言代碼。
首先,我介紹一下as支持的匯編語(yǔ)言的語(yǔ)法格式。大家知道,我們現(xiàn)在學(xué)習(xí)的匯編語(yǔ)言的格式主要是Intel風(fēng)格的,而在Linux的核心代碼中使用的則是AT&T格式的匯編語(yǔ)言代碼,應(yīng)該說(shuō)大部分人對(duì)這種格式的匯編語(yǔ)言還不是很了解,所以我覺(jué)得有必要介紹一下。
接著,我主要介紹一下gcc的內(nèi)嵌式匯編語(yǔ)言的格式。gcc的內(nèi)嵌式匯編語(yǔ)言提供了一種在C語(yǔ)言源程序中直接嵌入?yún)R編指令的很好的辦法,既能夠直接控制所形成的指令序列,又有著與C語(yǔ)言的良好接口,所以在Linux代碼中很多地方都使用了這一語(yǔ)句。
三.AT&T的匯編語(yǔ)言語(yǔ)法格式
我想我們大部分人對(duì)Intel格式的匯編語(yǔ)言都很了解了。但是,在Linux核心代碼中,所有的匯編語(yǔ)言指令都是用AT&T格式的匯編語(yǔ)言書寫的。這兩種匯編語(yǔ)言在語(yǔ)法格式上有著很大的不同:
1.在AT&T的匯編語(yǔ)言中,用'$'前綴表示一個(gè)立即操作數(shù);而在Intel的格式中,立即操作數(shù)的表示不帶任何前綴符。例如:下面兩個(gè)語(yǔ)句是完全相同的:
*AT&T: pushl $4
*Intel: push 4
2.AT&T和Intel的匯編語(yǔ)言格式中,源操作數(shù)和目標(biāo)操作數(shù)的位置正好相反。Intel的匯編語(yǔ)言中,目標(biāo)操作數(shù)在源操作數(shù)的左邊;而在AT&T的匯編語(yǔ)言中,目標(biāo)操作數(shù)則在源操作數(shù)的右邊。例如:
*AT&T : addl $4,%eax
*Intel: add eax,4
3.在AT&T的匯編語(yǔ)言中,操作數(shù)的字長(zhǎng)是由操作碼助記符的最后一個(gè)字母決定的,后綴'b'、'w'、'l'分別表示操作數(shù)的字長(zhǎng)為8比特(字節(jié),byte),16比特(字,word)和32比特(長(zhǎng)字,long),而Intel格式中操作數(shù)的字長(zhǎng)是用“wordptr”或者“byte ptr”等前綴來(lái)表示的。例如:
*AT&T: movb FOO,%al
*Intel: mov al,byte ptr FOO
4. 在AT&T匯編指令中,直接遠(yuǎn)跳轉(zhuǎn)/調(diào)用的指令格式是“lcall/ljmp$SECTION,$OFFSET”,同樣,遠(yuǎn)程返回的指令是“lret$STACK-ADJUST”;而在Intel格式中,相應(yīng)的指令分別為“call/jmpfar SECTION:OFFSET”和“ret farSTACK-ADJUST”。
①AT&T匯編指令操作助記符命名規(guī)則
AT&T匯編語(yǔ)言中,操作碼助記符的后綴字符指定了該指令中操作數(shù)的字長(zhǎng)。后綴字母'b'、'w'、'l'分別表示字長(zhǎng)為8比特(字節(jié),byte),16比特(字,word)和32比特(長(zhǎng)字,long)的操作數(shù)。如果助記符中沒(méi)有指定字長(zhǎng)后綴并且該指令中沒(méi)有內(nèi)存操作數(shù),匯編程序'as'會(huì)根據(jù)指令中指定的寄存器操作數(shù)補(bǔ)上相應(yīng)的后綴字符。所以,下面的兩個(gè)指令具有相同的效果(這只是GNU的匯編程序as的一個(gè)特性,AT&T的Unix匯編程序?qū)](méi)有字長(zhǎng)后綴的指令的操作數(shù)字長(zhǎng)假設(shè)為32比特):
mov %ax,%bx
movw %ax,%bx
AT&T中幾乎所有的操作助記符與Intel格式中的助記符同名,僅有一小部分例外。操作數(shù)擴(kuò)展指令就是例外之一。在AT&T匯編指令中,操作數(shù)擴(kuò)展指令有兩個(gè)后綴:一個(gè)指定源操作數(shù)的字長(zhǎng),另一個(gè)指定目標(biāo)操作數(shù)的字長(zhǎng)。AT&T的符號(hào)擴(kuò)展指令的基本助記符為'movs',零擴(kuò)展指令的基本助記符為'movz'(相應(yīng)的Intel指令為'movsx'和'movzx')。因此,'movsbl%al,%edx'表示對(duì)寄存器al中的字節(jié)數(shù)據(jù)進(jìn)行字節(jié)到長(zhǎng)字的符號(hào)擴(kuò)展,計(jì)算結(jié)果存放在寄存器edx中。下面是一些允許的操作數(shù)擴(kuò)展后綴:
*bl: 字節(jié)->長(zhǎng)字
*bw: 字節(jié)->字
*wl: 字->長(zhǎng)字
還有一些其他的類型轉(zhuǎn)換指令的對(duì)應(yīng)關(guān)系:
*Intel *AT&T
⑴ cbw cbtw
符號(hào)擴(kuò)展:al->ax
⑵ cwde cwtl
符號(hào)擴(kuò)展:ax->eax
⑶ cwd cwtd
符號(hào)擴(kuò)展:ax->dx:ax
⑷ cdq cltd
符號(hào)擴(kuò)展:eax->edx:eax
還有一個(gè)不同名的助記符就是遠(yuǎn)程跳轉(zhuǎn)/調(diào)用指令。在Intel格式中,遠(yuǎn)程跳轉(zhuǎn)/調(diào)用指令的助記符為“call/jmpfar”,而在AT&T的匯編語(yǔ)言中,相應(yīng)的指令為“lcall”和“ljmp”。
②AT&T中寄存器的命名
在AT&T匯編語(yǔ)言中,寄存器操作數(shù)總是以'%'作為前綴。80386芯片的寄存器包括:
⑴8個(gè)32位寄存器:'%eax','%ebx','%ecx','%edx','%edi','%esi','%ebp','%esp'
⑵8個(gè)16位寄存器:'%ax','%bx','%cx','%dx','%si','%di','%bp','%sp'
⑶8個(gè)8位寄存器:'%ah','%al','%bh','%bl','%ch','%cl','%dh','%dl'
⑷6個(gè)段寄存器:'%cs','%ds','%es','%ss','%fs','%gs'
⑸3個(gè)控制寄存器:'%cr0','%cr1','%cr2'
⑹6個(gè)調(diào)試寄存器:'%db0','%db1','%db2','%db3','%db6','%db7'
⑺2個(gè)測(cè)試寄存器:'%tr6','%tr7'
⑻8個(gè)浮點(diǎn)寄存器棧:'%st(0)','%st(1)','%st(2)','%st(3)','%st(4)','%st(5)','%st(6)','%st(7)'
*注:我對(duì)這些寄存器并不是都了解,這些資料只是摘自as.info文檔。
如果真的需要寄存器命名的資料,我想可以參考一下相應(yīng)GNU工具的機(jī)器描述方面的源文件。
③AT&T中的操作碼前綴
⑴段超越前綴'cs','ds','es','ss','fs','gs':當(dāng)匯編程序中對(duì)內(nèi)存操作數(shù)進(jìn)行SECTION:MEMORY-OPERAND引用時(shí),自動(dòng)加上相應(yīng)的段超越前綴。
⑵操作數(shù)/地址尺寸前綴'data16','addr16':這些前綴將32位的操作數(shù)/地址轉(zhuǎn)化為16位的操作數(shù)/地址。
⑶總線鎖定前綴'lock':總線鎖定操作。'lock'前綴在Linux核心代碼中使用很多,特別是SMP代碼中。
⑷協(xié)處理器等待前綴'wait':等待協(xié)處理器完成當(dāng)前操作。
⑸指令重復(fù)前綴'rep','repe','repne':在串操作中重復(fù)指令的執(zhí)行。
④AT&T中的內(nèi)存操作數(shù)
在Intel的匯編語(yǔ)言中,內(nèi)存操作數(shù)引用的格式如下:
SECTION : [BASE + INDEX*SCALE + DISP]
而在AT&T的匯編語(yǔ)言中,內(nèi)存操作數(shù)的應(yīng)用格式則是這樣的:
%SECTION : DISP(BASE,INDEX,SCALE)
下面是一些內(nèi)存操作數(shù)的例子:
*AT&T *Intel
⑴ -4(%ebp) [ebp-4]
⑵foo(,%eax,4) [foo+eax*4]
⑶ foo(,1) [foo]
⑷ %gs:foo gs:foo
還有,絕對(duì)跳轉(zhuǎn)/調(diào)用指令中的內(nèi)存操作數(shù)必須以'*'最為前綴,否則as總是假設(shè)這是一個(gè)相對(duì)跳轉(zhuǎn)/調(diào)用指令。
⑤AT&T中的跳轉(zhuǎn)指令
as匯編程序自動(dòng)對(duì)跳轉(zhuǎn)指令進(jìn)行優(yōu)化,總是使用盡可能小的跳轉(zhuǎn)偏移量。如果8比特的偏移量無(wú)法滿足要求的話,as會(huì)使用一個(gè)32位的偏移量,as匯編程序暫時(shí)還不支持16位的跳轉(zhuǎn)偏移量,所以對(duì)跳轉(zhuǎn)指令使用'addr16'前綴是無(wú)效的。
還有一些跳轉(zhuǎn)指令只支持8位的跳轉(zhuǎn)偏移量,這些指令包括:'jcxz','jecxz','loop','loopz','loope','loopnz'和'loopne'。所以,在as的匯編源程序中使用這些指令可能會(huì)出錯(cuò)。(幸運(yùn)的是,gcc并不使用這些指令)
對(duì)AT&T匯編語(yǔ)言語(yǔ)法的簡(jiǎn)單介紹差不多了,其中有些特性是as特有的。在Linux核心代碼中,并不涉及到所有上面這些提到的語(yǔ)法規(guī)則,其中有兩點(diǎn)規(guī)則特別重要:第一,as中對(duì)寄存器引用時(shí)使用前綴'%';第二,AT&T匯編語(yǔ)言中源操作數(shù)和目標(biāo)操作數(shù)的位置與我們熟悉的Intel的語(yǔ)法正好相反。
四.gcc的內(nèi)嵌匯編語(yǔ)言語(yǔ)句asm
利用gcc的asm語(yǔ)句,你可以在C語(yǔ)言代碼中直接嵌入?yún)R編語(yǔ)言指令,同時(shí)還可以使用C語(yǔ)言的表達(dá)式指定匯編指令所用到的操作數(shù)。這一特性提供了很大的方便。
要使用這一特性,首先要寫一個(gè)匯編指令的模板(這種模板有點(diǎn)類似于機(jī)器描述文件中的指令模板),然后要為每一個(gè)操作數(shù)指定一個(gè)限定字符串。例如:
extern __inline__ void change_bit(int nr,volatile void*addr)
{
__asm__ __volatile__( LOCK_PREFIX
"btcl%1,%0"
:"=m" (ADDR)
:"ir" (nr));
}
上面的函數(shù)中:
LOCK_PREFIX:這是一個(gè)宏,如果定義了__SMP__,擴(kuò)展為"lock;",用于指定總線鎖定前綴,否則擴(kuò)展為""。
ADDR:這也是一個(gè)宏,定義為(*(volatilestruct __dummy *) addr)
"btcl%1,%0":這就是嵌入的匯編語(yǔ)言指令,btcl為指令操作碼,%1,%0是這條指令兩個(gè)操作數(shù)的占位符。后面的兩個(gè)限定字符串就用于描述這兩個(gè)操作數(shù)。
: "=m"(ADDR):第一個(gè)冒號(hào)后的限定字符串用于描述指令中的“輸出”操作數(shù)。刮號(hào)中的ADDR將操作數(shù)與C語(yǔ)言的變量聯(lián)系起來(lái)。這個(gè)限定字符串表示指令中的“%0”就是addr指針指向的內(nèi)存操作數(shù)。這是一個(gè)“輸出”類型的內(nèi)存操作數(shù)。
: "ir"(nr):第二個(gè)冒號(hào)后的限定字符串用于描述指令中的“輸入”操作數(shù)。這條限定字符串表示指令中的“%1”就是變量nr,這個(gè)的操作數(shù)可以是一個(gè)立即操作數(shù)或者是一個(gè)寄存器操作數(shù)。
*注:限定字符串與操作數(shù)占位符之間的對(duì)應(yīng)關(guān)系是這樣的:在所有限定字符串中(包括第一個(gè)冒號(hào)后的以及第二個(gè)冒號(hào)后的所有限定字符串),最先出現(xiàn)的字符串用于描述操作數(shù)“%0”,第二個(gè)出現(xiàn)的字符串描述操作數(shù)“%1”,以此類推。
①匯編指令模板
asm語(yǔ)句中的匯編指令模板主要由匯編指令序列和限定字符串組成。在一個(gè)asm語(yǔ)句中可以包括多條匯編指令。匯編指令序列中使用操作數(shù)占位符引用C語(yǔ)言中的變量。一條asm語(yǔ)句中最多可以包含十個(gè)操作數(shù)占位符:%0,%1,...,%9。匯編指令序列后面是操作數(shù)限定字符串,對(duì)指令序列中的占位符進(jìn)行限定。限定的內(nèi)容包括:該占位符與哪個(gè)C語(yǔ)言變量對(duì)應(yīng),可以是什么類型的操作數(shù)等等。限定字符串可以分為三個(gè)部分:輸出操作數(shù)限定字符串(指令序列后第一個(gè)冒號(hào)后的限定字符串),輸入操作數(shù)限定字符串(第一個(gè)冒號(hào)與第二個(gè)冒號(hào)之間),還有第三種類型的限定字符串在第二個(gè)冒號(hào)之后。同一種類型的限定字符串之間用逗號(hào)間隔。asm語(yǔ)句中出現(xiàn)的第一個(gè)限定字符串用于描述占位符“%0”,第二個(gè)用于描述占位符“%1”,以此類推(不管該限定字符串的類型)。如果指令序列中沒(méi)有任何輸出操作數(shù),那么在語(yǔ)句中出現(xiàn)的第一個(gè)限定字符串(該字符串用于描述輸入操作數(shù))之前應(yīng)該有兩個(gè)冒號(hào)(這樣,編譯器就知道指令中沒(méi)有輸出操作數(shù))。
指令中的輸出操作數(shù)對(duì)應(yīng)的C語(yǔ)言變量應(yīng)該具有左值類型,當(dāng)然對(duì)于輸出操作數(shù)沒(méi)有這種左值限制。
輸出操作數(shù)必須是只寫的,也就是說(shuō),asm對(duì)取出某個(gè)操作數(shù),執(zhí)行一定計(jì)算以后再將結(jié)果存回該操作數(shù)這種類型的匯編指令的支持不是直接的,而必須通過(guò)特定的格式的說(shuō)明。如果匯編指令中包含了一個(gè)輸入-輸出類型的操作數(shù),那么在模板中必須用兩個(gè)占位符對(duì)該操作數(shù)的不同功能進(jìn)行引用:一個(gè)負(fù)責(zé)輸入,另一個(gè)負(fù)責(zé)輸出。例如:
asm ("addl %2,%0":"=r"(foo):"0"(foo),"g"(bar));
在上面這條指令中,“%0”是一個(gè)輸入-輸出類型的操作數(shù),"=r"(foo)用于限定其輸出功能,該指令的輸出結(jié)果會(huì)存放到C語(yǔ)言變量foo中;指令中沒(méi)有顯式的出現(xiàn)“%1”操作數(shù),但是針對(duì)它有一個(gè)限定字符串"0"(foo),事實(shí)上指令中隱式的“%1”操作數(shù)用于描述“%0”操作數(shù)的輸入功能,它的限定字符串中的"0"限定了“%1”操作數(shù)與“%0”具有相同的地址??梢赃@樣理解上述指令中的模板:該指令將“%1”和“%2”中的值相加,計(jì)算結(jié)果存放回“%0”中,指令中的“%1”與“%0”具有相同的地址。注意,用于描述“%1”的"0"限定字符足以保證“%1”與“%0”具有相同的地址。但是,如果用下面的指令完成這種輸入-輸出操作就不會(huì)正常工作:
asm ("addl %2,%0":"=r"(foo):"r"(foo),"g"(bar));
雖然該指令中“%0”和“%1”同樣引用了C語(yǔ)言變量foo,但是gcc并不保證在生成的匯編程序中它們具有相同的地址。
還有一些匯編指令可能會(huì)改變某些寄存器的值,相應(yīng)的匯編指令模板中必須將這種情況通知編譯器。所以在模板中還有第三種類型的限定字符串,它們跟在輸入操作數(shù)限定字符串的后面,之間用冒號(hào)間隔。這些字符串是某些寄存器的名稱,代表該指令會(huì)改變這些寄存器中的內(nèi)容。
在內(nèi)嵌的匯編指令中可能會(huì)直接引用某些硬件寄存器,我們已經(jīng)知道AT&T格式的匯編語(yǔ)言中,寄存器名以“%”作為前綴,為了在生成的匯編程序中保留這個(gè)“%”號(hào),在asm語(yǔ)句中對(duì)硬件寄存器的引用必須用“%%”作為寄存器名稱的前綴。如果匯編指令改變了硬件寄存器的內(nèi)容,不要忘記通知編譯器(在第三種類型的限定串中添加相應(yīng)的字符串)。還有一些指令可能會(huì)改變CPU標(biāo)志寄存器EFLAG的內(nèi)容,那么需要在第三種類型的限定字符串中加入"cc"。
為了防止gcc在優(yōu)化過(guò)程中對(duì)asm中的匯編指令進(jìn)行改變,可以在"asm"關(guān)鍵字后加上"volatile"修飾符。
可以在一條asm語(yǔ)句中描述多條匯編語(yǔ)言指令;各條匯編指令之間用“;”或者“/n”隔開(kāi)。
②操作數(shù)限定字符
操作數(shù)限定字符串中利用規(guī)定的限定字符來(lái)描述相應(yīng)的操作數(shù),一些常用的限定字符有:(還有一些沒(méi)有涉及的限定字符,參見(jiàn)gcc.info)
1。"m":操作數(shù)是內(nèi)存變量。
2。"o":操作數(shù)是內(nèi)存變量,但它的尋址方式必須是“偏移量”類型的,也就是基址尋址或者基址加變址尋址。
3。"V":操作數(shù)是內(nèi)存變量,其尋址方式非“偏移量”類型。
4。"":操作數(shù)是內(nèi)存變量,其地址自動(dòng)增量。
6。"r":操作數(shù)是通用寄存器。
7。"i":操作數(shù)是立即操作數(shù)。(其值可在匯編時(shí)確定)
8。"n":操作數(shù)是立即操作數(shù)。有些系統(tǒng)不支持除字(雙字節(jié))以外的立即操作數(shù),這些操作數(shù)要用"n"而不是"i"來(lái)描述。
9。"g":操作數(shù)可以是立即數(shù),內(nèi)存變量或者寄存器,只要寄存器屬于通用寄存器。
10。"X":操作數(shù)允許是任何類型。
11。"0","1",...,"9":操作數(shù)與某個(gè)指定的操作數(shù)匹配。也就是說(shuō),該操作數(shù)就是指定的那個(gè)作數(shù)。例如,如果用"0"來(lái)描述"%1"操作數(shù),那么"%1"引用的其實(shí)就是"%0"操作數(shù)。
12。"p":操作數(shù)是一個(gè)合法的內(nèi)存地址(指針)。
13。"=":操作數(shù)在指令中是只寫的(輸出操作數(shù))。
14。"+":操作數(shù)在指令中是讀-寫類型的(輸入-輸出操作數(shù))。
15。"a":寄存器EAX。
16。"b":寄存器EBX。
17。"c":寄存器ECX。
18。"d":寄存器EDX。
19。"q":寄存器"a","b","c"或者"d"。
20。"A":寄存器"a"或者"d"。
21。"a":寄存器EAX。
22。"f":浮點(diǎn)數(shù)寄存器。
23。"t":第一個(gè)浮點(diǎn)數(shù)寄存器。
24。"u":第二個(gè)浮點(diǎn)數(shù)寄存器。
25。"D":寄存器di。
26。"S":寄存器si。
27。"I":0-31之間的立即數(shù)。(用于32位的移位指令)
28。"J":0-63之間的立即數(shù)。(用于64位的移位指令)
29。"N":0-255之間的立即數(shù)。(用于"out"指令)
30。"G":標(biāo)準(zhǔn)的80387浮點(diǎn)常數(shù)。
*注:還有一些不常見(jiàn)的限定字符并沒(méi)有在此說(shuō)明,另外有一些限定字符,例如"%","&"等由于我缺乏編譯器方面的一些知識(shí),所以我也不是很理解它們的含義,如果有高手愿意補(bǔ)充,不慎感激!不過(guò)在核心代碼中出現(xiàn)的限定字符差不多就是上面這些了。
總結(jié)
以上是生活随笔為你收集整理的gcc中的内嵌汇编语言(Intel i386平台)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 不能在此路径中使用此配置节。如果在父级别
- 下一篇: asp网站配置错误解决汇总_1