汇编语言---GCC内联汇编
GCC支持在C/C++代碼中嵌入?yún)R編代碼,這些代碼被稱(chēng)作是"GCC Inline ASM"(GCC內(nèi)聯(lián)匯編);
一、基本內(nèi)聯(lián)匯編(寄存器前一個(gè)%)
GCC中基本的內(nèi)聯(lián)匯編非常易懂,格式如下:__asm__ [__volatile__] ("instruction list");
其中,
1.__asm__:
它是GCC定義的關(guān)鍵字asm的宏定義(#define __asm__ asm),它用來(lái)聲明一個(gè)內(nèi)聯(lián)匯編表達(dá)式,所以,任何一個(gè)內(nèi)聯(lián)匯編表達(dá)式都以它開(kāi)頭,它是必不可少的;如果要編寫(xiě)符合ANSI C標(biāo)準(zhǔn)的代碼(即:與ANSI C兼容),那就要使用__asm__;
2.__volatile__:
它是GCC關(guān)鍵字volatile的宏定義;這個(gè)選項(xiàng)是可選的;它向GCC聲明"不要?jiǎng)游宜鶎?xiě)的instruction list,我需要原封不動(dòng)地保留每一條指令";如果不使用__volatile__,則當(dāng)你使用了優(yōu)化選項(xiàng)-O進(jìn)行優(yōu)化編譯時(shí),GCC將會(huì)根據(jù)自己的判斷來(lái)決定是否將這個(gè)內(nèi)聯(lián)匯編表達(dá)式中的指令優(yōu)化掉;如果要編寫(xiě)符合ANSI C標(biāo)準(zhǔn)的代碼(即:與ANSI C兼容),那就要使用__volatile__;
3.instruction list:
它是匯編指令列表;它可以是空列表,比如:__asm__ __volatile__("");或__asm__("");都是合法的內(nèi)聯(lián)匯編表達(dá)式,只不過(guò)這兩條語(yǔ)句什么都不做,沒(méi)有什么意義;但并非所有"instruction list"為空的內(nèi)聯(lián)匯編表達(dá)式都是沒(méi)意義的,比如:__asm__("":::"memory");就是非常有意義的,它向GCC聲明:"我對(duì)內(nèi)存做了改動(dòng)",這樣,GCC在編譯的時(shí)候,就會(huì)將此因素考慮進(jìn)去;
例如:
__asm__("movl %esp,%eax");
或者是
__asm__("movl $1,%eax
? ? ? xor %ebx,%ebx
? ? ? int $0x80");
或者是
__asm__("movl $1,%eax\n\t"
? ? ?"xor %ebx,%ebx\n\t"
? ? ?"int $0x80");
instruction list的編寫(xiě)規(guī)則:當(dāng)指令列表里面有多條指令時(shí),可以在一對(duì)雙引號(hào)中全部寫(xiě)出,也可將一條或多條指令放在一對(duì)雙引號(hào)中,所有指令放在多對(duì)雙引號(hào)中;如果是將所有指令寫(xiě)在一對(duì)雙引號(hào)中,那么,相鄰倆條指令之間必須用分號(hào)";"或換行符(\n)隔開(kāi),如果使用換行符(\n),通常\n后面還要跟一個(gè)\t;或者是相鄰兩條指令分別單獨(dú)寫(xiě)在兩行中;
規(guī)則1:任意兩條指令之間要么被分號(hào)(;)或換行符(\n)或(\n\t)分隔開(kāi),要么單獨(dú)放在兩行;
規(guī)則2:單獨(dú)放在兩行的方法既可以通過(guò)\n或\n\t的方法來(lái)實(shí)現(xiàn),也可以真正地放在兩行;
規(guī)則3:可以使用1對(duì)或多對(duì)雙引號(hào),每1對(duì)雙引號(hào)里面可以放1條或多條指令,所有的指令都要放在雙引號(hào)中;
例如,下面的內(nèi)聯(lián)匯編語(yǔ)句都是合法的:
__asm__("movl %eax,%ebx
? ? ? sti
? ? ? popl %edi
? ? ? subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti
? ? ? popl %edi; subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti\n\t popl %edi
? ? ? subl %ecx,%ebx");
如果將指令放在多對(duì)雙引號(hào)中,則,除了最后一對(duì)雙引號(hào)之外,前面的所有雙引號(hào)里的最后一條指令后面都要有一個(gè)分號(hào)(;)或(\n)或(\n\t);比如,下面的內(nèi)聯(lián)匯編語(yǔ)句都是合法的:
__asm__("movl %eax,%ebx
? ? ? sti\n"
? ? ?"popl %edi;"
? ? ?"subl %ecx,%bx");
__asm__("movl %eax,%ebx; sti\n\t"
? ? ?"popl %edi; subl %ecx,%ebx");
__asm__("movl %eax,%ebx; sti\n\t popl %edi\n"
? ? ?"subl %ecx,%ebx");?
二、帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編(寄存器器前兩個(gè)%)
GCC允許你通過(guò)C/C++表達(dá)式指定內(nèi)聯(lián)匯編中"instruction list"中的指令的輸入和輸出,你甚至可以不關(guān)心到底使用哪些寄存器,完全依靠GCC來(lái)安排和指定;這一點(diǎn)可以讓程序員免去考慮有限的寄存器的使用,也可以提高目標(biāo)代碼的效率;1.帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編語(yǔ)句的格式:
__asm__ [__volatile__]("instruction list":Output:Input:Clobber/Modify);
圓括號(hào)中的內(nèi)容被冒號(hào)":"分為四個(gè)部分:
A.如果第四部分的"Clobber/Modify"可以為空;如果"Clobber/Modify"為空,則其前面的冒號(hào)(:)必須省略;比如:語(yǔ)句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp):);是非法的,而語(yǔ)句__asm__("movl %%eax,%%ebx":"=b"(foo):"a"(inp));則是合法的;
B.如果第一部分的"instruction list"為空,則input、output、Clobber/Modify可以為空,也可以不為空;比如,語(yǔ)句__asm__("":::"memory");和語(yǔ)句__asm__(""::);都是合法的寫(xiě)法;
C.如果Output、Input和Clobber/Modify都為空,那么,Output、Input之前的冒號(hào)(:)可以省略,也可以不省略;如果都省略,則此匯編就退化為一個(gè)基本匯編,否則,仍然是一個(gè)帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編,此時(shí)"instruction list"中的寄存器的寫(xiě)法要遵循相關(guān)規(guī)定,比如:寄存器名稱(chēng)前面必須使用兩個(gè)百分號(hào)(%%);基本內(nèi)聯(lián)匯編中的寄存器名稱(chēng)前面只有一個(gè)百分號(hào)(%);比如,語(yǔ)句__asm__("movl %%eax,%%ebx"::);__asm__("movl %%eax,%%ebx":);和語(yǔ)句__asm__("movl %%eax,%%ebx");都是正確的寫(xiě)法,而語(yǔ)句__asm__("movl %eax,%ebx"::);__asm__("movl %eax,%ebx":);和語(yǔ)句__asm__("movl %%eax,%%ebx");都是錯(cuò)誤的寫(xiě)法;
D.如果Input、Clobber/Modify為空,但Output不為空,則,Input前面的冒號(hào)(:)可以省略,也可以不省略;比如,語(yǔ)句__asm__("movl %%eax,%%ebx":"=b"(foo):);和語(yǔ)句__asm__("movl %%eax,%%ebx":"=b"(foo));都是正確的;
E.如果后面的部分不為空,而前面的部分為空,則,前面的冒號(hào)(:)都必須保留,否則無(wú)法說(shuō)明不為空的部分究竟是第幾部分;比如,Clobber/Modify、Output為空,而Input不為空,則Clobber/Modify前面的冒號(hào)必須省略,而Output前面的冒號(hào)必須保留;如果Clobber/Modify不為空,而Input和Output都為空,則Input和Output前面的冒號(hào)都必須保留;比如,語(yǔ)句__asm__("movl %%eax,%%ebx"::"a"(foo));和__asm__("movl %%eax,%%ebx":::"ebx");
注意:基本內(nèi)聯(lián)匯編中的寄存器名稱(chēng)前面只能有一個(gè)百分號(hào)(%),而帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編中的寄存器名臣前面必須有兩個(gè)百分號(hào)(%%);
2.Output:
Output部分用來(lái)指定當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句的輸出,稱(chēng)為輸出表達(dá)式;
格式為:?"操作約束"(輸出表達(dá)式)
例如:
__asm__("movl %%rc0,%1":"=a"(cr0));
這個(gè)語(yǔ)句中的Output部分就是("=a"(cr0)),它是一個(gè)操作表達(dá)式,指定了一個(gè)內(nèi)聯(lián)匯編語(yǔ)句的輸出部分;
Output部分由兩個(gè)部分組成:由雙引號(hào)括起來(lái)的部分和由圓括號(hào)括起來(lái)的部分,這兩個(gè)部分是一個(gè)Output部分所不可缺少的部分;
用圓括號(hào)括起來(lái)的部分就是C/C++表達(dá)式,它用于保存當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句的一個(gè)輸出值,其操作就是C/C++賦值語(yǔ)句"="的左值部分,因此,圓括號(hào)中指定的表達(dá)式只能是C/C++中賦值語(yǔ)句的左值表達(dá)式,即:放在等號(hào)=左邊的表達(dá)式;也就是說(shuō),Output部分只能作為C/C++賦值操作左邊的表達(dá)式使用;
用雙引號(hào)括起來(lái)的部分就指定了C/C++中賦值表達(dá)式的右值來(lái)源;這個(gè)部分被稱(chēng)作是"操作約束"(Operation Constraint),也可以稱(chēng)為"輸出約束";在這個(gè)例子中的操作約束是"=a",這個(gè)操作約束包含兩個(gè)組成部分:等號(hào)(=)和字母a,其中,等號(hào)(=)說(shuō)明圓括號(hào)中的表達(dá)式cr0是一個(gè)只寫(xiě)的表達(dá)式,只能被用作當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句的輸出,而不能作為輸入;字母a是寄存器EAX/AX/AL的縮寫(xiě),說(shuō)明cr0的值要從寄存器EAX中獲取,也就是說(shuō)cr0=eax,最終這一點(diǎn)被轉(zhuǎn)化成匯編指令就是:movl %eax,address_of_cr0;
注意:很多文檔中都聲明,所有輸出操作的的操作約束都必須包含一個(gè)等號(hào)(=),但是GCC的文檔中卻明確地聲明,并非如此;因?yàn)榈忍?hào)(=)約束說(shuō)明當(dāng)前的表達(dá)式是一個(gè)只寫(xiě)的,但是還有另外一個(gè)符號(hào):加號(hào)(+),也可以用來(lái)說(shuō)明當(dāng)前表達(dá)式是可讀可寫(xiě)的;如果一個(gè)操作約束中沒(méi)有給出這兩個(gè)符號(hào)中的任何一個(gè),則說(shuō)明當(dāng)前表達(dá)式是只讀的;因此,對(duì)于輸出操作來(lái)說(shuō),肯定必須是可寫(xiě)的,而等號(hào)(=)和加號(hào)(+)都可表示可寫(xiě),只不過(guò)加號(hào)(+)同時(shí)也可以表示可讀;所以,對(duì)于一個(gè)輸出操作來(lái)說(shuō),其操作約束中只要包含等號(hào)(=)或加號(hào)(+)中的任意一個(gè)就可以了;
等號(hào)(=)與加號(hào)(+)的區(qū)別:等號(hào)(=)表示當(dāng)前表達(dá)式是一個(gè)純粹的輸出操作,而加號(hào)(+)則表示當(dāng)前表達(dá)式不僅僅是一個(gè)輸出操作,還是一個(gè)輸入操作;但無(wú)論是等號(hào)(=)還是加號(hào)(+),所表示的都是可寫(xiě),只能用于輸出,只能出現(xiàn)在Output部分,而不能出現(xiàn)在Input部分;
在Output部分可以出現(xiàn)多個(gè)輸出操作表達(dá)式,多個(gè)輸出操作表達(dá)式之間必須用逗號(hào)(,)隔開(kāi);
3、Input:
Input部分用來(lái)指定當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句的輸入;稱(chēng)為輸入表達(dá)式;
格式為:?"操作約束"(輸入表達(dá)式)
例如:
__asm__("movl %0,%%db7"::"a"(cpu->db7));
其中,表達(dá)式"a"(cpu->db7)就稱(chēng)為輸入表達(dá)式,用于表示一個(gè)對(duì)當(dāng)前內(nèi)聯(lián)匯編的輸入;
Input同樣也由兩部分組成:由雙引號(hào)括起來(lái)的部分和由圓括號(hào)括起來(lái)的部分;這兩個(gè)部分對(duì)于當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句的輸入來(lái)說(shuō)也是必不可少的;
在這個(gè)例子中,由雙引號(hào)括起來(lái)的部分是"a",用圓括號(hào)括起來(lái)的部分是(cpu->db7);
用雙引號(hào)括起來(lái)的部分就是C/C++表達(dá)式賦值語(yǔ)句的右值部分,它為當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句提供一個(gè)輸入值;在這里,圓括號(hào)中的表達(dá)式cpu->db7是一個(gè)C/C++語(yǔ)言的表達(dá)式,它不必是左值表達(dá)式,也就是說(shuō),它不僅可以是放在C/C++賦值操作左邊的表達(dá)式,還可以是放在C/C++賦值操作右邊的表達(dá)式;所以,Input可以是一個(gè)變量、一個(gè)數(shù)字,還可以是一個(gè)復(fù)雜的表達(dá)式(如:a+b/c*d);
比如,上例還可以這樣寫(xiě):
__asm__("movl %0,%%db7"::"a"(foo));__asm__("movl %0,%%db7"::"a"(0x12345));__asm__("movl %0,%%db7"::"a"(va:vb/vc));
用雙引號(hào)括起來(lái)的部分就是C/C++中賦值表達(dá)式的右值表達(dá)式,用于約束當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句中的當(dāng)前輸入;這個(gè)部分也成為"操作約束",也可以成為是"輸入約束";與輸出表達(dá)式中的操作約束不同的是,輸入表達(dá)式中的操作約束不允許指定等號(hào)(=)約束或加號(hào)(+)約束,也就是說(shuō),它只能是只讀的;約束中必須指定一個(gè)寄存器約束;例子中的字母a表示當(dāng)前輸入變量cpu->db7要通過(guò)寄存器EAX輸入到當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句中;
三、操作約束:Operation Constraint
操作約束只會(huì)出現(xiàn)在帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編語(yǔ)句中;每一個(gè)Input和Output表達(dá)式都必須指定自己的操作約束Operation Constraint;約束的類(lèi)型有:寄存器約束、內(nèi)存約束、立即數(shù)約束、通用約束;
操作表達(dá)式的格式:
"約束"(C/C++表達(dá)式)
即:"Constraint"(C/C++ expression)
1.寄存器約束:
當(dāng)你的輸入或輸出需要借助于一個(gè)寄存器時(shí),你需要為其指定一個(gè)寄存器約束;
可以直接指定一個(gè)寄存器名字;比如:
__asm__ __volatile__("movl %0,%%cr0"::"eax"(cr0));
也可以指定寄存器的縮寫(xiě)名稱(chēng);比如:
__asm__ __volatile__("movl %0,%%cr0"::"a"(cr0));
如果指定的是寄存器的縮寫(xiě)名稱(chēng),比如:字母a;那么,GCC將會(huì)根據(jù)當(dāng)前操作表達(dá)式中C/C++表達(dá)式的寬度來(lái)決定使用%eax、%ax還是%al;比如:
unsigned short __shrt;
__asm__ __volatile__("movl %0,%%bx"::"a"(__shrt));
由于變量__shrt是16位無(wú)符號(hào)類(lèi)型m大小是兩個(gè)字節(jié),所以,編譯器編譯出來(lái)的匯編代碼中,則會(huì)讓此變量使用寄存器%ax;
無(wú)論是Input還是Output操作約束,都可以使用寄存器約束;
常用的寄存器約束的縮寫(xiě):
r:I/O,表示使用一個(gè)通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中選取一個(gè)GCC認(rèn)為是合適的;
q:I/O,表示使用一個(gè)通用寄存器,與r的意義相同;
g:I/O,表示使用寄存器或內(nèi)存地址;
m:I/O,表示使用內(nèi)存地址;
a:I/O,表示使用%eax/%ax/%al;
b:I/O,表示使用%ebx/%bx/%bl;
c:I/O,表示使用%ecx/%cx/%cl;
d:I/O,表示使用%edx/%dx/%dl;
D:I/O,表示使用%edi/%di;
S:I/O,表示使用%esi/%si;
f:I/O,表示使用浮點(diǎn)寄存器;
t:I/O,表示使用第一個(gè)浮點(diǎn)寄存器;
u:I/O,表示使用第二個(gè)浮點(diǎn)寄存器;
A:I/O,表示把%eax與%edx組合成一個(gè)64位的整數(shù)值;
o:I/O,表示使用一個(gè)內(nèi)存位置的偏移量;
V:I/O,表示僅僅使用一個(gè)直接內(nèi)存位置;
i:I/O,表示使用一個(gè)整數(shù)類(lèi)型的立即數(shù);
n:I/O,表示使用一個(gè)帶有已知整數(shù)值的立即數(shù);
F:I/O,表示使用一個(gè)浮點(diǎn)類(lèi)型的立即數(shù);
2.內(nèi)存約束:
如果一個(gè)Input/Output操作表達(dá)式的C/C++表達(dá)式表現(xiàn)為一個(gè)內(nèi)存地址(指針變量),不想借助于任何寄存器,則可以使用內(nèi)存約束;比如:
__asm__("lidt %0":"=m"(__idt_addr));或__asm__("lidt %0"::"m"(__idt_addr));
內(nèi)存約束使用約束名"m",表示的是使用系統(tǒng)支持的任何一種內(nèi)存方式,不需要借助于寄存器;
使用內(nèi)存約束方式進(jìn)行輸入輸出時(shí),由于不借助于寄存器,所以,GCC不會(huì)按照你的聲明對(duì)其做任何的輸入輸出處理;GCC只會(huì)直接拿來(lái)使用,對(duì)這個(gè)C/C++表達(dá)式而言,究竟是輸入還是輸出,完全依賴(lài)于你寫(xiě)在"instruction list"中的指令對(duì)其操作的方式;所以,不管你把操作約束和操作表達(dá)式放在Input部分還是放在Output部分,GCC編譯生成的匯編代碼都是一樣的,程序的執(zhí)行結(jié)果也都是正確的;本來(lái)我們將一個(gè)操作表達(dá)式放在Input或Output部分是希望GCC能為我們自動(dòng)通過(guò)寄存器將表達(dá)式的值輸入或輸出;既然對(duì)于內(nèi)存約束類(lèi)型的操作表達(dá)式來(lái)說(shuō),GCC不會(huì)為它做任何事情,那么放在哪里就無(wú)所謂了;但是從程序員的角度來(lái)看,為了增強(qiáng)代碼的可讀性,最好能夠把它放在符合實(shí)際情況的地方;
3.立即數(shù)約束:
如果一個(gè)Input/Output操作表達(dá)式的C/C++表達(dá)式是一個(gè)數(shù)字常數(shù),不想借助于任何寄存器或內(nèi)存,則可以使用立即數(shù)約束;
由于立即數(shù)在C/C++表達(dá)式中只能作為右值使用,所以,對(duì)于使用立即數(shù)約束的表達(dá)式而言,只能放在Input部分;比如:
__asm__ __volatile__("movl %0,%%eax"::"i"(100));
立即數(shù)約束使用約束名"i"表示輸入表達(dá)式是一個(gè)整數(shù)類(lèi)型的立即數(shù),不需要借助于任何寄存器,只能用于Input部分;使用約束名"F"表示輸入表達(dá)式是一個(gè)浮點(diǎn)數(shù)類(lèi)型的立即數(shù),不需要借助于任何寄存器,只能用于Input部分;
4.通用約束:
約束名"g"可以用于輸入和輸出,表示可以使用通用寄存器、內(nèi)存、立即數(shù)等任何一種處理方式;
約束名"0,1,2,3,4,5,6,7,8,9"只能用于輸入,表示與第n個(gè)操作表達(dá)式使用相同的寄存器/內(nèi)存;
通用約束"g"是一個(gè)非常靈活的約束,當(dāng)程序員認(rèn)為一個(gè)C/C++表達(dá)式在實(shí)際操作中,無(wú)論使用寄存器方式、內(nèi)存方式還是立即數(shù)方式都無(wú)所謂時(shí),或者程序員想實(shí)現(xiàn)一個(gè)靈活的模板,以讓GCC可以根據(jù)不同的C/C++表達(dá)式生成不同的訪(fǎng)問(wèn)方式時(shí),就可以使用通用約束g;
例如:
#define JUST_MOV(foo) __asm__("movl %0,%%eax"::"g"(foo))
則,JUST_MOV(100)和JUST_MOV(var)就會(huì)讓編譯器產(chǎn)生不同的匯編代碼;
對(duì)于JUST_MOV(100)的匯編代碼為:
#APP
?movl $100,%eax????? #立即數(shù)方式;
#NO_APP
對(duì)于JUST_MOV(var)的匯編代碼為:
#APP
?movl 8(%ebp),%eax?? #內(nèi)存方式;
#NO_APP
像這樣的效果,就是通用約束g的作用;
5.修飾符:
等號(hào)(=)和加號(hào)(+)作為修飾符,只能用于Output部分;等號(hào)(=)表示當(dāng)前輸出表達(dá)式的屬性為只寫(xiě),加號(hào)(+)表示當(dāng)前輸出表達(dá)式的屬性為可讀可寫(xiě);這兩個(gè)修飾符用于約束對(duì)輸出表達(dá)式的操作,它們倆被寫(xiě)在輸出表達(dá)式的約束部分中,并且只能寫(xiě)在第一個(gè)字符的位置;
符號(hào)&也寫(xiě)在輸出表達(dá)式的約束部分,用于約束寄存器的分配,但是只能寫(xiě)在約束部分的第二個(gè)字符的位置上;
用符號(hào)&進(jìn)行修飾時(shí),等于向GCC聲明:"GCC不得為任何Input操作表達(dá)式分配與此Output操作表達(dá)式相同的寄存器";
其原因是修飾符&意味著被其修飾的Output操作表達(dá)式要在所有的Input操作表達(dá)式被輸入之前輸出;
即:GCC會(huì)先使用輸出值對(duì)被修飾符&修飾的Output操作表達(dá)式進(jìn)行填充,然后,才對(duì)Input操作表達(dá)式進(jìn)行輸入;
這樣的話(huà),如果不使用修飾符&對(duì)Output操作表達(dá)式進(jìn)行修飾,一旦后面的Input操作表達(dá)式使用了與Output操作表達(dá)式相同的寄存器,就會(huì)產(chǎn)生輸入輸出數(shù)據(jù)混亂的情況;相反,如果沒(méi)有用修飾符&修飾輸出操作表達(dá)式,那么,就意味著GCC會(huì)先把Input操作表達(dá)式的值輸入到選定的寄存器中,然后經(jīng)過(guò)處理,最后才用輸出值填充對(duì)應(yīng)的Output操作表達(dá)式;
所以,修飾符&的作用就是要求GCC編譯器為所有的Input操作表達(dá)式分配別的寄存器,而不會(huì)分配與被修飾符&修飾的Output操作表達(dá)式相同的寄存器;修飾符&也寫(xiě)在操作約束中,即:&約束;由于GCC已經(jīng)規(guī)定加號(hào)(+)或等號(hào)(=)占據(jù)約束的第一個(gè)字符,那么&約束只能占用第二個(gè)字符;
例如:
int __out, __in1, __in2;
__asm__("popl %0\n\t"
??????? "movl %1,%%esi\n\t"
??????? "movl %2,%%edi\n\t"
??????? :"=&a"(__out)
??????? :"r"(__in1),"r"(__in2));
注意:如果一個(gè)Output操作表達(dá)式的寄存器約束被指定為某個(gè)寄存器,只有當(dāng)至少存在一個(gè)Input操作表達(dá)式的寄存器約束為可選約束(意思是GCC可以從多個(gè)寄存器中選取一個(gè),或使用非寄存器方式)時(shí),比如"r"或"g"時(shí),此Output操作表達(dá)式使用符號(hào)&修飾才有意義;如果你為所有的Input操作表達(dá)式指定了固定的寄存器,或使用內(nèi)存/立即數(shù)約束時(shí),則此Output操作表達(dá)式使用符號(hào)&修飾沒(méi)有任何意義;
比如:
__asm__("popl %0\n\t"
??????? "movl %1,%esi\n\t"
??????? "movl %2,%edi\n\t"
??????? :"=&a"(__out)
??????? :"m"(__in1),"c"(__in2));
此例中的Output操作表達(dá)式完全沒(méi)有必要使用符號(hào)&來(lái)修飾,因?yàn)開(kāi)_in1和__in2都已經(jīng)被指定了固定的寄存器,或使用了內(nèi)存方式,GCC無(wú)從選擇;
如果你已經(jīng)為某個(gè)Output操作表達(dá)式指定了修飾符&,并指定了固定的寄存器,那么,就不能再為任何Input操作表達(dá)式指定這個(gè)寄存器了,否則會(huì)出現(xiàn)編譯報(bào)錯(cuò);
比如:
__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&a"(__out):"a"(__in1),"c"(__in2));
對(duì)這條語(yǔ)句的編譯就會(huì)報(bào)錯(cuò);
相反,你也可以為Output指定可選約束,比如"r"或"g"等,讓GCC為此Output操作表達(dá)式選擇合適的寄存器,或使用內(nèi)存方式,GCC在選擇的時(shí)候,會(huì)排除掉已經(jīng)被Input操作表達(dá)式所使用過(guò)的所有寄存器,然后在剩下的寄存器中選擇,或者干脆使用內(nèi)存方式;
比如:
__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&r"(__out):"a"(__in1),"c"(__in2));
這三個(gè)修飾符只能用在Output操作表達(dá)式中,而修飾符%則恰恰相反,它只能用在Input操作表達(dá)式中;
修飾符%用于向GCC聲明:"當(dāng)前Input操作表達(dá)式中的C/C++表達(dá)式可以與下一個(gè)Input操作表達(dá)式中的C/C++表達(dá)式互換";這個(gè)修飾符一般用于符合交換律運(yùn)算的地方;比如:加、乘、按位與&、按位或|等等;
例如:
__asm__("addl %1,%0\n\t":"=r"(__out):"%r"(__in1),"0"(__in2));
其中,"0"(__in2)表示使用與第一個(gè)Input操作表達(dá)式("r"(__in1))相同的寄存器或內(nèi)存;
由于使用符號(hào)%修飾__in1的寄存器方式r,那么就表示,__in1與__in2可以互換位置;加法的兩個(gè)操作數(shù)交換位置之后,和不變;
修飾符? I/O? 意義
=??????? O??? 表示此Output操作表達(dá)式是只寫(xiě)的
+??????? O??? 表示此Output操作表達(dá)式是可讀可寫(xiě)的
&??????? O??? 表示此Output操作表達(dá)式獨(dú)占為其指定的寄存器
%??????? I??? 表示此Input操作表達(dá)式中的C/C++表達(dá)式可以與下一個(gè)Input操作表達(dá)式中的C/C++表達(dá)式互換
四、占位符
每一個(gè)占位符對(duì)應(yīng)一個(gè)Input/Output操作表達(dá)式;帶C/C++表達(dá)式的內(nèi)聯(lián)匯編中有兩種占位符:序號(hào)占位符和名稱(chēng)占位符;
1.序號(hào)占位符:
GCC規(guī)定:一個(gè)內(nèi)聯(lián)匯編語(yǔ)句中最多只能有10個(gè)Input/Output操作表達(dá)式,這些操作表達(dá)式按照他們被列出來(lái)的順序依次賦予編號(hào)0到9;對(duì)于占位符中的數(shù)字而言,與這些編號(hào)是對(duì)應(yīng)的;比如:占位符%0對(duì)應(yīng)編號(hào)為0的操作表達(dá)式,占位符%1對(duì)應(yīng)編號(hào)為1的操作表達(dá)式,依次類(lèi)推;
由于占位符前面要有一個(gè)百分號(hào)%,為了去邊占位符與寄存器,GCC規(guī)定:在帶有C/C++表達(dá)式的內(nèi)聯(lián)匯編語(yǔ)句的指令列表里列出的寄存器名稱(chēng)前面必須使用兩個(gè)百分號(hào)(%%),一區(qū)別于占位符語(yǔ)法;
GCC對(duì)占位符進(jìn)行編譯的時(shí)候,會(huì)將每一個(gè)占位符替換為對(duì)應(yīng)的Input/Output操作表達(dá)式所指定的寄存器/內(nèi)存/立即數(shù);
例如:
__asm__("addl %1,%0\n\t":"=a"(__out):"m"(__in1),"a"(__in2));
這個(gè)語(yǔ)句中,%0對(duì)應(yīng)Output操作表達(dá)式"=a"(__out),而"=a"(__out)指定的寄存器是%eax,所以,占位符%0被替換為%eax;占位符%1對(duì)應(yīng)Input操作表達(dá)式"m"(__in1),而"m"(__in1)被指定為內(nèi)存,所以,占位符%1被替換位__in1的內(nèi)存地址;
用一句話(huà)描述:序號(hào)占位符就是前面描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9;其中,每一個(gè)占位符對(duì)應(yīng)一個(gè)Input/Output的C/C++表達(dá)式;
2.名稱(chēng)占位符:
由于GCC中限制這種占位符的個(gè)數(shù)最多只能由這10個(gè),這也就限制了Input/Output操作表達(dá)式中C/C++表達(dá)式的數(shù)量做多只能有10個(gè);如果需要的C/C++表達(dá)式的數(shù)量超過(guò)10個(gè),那么,這些需要占位符就不夠用了;
GCC內(nèi)聯(lián)匯編提供了名稱(chēng)占位符來(lái)解決這個(gè)問(wèn)題;即:使用一個(gè)名字字符串與一個(gè)C/C++表達(dá)式對(duì)應(yīng);這個(gè)名字字符串就稱(chēng)為名稱(chēng)占位符;而這個(gè)名字通常使用與C/C++表達(dá)式中的變量完全相同的名字;
使用名字占位符時(shí),內(nèi)聯(lián)匯編的Input/Output操作表達(dá)式中的C/C++表達(dá)式的格式如下:
[name] "constraint"(變量)
此時(shí),指令列表中的占位符的書(shū)寫(xiě)格式如下:
%[name]
這個(gè)格式等價(jià)于序號(hào)占位符中的%0,%1,$2等等;
使用名稱(chēng)占位符時(shí),一個(gè)name對(duì)應(yīng)一個(gè)變量;
例如:
__asm__("imull %[value1],%[value2]"
??????? :[value2] "=r"(data2)
??????? :[value1] "r"(data1),"0"(data2));
此例中,名稱(chēng)占位符value1就對(duì)應(yīng)變量data1,名稱(chēng)占位符value2對(duì)應(yīng)變量data2;GCC編譯的時(shí)候,同樣會(huì)把這兩個(gè)占位符分別替換成對(duì)應(yīng)的變量所使用的寄存器/內(nèi)存地址/立即數(shù);而且也增強(qiáng)了代碼的可讀性;
這個(gè)例子,使用序號(hào)占位符的寫(xiě)法如下:
__asm__("imull %1,%0"
??????? :"=r"(data2)
??????? :"r"(data1),"0"(data2));
五、寄存器/內(nèi)存修改標(biāo)示(Clobber/Modify)
有時(shí)候,當(dāng)你想通知GCC當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句可能會(huì)對(duì)某些寄存器或內(nèi)存進(jìn)行修改,希望GCC在編譯時(shí)能夠?qū)⑦@一點(diǎn)考慮進(jìn)去;那么你就可以在Clobber/Modify部分聲明這些寄存器或內(nèi)存;1.寄存器修改通知:
這種情況一般發(fā)生在一個(gè)寄存器出現(xiàn)在指令列表中,但又不是Input/Output操作表達(dá)式所指定的,也不是在一些Input/Output操作表達(dá)式中使用"r"或"g"約束時(shí)由GCC選擇的,同時(shí),此寄存器被指令列表中的指令所修改,而這個(gè)寄存器只供當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句使用的情況;比如:
__asm__("movl %0,%%ebx"::"a"(__foo):"bx");
//這個(gè)內(nèi)聯(lián)匯編語(yǔ)句中,%ebx出現(xiàn)在指令列表中,并且被指令修改了,但是卻未被任何Input/Output操作表達(dá)式是所指定,所以,你需要在Clobber/Modify部分指定"bx",以讓GCC知道這一點(diǎn);
因?yàn)槟阍贗nput/Output操作表達(dá)式中指定的寄存器,或當(dāng)你為一些Input/Output操作表達(dá)式使用"r"/"g"約束,讓GCC為你選擇一個(gè)寄存器時(shí),GCC對(duì)這些寄存器的狀態(tài)是非常清楚的,它知道這些寄存器是被修改的,你根本不需要在Clobber/Modify部分聲明它們;但除此之外,GCC對(duì)剩下的寄存器中哪些會(huì)被當(dāng)前內(nèi)聯(lián)匯編語(yǔ)句所修改則一無(wú)所知;所以,如果你真的在當(dāng)前內(nèi)聯(lián)匯編指令中修改了它們,那么就最好在Clobber/Modify部分聲明它們,讓GCC針對(duì)這些寄存器做相應(yīng)的處理;否則,有可能會(huì)造成寄存器不一致,從而造成程序執(zhí)行錯(cuò)誤;
在Clobber/Modify部分聲明這些寄存器的方法很簡(jiǎn)單,只需要將寄存器的名字用雙引號(hào)括起來(lái)就可以;如果要聲明多個(gè)寄存器,則相鄰兩個(gè)寄存器名字之間用逗號(hào)隔開(kāi);
例如:
__asm__("movl %0,%%ebx; popl %%ecx"::"a"(__foo):"bx","cx");
這個(gè)語(yǔ)句中,聲明了bx和cx,告訴GCC:寄存器%ebx和%ecx可能會(huì)被修改,要求GCC考慮這個(gè)因素;
寄存器名稱(chēng)串:
"al"/"ax"/"eax":代表寄存器%eax
"bl"/"bx"/"ebx":代表寄存器%ebx
"cl"/"cx"/"ecx":代表寄存器%ecx
"dl"/"dx"/"edx":代表寄存器%edx
"si"/"esi":代表寄存器%esi
"di"/"edi":代表寄存器%edi
所以,只需要使用"ax","bx","cx","dx","si","di"就可以了,因?yàn)樗麄兌即韺?duì)應(yīng)的寄存器;
如果你在一個(gè)內(nèi)斂匯編語(yǔ)句的Clobber/Modify部分向GCC聲明了某個(gè)寄存器內(nèi)存發(fā)生了改變,GCC在編譯時(shí),如果發(fā)現(xiàn)這個(gè)被聲明的寄存器的內(nèi)容在此內(nèi)聯(lián)匯編之后還要繼續(xù)使用,那么,GCC會(huì)首先將此寄存器的內(nèi)容保存起來(lái),然后在此內(nèi)聯(lián)匯編語(yǔ)句的相關(guān)代碼生成之后,再將其內(nèi)容回復(fù);
另外需要注意的是,如果你在Clobber/Modify部分聲明了一個(gè)寄存器,那么這個(gè)寄存器將不能再被用作當(dāng)前內(nèi)斂匯編語(yǔ)句的Input/Output操作表達(dá)式的寄存器約束,如果Input/Output操作表達(dá)式的寄存器約束被指定為"r"/"g",GCC也不會(huì)選擇已經(jīng)被聲明在Clobber/Modify部分中的寄存器;
例如:
__asm__("movl %0,%%ebx"::"a"(__foo):"ax","bx");
這條語(yǔ)句中的Input操作表達(dá)式"a"(__foo)中已經(jīng)指定了寄存器%eax,那么在Clobber/Modify部分中個(gè)列出的"ax"就是非法的;編譯時(shí),GCC會(huì)報(bào)錯(cuò);
2.內(nèi)存修改通知:
除了寄存器的內(nèi)容會(huì)被修改之外,內(nèi)存的內(nèi)容也會(huì)被修改;如果一個(gè)內(nèi)聯(lián)匯編語(yǔ)句的指令列表中的指令對(duì)內(nèi)存進(jìn)行了修改,或者在此內(nèi)聯(lián)匯編出現(xiàn)的地方,內(nèi)存內(nèi)容可能發(fā)生改變,而被改變的內(nèi)存地址你沒(méi)有在其Output操作表達(dá)式中使用"m"約束,這種情況下,你需要使用在Clobber/Modify部分使用字符串"memory"向GCC聲明:"在這里,內(nèi)存發(fā)生了,或可能發(fā)生了改變";
例如:
void* memset(void* s, char c, size_t count)
{
? __asm__("cld\n\d"
????????? "rep\n\t"
????????? "stosb"
????????? :/*no output*/
????????? :"a"(c),"D"(s),"c"(count)
????????? :"cx","di","memory");
? return s;
}
如果一個(gè)內(nèi)聯(lián)匯編語(yǔ)句的Clobber/Modify部分存在"memory",那么GCC會(huì)保證在此內(nèi)聯(lián)匯編之前,如果某個(gè)內(nèi)存的內(nèi)容被裝入了寄存器,那么,在這個(gè)內(nèi)聯(lián)匯編之后,如果需要使用這個(gè)內(nèi)存處的內(nèi)容,就會(huì)直接到這個(gè)內(nèi)存處重新讀取,而不是使用被存放在寄存器中的拷貝;因?yàn)檫@個(gè)時(shí)候寄存器中的拷貝很可能已經(jīng)和內(nèi)存處的內(nèi)容不一致了;
3.標(biāo)志寄存器修改通知:
當(dāng)一個(gè)內(nèi)聯(lián)匯編中包含影響標(biāo)志寄存器eflags的條件,那么也需要在Clobber/Modify部分中使用"cc"來(lái)向GCC聲明這一點(diǎn);
本文轉(zhuǎn)自:http://www.cnblogs.com/taek/archive/2012/02/05/2338838.html,向作者致敬
總結(jié)
以上是生活随笔為你收集整理的汇编语言---GCC内联汇编的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 知乎高赞:有哪些你看了以后大呼过瘾的数据
- 下一篇: TLS配置和流量分析实验