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

原文: GCC-Inline-Assembly-HOWTO

1. 簡介(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項目BWAPI移植mac/linux時消恍,遇到Visual C內(nèi)聯(lián)匯編遷移到GCC的問題,于是研習此文并譯之炒考。

2. 概覽(Overview of the whole thing.)

  • 我們在此學習GCC內(nèi)聯(lián)匯編煮盼。內(nèi)聯(lián)是什么郎逃?

我們可以指導編譯器將函數(shù)的代碼直接插入調用的位置,這類函數(shù)叫做內(nèi)聯(lián)函數(shù)。聽起來像是宏衰齐?事實上還真挺像。

  • 內(nèi)聯(lián)函數(shù)有什么好處继阻?

內(nèi)聯(lián)的方法降低了函數(shù)調用的問題耻涛。而且如果任何參數(shù)是常量的話废酷,在編譯器將得到明顯優(yōu)化,而不是所有的內(nèi)聯(lián)函數(shù)代碼都被包含抹缕。代碼量會更少澈蟆,取決于具體的情況。為了定義內(nèi)聯(lián)函數(shù)歉嗓,我們使用關鍵字inline聲明丰介。

  • 什么是內(nèi)聯(lián)匯編?

內(nèi)聯(lián)匯編是寫在內(nèi)聯(lián)函數(shù)中的匯編過程(assembly routines)鉴分。它非常方便哮幢、快速,在系統(tǒng)編程中非常有用志珍。我們主要關注學習GCC內(nèi)聯(lián)匯編函數(shù)的基礎格式和用法橙垢。要聲明內(nèi)聯(lián)匯編函數(shù),我們使用關鍵字asm伦糯。

內(nèi)聯(lián)匯編很重要柜某,因為有能力操作并輸出到C變量中。因為這些能力敛纲,asm作為了C和匯編指令間的接口篙骡。

3、GCC匯編語法(GCC Assembler Syntax.)

GCC使用AT&T/UNIX匯編語法永丝。其與Intel語法區(qū)別較大迅诬,主要區(qū)別有:

3.1. 源-目標順序(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ù)以$開頭监嗜,對staic “C”變量也前置$。16進制常量抡谐,Intel語法后綴h裁奇,AT&T前綴0x。所以對于16進制數(shù)麦撵,我們會先看到$,然后是0x,最后是常量刽肠。

3.4. 操作數(shù)大小(Operand Size)

譯注:操作數(shù)(operand),很多情況下指操作對象免胃,即寄存器或內(nèi)存地址五垮。

AT&T語法中操作數(shù)大小取決于操作碼最后一個字符。操作碼后綴b,w,l 對應 byte(8-bit), word(16-bit), 和 long(32-bit)杜秸。Intel語法中放仗,通過在操作數(shù)(非操作碼)前綴 byte ptr, word ptr, 和 dword ptr 實現(xiàn)該功能。

因此, Intel 之 mov al, byte ptr foomovb foo, %al 于 AT&T.

3.5. 內(nèi)存操作數(shù)(Memory Operands)

Intel語法中基址寄存器(The base register)內(nèi)于[撬碟、]之間诞挨,而AT&T于(莉撇、) 之間。此外惶傻,間接內(nèi)存引用(indirect memory reference)Intel風格為

section:[base + index*scale + disp] 棍郎,改變?yōu)?/p>

section:disp(base, index, scale)于 AT&T.

需指出,當常量使用disp/scale银室,$ 無需前置涂佃。

以上是Intel于AT&T語法的主要區(qū)別,完整信息請參加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)基礎(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__抓狭。如果有多行代碼伯病,我們將每一個使用"包含,并后綴\n\t否过。因gcc將每行作為一個stringas(GAS)午笛,通過換行/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)容)一些寄存器药磺,而后不修復這些改變直接從asm返回的話,一些不好的事就會發(fā)生煤伟。這是因為GCC不知道對寄存器內(nèi)容的改變与涡,而這將我們帶向問題,又起當編譯器進行了某些優(yōu)化的時候持偏。它將假設一些寄存器包含了一些變量的值,而我們已經(jīng)改變了沒有告知GCC氨肌, 然后它繼續(xù)執(zhí)行就像什么也沒發(fā)生一樣鸿秆。我們可以做的是使用一些沒有副作用的指令,或者在我們退出前修復問題怎囚,或者等待崩潰卿叽。這就是我們想要一些擴展功能性(functionality)的地方。擴展asm(Extended asm)提供了我們這種功能性恳守。

5. 擴展Asm(Extended Asm.)

基本匯編中我們只有指令考婴。在擴展匯編中,我們可以指定操作對象(operand)催烘。它允許我們指定輸入寄存器沥阱,輸出寄存器及一列受影響(clobbered)寄存器。它不是mandatory to指定寄存器使用伊群,我們可以將麻煩留給GCC而GCC有可能(probably)更好的適配GCC的優(yōu)化機制考杉。反正(Anyway)基本形式如下:

asm ( assembler template
    : output operands /* optional */
    : input operands /* optional */
    : list of clobbered registers /* optional */
    );

匯編模板(assembler template)由匯編指令構成策精。每個操作數(shù)(operand)
描述為一個操作限制符(operand-constraint string),followed by the C expression in 括號崇棠。冒號分割匯編模板咽袜、輸出操作數(shù)組、輸入操作數(shù)組枕稀、clobbered寄存器組询刹。逗號分割每個組內(nèi)的操作數(shù)。操作數(shù)總數(shù)限制在10個或the maximum number of operands in any instruction pattern in the machine description萎坷,whichever is greater.

如果沒有輸出操作數(shù)但有輸入操作數(shù)凹联,你必須放兩個連續(xù)冒號。

例如:

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. 它也同時告訴gcc, 寄存器 eax and edi 的內(nèi)容不再有效. 讓我們看看另一個例子來更好的理解:

int a=10, b;
asm ("movl %1, %%eax; movl %%eax, %0;"
    :"=r"(b) /* output */
    :"r"(a) /* input */
    :"%eax" /* clobbered register */
    );

這里我們使用匯編指令讓b的值等于a的值匕垫。有趣的點是:

b 是 output operand, referred to by %0a 是 input operand, referred to by %1.
r is 限制(constraints)對于 operands. 我們后面會詳細討論“限制”. 此時, r 告訴 GCC 使用任意register來儲存操作數(shù)。輸出操作數(shù)限制應該有一個限時修飾符=虐呻。這個修飾符意味著它是一個輸出操作數(shù)且是只寫的(write-only)象泵。

在寄存器名稱前出現(xiàn)了兩個%。這幫助GCC來區(qū)分操作數(shù)和寄存器斟叼。操作數(shù)有一個單獨的%作為前綴偶惠。

受影響(clobbered)寄存器%eax在第三個冒號之后,告訴GCC %eax的值已在asm內(nèi)被修改朗涩,所以GCC不會使用這個寄存器去保存其他的值忽孽。

asm執(zhí)行結束后,b將反射更新后的值谢床,因為它被指定為一個輸出操作數(shù)兄一。另一方面,asm內(nèi)部對b的改變應該(is supposed to)在asm外部被反射.

現(xiàn)在我們詳細的看一下每一個區(qū)域识腿。

5.1 匯編模板(Assembler Template).

匯編模板包含一組嵌入到C程序中的指令出革。格式類似:或者每個指令包圍在雙引號中,或整組指令包含在雙引號中渡讼。每個指令也應該以一個分隔符結束骂束。合法的分隔符可以是\n;\n可以跟隨一個\t成箫。C表達式的操作數(shù)呈現(xiàn)為 %0, %1 ...等展箱。

5.2 操作數(shù)(Operands).

C expressions serve as operands for the assembly instructions inside "asm". 每個操作數(shù)首先寫作一個雙引號內(nèi)的操作數(shù)限制符(operand constraint)。 對于輸出操作數(shù), 引號內(nèi)還有一個限制修飾符蹬昌, 然后跟隨操作數(shù)對應的 C 表達式 混驰。 即,

"constraint" (C expression) 乃通用形式。對輸出操作數(shù)會有一個額外的修飾符账胧。限制符(constraint)主要用于決定操作數(shù)的地址模式竞慢。他們也被用于指定要使用的寄存器。

如我們使用超過一個操作數(shù)治泥,以逗號,分隔筹煮。

在匯編模板中,每個操作數(shù)按數(shù)字被引用居夹。數(shù)字按如下規(guī)則排列败潦。如果有n個操作數(shù)(包括輸入、輸出)准脂,那么第一個輸出操作數(shù)是數(shù)字0劫扒,連續(xù)增加,最后一個輸入操作數(shù)是數(shù)字n-1狸膏。最大操作數(shù)數(shù)量如上一段所述沟饥。

輸出操作數(shù)表達式必須是lvalues(32-bit)。輸入操作數(shù)無此限制湾戳。他們必須是表達式贤旷。擴展匯編功能是最常用于編譯器自身不知曉的機器指令;-)。如果輸出表達式無法被直接尋址(addressed)(比如砾脑,它是一個bit-field)幼驶,我們限制符必須“允許”(allow)一個寄存器。在那種情況下韧衣,GCC將使用該寄存器為asm的輸出盅藻,然后將寄存器內(nèi)容存儲到輸出。

如上所述畅铭,原始輸出操作數(shù)必須是只寫的氏淑;GCC將假設那個操作對象中的值在指令前已失效且無需生成。擴展匯編也支持“輸入-輸出”或“讀-寫”操作數(shù)硕噩。

我們現(xiàn)在看一些例子假残。我們希望將一個數(shù)乘以5。對此我們使用lea指令榴徐。

asm ("leal (%1,%1,4), %0"
    : "=r" (five_times_x)
    : "r" (x)
    );

此處我們的輸入是x。我們沒有指定使用哪個寄存器匀归。GCC會為輸入選擇一些寄存器用來輸入坑资,一個用來輸出,執(zhí)行我們的要求穆端。如果我們希望輸入和輸出放在(reside)同一個寄存器中袱贮,我們可以讓GCC來實現(xiàn)。這里我們使用那種"讀-寫"操作數(shù)体啰,通過指定合適的限制符攒巍,這里我們來實現(xiàn)它:

asm ("leal (%0,%0,4), %0"
    : "=r" (five_times_x)
    : "0" (x)
    );

現(xiàn)在輸入和輸出操作數(shù)在同一個寄存器內(nèi)了嗽仪。但我們不知道是哪個寄存器。現(xiàn)在如果我們也想要指定柒莉,有一個辦法:

asm ("leal (%%ecx,%%ecx,4), %%ecx"
    : "=c" (x)
    : "c" (x)
    );

以上三個例子中闻坚,我們沒有把任何一個寄存器放在受影響列表中。為什么兢孝?前兩個例子中窿凤,GCC決定使用哪個寄存器,因此知道發(fā)生了什么改變跨蟹。在最后一個中雳殊,我們不需要將ecx放在受影響列表中,gcc知道它會放入x中窗轩。因為它可以知道ecx的值夯秃,它不會被視為受影響的。

5.3 受影響列表(Clobber List.)

一些指令會影響一些硬件寄存器痢艺。我們必須在受影響列表中列出那些寄存器仓洼,即asm函數(shù)第三個:后的區(qū)域。這用于指示gcc我們將使用并修改它們腹备。所以gcc將補不回假設它加載到這些寄存器中的值是合法的衬潦。我們不應該列出輸入和輸出寄存器。因為gcc知道asm使用它們(因為它們被明確指定為限制符(constraints))植酥。如果指令使用了任何其他寄存器镀岛,顯式或隱式的(并且這些寄存器沒有出現(xiàn)在輸入和輸出列表上),那么那些寄存器必須在受影響列表中指定友驮。

如果我們的指令可以修改條件碼寄存器(the condition code register)漂羊,我們必須增加cc到受影響寄存器列表。

如果我們的指令用一個不可預期的方法(fashion)修改了內(nèi)存卸留,添加memory到受影響寄存器走越。這會使GCC在匯編指令期間不在寄存器內(nèi)保持內(nèi)存值的緩存。我們也必須添加volatile關鍵字耻瑟,如果內(nèi)存影響(memory affected)未列在asm的輸入和輸出中旨指。

我們可以讀寫受影響寄存器任意多次。注意模板中乘法指令的例子喳整;它假設子過程(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)美代碼框都,你必然已見過很多函數(shù)聲明為volatile__volatile__搬素,跟隨在__asm__之后。我之前提到過關于關鍵字asm__asm__。所以什么是volatile熬尺?

如果我們的匯編語句必須在我們放置它的地方執(zhí)行摸屠,(即,必須不被作為一個優(yōu)化而移出循環(huán))粱哼,則將volatile放在asm之后季二。所以防止它被移動、刪除和任何改變皂吮,我們?nèi)绱寺暶?code>asm volatile(... : ... : ... : ...); 當我們必須非常小心時戒傻,使用__volatile__

如果我們的匯編只是做一些計算而沒有任何副作用蜂筹,最好不要使用volatile關鍵字需纳。忽略它將幫助GCC優(yōu)化代碼使其更優(yōu)美。

在“一些有用的代碼”小節(jié)艺挪,我已經(jīng)提供了很多內(nèi)聯(lián)匯編函數(shù)的例子不翩。我們可以詳細了解受影響列表。

6. 詳解限制符(More about constraints.)

此時麻裳,你可能已經(jīng)理解限制符必須要做很多的事口蝠。但關于限制符我們說的很少。限制符可以說出操作數(shù)是否可能是一個寄存器津坑,及哪類寄存器妙蔗;操作數(shù)是否可以是一個內(nèi)存引用,及哪一類地址疆瑰;操作數(shù)是否可能是一個立即常量眉反,及它可以有哪些可能的值(即值的范圍)...等。

6.1 常用限制符(Commonly used constraints.)

有許多限制符穆役,只有一部分是常用的寸五。我們看一看這些限制符。

1. 寄存器操作數(shù)限制符(Register operand constraint)(r)
當操作數(shù)指定使用此限制符時耿币,它們會存儲在常規(guī)寄存器中(General Purpose Registers(GPR))梳杏。如:

asm ("movl %%eax, %0\n"
    :"=r"(myval)
    );

此處myval變量保存在一個寄存器中,eax的值會復制到那個寄存器淹接,而myval的值會從這個寄存器中更新到內(nèi)存十性。當"r"限制符被指定后,gcc可以在任何可用的GPR中保存這個變量塑悼。要指定該寄存器劲适,你必須使用特定寄存器限制符指定寄存器名稱。它們是:

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)

當操作數(shù)是在內(nèi)存中時拢肆,任何在它上的操作將直接在內(nèi)存位置進行减响,而寄存器限制符,則優(yōu)先存于寄存器而后修改再寫回內(nèi)存郭怪。但寄存器限制符通常只在指令必需或者明顯提升性能時使用支示。當C變量需在asm中修改且無需寄存器保持其值時,內(nèi)存限制符可最大化性能鄙才。如颂鸿,將idtr的值存儲于loc的內(nèi)存位置中:

asm("sidt %0\n" : :"m"(loc));

3. 匹配(數(shù)字)限制符(Matching(Digit) constraints)
有時,一個單獨變量既是輸入也是輸出操作符攒庵,這時可使用匹配限制符嘴纺。

asm ("incl %0" :"=a"(var):"0"(var));

我們在操作數(shù)一節(jié)看到了類似的例子,在這個例子中寄存器%eax既是輸入也是輸出變量浓冒。var輸入讀入%eax并更新到%eax最后在自增后存入var栽渴。這里的"0"指定了和輸出變量一樣的第0個限制符。也就是說稳懒,它指定了var的輸出過程應該只存于%eax中闲擦。這類限制符可用于:

  • 輸入輸出是統(tǒng)一變量,或變量被修改并被寫會同一變量時场梆。
  • 將輸入和輸出操作符分開是不必要的時候墅冷。

使用匹配限制符最重要的效果是使可用寄存器的使用更有效。

一些其他的限制符有:

  • m: 接受內(nèi)存操作數(shù)或油,任意的機器支持的地址寞忿。
  • o: 接受內(nèi)存操作數(shù),只接受偏移地址(offsettable)顶岸。即對某個合法地址添加一個微小的偏移量腔彰。
  • V: 非偏移內(nèi)存操作數(shù)。換句話說蜕琴,任何符合"m"但不符合"o"限制符的地址萍桌。
  • i: 立即整型操作數(shù),允許在編譯期(assembly-time)可知常量符號凌简。
  • n: 立即整型操作數(shù)上炎,允許已知數(shù)字值。許多系統(tǒng)不支持小于16-bit的(word wide)編譯期(assembly-time)常量作為操作數(shù)雏搂。這些操作數(shù)應該使用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.)

當使用限制符時,若要精確控制其效果芙沥,GCC提供了修飾符诲祸。常用當有:

  • = : 意味著操作數(shù)對該指令是只寫的浊吏;前一個值將被忽略并替換為輸出數(shù)據(jù)。
  • &: 意味著操作數(shù)是一個早期受影響的操作數(shù)救氯,也就是在指令結束前已被修改找田。因此,該操作數(shù)不可停留在輸入寄存器中或任何內(nèi)存中着憨。在被寫入前僅用于輸入的輸入操作數(shù)可設為一個早期受影響操作數(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)墩衙。

關于限制符的描述并不意味結束。例子可以幫助我們更好地理解內(nèi)聯(lián)匯編甲抖。下一節(jié)我們會看一些例子漆改,我們會發(fā)現(xiàn)更多關于受影響列表和限制符的使用。

7. 一些有用的代碼(Some Useful Recipes.)

現(xiàn)在我們已經(jīng)基本涵蓋了GCC內(nèi)聯(lián)匯編內(nèi)容准谚,我們應該關注一些簡單的例子挫剑。使用宏來定義內(nèi)聯(lián)匯編總是方便的。我們可以看到很多內(nèi)核(kernel)代碼的asm函數(shù)例子柱衔。(/usr/src/linux/include/asm/*.h).

  1. 首先我們從一個簡單的例子開始暮顺。我們寫一個程序,將兩個數(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捶码,然后我們希望結果也存在%eax中。=符號表示那是一個輸出寄存器』蛄矗現(xiàn)在我們可以用另一種方式讓變量加整數(shù)惫恼。

 __asm__ __volatile__(
                      "   lock       ;\n"
                      "   addl %1,%0 ;\n"
                      : "=m"  (my_var)
                      : "ir"  (my_int), "m" (my_var)
                      :                                 /* no clobber-list */
                      );

這是一個原子加法。我們可以移除lock指令來移除原子性澳盐。輸出段=m意為my_var是一個輸出操作數(shù)且在內(nèi)存中祈纯。類似的ir說明my_int是一個整數(shù)并應該載入(reside)到寄存器中。沒有受影響寄存器列表叼耙。

  1. 現(xiàn)在我們會執(zhí)行一些動作在寄存器/變量上并比較它們到值腕窥。
__asm__ __volatile__(  "decl %0; sete %1"
                      : "=m" (my_var), "=q" (cond)
                      : "m" (my_var) 
                      : "memory"
                      );

此處,my_var的值減1筛婉,如果結果為0則cond變量被設置簇爆。我們同樣可以添加lock;\n\t在第一句來實現(xiàn)原子性。類似的爽撒,我們可以用incl %0代替decl %0來實現(xiàn)my_var的加1入蛆。

此處需要指出
(i) my_var 是一個位于(residing in)內(nèi)存中的變量。 (ii)cond可以在eax,ebx,ecxedx中的任意一個硕勿。=q限制符確保了這一點哨毁。 (iii) 受影響列表中包含memory`,即代碼將改變內(nèi)存中的值源武。

  1. 如何設置/清除寄存器中的一個位扼褪?
__asm__ __volatile__(   "btsl %1,%0"
                      : "=m" (ADDR)
                      : "Ir" (pos)
                      : "cc"
                      );

此處想幻,`ADDR(一個內(nèi)存變量)中的pos`變量對應的比特位將設為1.

我們可以用btrl替代btsl來清除一個位。限制符Ir指出话浇,pos是一個寄存器举畸,且它的值介于0-31(x86限制符)。即我們可以設置/清除ADDR變量中任意0~31位值凳枝。因為條件碼將被改變,我們增加cc到受影響列表跋核。

  1. 現(xiàn)在我們看一些復雜但有用的函數(shù)岖瑰。字符串復制。
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中砂代,目標地址在edi中蹋订,然后開始復制,當我們到達0時刻伊,復制結束露戒。限制符&S,&D,&a說明寄存器esi, edi, eax是早期受影響寄存器。即捶箱,它們的內(nèi)容將在函數(shù)完成前被改變智什。此處明顯memory也在受影響之列。

我們看一個類似的函數(shù)丁屎,移動一塊雙字(double words)荠锭。注意函數(shù)聲明為一個宏。

#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上证九,是塊移動的副作用。所以我們將它們加在受影響列表上共虑。

Linux中愧怜,系統(tǒng)調用是由GCC內(nèi)聯(lián)匯編實現(xiàn)的。讓我們看一些一個系統(tǒng)調用是如何實現(xiàn)的妈拌。所有的系統(tǒng)調用被寫作一個宏(linux/unistd.h)拥坛。如,一個有3個參數(shù)的系統(tǒng)調用被寫作如下的宏:

#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); \
}

無論任何的3個參數(shù)的系統(tǒng)調用尘分,都使用以上宏進行渴逻。syscall數(shù)字放在eax中,然后每個參數(shù)放在ebx, ecx, edx音诫。最終int 0x80指令真正的執(zhí)行調用惨奕。返回碼可以在eax中獲得。

每個系統(tǒng)調用實現(xiàn)方法類似竭钝。退出是一個單參數(shù)系統(tǒng)調用梨撞,讓我們看看它的代碼是什么樣的雹洗,如下:

{
        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,通過int $0x80執(zhí)行exit(0)港粱。這就是exit的工作原理螃成。

8. 總結(Concluding Remarks.)

本文檔簡要敘述了GCC內(nèi)聯(lián)匯編的基礎。一旦你理解了這些基礎概念查坪,自己嘗試下一步就不再困難了寸宏。我們已經(jīng)看了一些對理解常用GCC內(nèi)聯(lián)匯編功能很有幫助的例子。

GCC內(nèi)聯(lián)是一個很大的主題偿曙,而這篇文章只是一個的開始氮凝。更多語法細節(jié)可以在官方GNU匯編文檔中查閱。同樣的望忆,完整的限制符說明也在GCC官方文檔中列出罩阵。

當然,Linux內(nèi)核大規(guī)模使用了GCC內(nèi)聯(lián)匯編启摄。所以我們可以在其中找到很多的不同類型的例子稿壁。對我們非常有幫助。

如果你發(fā)現(xiàn)任何文字錯誤歉备,或本文中的內(nèi)容已經(jī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
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末威创,一起剝皮案震驚了整個濱河市落午,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肚豺,老刑警劉巖溃斋,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吸申,居然都是意外死亡梗劫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門截碴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梳侨,“玉大人,你說我怎么就攤上這事日丹∽卟福” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵哲虾,是天一觀的道長。 經(jīng)常有香客問我,道長贯吓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任网持,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己谈秫,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布鱼鼓。 她就那樣靜靜地躺著拟烫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚓哩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天上渴,我揣著相機與錄音岸梨,去河邊找鬼。 笑死稠氮,一個胖子當著我的面吹牛曹阔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隔披,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赃份,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奢米?” 一聲冷哼從身側響起抓韩,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鬓长,沒想到半個月后谒拴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涉波,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年英上,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啤覆。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡苍日,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窗声,到底是詐尸還是另有隱情相恃,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布笨觅,位于F島的核電站豆茫,受9級特大地震影響侨歉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜揩魂,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一幽邓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧火脉,春花似錦牵舵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至方援,卻和暖如春没炒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背犯戏。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工送火, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人先匪。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓种吸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呀非。 傳聞我的和親對象是個殘疾皇子坚俗,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容