1. 簡(jiǎn)介(Introduction.)
1.1 Copyright and License.
Copyright (C)2017 桂糊涂
Copyright (C)2003 Sandeep S.
This document is free; you can redistribute and/or modify this under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
1.2 反饋(略)
1.3 背景(略)
希望將Windows項(xiàng)目BWAPI移植mac/linux時(shí),遇到Visual C內(nèi)聯(lián)匯編遷移到GCC的問(wèn)題,于是研習(xí)此文并譯之。
2. 概覽(Overview of the whole thing.)
- 我們?cè)诖藢W(xué)習(xí)GCC內(nèi)聯(lián)匯編。內(nèi)聯(lián)是什么?
我們可以指導(dǎo)編譯器將函數(shù)的代碼直接插入調(diào)用的位置,這類函數(shù)叫做內(nèi)聯(lián)函數(shù)。聽起來(lái)像是宏?事實(shí)上還真挺像。
- 內(nèi)聯(lián)函數(shù)有什么好處?
內(nèi)聯(lián)的方法降低了函數(shù)調(diào)用的問(wèn)題。而且如果任何參數(shù)是常量的話,在編譯器將得到明顯優(yōu)化,而不是所有的內(nèi)聯(lián)函數(shù)代碼都被包含。代碼量會(huì)更少,取決于具體的情況。為了定義內(nèi)聯(lián)函數(shù),我們使用關(guān)鍵字inline聲明。
- 什么是內(nèi)聯(lián)匯編?
內(nèi)聯(lián)匯編是寫在內(nèi)聯(lián)函數(shù)中的匯編過(guò)程(assembly routines)。它非常方便、快速,在系統(tǒng)編程中非常有用。我們主要關(guān)注學(xué)習(xí)GCC內(nèi)聯(lián)匯編函數(shù)的基礎(chǔ)格式和用法。要聲明內(nèi)聯(lián)匯編函數(shù),我們使用關(guān)鍵字asm。
內(nèi)聯(lián)匯編很重要,因?yàn)橛心芰Σ僮鞑⑤敵龅紺變量中。因?yàn)檫@些能力,asm作為了C和匯編指令間的接口。
3、GCC匯編語(yǔ)法(GCC Assembler Syntax.)
GCC使用AT&T/UNIX匯編語(yǔ)法。其與Intel語(yǔ)法區(qū)別較大,主要區(qū)別有:
3.1. 源-目標(biāo)順序(Source-Destination Ordering)
Intel:Op-code dst src
AT&T:Op-code src dst
3.2. 寄存次命名(Registry Naming)
以%為前綴,如:使用eax寫作%eax。
3.3. 立即操作數(shù)(Immediate Operands)
AT&T立即操作數(shù)以$開頭,對(duì)staic “C”變量也前置$。16進(jìn)制常量,Intel語(yǔ)法后綴h,AT&T前綴0x。所以對(duì)于16進(jìn)制數(shù),我們會(huì)先看到$,然后是0x,最后是常量。
3.4. 操作數(shù)大小(Operand Size)
譯注:操作數(shù)(operand),很多情況下指操作對(duì)象,即寄存器或內(nèi)存地址。
AT&T語(yǔ)法中操作數(shù)大小取決于操作碼最后一個(gè)字符。操作碼后綴b,w,l 對(duì)應(yīng) byte(8-bit), word(16-bit), 和 long(32-bit)。Intel語(yǔ)法中,通過(guò)在操作數(shù)(非操作碼)前綴 byte ptr, word ptr, 和 dword ptr 實(shí)現(xiàn)該功能。
因此, Intel 之 mov al, byte ptr foo 即 movb foo, %al 于 AT&T.
3.5. 內(nèi)存操作數(shù)(Memory Operands)
Intel語(yǔ)法中基址寄存器(The base register)內(nèi)于[、]之間,而AT&T于(、) 之間。此外,間接內(nèi)存引用(indirect memory reference)Intel風(fēng)格為
section:[base + index*scale + disp] ,改變?yōu)?/p>
section:disp(base, index, scale)于 AT&T.
需指出,當(dāng)常量使用disp/scale,$ 無(wú)需前置。
以上是Intel于AT&T語(yǔ)法的主要區(qū)別,完整信息請(qǐng)參加GNU Assembler documentations。以下一些例子有助于我們更好的理解:
| Intel Code | AT&T Code |
|---|---|
mov eax,1 |
movl $1,%eax |
mov ebx,0ffh |
movl $0xff,%ebx |
int 80h |
int $0x80 |
mov ebx, eax |
movl %eax, %ebx |
mov eax,[ecx] |
movl (%ecx),%eax |
mov eax,[ebx+3] |
movl 3(%ebx),%eax |
mov eax,[ebx+20h] |
movl 0x20(%ebx),%eax |
add eax,[ebx+ecx*2h] |
addl (%ebx,%ecx,0x2),%eax |
lea eax,[ebx+ecx] |
leal (%ebx,%ecx),%eax |
sub eax,[ebx+ecx*4h-20h] |
subl -0x20(%ebx,%ecx,0x4),%eax |
4. 內(nèi)聯(lián)基礎(chǔ)(Basic Inline.)
內(nèi)聯(lián)匯編的基本形式
asm("assembly code");
例:
asm("movl %ecx %eax"); /* moves the contents of ecx to eax */
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */
asm與__asm__都是合法的,如asm與你的程序沖突,你可以使用__asm__。如果有多行代碼,我們將每一個(gè)使用"包含,并后綴\n\t。因gcc將每行作為一個(gè)string到as(GAS),通過(guò)換行/tab我們可以發(fā)送正確的格式給匯編器(assembler)。
例:
__asm__ ("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");
如果我們的代碼觸及(touch)(如,改變內(nèi)容)一些寄存器,而后不修復(fù)這些改變直接從asm返回的話,一些不好的事就會(huì)發(fā)生。這是因?yàn)镚CC不知道對(duì)寄存器內(nèi)容的改變,而這將我們帶向問(wèn)題,又起當(dāng)編譯器進(jìn)行了某些優(yōu)化的時(shí)候。它將假設(shè)一些寄存器包含了一些變量的值,而我們已經(jīng)改變了沒有告知GCC, 然后它繼續(xù)執(zhí)行就像什么也沒發(fā)生一樣。我們可以做的是使用一些沒有副作用的指令,或者在我們退出前修復(fù)問(wèn)題,或者等待崩潰。這就是我們想要一些擴(kuò)展功能性(functionality)的地方。擴(kuò)展asm(Extended asm)提供了我們這種功能性。
5. 擴(kuò)展Asm(Extended Asm.)
基本匯編中我們只有指令。在擴(kuò)展匯編中,我們可以指定操作對(duì)象(operand)。它允許我們指定輸入寄存器,輸出寄存器及一列受影響(clobbered)寄存器。它不是mandatory to指定寄存器使用,我們可以將麻煩留給GCC而GCC有可能(probably)更好的適配GCC的優(yōu)化機(jī)制。反正(Anyway)基本形式如下:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
匯編模板(assembler template)由匯編指令構(gòu)成。每個(gè)操作數(shù)(operand)
描述為一個(gè)操作限制符(operand-constraint string),followed by the C expression in 括號(hào)。冒號(hào)分割匯編模板、輸出操作數(shù)組、輸入操作數(shù)組、clobbered寄存器組。逗號(hào)分割每個(gè)組內(nèi)的操作數(shù)。操作數(shù)總數(shù)限制在10個(gè)或the maximum number of operands in any instruction pattern in the machine description,whichever is greater.
如果沒有輸出操作數(shù)但有輸入操作數(shù),你必須放兩個(gè)連續(xù)冒號(hào)。
例如:
asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* no output registers */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);
以上代碼是什么作用? The above inline fills the fill_value count times to the location pointed to by the register edi. 它也同時(shí)告訴gcc, 寄存器 eax and edi 的內(nèi)容不再有效. 讓我們看看另一個(gè)例子來(lái)更好的理解:
int a=10, b;
asm ("movl %1, %%eax; movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
這里我們使用匯編指令讓b的值等于a的值。有趣的點(diǎn)是:
b 是 output operand, referred to by %0 而 a 是 input operand, referred to by %1.
r is 限制(constraints)對(duì)于 operands. 我們后面會(huì)詳細(xì)討論“限制”. 此時(shí), r 告訴 GCC 使用任意register來(lái)儲(chǔ)存操作數(shù)。輸出操作數(shù)限制應(yīng)該有一個(gè)限時(shí)修飾符=。這個(gè)修飾符意味著它是一個(gè)輸出操作數(shù)且是只寫的(write-only)。
在寄存器名稱前出現(xiàn)了兩個(gè)%。這幫助GCC來(lái)區(qū)分操作數(shù)和寄存器。操作數(shù)有一個(gè)單獨(dú)的%作為前綴。
受影響(clobbered)寄存器%eax在第三個(gè)冒號(hào)之后,告訴GCC %eax的值已在asm內(nèi)被修改,所以GCC不會(huì)使用這個(gè)寄存器去保存其他的值。
當(dāng)asm執(zhí)行結(jié)束后,b將反射更新后的值,因?yàn)樗恢付橐粋€(gè)輸出操作數(shù)。另一方面,asm內(nèi)部對(duì)b的改變應(yīng)該(is supposed to)在asm外部被反射.
現(xiàn)在我們?cè)敿?xì)的看一下每一個(gè)區(qū)域。
5.1 匯編模板(Assembler Template).
匯編模板包含一組嵌入到C程序中的指令。格式類似:或者每個(gè)指令包圍在雙引號(hào)中,或整組指令包含在雙引號(hào)中。每個(gè)指令也應(yīng)該以一個(gè)分隔符結(jié)束。合法的分隔符可以是\n和;。\n可以跟隨一個(gè)\t。C表達(dá)式的操作數(shù)呈現(xiàn)為 %0, %1 ...等。
5.2 操作數(shù)(Operands).
C expressions serve as operands for the assembly instructions inside "asm". 每個(gè)操作數(shù)首先寫作一個(gè)雙引號(hào)內(nèi)的操作數(shù)限制符(operand constraint)。 對(duì)于輸出操作數(shù), 引號(hào)內(nèi)還有一個(gè)限制修飾符, 然后跟隨操作數(shù)對(duì)應(yīng)的 C 表達(dá)式 。 即,
"constraint" (C expression) 乃通用形式。對(duì)輸出操作數(shù)會(huì)有一個(gè)額外的修飾符。限制符(constraint)主要用于決定操作數(shù)的地址模式。他們也被用于指定要使用的寄存器。
如我們使用超過(guò)一個(gè)操作數(shù),以逗號(hào),分隔。
在匯編模板中,每個(gè)操作數(shù)按數(shù)字被引用。數(shù)字按如下規(guī)則排列。如果有n個(gè)操作數(shù)(包括輸入、輸出),那么第一個(gè)輸出操作數(shù)是數(shù)字0,連續(xù)增加,最后一個(gè)輸入操作數(shù)是數(shù)字n-1。最大操作數(shù)數(shù)量如上一段所述。
輸出操作數(shù)表達(dá)式必須是lvalues(32-bit)。輸入操作數(shù)無(wú)此限制。他們必須是表達(dá)式。擴(kuò)展匯編功能是最常用于編譯器自身不知曉的機(jī)器指令;-)。如果輸出表達(dá)式無(wú)法被直接尋址(addressed)(比如,它是一個(gè)bit-field),我們限制符必須“允許”(allow)一個(gè)寄存器。在那種情況下,GCC將使用該寄存器為asm的輸出,然后將寄存器內(nèi)容存儲(chǔ)到輸出。
如上所述,原始輸出操作數(shù)必須是只寫的;GCC將假設(shè)那個(gè)操作對(duì)象中的值在指令前已失效且無(wú)需生成。擴(kuò)展匯編也支持“輸入-輸出”或“讀-寫”操作數(shù)。
我們現(xiàn)在看一些例子。我們希望將一個(gè)數(shù)乘以5。對(duì)此我們使用lea指令。
asm ("leal (%1,%1,4), %0"
: "=r" (five_times_x)
: "r" (x)
);
此處我們的輸入是x。我們沒有指定使用哪個(gè)寄存器。GCC會(huì)為輸入選擇一些寄存器用來(lái)輸入,一個(gè)用來(lái)輸出,執(zhí)行我們的要求。如果我們希望輸入和輸出放在(reside)同一個(gè)寄存器中,我們可以讓GCC來(lái)實(shí)現(xiàn)。這里我們使用那種"讀-寫"操作數(shù),通過(guò)指定合適的限制符,這里我們來(lái)實(shí)現(xiàn)它:
asm ("leal (%0,%0,4), %0"
: "=r" (five_times_x)
: "0" (x)
);
現(xiàn)在輸入和輸出操作數(shù)在同一個(gè)寄存器內(nèi)了。但我們不知道是哪個(gè)寄存器。現(xiàn)在如果我們也想要指定,有一個(gè)辦法:
asm ("leal (%%ecx,%%ecx,4), %%ecx"
: "=c" (x)
: "c" (x)
);
以上三個(gè)例子中,我們沒有把任何一個(gè)寄存器放在受影響列表中。為什么?前兩個(gè)例子中,GCC決定使用哪個(gè)寄存器,因此知道發(fā)生了什么改變。在最后一個(gè)中,我們不需要將ecx放在受影響列表中,gcc知道它會(huì)放入x中。因?yàn)樗梢灾?code>ecx的值,它不會(huì)被視為受影響的。
5.3 受影響列表(Clobber List.)
一些指令會(huì)影響一些硬件寄存器。我們必須在受影響列表中列出那些寄存器,即asm函數(shù)第三個(gè):后的區(qū)域。這用于指示gcc我們將使用并修改它們。所以gcc將補(bǔ)不回假設(shè)它加載到這些寄存器中的值是合法的。我們不應(yīng)該列出輸入和輸出寄存器。因?yàn)間cc知道asm使用它們(因?yàn)樗鼈儽幻鞔_指定為限制符(constraints))。如果指令使用了任何其他寄存器,顯式或隱式的(并且這些寄存器沒有出現(xiàn)在輸入和輸出列表上),那么那些寄存器必須在受影響列表中指定。
如果我們的指令可以修改條件碼寄存器(the condition code register),我們必須增加cc到受影響寄存器列表。
如果我們的指令用一個(gè)不可預(yù)期的方法(fashion)修改了內(nèi)存,添加memory到受影響寄存器。這會(huì)使GCC在匯編指令期間不在寄存器內(nèi)保持內(nèi)存值的緩存。我們也必須添加volatile關(guān)鍵字,如果內(nèi)存影響(memory affected)未列在asm的輸入和輸出中。
我們可以讀寫受影響寄存器任意多次。注意模板中乘法指令的例子;它假設(shè)子過(guò)程(subroutine) _foo 接受eax 、ecx寄存器中的參數(shù)。
asm ("movl %0,%%eax; movl %1,%%ecx; call _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "eax", "ecx"
);
5.4 Volatile ...? (不穩(wěn)定的...?)
如果你熟悉內(nèi)核源碼或者一些類似的優(yōu)美代碼,你必然已見過(guò)很多函數(shù)聲明為volatile或__volatile__,跟隨在__asm__之后。我之前提到過(guò)關(guān)于關(guān)鍵字asm和__asm__。所以什么是volatile?
如果我們的匯編語(yǔ)句必須在我們放置它的地方執(zhí)行,(即,必須不被作為一個(gè)優(yōu)化而移出循環(huán)),則將volatile放在asm之后。所以防止它被移動(dòng)、刪除和任何改變,我們?nèi)绱寺暶?code>asm volatile(... : ... : ... : ...); 當(dāng)我們必須非常小心時(shí),使用__volatile__。
如果我們的匯編只是做一些計(jì)算而沒有任何副作用,最好不要使用volatile關(guān)鍵字。忽略它將幫助GCC優(yōu)化代碼使其更優(yōu)美。
在“一些有用的代碼”小節(jié),我已經(jīng)提供了很多內(nèi)聯(lián)匯編函數(shù)的例子。我們可以詳細(xì)了解受影響列表。
6. 詳解限制符(More about constraints.)
此時(shí),你可能已經(jīng)理解限制符必須要做很多的事。但關(guān)于限制符我們說(shuō)的很少。限制符可以說(shuō)出操作數(shù)是否可能是一個(gè)寄存器,及哪類寄存器;操作數(shù)是否可以是一個(gè)內(nèi)存引用,及哪一類地址;操作數(shù)是否可能是一個(gè)立即常量,及它可以有哪些可能的值(即值的范圍)...等。
6.1 常用限制符(Commonly used constraints.)
有許多限制符,只有一部分是常用的。我們看一看這些限制符。
1. 寄存器操作數(shù)限制符(Register operand constraint)(r)
當(dāng)操作數(shù)指定使用此限制符時(shí),它們會(huì)存儲(chǔ)在常規(guī)寄存器中(General Purpose Registers(GPR))。如:
asm ("movl %%eax, %0\n"
:"=r"(myval)
);
此處myval變量保存在一個(gè)寄存器中,eax的值會(huì)復(fù)制到那個(gè)寄存器,而myval的值會(huì)從這個(gè)寄存器中更新到內(nèi)存。當(dāng)"r"限制符被指定后,gcc可以在任何可用的GPR中保存這個(gè)變量。要指定該寄存器,你必須使用特定寄存器限制符指定寄存器名稱。它們是:
| r | Register (s) |
|---|---|
| a | %eax, %ax, %al |
| b | %ebx, %bx, %bl |
| c | %ecx, %cx, %cl |
| d | %edx, %dx, %dl |
| S | %esi, %si |
| D | %edi, %di |
2. 內(nèi)存操作數(shù)限制符(Memory operand constraint)(m)
當(dāng)操作數(shù)是在內(nèi)存中時(shí),任何在它上的操作將直接在內(nèi)存位置進(jìn)行,而寄存器限制符,則優(yōu)先存于寄存器而后修改再寫回內(nèi)存。但寄存器限制符通常只在指令必需或者明顯提升性能時(shí)使用。當(dāng)C變量需在asm中修改且無(wú)需寄存器保持其值時(shí),內(nèi)存限制符可最大化性能。如,將idtr的值存儲(chǔ)于loc的內(nèi)存位置中:
asm("sidt %0\n" : :"m"(loc));
3. 匹配(數(shù)字)限制符(Matching(Digit) constraints)
有時(shí),一個(gè)單獨(dú)變量既是輸入也是輸出操作符,這時(shí)可使用匹配限制符。
asm ("incl %0" :"=a"(var):"0"(var));
我們?cè)诓僮鲾?shù)一節(jié)看到了類似的例子,在這個(gè)例子中寄存器%eax既是輸入也是輸出變量。var輸入讀入%eax并更新到%eax最后在自增后存入var。這里的"0"指定了和輸出變量一樣的第0個(gè)限制符。也就是說(shuō),它指定了var的輸出過(guò)程應(yīng)該只存于%eax中。這類限制符可用于:
- 輸入輸出是統(tǒng)一變量,或變量被修改并被寫會(huì)同一變量時(shí)。
- 將輸入和輸出操作符分開是不必要的時(shí)候。
使用匹配限制符最重要的效果是使可用寄存器的使用更有效。
一些其他的限制符有:
-
m: 接受內(nèi)存操作數(shù),任意的機(jī)器支持的地址。 -
o: 接受內(nèi)存操作數(shù),只接受偏移地址(offsettable)。即對(duì)某個(gè)合法地址添加一個(gè)微小的偏移量。 -
V: 非偏移內(nèi)存操作數(shù)。換句話說(shuō),任何符合"m"但不符合"o"限制符的地址。 -
i: 立即整型操作數(shù),允許在編譯期(assembly-time)可知常量符號(hào)。 -
n: 立即整型操作數(shù),允許已知數(shù)字值。許多系統(tǒng)不支持小于16-bit的(word wide)編譯期(assembly-time)常量作為操作數(shù)。這些操作數(shù)應(yīng)該使用n而不是i。 -
g: 任何寄存器,內(nèi)存或立即整型操作數(shù)都可用,要求寄存器不是常規(guī)寄存器(general registers)。
以下限制符為x86限定:
-
r: Register operand constraint, look table given above. -
q: Registers a, b, c or d. -
I: Constant in range 0 to 31 (for 32-bit shifts). -
J: Constant in range 0 to 63 (for 64-bit shifts). -
K: 0xff. -
L: 0xffff. -
M: 0, 1, 2, or 3 (shifts for lea instruction). -
N: Constant in range 0 to 255 (for out instruction). -
f: Floating point register -
t: First (top of stack) floating point register -
u: Second floating point register -
A: Specifies thea’ ord’ registers. This is primarily useful for 64-bit integer values intended to be returned with thed’ register holding the most significant bits and thea’ register holding the least significant bits.
6.2 限制符修飾符(Constraint Modifiers.)
當(dāng)使用限制符時(shí),若要精確控制其效果,GCC提供了修飾符。常用當(dāng)有:
-
=: 意味著操作數(shù)對(duì)該指令是只寫的;前一個(gè)值將被忽略并替換為輸出數(shù)據(jù)。 -
&: 意味著操作數(shù)是一個(gè)早期受影響的操作數(shù),也就是在指令結(jié)束前已被修改。因此,該操作數(shù)不可停留在輸入寄存器中或任何內(nèi)存中。在被寫入前僅用于輸入的輸入操作數(shù)可設(shè)為一個(gè)早期受影響操作數(shù) (An input operand can be tied to an earlyclobber operand if its only use as an input occurs before the early result is written)。
關(guān)于限制符的描述并不意味結(jié)束。例子可以幫助我們更好地理解內(nèi)聯(lián)匯編。下一節(jié)我們會(huì)看一些例子,我們會(huì)發(fā)現(xiàn)更多關(guān)于受影響列表和限制符的使用。
7. 一些有用的代碼(Some Useful Recipes.)
現(xiàn)在我們已經(jīng)基本涵蓋了GCC內(nèi)聯(lián)匯編內(nèi)容,我們應(yīng)該關(guān)注一些簡(jiǎn)單的例子。使用宏來(lái)定義內(nèi)聯(lián)匯編總是方便的。我們可以看到很多內(nèi)核(kernel)代碼的asm函數(shù)例子。(/usr/src/linux/include/asm/*.h).
- 首先我們從一個(gè)簡(jiǎn)單的例子開始。我們寫一個(gè)程序,將兩個(gè)數(shù)字相加:
int main(void)
{
int foo = 10, bar = 15;
__asm__ __volatile__("addl %%ebx,%%eax"
:"=a"(foo)
:"a"(foo), "b"(bar)
);
printf("foo+bar=%d\n", foo);
return 0;
}
此處我們讓GCC將foo存入%eax,將bar存入%ebx,然后我們希望結(jié)果也存在%eax中。=符號(hào)表示那是一個(gè)輸出寄存器?,F(xiàn)在我們可以用另一種方式讓變量加整數(shù)。
__asm__ __volatile__(
" lock ;\n"
" addl %1,%0 ;\n"
: "=m" (my_var)
: "ir" (my_int), "m" (my_var)
: /* no clobber-list */
);
這是一個(gè)原子加法。我們可以移除lock指令來(lái)移除原子性。輸出段=m意為my_var是一個(gè)輸出操作數(shù)且在內(nèi)存中。類似的ir說(shuō)明my_int是一個(gè)整數(shù)并應(yīng)該載入(reside)到寄存器中。沒有受影響寄存器列表。
- 現(xiàn)在我們會(huì)執(zhí)行一些動(dòng)作在寄存器/變量上并比較它們到值。
__asm__ __volatile__( "decl %0; sete %1"
: "=m" (my_var), "=q" (cond)
: "m" (my_var)
: "memory"
);
此處,my_var的值減1,如果結(jié)果為0則cond變量被設(shè)置。我們同樣可以添加lock;\n\t在第一句來(lái)實(shí)現(xiàn)原子性。類似的,我們可以用incl %0代替decl %0來(lái)實(shí)現(xiàn)my_var的加1。
此處需要指出
(i) my_var 是一個(gè)位于(residing in)內(nèi)存中的變量。 (ii)cond可以在eax,ebx,ecx和edx中的任意一個(gè)。=q限制符確保了這一點(diǎn)。 (iii) 受影響列表中包含memory`,即代碼將改變內(nèi)存中的值。
- 如何設(shè)置/清除寄存器中的一個(gè)位?
__asm__ __volatile__( "btsl %1,%0"
: "=m" (ADDR)
: "Ir" (pos)
: "cc"
);
此處,`ADDR(一個(gè)內(nèi)存變量)中的pos`變量對(duì)應(yīng)的比特位將設(shè)為1.
我們可以用btrl替代btsl來(lái)清除一個(gè)位。限制符Ir指出,pos是一個(gè)寄存器,且它的值介于0-31(x86限制符)。即我們可以設(shè)置/清除ADDR變量中任意0~31位值。因?yàn)闂l件碼將被改變,我們?cè)黾?code>cc到受影響列表。
- 現(xiàn)在我們看一些復(fù)雜但有用的函數(shù)。字符串復(fù)制。
static inline char * strcpy(char * dest,const char *src)
{
int d0, d1, d2;
__asm__ __volatile__( "1:\tlodsb\n\t"
"stosb\n\t"
"testb %%al,%%al\n\t"
"jne 1b"
: "=&S" (d0), "=&D" (d1), "=&a" (d2)
: "0" (src),"1" (dest)
: "memory");
return dest;
}
源地址保存在esi中,目標(biāo)地址在edi中,然后開始復(fù)制,當(dāng)我們到達(dá)0時(shí),復(fù)制結(jié)束。限制符&S,&D,&a說(shuō)明寄存器esi, edi, eax是早期受影響寄存器。即,它們的內(nèi)容將在函數(shù)完成前被改變。此處明顯memory也在受影響之列。
我們看一個(gè)類似的函數(shù),移動(dòng)一塊雙字(double words)。注意函數(shù)聲明為一個(gè)宏。
#define mov_blk(src, dest, numwords) \
__asm__ __volatile__ ( \
"cld\n\t" \
"rep\n\t" \
"movsl" \
: \
: "S" (src), "D" (dest), "c" (numwords) \
: "%ecx", "%esi", "%edi" \
)
這里我們沒有輸出,所以改變發(fā)生在寄存器ecx, esi 和 edi上,是塊移動(dòng)的副作用。所以我們將它們加在受影響列表上。
Linux中,系統(tǒng)調(diào)用是由GCC內(nèi)聯(lián)匯編實(shí)現(xiàn)的。讓我們看一些一個(gè)系統(tǒng)調(diào)用是如何實(shí)現(xiàn)的。所有的系統(tǒng)調(diào)用被寫作一個(gè)宏(linux/unistd.h)。如,一個(gè)有3個(gè)參數(shù)的系統(tǒng)調(diào)用被寫作如下的宏:
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
無(wú)論任何的3個(gè)參數(shù)的系統(tǒng)調(diào)用,都使用以上宏進(jìn)行。syscall數(shù)字放在eax中,然后每個(gè)參數(shù)放在ebx, ecx, edx。最終int 0x80指令真正的執(zhí)行調(diào)用。返回碼可以在eax中獲得。
每個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn)方法類似。退出是一個(gè)單參數(shù)系統(tǒng)調(diào)用,讓我們看看它的代碼是什么樣的,如下:
{
asm("movl $1,%%eax; /* SYS_exit is 1 */
xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */
int $0x80" /* Enter kernel mode */
);
}
退出數(shù)字是1,參數(shù)(parameter)是0。所以我們安排eax包含1,ebx包含0,通過(guò)int $0x80執(zhí)行exit(0)。這就是exit的工作原理。
8. 總結(jié)(Concluding Remarks.)
本文檔簡(jiǎn)要敘述了GCC內(nèi)聯(lián)匯編的基礎(chǔ)。一旦你理解了這些基礎(chǔ)概念,自己嘗試下一步就不再困難了。我們已經(jīng)看了一些對(duì)理解常用GCC內(nèi)聯(lián)匯編功能很有幫助的例子。
GCC內(nèi)聯(lián)是一個(gè)很大的主題,而這篇文章只是一個(gè)的開始。更多語(yǔ)法細(xì)節(jié)可以在官方GNU匯編文檔中查閱。同樣的,完整的限制符說(shuō)明也在GCC官方文檔中列出。
當(dāng)然,Linux內(nèi)核大規(guī)模使用了GCC內(nèi)聯(lián)匯編。所以我們可以在其中找到很多的不同類型的例子。對(duì)我們非常有幫助。
如果你發(fā)現(xiàn)任何文字錯(cuò)誤,或本文中的內(nèi)容已經(jīng)過(guò)期,請(qǐng)告知。