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 foo
即 movb 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將每行作為一個string
到as(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 %0
而 a
是 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 thea’ or
d’ registers. This is primarily useful for 64-bit integer values intended to be returned with thed’ register holding the most significant bits and the
a’ 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).
- 首先我們從一個簡單的例子開始暮顺。我們寫一個程序,將兩個數(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)到寄存器中。沒有受影響寄存器列表叼耙。
- 現(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,
ecx和
edx中的任意一個硕勿。
=q限制符確保了這一點哨毁。 (iii) 受影響列表中包含
memory`,即代碼將改變內(nèi)存中的值源武。
- 如何設置/清除寄存器中的一個位扼褪?
__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
到受影響列表跋核。
- 現(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
, esi
和 edi
上证九,是塊移動的副作用。所以我們將它們加在受影響列表上共虑。
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)過期常摧,請告知。