AT&T匯編語言與GCC內(nèi)嵌匯編簡(jiǎn)介
??版本?0.1
??時(shí)間04/3/30
EMAIL?chforest_chang@hotmail.com
1?AT&T?與INTEL的匯編語言語法的區(qū)別
1.1大小寫
1.2操作數(shù)賦值方向
1.3前綴
1.4間接尋址語法
1.5后綴
1.6指令
2?GCC內(nèi)嵌匯編
2.1簡(jiǎn)介
2.2內(nèi)嵌匯編舉例
2.3語法
2.3.1匯編語句模板
2.3.2輸出部分
2.3.3輸入部分
2.3.4限制字符
2.3.5破壞描述部分
2.4GCC如何編譯內(nèi)嵌匯編代碼
3后記
本節(jié)先介紹
AT&T匯編語言語法與INTEL匯編語法的差別,然后介紹GCC內(nèi)嵌匯編語法。閱讀本節(jié)需要讀者具有INTEL
匯編語言基礎(chǔ)。
1?AT&T?與INTEL的匯編語言語法的區(qū)別
1.1???
指令大小寫
INTEL格式的指令使用大寫字母,而AT&T
格式的使用小寫字母。
例:
INTEL AT&T
MOV?EAX,EBX movl?%ebx,%eax
1.2???
指令操作數(shù)賦值方向
在INTEL語法中,第一個(gè)表示目的操作數(shù),第二個(gè)表示源操作數(shù),賦值方向從右向左。
???AT&T語法第一個(gè)為源操作數(shù),第二個(gè)為目的操作數(shù),方向從左到右,合乎自然。
例:
INTEL AT&T
MOV?EAX,EBX movl?%ebx,%eax
1.3???
指令前綴
在INTEL語法中寄存器和立即數(shù)不需要前綴;
???AT&T中寄存器需要加前綴“%”;立即數(shù)需要加前綴“___FCKpd___0rdquo;。
例:
INTEL AT&T
MOV?EAX,1 movl?$1,%eax
符號(hào)常數(shù)直接引用,不需要加前綴,如:
movl?value?,?%ebx
value為一常數(shù);
在符號(hào)前加前綴?$,?表示引用符號(hào)地址,?
如
movl?$value,?%ebx
是將value的地址放到ebx中。
總線鎖定前綴“l(fā)ock”:
總線鎖定操作?!發(fā)ock”前綴在Linux
核心代碼中使用很多,特別是SMP
代碼中。當(dāng)總線鎖定后其它CPU
不能存取鎖定地址處的內(nèi)存單元。
遠(yuǎn)程跳轉(zhuǎn)指令和子過程調(diào)用指令的操作碼使用前綴“l(fā)“,分別為ljmp,lcall,
與之相應(yīng)的返回指令偽lret。
例:
??INTEL AT&T
lcall?$secion:$offset
JMP?FAR?SECTION:OFFSET ljmp?$secion:$offset
RET?FAR?SATCK_ADJUST lret?$stack_adjust
1.4???間接尋址語法
INTEL中基地址使用“[”、“]”,而在AT&T“(”、“)”;
另外處理復(fù)雜操作數(shù)的語法也不同,
INTEL為Segreg:[base+index*scale+disp]
,而在AT&T中為%segreg:disp(base,index,sale),其中segreg
,index,scale,disp都是可選的,在指定index而沒有顯式指定Scale
的情況下使用默認(rèn)值1。Scale,disp不需要加前綴“&”。
?INTEL AT&T
Instr??foo,segreg:[base+index*scale+disp] instr?%segreg:disp(base,index,scale),foo
1.5???
指令后綴
???????AT&T
語法中大部分指令操作碼的最后一個(gè)字母表示操作數(shù)大小,“b”表示byte
(一個(gè)字節(jié));“w”表示word(2,個(gè)字節(jié));“l(fā)”表示long(4,個(gè)字節(jié))。
INTEL中處理內(nèi)存操作數(shù)時(shí)也有類似的語法如:
BYTE?PTR、WORD?PTR、DWORD?PTR。
例:
??INTEL AT&T
??mov?al,?bl movb?%bl,%al
??mov?ax,bx movw?%bx,%ax
??mov?eax,?dword?ptr?[ebx] movl?(%ebx),?%eax
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ò)展后綴:?
l????????
bl:?,字節(jié)>->長(zhǎng)字?l????????
bw:?,字節(jié)>->字?l????????
wl:?,字->長(zhǎng)字?
跳轉(zhuǎn)指令標(biāo)號(hào)后的后綴表示跳轉(zhuǎn)方向,“f”表示向前(forward),
“b,”表示向后(back)。
例:
jmp?1f
jmp?1f
1.6???指令
INTEL匯編與AT&T匯編指令基本相同,差別僅在語法上。關(guān)于每條指令的語法可以參考I386Manual。
2??????GCC內(nèi)嵌匯編
2.1???簡(jiǎn)介
內(nèi)核代碼絕大部分使用C
語言編寫,只有一小部分使用匯編語言編寫,例如與特定體系結(jié)構(gòu)相關(guān)的代碼和對(duì)性能影響很大的代碼。GCC提供了內(nèi)嵌匯編的功能,可以在C代碼中直接內(nèi)嵌匯編語言語句,大大方便了程序設(shè)計(jì)。
?簡(jiǎn)單的內(nèi)嵌匯編很容易理解
例:
__asm__
__volatile__("hlt");
?“__asm__”表示后面的代碼為內(nèi)嵌匯編,“asm”是“__asm__”的別名。
?“__volatile__”表示編譯器不要優(yōu)化代碼,后面的指令保留原樣,
?“volatile”是它的別名。括號(hào)里面是匯編指令。
2.2???內(nèi)嵌匯編舉例在內(nèi)嵌匯編中,可以將C
語言表達(dá)式指定為匯編指令的操作數(shù),而且不用去管如何將C
語言表達(dá)式的值讀入哪個(gè)寄存器,以及如何將計(jì)算結(jié)果寫回C
變量,你只要告訴程序中C語言表達(dá)式與匯編指令操作數(shù)之間的對(duì)應(yīng)關(guān)系即可,?GCC
會(huì)自動(dòng)插入代碼完成必要的操作。
使用內(nèi)嵌匯編,要先編寫匯編指令模板,然后將C語言表達(dá)式與指令的操作數(shù)相關(guān)聯(lián),并告訴
GCC對(duì)這些操作有哪些限制條件。例如在下面的匯編語句:
__asm__?__violate__
??("movl?%1,%0"?:?"=r"?(result)?:?"m"?(input));
“movl?%1,%0”是指令模板;“%0”和“%1”代表指令的操作數(shù),稱為占位符,內(nèi)嵌匯編靠它們將C
語言表達(dá)式與指令操作數(shù)相對(duì)應(yīng)。指令模板后面用小括號(hào)括起來的是C
語言表達(dá)式,本例中只有兩個(gè):“result”和“input”,他們按照出現(xiàn)的順序分別與指令操作
數(shù)“%0”,“%1,”對(duì)應(yīng);注意對(duì)應(yīng)順序:第一個(gè)C表達(dá)式對(duì)應(yīng)“%0”;第二個(gè)表達(dá)式對(duì)應(yīng)“%1
”,依次類推,操作數(shù)至多有10個(gè),分別用“%0”,“%1”….“%9,”表示。在每個(gè)操作數(shù)前
面有一個(gè)用引號(hào)括起來的字符串,字符串的內(nèi)容是對(duì)該操作數(shù)的限制或者說要求?!皉esult”前面
的限制字符串是“=r”,其中“=”表示“result”是輸出操作數(shù),“r
”表示需要將“result”與某個(gè)通用寄存器相關(guān)聯(lián),先將操作數(shù)的值讀入寄存器,然后
在指令中使用相應(yīng)寄存器,而不是“result”本身,當(dāng)然指令執(zhí)行完后需要將寄存器中的值
存入變量“result”,從表面上看好像是指令直接對(duì)“result”進(jìn)行操作,實(shí)際上GCC
做了隱式處理,這樣我們可以少寫一些指令?!癷nput”前面的“r”表示該表達(dá)式需要先放入
某個(gè)寄存器,然后在指令中使用該寄存器參加運(yùn)算。
我們將上面的內(nèi)嵌代碼放到一個(gè)C源文件中,然后使用gcc?–c–S得到該C
文件源代碼相對(duì)應(yīng)的匯編代碼,然后查看一下匯編代碼,看看GCC是如何處理的。
C源文件如下內(nèi)容如下,注意該代碼沒有實(shí)際意義,僅僅作為例子。
??extern?????int
??input,result;
??void?test(void)
??{
?????????input
??=?1;
??__asm__?__volatile__?("movl?%1,%0"?:
??"=r"?(result)?:?"r"?(input));
?????????return
??;
??}
對(duì)應(yīng)的匯編代碼如下;
??行號(hào) ??代碼 解釋
??1
??7
??8 movl????$1,?input 對(duì)應(yīng)C語言語句input?=?1;
??9 input,?%eax
?10 #APP GCC插入的注釋,表示內(nèi)嵌匯編開始
?11 movl %eax,%eax 我們的內(nèi)嵌匯編語句
?12 #NO_APP GCC??插入的注釋,表示內(nèi)嵌匯編結(jié)束
?13 movl %eax,?result 將結(jié)果存入result變量
?14
?-
?18
。。。。。。
從匯編代碼可以看出,第9行和第13行是GCC,自動(dòng)增加的代碼,GCC
根據(jù)限定字符串決定如何處理C表達(dá)式,本例兩個(gè)表達(dá)式都被指定為“r”型,所以先使用指令:
movl????input,?%eax
將input讀入寄存器%eax;GCC,也指定一個(gè)寄存器與輸出變量result
相關(guān),本例也是%eax,等得到操作結(jié)果后再使用指令:
movl?%eax,?result
將寄存器的值寫回C變量result中。從上面的匯編代碼我們可以看出與result
和input,相關(guān)連的寄存器都是%eax,GCC使用%eax,替換內(nèi)嵌匯編指令模板中的
%0,%1?
movl?%eax,%eax
顯然這一句可以不要。但是沒有優(yōu)化,所以這一句沒有被去掉。
由此可見,C表達(dá)式或者變量與寄存器的關(guān)系由GCC自動(dòng)處理,我們只需使用限制字符串指導(dǎo)GCC
如何處理即可。限制字符必須與指令對(duì)操作數(shù)的要求相匹配,否則產(chǎn)生的匯編代碼
將會(huì)有錯(cuò),讀者可以將上例中的兩個(gè)“r”,都改為“m”(m,表示操作數(shù)放在內(nèi)存,而不是寄
存器中),編譯后得到的結(jié)果是:
movl?input,?result
很明顯這是一條非法指令,因此限制字符串必須與指令對(duì)操作數(shù)的要求匹配。例如指令movl
允許寄存器到寄存器,立即數(shù)到寄存器等,但是不允許內(nèi)存到內(nèi)存的操作,因此兩個(gè)操作數(shù)
不能同時(shí)使用“m”作為限定字符。
2.3???語法
內(nèi)嵌匯編語法如下:
__asm__(
匯編語句模板:?
輸出部分:?
輸入部分:?
破壞描述部分)
共四個(gè)部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用“:”格
開,匯編語句模板必不可少,其他三部分可選,如果使用了后面的部分,而前面部分為空,
也需要用“:”格開,相應(yīng)部分內(nèi)容為空。例如:
??__asm__?__volatile__(
??"cli":
??:
??:"memory")
2.3.1???匯編語句模板
匯編語句模板由匯編語句序列組成,語句之間使用“;”、“/n”或“/n/t”分開。
指令中的操作數(shù)可以使用占位符引用C語言變量,操作數(shù)占位符最多10個(gè),名稱如下:%0,%1…,%9。
指令中使用占位符表示的操作數(shù),總被視為long型(4,個(gè)字節(jié)),但對(duì)其施加的操作
根據(jù)指令可以是字或者字節(jié),當(dāng)把操作數(shù)當(dāng)作字或者字節(jié)使用時(shí),默認(rèn)為低字或者低字節(jié)。
對(duì)字節(jié)操作可以顯式的指明是低字節(jié)還是次字節(jié)。方法是在%和序號(hào)之間插入一個(gè)字母,
“b”代表低字節(jié),“h”代表高字節(jié),例如:%h1。
2.3.2?輸出部分
輸出部分描述輸出操作數(shù),不同的操作數(shù)描述符之間用逗號(hào)格開,每個(gè)操作數(shù)描述符由限定字符串和
C語言變量組成。每個(gè)輸出操作數(shù)的限定字符串必須包含“=”表示他是一個(gè)輸出操作數(shù)。
例:
??__asm__?__volatile__("pushfl?;?popl?%0?;?cli":"=g"?(x)?)
?描述符字符串表示對(duì)該變量的限制條件,這樣GCC就可以根據(jù)這些條件決定如何
?分配寄存器,如何產(chǎn)生必要的代碼處理指令操作數(shù)與C表達(dá)式或C變量之間的聯(lián)系。
?2.3.3???輸入部分
輸入部分描述輸入操作數(shù),不同的操作數(shù)描述符之間使用逗號(hào)格開,每個(gè)操作數(shù)描述符由
限定字符串和C語言表達(dá)式或者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位設(shè)為1。第一個(gè)占位符%0與C,語言變量ADDR
對(duì)應(yīng),第二個(gè)占位符%1與C,語言變量nr對(duì)應(yīng)。因此上面的匯編語句代碼與下面的偽代碼等價(jià):
btsl?nr,?ADDR,該指令的兩個(gè)操作數(shù)不能全是內(nèi)存變量,因此將nr的限定字符串指定為“Ir”,
將nr,與立即數(shù)或者寄存器相關(guān)聯(lián),這樣兩個(gè)操作數(shù)中只有ADDR為內(nèi)存變量。
2.3.4???限制字符
2.3.4.1????????????限制字符列表
限制字符有很多種,有些是與特定體系結(jié)構(gòu)相關(guān),此處僅列出常用的限定字符和i386
中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其后的C
語言變量與指令操作數(shù)之間的關(guān)系,例如是將變量放在寄存器中還是放在內(nèi)存中等,
下表列出了常用的限定字母。
??分類
??限定符 描述??通用寄存器
??“a”將輸入變量放入eax
??這里有一個(gè)問題:假設(shè)eax已經(jīng)被使用,那怎么辦?
??其實(shí)很簡(jiǎn)單:因?yàn)镚CC知道eax已經(jīng)被使用,它在這段匯編代碼的起始處插入一條
??語句pushl?%eax,將eax內(nèi)容保存到堆棧,然后在這段代碼結(jié)束處再增加一條
??語句popl?%eax,恢復(fù)eax的內(nèi)容
??“b”將輸入變量放入ebx
??“c”將輸入變量放入ecx
??“d”將輸入變量放入edx
??“s”將輸入變量放入esi
??“d”將輸入變量放入edi
??“q”將輸入變量放入eax,ebx??,ecx??,edx中的一個(gè)
??“r”將輸入變量放入通用寄存器,也就是eax?,ebx,ecx,edx,esi,edi中的一個(gè)
??“A”把eax和edx,合成一個(gè)64位的寄存器(uselong?longs)?
??“m”內(nèi)存變量
??“o”操作數(shù)為內(nèi)存變量,但是其尋址方式是偏移量類型,也即是基址尋址,或者是基址加變址尋址
??“V”操作數(shù)為內(nèi)存變量,但尋址方式不是偏移量類型
??“,”?操作數(shù)為內(nèi)存變量,但尋址方式為自動(dòng)增量
??“p”操作數(shù)是一個(gè)合法的內(nèi)存地址(指針)
??寄存器或內(nèi)存
??“g”?將輸入變量放入eax,ebx,ecx??,edx中的一個(gè)或者作為內(nèi)存變量?
??“X”操作數(shù)可以是任何類型
??立即數(shù)
??“I”?0-31?之間的立即數(shù)(用于32位移位指令)
??“J”?0-63?之間的立即數(shù)(用于64?位移位指令)
??“N”?0-255??,之間的立即數(shù)(用于out??指令)
??“i”?立即數(shù)
??“n”?立即數(shù),有些系統(tǒng)不支持除字以外的立即數(shù),這些系統(tǒng)應(yīng)該使用“n”而不是“i”
??匹配
??“0”,“1??,”...??“9?”
??表示用它限制的操作數(shù)與某個(gè)指定的操作數(shù)匹配,也即該操作數(shù)就是指定的那個(gè)操作數(shù),
??例如用“0??”去描述“%1”操作數(shù),那么“%1”引用的其實(shí)就是“%0”操作數(shù),注意作為
??限定符字母的0-9?,與指令中的“%0”-“%9”的區(qū)別,前者描述操作數(shù),后者代表操作數(shù)。
??后面有詳細(xì)描述?&??該輸出操作數(shù)不能使用過和輸入操作數(shù)相同的寄存器
??后面有詳細(xì)描述
??操作數(shù)類型
??“=”?操作數(shù)在指令中是只寫的(輸出操作數(shù))
??“+”?操作數(shù)在指令中是讀寫類型的(輸入輸出操作數(shù))
???浮點(diǎn)數(shù)
??“f”
??浮點(diǎn)寄存器
??“t”第一個(gè)浮點(diǎn)寄存器
??“u”第二個(gè)浮點(diǎn)寄存器
??“G”標(biāo)準(zhǔn)的80387
??浮點(diǎn)常數(shù)
???%??該操作數(shù)可以和下一個(gè)操作數(shù)交換位置
??例如addl的兩個(gè)操作數(shù)可以交換順序(當(dāng)然兩個(gè)操作數(shù)都不能是立即數(shù))
??#??部分注釋,從該字符到其后的逗號(hào)之間所有字母被忽略
??*??表示如果選用寄存器,則其后的字母被忽略
?現(xiàn)在繼續(xù)看上面的例子,
"=m"?(ADDR)表示ADDR為內(nèi)存變量(“m”),而且是輸出變量(“=”);"Ir"?(nr)表示nr,為
0-31之間的立即數(shù)(“I”)或者一個(gè)寄存器操作數(shù)(“r”)。
2.3.4.2????????????
匹配限制符
I386
指令集中許多指令的操作數(shù)是讀寫型的(讀寫型操作數(shù)指先讀取原來的值然后參加運(yùn)算,最后
將結(jié)果寫回操作數(shù)),例如addl?%1,%0,它的作用是將操作數(shù)%0與操作數(shù)%1的和存入操作數(shù)%0,
因此操作數(shù)%0是讀寫型操作數(shù)。老版本的GCC對(duì)這種類型操作數(shù)的支持不是很好,它將操作數(shù)嚴(yán)格
分為輸入和輸出兩種,分別放在輸入部分和輸出部分,而沒有一個(gè)單獨(dú)部分描述讀寫型操作數(shù),
因此在GCC中讀寫型的操作數(shù)需要在輸入和輸出部分分別描述,靠匹配限制符將兩者關(guān)聯(lián)到一起
注意僅在輸入和輸出部分使用相同的C變量,但是不用匹配限制符,產(chǎn)生的代碼很可能不對(duì),后
面會(huì)分析原因。
匹配限制符是一位數(shù)字:“0”、“1”……“9,”,分別表示它限制的C表達(dá)式分別與
占位符%0,%1,……%9對(duì)應(yīng)的C變量匹配。例如使用“0”作為%1,的限制字符,那么
%0和%1表示同一個(gè)C,變量。
看一下下面的代碼就知道為什么要將讀寫型操作數(shù),分別在輸入和輸出部分加以描述。
該例功能是求input+result的和,然后存入result:
??extern?int?input,result;
??void?test_at_t()
??{
?????????result=?0;
?????????input?=?1;
?????????__asm__
?__volatile__?("addl?%1,%0":"=r"(result):?"r"(input));
??}
?對(duì)應(yīng)的匯編代碼為:
?????????movl??$0,_result
?????????movl??$1,_input
?????????movl??_input,%edx??/APP
?????????addl??%edx,%eax??/NO_APP
?????????movl??%eax,%edx
?????????movl??%edx,_result
input?為輸入型變量,而且需要放在寄存器中,GCC給它分配的寄存器是%edx,在執(zhí)行addl之前%edx,
的內(nèi)容已經(jīng)是input的值??梢妼?duì)于使用“r”限制的輸入型變量或者表達(dá)式,在使用之前GCC會(huì)插入
必要的代碼將他們的值讀到寄存器;“m”型變量則不需要這一步。讀入input后執(zhí)行addl,顯然%eax
的值不對(duì),需要先讀入result的值才行。再往后看:movl?%eax,%edx和movl?%edx,_result
的作用是將結(jié)果存回result,分配給result的寄存器與分配給input的一樣,都是%edx。
?綜上可以總結(jié)出如下幾點(diǎn):
1.???????使用“r”限制的輸入變量,GCC先分配一個(gè)寄存器,然后將值讀入寄存器,最后
?用該寄存器替換占位符;
2.????????使用“r”限制的輸出變量,GCC會(huì)分配一個(gè)寄存器,然后用該寄存器替換占位符,
但是在使用該寄存器之前并不將變量值先讀入寄存器,GCC認(rèn)為所有輸出變量以前的
值都沒有用處,不讀入寄存器(可能是因?yàn)锳T&T匯編源于CISC架構(gòu)處理器的匯編語言
,在CISC處理器中大部分指令的輸入輸出明顯分開,而不像RISC那樣一個(gè)操作數(shù)既
做輸入又做輸出,例如add?r0,r1,r2,r0,和r1是輸入,r2是輸出,輸入和輸出分開,
沒有使用輸入輸出型操作數(shù),這樣我們就可以認(rèn)為r2對(duì)應(yīng)的操作數(shù)原來的值沒有用處,
也就沒有必要先將操作數(shù)的值讀入r2,因?yàn)檫@是浪費(fèi)處理器的CPU周期),最后GCC插入代碼,
將寄存器的值寫回變量;
3. 輸入變量使用的寄存器在最后一處使用它的指令之后,就可以挪做其他用處,因?yàn)?已經(jīng)不再使用。例如上例中的%edx。在執(zhí)行完addl之后就作為與result對(duì)應(yīng)的寄存器。
因?yàn)榈诙l,上面的內(nèi)嵌匯編指令不能奏效,因此需要在執(zhí)行addl之前把result的值讀入
寄存器,也許再將result放入輸入部分就可以了(因?yàn)榈谝粭l會(huì)保證將result
先讀入寄存器)。修改后的指令如下(為了更容易說明問題將input限制符由“r,”改為“m”):
??extern?int?input,result;
??void?test_at_t()
??{
?????????result??=?0;
?????????input??=?1;
?????????__asm__
??__volatile__?("addl??%2,%0":"=r"(result):"r"(result),"m"(input));
??}
看上去上面的代碼可以正常工作,因?yàn)槲覀冎?0和%1都和result相關(guān),應(yīng)該使用同一個(gè)
寄存器,但是GCC并不去判斷%0和%1,是否和同一個(gè)C表達(dá)式或變量相關(guān)聯(lián)(這樣易于產(chǎn)生與
內(nèi)嵌匯編相應(yīng)的匯編代碼),因此%0和%1使用的寄存器可能不同。我們看一下匯編代碼就知道了。
?movl??$0,_result
?????????movl??$1,_input
?????????movl??_result,%edx??/APP
?????????addl??_input,%eax??/NO_APP
?????????movl??%eax,%edx
?????????movl??%edx,_result
?現(xiàn)在在執(zhí)行addl之前將result的值被讀入了寄存器%edx,但是addl指令的操作數(shù)%0
卻成了%eax,而不是%edx,與預(yù)料的不同,這是因?yàn)镚CC給輸出和輸入部分的變量分配了不同
的寄存器,GCC沒有去判斷兩者是否都與result相關(guān),后面會(huì)講GCC如何翻譯內(nèi)嵌匯編,看完之后
就不會(huì)驚奇啦。
使用匹配限制符后,GCC知道應(yīng)將對(duì)應(yīng)的操作數(shù)放在同一個(gè)位置(同一個(gè)寄存器或者同一個(gè)
內(nèi)存變量)。使用匹配限制字符的代碼如下:
??extern?int?input,result;
??void?test_at_t()
??{
?????????result??=?0;
?????????input??=?1;
?????????__asm__
?__volatile__?("addl??%2,%0":"=r"(result):"0"(result),"m"(input));
??}
?輸入部分中的result用匹配限制符“0”限制,表示%1與%0,代表同一個(gè)變量,
?輸入部分說明該變量的輸入功能,輸出部分說明該變量的輸出功能,兩者結(jié)合表示result
是讀寫型。因?yàn)?0和%1,表示同一個(gè)C變量,所以放在相同的位置,無論是寄存器還是內(nèi)存。
?相應(yīng)的匯編代碼為:
?????????movl??$0,_result
?????????movl??$1,_input
?????????movl??_result,%edx
?????????movl??%edx,%eax??/APP
?????????addl??_input,%eax??/NO_APP
?????????movl??%eax,%edx
?????????movl??%edx,_result
可以看到與result相關(guān)的寄存器是%edx,在執(zhí)行指令addl之前先從%edx將result讀入%eax,
執(zhí)行之后需要將結(jié)果從%eax讀入%edx,最后存入result中。這里我們可以看出GCC
處理內(nèi)嵌匯編中輸出操作數(shù)的一點(diǎn)點(diǎn)信息:addl并沒有使用%edx,可見它不是簡(jiǎn)單的用result
對(duì)應(yīng)的寄存器%edx去替換%0,而是先分配一個(gè)寄存器,執(zhí)行運(yùn)算,最后才將運(yùn)算結(jié)果存入
對(duì)應(yīng)的變量,因此GCC是先看該占位符對(duì)應(yīng)的變量的限制符,發(fā)現(xiàn)是一個(gè)輸出型寄存器變量,
就為它分配一個(gè)寄存器,此時(shí)沒有去管對(duì)應(yīng)的C變量,最后GCC,知道還要將寄存器的值寫回變量,
與此同時(shí),它發(fā)現(xiàn)該變量與%edx關(guān)聯(lián),因此先存入%edx,再存入變量。
至此讀者應(yīng)該明白了匹配限制符的意義和用法。在新版本的GCC中增加了一個(gè)限制字符“+”,
它表示操作數(shù)是讀寫型的,GCC知道應(yīng)將變量值先讀入寄存器,然后計(jì)算,最后寫回變量,而
無需在輸入部分再去描述該變量。
例;
??extern?int?input,result;
??void?test_at_t()
??{
?????????result??=?0;
?????????input??=?1;
?????????__asm__
??__volatile__?("addl??%1,%0":"+r"(result):"m"(input));
??}
此處用“+”替換了“=”,而且去掉了輸入部分關(guān)于result的描述,產(chǎn)生的匯編代碼如下:
?????????movl??$0,_result
?????????movl??$1,_input
?????????movl??_result,%eax??/APP
?????????addl??_input,%eax??/NO_APP
?????????movl??%eax,_result
??L2:
?????????movl??%ebp,%esp
?處理的比使用匹配限制符的情況還要好,省去了好幾條匯編代碼。
?2.3.4.3???????“&”限制符
限制符“&”在內(nèi)核中使用的比較多,它表示輸入和輸出操作數(shù)不能使用相同的寄存器,
這樣可以避免很多錯(cuò)誤。
舉一個(gè)例子,下面代碼的作用是將函數(shù)foo的返回值存入變量ret中:
??__asm__?(?“call?foo;movl?%%edx,%1”,?:”=a”(ret)??:??”r”(bar)?);
我們知道函數(shù)的int型返回值存放在%eax中,但是gcc編譯的結(jié)果是輸入和輸出同時(shí)使用了
寄存器%eax,如下:
????movl?bar,?%eax
????#APP
????call?foo
????movl?%ebx,%eax
#NO_APP
????movl?%eax,?ret
結(jié)果顯然不對(duì),原因是GCC并不知道%eax中的值是我們所要的。避免這種情況的方法是使用“&”
限定符,這樣bar就不會(huì)再使用%eax寄存器,因?yàn)橐驯籸et指定使用。
?_asm__?(?“call?foo;movl?%%edx,%1”,:”=&a”(ret)?:??”r”(bar)?);
2.3.5?破壞描述部分
2.3.5.1????????????寄存器破壞描述符
通常編寫程序只使用一種語言:高級(jí)語言或者匯編語言。高級(jí)語言編譯的步驟大致如下:
l????????
預(yù)處理;
l????????
編譯
l????????
匯編
l????????
鏈接
我們這里只關(guān)心第二步編譯(將C代碼轉(zhuǎn)換成匯編代碼):因?yàn)樗械拇a都是用高級(jí)語言編寫,
編譯器可以識(shí)別各種語句的作用,在轉(zhuǎn)換的過程中所有的寄存器都由編譯器決定如何分配使用,
它有能力保證寄存器的使用不會(huì)沖突;也可以利用寄存器作為變量的緩沖區(qū),因?yàn)榧拇嫫鞯脑L問
速度比內(nèi)存快很多倍。如果全部使用匯編語言則由程序員去控制寄存器的使用,只能靠程序員去
保證寄存器使用的正確性。但是如果兩種語言混用情況就變復(fù)雜了,因?yàn)閮?nèi)嵌的匯編代碼可以直接
使用寄存器,而編譯器在轉(zhuǎn)換的時(shí)候并不去檢查內(nèi)嵌的匯編代碼使用了哪些寄存器(因?yàn)楹茈y檢測(cè)
匯編指令使用了哪些寄存器,例如有些指令隱式修改寄存器,有時(shí)內(nèi)嵌的匯編代碼會(huì)調(diào)用其他子過程,
而子過程也會(huì)修改寄存器),因此需要一種機(jī)制通知編譯器我們使用了哪些寄存器(程序員自己知道
內(nèi)嵌匯編代碼中使用了哪些寄存器),否則對(duì)這些寄存器的使用就有可能導(dǎo)致錯(cuò)誤,修改描述部分
可以起到這種作用。當(dāng)然內(nèi)嵌匯編的輸入輸出部分指明的寄存器或者指定為“r”,“g”型由編譯器
去分配的寄存器就不需要在破壞描述部分去描述,因?yàn)榫幾g器已經(jīng)知道了。
破壞描述符由逗號(hào)格開的字符串組成,每個(gè)字符串描述一種情況,一般是寄存器名;除寄存器外
還有“memory”。例如:“%eax”,“%ebx”,“memory”等。
下面看個(gè)例子就很清楚為什么需要通知GCC內(nèi)嵌匯編代碼中隱式(稱它為隱式是因?yàn)镚CC并不知道)
使用的寄存器。
?在內(nèi)嵌的匯編指令中可能會(huì)直接引用某些寄存器,我們已經(jīng)知道AT&T格式的匯編語言中,寄存器
?名以“%”作為前綴,為了在生成的匯編程序中保留這個(gè)“%”號(hào),在asm語句中對(duì)寄存器的
?引用必須用“%%”作為寄存器名稱的前綴。原因是“%”在asm,內(nèi)嵌匯編語句中的作用與“/”在C
語言中的作用相同,因此“%%”轉(zhuǎn)換后代表“%”。
?例(沒有使用修改描述符):
?int?main(void)???
{
?????int?input,?output,temp;????
????input?=?1;
__asm__?__volatile__??("movl?$0,?%%eax;/n/t?
movl?%%eax,?%1;/n/t
movl?%2,?%%eax;/n/t
movl?%%eax,?%0;/n/t"
:"=m"(output),"=m"(temp)????/*?output?*/????????????
:"r"(input)?????/*?input?*/????
);??
return?0;
?}
這段代碼使用%eax作為臨時(shí)寄存器,功能相當(dāng)于C代碼:“temp?=?0;output=input”,?
對(duì)應(yīng)的匯編代碼如下:
?????????movl??$1,-4(%ebp)
?????????movl?-4(%ebp),%eax?? ?/APP
?????????movl??$0,?%eax;
?movl?%eax,?-12(%ebp);
?????????movl?%eax,?%eax;
?????????movl?%eax,?-8(%ebp);???????/NO_APP
?顯然GCC給input分配的寄存器也是%eax,發(fā)生了沖突,output的值始終為0,而不是input。
?使用破壞描述后的代碼:
??int?main(void)
?{
int?input,?output,temp;
?????????input??=?1;
???__asm__?__volatile__
??( "movl?$0,?%%eax;/n/t
?????????????????????????movl??%%eax,?%1;/n/t????
?????????????????????????movl??%2,?%%eax;/n/t
?????????????????????????movl??%%eax,?%0;/n/t"
?????????????????????????:"=m"(output),"=m"(temp)????/*?output?*/
?????????????????????????:"r"(input)?????/*?input?*/
?????????????????????????:"eax");??/*?描述符?*/
???return?0;
?}
?對(duì)應(yīng)的匯編代碼:
?????????movl?$1,-4(%ebp)
?????????movl??-4(%ebp),%edx?? ?/APP
?????????movl??$0,?%eax;
?????????movl??%eax,?-12(%ebp);
?????????movl??%edx,?%eax;
?????????movl??%eax,?-8(%ebp);??/NO_APP
通過破壞描述部分,GCC得知%eax已被使用,因此給input分配了%edx。在使用內(nèi)嵌匯編時(shí)請(qǐng)記
住一點(diǎn):盡量告訴GCC盡可能多的信息,以防出錯(cuò)。
如果你使用的指令會(huì)改變CPU的條件寄存器cc,需要在修改描述部分增加“cc”。
2.3.5.2????????memory破壞描述符
“memory”比較特殊,可能是內(nèi)嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的
優(yōu)化知識(shí),再看C關(guān)鍵字volatile。最后去看該描述符。
2.3.5.2.1??????編譯器優(yōu)化介紹
內(nèi)存訪問速度遠(yuǎn)不及CPU處理速度,為提高機(jī)器整體性能,在硬件上引入硬件高速緩存Cache,
加速對(duì)內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴(yán)格按照順序執(zhí)行,沒有相關(guān)性
的指令可以亂序執(zhí)行,以充分利用CPU的指令流水線,提高執(zhí)行速度。以上是硬件級(jí)別的優(yōu)化。
再看軟件一級(jí)的優(yōu)化:一種是在編寫代碼時(shí)由程序員優(yōu)化,另一種是由編譯器進(jìn)行優(yōu)化。編譯器
優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器;調(diào)整指令順序充分利用CPU指令流水線,常見的
是重新排序讀寫指令。
對(duì)常規(guī)內(nèi)存進(jìn)行優(yōu)化的時(shí)候,這些優(yōu)化是透明的,而且效率很好。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory?barrier),linux提供了一個(gè)宏解決編譯器的執(zhí)行順序問題。
void??Barrier(void)
這個(gè)函數(shù)通知編譯器插入一個(gè)內(nèi)存屏障,但對(duì)硬件無效,編譯后的代碼會(huì)把當(dāng)前CPU
寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時(shí)候再重新從內(nèi)存中讀出。
?2.3.5.2.2??????C?語言關(guān)鍵字volatile
C?語言關(guān)鍵字volatile(注意它是用來修飾變量而不是上面介紹的__volatile__)表明某個(gè)變量
的值可能在外部被改變,因此對(duì)這些變量的存取不能緩存到寄存器,每次使用時(shí)需要重新存取。
該關(guān)鍵字在多線程環(huán)境下經(jīng)常使用,因?yàn)樵诰帉懚嗑€程的程序時(shí),同一個(gè)變量可能被多個(gè)線程修
改,而程序通過該變量同步各個(gè)線程,例如:
??DWORD?__stdcall?threadFunc(LPVOID?signal)
{
int*??intSignal=reinterpret_cast