原文爲GCC-Inline-Assembly-HOWTO,在google上能夠找到原文,歡迎指出翻譯錯誤。linux
中文版說明express
因爲譯者水平有限,故譯文出錯之處,還請見諒。C語言的關鍵字不譯,一些單詞或詞組(如colbber等)因爲恐怕譯後詞不達意,故並不翻譯,由下面的單詞表代爲解釋,敬請見諒。編程
英文原文中的單詞和詞組:ide
operand:操做數,能夠是寄存器,內存,當即數。函數
volatile:易揮發的,是C語言的關鍵字。學習
constraint: 約束。優化
register: 本文指CPU寄存器。ui
asm:「asm」和「__asm__」在C語言中是關鍵字。原文中常常出現這個單詞,是指嵌入到C語言(或者其它語言)的彙編程序片段。google
basic inline assembly:指C語言中內聯彙編程序的一種形式,和extended asm對應。基本格式以下:編碼
asm("assembly code");
extended assembly:和basic inline assembly對應,比它多了一些特性,如能夠指明輸入,輸出等。基本格式以下:
asm ( assembler template
: output operands
: input operands
: list of clobbered registers
);
clobber list:實際上就是被使用的寄存器的列表,用來告訴GCC它們已經被asm使用了,不要在asm程序外使用它們。否則可能帶來不可預見的後果。
clobbered registers:它和clobber list對應。
assembler template:就是彙編模板,全部內聯彙編代碼都有按必定的格式。
見extended assembly的說明
做者:Sandeep.S
譯者:吳遙
版本號 v0.1 2003年3月01日
翻譯版更新日期 2008/06/11
這篇HOWTO解釋GCC提供的內聯彙編特性的用途和用法。學習這篇文章只須具有兩個前提條件,顯然那就是對x86彙編語言和C語言有基本的瞭解。
目 錄
1. 前言
1.1版權與許可證
1.2回饋與更正
1.3感謝
2.簡介
3.GCC彙編語法
4.基本內聯彙編
5.擴展內聯彙編
5.1彙編程序模板
5.2操做數
5.3 Clobber列表
5.4 Volatile … ?
6.更多關於約束條件
6.1 經常使用的約束
6.2 約束脩飾符
7. 一些有用的訣竅
8. 結束語
9. 參考
版權全部 (c)2003 Sandeep S.
這篇文檔是免費的,你能夠在依據自由軟件組織GNU通用公共許可證條款下從新發布或者修改它。不管是版本2的許可證仍是後來的版本(由你本身選擇)。
這份文檔的發佈是但願它有用,可是並無任何保證。
歡迎善意的回饋和批評,我感謝每個指出本文錯誤的人並儘快地更正錯誤。
我向GNU開發者提供這個功能強大的特性表達最誠摯的感謝。感謝Mr.Pramode C E的幫助。感謝政府工程學院的朋友尤爲是Nisha Kurur和Sakeeb S精神上的支持。感謝政府工程學院老師對個人幫助。
另外,還要感謝 Phillip、Brennan、Underwood 和 colin@nyx.net ,他們解決了不少難題。
如今咱們開始學GCC內聯彙編。內聯意味着什麼?
咱們能夠指示編譯器插入一個函數的代碼到調用者的代碼中,也就是實際上調用產生的地方。這樣的函數就是內聯函數。看上去很像宏?實際上它們很類似。
內聯函數有什麼好處呢?
內聯的方法減小了函數調用的額外開銷。並且若是有實際的參數值是常數,那麼在編譯的時候編譯器知道可能充許參數值的單一化,因此並非全部的內聯函數的代碼都要
包含進來。對可執行代碼大小的影響是不可預測的,它視乎對特定的狀況。聲明一個內聯函數,咱們聲明中使用關鍵字inline。
如今咱們站在一個位置來猜什麼是內聯彙編。它只是一些寫在函數內的彙編語言的程序。在系統編程時候它們會顯得很便利,快速,很是有用。咱們的主要目標是學習GCC
內聯彙編函數的基本格式和用法。
內聯彙編之因此如此重要主要是由於它操做的能力和讓它的輸出在C語言變量中可見。(這個句話譯得不太好)由於這樣的能力,「asm」(譯者:asm指內聯函數)就像一個彙編指令和包含它的C語言程序之間的接口。
GCC,即Linux平臺下的GNU C語言編譯器,它使用AT&T&sol(譯者:應該是指AT&T語法,可是sol就不知道是什麼);UNIX彙編語法。如今讓咱們使用AT&T語法來進行彙編編碼。若是你對AT&T語法不熟悉也不用擔憂,我將會教你。這種語法和Intel語法有很大的不一樣。如下我將給出主要的不一樣。
一、來源地-目的地 定序
AT&T語法和Intel語法在操做數的方向上是相反的。Intel語法的第一個操做數是目的地,第二個是來源地。然而AT&T語法的第一個操做數是來源地,第二的是目的地。也即:
「Op-code dst src」在Intel語法中變爲「Op-code src dst」AT&T語法。
二、寄存器命名
寄存器名字要有前綴「%」。也即若是寄存器eax被使用,應寫做%eax。
三、當即操做數
AT&T當即操做數以前要有一個「$」符號。對於靜態C語言變量也要有前綴「$」。在Intel語法裏,十六進制常數要有「h」做爲後綴。在AT&T語法裏咱們用「0x」做爲代
替。因此,對於十六進制的數,咱們看到一個「$」,而後一個「0x」,最後纔是常數自己。
四、操做數大小
在AT&T語法裏內存操做數的大小取決於操做碼名字的最後一個字母。操做碼後「b」,「w」和「l」分別指定byte(8字節長度),word(16字節長度)和long(32字節長度)的內存引用。Intel語法採用對內存操做數(不是操做碼)加上前綴「tyte ptr」,「word ptr」和「dword ptr」的方法來實現。
這樣,Intel語法的「mov al, byte ptr foo」等同於AT&T語法的「movb foo, %al」。
五、內存操做數
在Intel語法裏寄存器包含在「[」和「]」裏,而在AT&T語法裏卻改成「(」和「)」。另外,在Intel語法一個非直接內存引用是這樣的:section:[base + index*scale + disp],而在AT&T語法裏倒是這樣的:section:disp(base, index, scale)。
有一點要記住的是當一個常數看成disp/scale時,「$」符號不能前綴。
如今咱們來看一下Intel語法和AT&T語法的主要不一樣點。我只是寫了不多的一部分。若是要了解所有的內容,請參考GNU彙編文檔(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 |
基本內聯彙編的格式是很是簡單的,以下:
asm("assembly code");
例子以下:
asm("movl %ecx %eax");/*將ecx的值傳給eax了/
__asm__("movb %bh (%eax)");/*將bh的值傳到eax指向的內存處*/
你可能已經注意到在這裏我使用asm和__asm__兩個關鍵字。它們都是正確的。若是asm關鍵字與程序裏的某些程序發生衝突,那麼你可使用__asm__代替。若是咱們有不止一條指令,那麼咱們每行在雙引號裏寫一條指令,而且在指令最後加上一個'/n'和'/t'。這是由於gcc以字符串的形式發送每條指給as(GAS),而且經過使用newline&tab的方法發送正確的行格式給彙編器。例子:
__asm__ ("movl %eax, %ebx/n/t"
"movl $56, %esi/n/t"
"movl %ecx, $label(%edx,%ebx,$4)/n/t"
"movb %ah, (%ebx)");
若是在咱們的代碼中咱們改變了一些寄存器的值而且沒有記下這些改變便返回,可能會致使錯誤的發生。這是由於GCC並不知道寄存器的值改變了,這會給咱們帶來麻煩,尤爲是編譯器對程序進行一些優化處理時。假設有這樣的狀況發生:某些寄存器保存着某些變量的值,而咱們沒有有告訴GCC便改變了它,程序會如常地運行。這就是咱們所的擴展功能的緣由。擴展asm提供咱們這種的功能。
在基本彙編內聯裏,咱們只使用了指令。而在擴展彙編內聯裏,咱們可以指定操做數。它容許咱們指定輸入寄存器,輸出寄存器和一列clobbered registers(譯者注:實際就是指一些被內聯彙編使用的寄存器,不知道如何翻譯,因此下文也是以英文寫出)。沒有強性規定必定要指定使用寄存器,咱們能夠把頭痛的事情留給GCC,而且這樣可能更有利於GCC對程序優化。基本的格式以下:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
彙編程序模板(The assembler template)由彙編指令組成。每個操做數由在括號內的C語言表達式後的操做數約束字符串描述。第一個冒號把彙編程序模板和第一個輸出操做數分開,第二個冒號則把最後一個輸出操做數和第一個輸入操做數分開,假設有這樣的操做數。逗號則在每組中分開操做數。操做數的量最多爲10,或者機器描述裏最大操做數的的指令模式,這取決於那一個比較大。
若是沒有輸出操做數卻有輸入操做數,就要寫上兩個連續冒號,以說明沒有輸出操做數。例子:
asm ("cld/n/t"
"rep/n/t"
"stosl"
: /* no output registers */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);
如今,看看這代碼都作了什麼。上面的內聯代碼把fill_value的值寫到edi指向的內存地址count次。而且告訴GCC寄存器eax和edi不能夠再用了(也則是說被使用了)。讓咱們看多一個例子來更好地理解。
int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
這裏咱們使用匯編指令讓「b」的值等於「a」的值。下面是
一些要點:
「b」是一個輸出操做數,經過%0聯繫起來;而「a」是一個輸入操做數,經過%1聯繫起來。「r」是對操做數的一個約束。在後面咱們將會談到關於約束的細節。「r」告訴GCC使用任何一個寄存器來存儲操做數的值。輸出操做數的約束必須包含一個約束脩飾符「=」。這個修飾符說明輸出操做數是隻寫的。
這裏有兩個「%」在寄存器以前。這樣能夠幫助GCC辨別操做數和寄存器,操做數只有一個「%」做爲前綴。第三個冒號後的clobbered register %eax告訴GCC%eax的值將會在「asm」裏被修改,因此GCC不會使用這個寄存器去存儲其它的數值。
當「asm」程序執行完後,「b」會映射出更新後的值,由於它被指定爲一個輸出操做數。換句話說,在「asm」裏對「b」的改變將會影響到「asm」的外面的程序。
如今咱們來看看各部分的細節。
彙編程序模板包含被插入到C語言程序裏的彙編指令的集合。它的格式像這樣:每個指令必須包括在雙引號裏面,或者所有的指令包括在雙引號裏面。每個指令還必須以一個定界符(delimiter)結束。合法的定界符能夠是newline(/n)和semicolon(;)。'/n'能夠接一個tab(/t)。咱們都知道了使用newline/tab的理由了吧?對應於C語言表達式的操做數被表示爲%0,%1...等等。
C語言表達式在「asm」裏做爲彙編指令的操做數。每個操做數首先要有一個在雙引號裏的一個操做數約束。對於輸出操做數,還必需要有一個約束脩飾符(它也是在引號裏面),最後纔是一個C語言表達式表示這個操做數。也就是說,"constraint"(C expression)是通常的形式,對於輸出操做數則會有一個額外的修飾符。約束主要用來決定操做數的尋址模式。約束一樣可以用來指定使用哪一個寄存器。
若是咱們使用不止一個操做數,那麼它們用逗號分開。
在彙編程序模板裏,每個操做數經過號碼來引用。編碼方式以下。若是總共有n個操做數(包括輸入和輸出),那麼第一個輸出操做數編號爲0,,第二個編號爲1,以此類推,最後一個輸入操做數編號爲n-1。最大的操做數號如上一節所說的。
輸出操做數必須是值。而輸入操做數則沒有這麼嚴格,它們能夠是表達式。擴展asm屬性常常用於機器指令,編譯器自己並不知道它的存在。若是輸出表達式不能直接地尋址(例如,它是一個bit範圍的值),咱們的約束就必須容許使用一個寄存器。那樣的話,GCC將會使用這個寄存器做爲asm的輸出,而後把這個寄存器的內容保存到輸出。
正如上面所說的,普通的輸出操做數必須是隻寫的(write-only);GCC將會假設在指令以前的這樣的操做數的值是沒有用的,而且不需要被產生。擴展asm也支持input-output或者read-write操做數。
如今讓咱們專一於一些例子。咱們想要一個數乘於5,咱們使用lea指令。
asm ("leal (%1,%1,4), %0"
: "=r" (five_times_x)
: "r" (x)
);
這裏咱們的輸入是「x」。我沒有指定使用哪個寄存器。GCC會選擇某個寄存器來做爲輸入,另外某個來做爲輸出。若是咱們想輸入和輸出都在同一個寄存器,咱們能夠命令GCC這樣作。這裏咱們使用讀寫類型(types of read write)的操做數。經過指定合適的約束,下面咱們實現它:
asm ("leal (%0,%0,4), %0"
: "=r" (five_times_x)
: "0" (x)
);
如今輸入和輸出操做數都在同一個寄存器裏,可是咱們仍是不知道是哪個寄存器。若是咱們想指定寄存器,能夠這樣:
asm ("leal (%%ecx,%%ecx,4), %%ecx"
: "=c" (x)
: "c" (x)
);
在以上的三個例子中,咱們沒有在clobber列表上指定任何寄存器。爲何呢?在前兩個例子中,GCC決定了使哪一個寄存器而且它知道什麼改變了?在最後一個例子中,咱們也不用在clobber列表加入ecx,GCC知道它和x之間傳遞數值。因此GCC知道ecx的值,不用考慮把它加入clobber列表。
若是指令連續使用一些寄存器。咱們必須把這麼寄存器列在clobber列表之中,也則是內聯彙編程序裏第三個冒號後的範圍。這樣是爲了告訴GCC咱們本身將會使用而且修改它們。這樣GCC將不會認爲這些寄存器裏的值是可用的。咱們沒有必要列出用於輸入和輸出的寄存器。由於GCC知道asm程序使用到它們(由於它們在約束中明顯地指出來)。若是指令使用任何其它的寄存器,不管是顯式仍是隱式的指出(同是這樣的寄存器也沒有在輸入或者輸出約束列表中出現),那麼這些寄存器必須在clobber列表中出現。
若是咱們的指令會改變寄存器的值,咱們必須加上"cc"到clobber列表上。
若是咱們的指令在不可預知的狀況修改了內存,則要增長這個到clobber 列表增長」memory」。這樣的話GCC在執行彙編指令時就不會在寄存器中保存這個內存的值。若是對內存的影響並無列在input或者output中,那麼咱們還要增長volatile這個關鍵字。
咱們能夠讀寫無限屢次clobber寄存器。考慮到模板裏多指令的例子,程序假設子程序_foo從寄存器eax和ebx接收參數:
asm ("movl %0,%%eax;
movl %1,%%ecx;
call _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "eax", "ecx"
);
若是你熟悉內核代碼之類的代碼,你必定常常看到許多函數被聲明爲 volatile 或者 __volatile__,這個聲明在asm或者__asm__後面。前面咱們說到了不少關於asm和__asm__。那麼什麼是volatile呢?
若是咱們寫的彙編語句一個要在咱們寫的地方執行(例如,必定不能夠爲了優化從一個循環裏移出來),那麼把關鍵字volatile放在asm和括號之間。這樣就可以避免移動,刪除代碼。聲明以下:
asm volatile ( ... : ... : ... : ...);
使用__volatile__關鍵字時咱們必須很當心。
若是咱們寫的彙編語句只是爲了作一些計算,而且對外面不形成任何影響,那麼最好仍是不要用volatile關鍵字。這樣作有利於gcc對代碼的優化和美化。對於這一章的一些有用的技巧,我已經提供了不少內聯asm函數的例子。詳細內容能夠查看clobber列表。
到目前爲止,你可能已經知道了約束跟內聯彙編關係密切。可是咱們尚未詳細地說到約束。約束能夠用來講明一個操做數是否存放在一個寄存器中,而且在哪一個寄存器中;也能夠用來講明是否在內存中,而且是什麼類型的地址;說明是不是一個當即數,而且可能取什麼值(好比取值範圍)等等。
約束有不少種,可是經常使用的比較很少,如今讓咱們來了解一下這些約束。
1. 寄存器操做數約束(r)
當操做數被指定使用如下的約束時,它們就會被保存在通用寄存器(General Purpose Registers),請看下面的例子:
asm ("movl %%eax, %0/n" :"=r"(myval));
變量myval被保存在寄存器裏,寄存器eax的值被複制到這個寄存器,接着變量myval的值從這個寄存器傳到內存中,更新內存的值。當"r"約束聲明時,gcc會保存變量的值到任何一個可用的通用寄存器中。想要指定某個寄存器,你必須使用專用的寄存器約束符直接指定寄存器的名稱。這些約束符以下所示:
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. 內存操做數約束(m)
對於寄存器操做數約束,先保存要運算的值到一個寄存器,運算後再把值傳到內存中去。內豐操做數約束與相反,當操做數在內存中時,任何運算都會直接在內存中執行。寄存器約束常常在指令可以大大地提升進程運行的速度時被使用。而在C語言變量需要在內聯彙編語句中更新和不需要使用寄存器保存這個變量值時,內存約束則是最有效的。以下面的例子,idtr的值被保存在內存loc中:
asm("sidt %0/n" : :"m"(loc));
3. 匹配(數字)約束
有時候,一個變量同時做爲輸入和輸出的變量。這樣的狀況在內聯彙編裏能夠用「匹配約束」來講明:
asm ("incl %0" :"=a"(var):"0"(var));
上一節,咱們看到了一些關於操做數的例子。而這個例子是爲了說明匹配約束,寄存器%eax同時做爲輸入和輸出的變量。輸入的var被讀到%eax裏,在%eax自增後,%eax的值又保存在var裏。在這裏"0"說明和第0個輸入變量使用一樣的約束。也便是var的值只保存在%eax。這種約束可以用在:
1。輸入和輸出是同一個變量
2。輸入和輸出的操做數實例是不重要的。
使用匹配約束最重要的影響就是這樣可以更加高效地利用可用的寄存器。
其它被使用的約束有:
1. "m":容許使用一個內存操做數,一般這個內存的地址能夠是機器支持的任何值。
2. "o":容許使用一個內存操做數,不過這個地址必須是可移位的。例如,這個地址增長一個較小的位移後,這個地址仍是可用的。
3. "V":一個內存操做數,它是不可移位的。
4. "i":容許使用一個當即整型數(一個常數),這包括一個符號常量,它的值在彙編時知道。
5. "n":容許使用一個當即整型數,它是一個已知的數字值。
6. "g":容許使用任意通用寄存器、內存和當即數。
下面的約束是x86專用的:
1. 「r」:寄存器操做數據約束;
2. 「q」:寄存器a、b、c、d;
3. 「I」: 常量0到31;
4. 「J」: 常量0到63;
5. 「K」:0xff;
6. 「L」:0xffff;
7. 「M」:0, 1, 2, 3;
8. 「N」:0到255的常量;
9. 「f」:浮點數寄存器;
10. 「t」:第一個浮點數寄存器;
11. 「u」:第二個浮點數寄存器;
12. 「A」:指定爲寄存器a或者d,主要用於64位的值;
使用約束時,爲了使對約束效果的控制更加精確,GCC爲咱們提供了約束脩飾符。經常使用的約束脩飾符以下:
1. "=" : 意味着操做數對於這個指令只寫,以前的值被放棄而且被輸入值代替。
2. "&" : 意味着操做數在clobber列表中,而且在被用來做爲輸入操做數以前就已經被改過。所以操做數不能做爲輸入操做數存放在寄存器裏。
上面的列表和說明並不徹底,學習例子可以更好地瞭解到內聯彙編的使用方法。下一章咱們將會學習一些例子,這些例子裏有更多關於clobber列表和約束。
上面所說的覆蓋了GCC內聯彙編的基本原理,如今咱們開始關注一些簡單的例子。用宏來寫內聯彙編函數老是方便的。咱們能在內核代碼裏找到不少內聯彙編函數。(/usr/src/linux/include/asm/*.h)
一、首先咱們以一個簡單的例子開始。咱們要寫一個程序對兩個數據進行相加。
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;
}
上面的例子咱們把foo存放到%eax,bar到%ebx,而且把結果放到%eax。符號"="說明它是一個輸出寄存器。咱們也能夠用另外一種方式給變量加一個數值:
__asm__ __volatile__(
" lock ;/n"
" addl %1,%0 ;/n"
: "=m" (my_var)
: "ir" (my_int), "m" (my_var)
: /* no clobber-list */
);
這是一個原子式的加法。咱們能夠去除指令"lock"來去除它的原子性。在輸出區域,"=m"說明my_var是一個輸出而且存放在內存裏。一樣的,"ir"說明my_int是一個數字而且應該放在某些寄存器裏。clobber列表上沒有寄存器。
二、如今咱們執行一些程序在變量或者寄存器上而且比較它們的值。
__asm__ __volatile__( "decl %0; sete %1"
: "=m" (my_var), "=q" (cond)
: "m" (my_var)
: "memory"
);
my_var的值自減1,若是自減的結果是0,那麼變量cond的值被設置。咱們可以加入一句"lock;/n/t"來增長程序的原子性。
同理,咱們可使用"incl %0"來代替"decl %0",來自增my_var。
說明:
(i)my_var是在內存裏的一個變量
(ii)cond是寄存器eax, ebx, ecx和edx裏的任一個
(iii)咱們能看到"memorg"是在clobber列表上。例如:代碼修改內存的內容。
三、怎麼設置,請空一個寄存器的任一個二進制位。做爲一個技巧,請看下面的例子:
__asm__ __volatile__( "btsl %1,%0"
: "=m" (ADDR)
: "Ir" (pos)
: "cc"
);
在上面的例子裏,內存「ADDR」的第pos個位被設置爲1。咱們能夠用「btrl」代替「btsl」來清空這個位。pos的約束符「Ir」說明pos在一個寄存器裏面,而且它的值的範圍爲是0到31。
四、如今咱們來看看一些複雜可是有用的功能——字符串拷貝。
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是以前被用到的clobber列表。也便是說,在函數結束以前它們的內容會被改變。內存在clobber列表的緣由也是顯而易見的。
下面是一個相似的函數移動一塊雙字節,函數被聲明爲宏:
#define mov_blk(src, dest, numwords) /
__asm__ __volatile__ ( /
"cld/n/t" /
"rep/n/t" /
"movsl" /
: /
: "S" (src), "D" (dest), "c" (numwords) /
: "%ecx", "%esi", "%edi" /
)
五、在Linux內核裏,系統調用就是用內聯彙編來實現的。如今咱們來看看一個系統調用是怎麼實現的。全部的系統調用都被寫成宏(在文件Linux/unistd.h裏)。例如,一個被定義爲宏的有三個參數的系統調用以下:
#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); /
}
系統調用號被放到寄存器eax裏,而後三個參數放到ebx, ecx, edx裏。最後是指令」int $0x80」調用這個系統調用。返回值被放到eax。
每個系統調用均可以用相似的方法實現。」Exit」是一個只有一個參數的系統調用,咱們來看看它的代碼是怎樣的:
{
asm("movl $1,%%eax; /* SYS_exit is 1 */
xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */
int $0x80" /* Enter kernel mode */
);
}
它的調用號是1,參數是0。因此執行」int $0x80」時,咱們將eax設置爲1,ebx爲0。
這篇文章介紹GCC內聯彙編的基礎。只要你能理解它的基本概念,就不難靠本身一步步學習。咱們已經學習了一些有助於理解GCC內聯彙編特性的經常使用例子。
GCC內聯是一個很大的內容,同時這個文章在這個意義上並不完整。關於語法的更多的細節能夠在GNU Assembler的官方文檔上找到。
固然,Linux內核裏大量地用到了GCC內聯彙編。因此咱們能夠在源代碼上找到各類各樣的例子。它們能爲我帶來不少的幫助。
若是你找到任何的排版出錯,或者過期的信息,請讓咱們都知道。
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