譯:GCC內(nèi)聯(lián)匯編入門

原文: GCC-Inline-Assembly-HOWTO

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 foomovb 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è)stringas(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 %0a 是 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 the a’ ord’ registers. This is primarily useful for 64-bit integer values intended to be returned with the d’ 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).

  1. 首先我們從一個(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)到寄存器中。沒有受影響寄存器列表。

  1. 現(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,ecxedx中的任意一個(gè)。=q限制符確保了這一點(diǎn)。 (iii) 受影響列表中包含memory`,即代碼將改變內(nèi)存中的值。

  1. 如何設(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到受影響列表。

  1. 現(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, esiedi上,是塊移動(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)告知。

9. 引用(References.)

  1. Brennan’s Guide to Inline Assembly
  2. Using Assembly Language in Linux
  3. Using as, The GNU Assembler
  4. Using and Porting the GNU Compiler Collection (GCC)
  5. Linux Kernel Source
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容