C/C++ 中嵌入汇编总结
GCC匯編語(yǔ)法梗概
AT&T 與 Intel 匯編區(qū)別
Linux GCC(GNU, C Compiler)使用AT&T匯編語(yǔ)法。下面列一下AT&T 和Intel匯編語(yǔ)法中的不同:
源-目的 順序
AT&T中源和目的操作數(shù)的順序相反。Intel語(yǔ)法中第一個(gè)操作數(shù)是目的,第二個(gè)是源。而AT&T語(yǔ)法中,第一個(gè)是源第二個(gè)操作數(shù)是目的。
“Op-code src, dst” —— AT&T 語(yǔ)法
“Op-code dst, src” —— Intel語(yǔ)法
寄存器命名
AT&T語(yǔ)法中,寄存器需要有‘%’前綴,例如eax需要寫(xiě)作%eax。這里只是強(qiáng)調(diào)Intel匯編語(yǔ)法中不使用各種前綴,具體寄存器命名后面會(huì)繼續(xù)涉及。
立即數(shù)
AT&T中立即數(shù)需要有‘$’前綴。如果是靜態(tài)“C”變量,同樣需要’$’前綴。
Intel語(yǔ)法中,16進(jìn)制需要’h’前綴,而AT&T則需要‘0x’前綴。所以,對(duì)于16進(jìn)制的立即數(shù)寫(xiě)作 ‘$0x1234’
操作數(shù)大小
在AT&T語(yǔ)法中,內(nèi)存操作數(shù)的大小取決于Op-code的后綴字母’b’’w’以及’l’,分別指代’字節(jié)(8bit) 字(16bit) 和長(zhǎng)字(32bit)內(nèi)存指針。而Intel語(yǔ)法使用前置限定符’byte ptr’ ‘word ptr’ 以及’dword prt’。
所以下面兩句等效:
| 1 2 | Intel "mov al, byte ptr foo" At&T "movb foo, %al" |
內(nèi)存地址的訪問(wèn)
在AT&T中基址寄存器使用’()’,Intel中使用’[]’。 下面兩句等效:
| 1 2 | Intel "section: [base + index*scale + disp]" AT&T "section: disp(base, index, scale)" |
GNU AT&T匯編
寄存器
現(xiàn)代X86處理器(例如386及其以后)有8個(gè)32位通用寄存器(general purpose registers,GPR)如圖:
寄存器名字是繼承過(guò)來(lái)的。例如EAX成為累加器,因?yàn)橹氨淮罅康乃惴ㄟ@樣操作,ECX被稱(chēng)為計(jì)數(shù)器,因?yàn)樗ǔS脕?lái)作為循環(huán)的索引。然而,在現(xiàn)代指令集中,大多數(shù)寄存器已經(jīng)失去了它之前特殊的用途。但有兩個(gè)例外的——堆指針(ESP)和基址指針(EBP)。
對(duì)于EAX EBX ECX以及EDX,可以分段使用。例如,EAX的低2字節(jié)可以看做是16位寄存器,稱(chēng)作AX;低1字節(jié)可以看做8位寄存器,稱(chēng)作AL,而AX的高字節(jié)也可以看作是8位寄存器,成為AH。這些寄存器名字都指向相同的物理寄存器。當(dāng)2字節(jié)數(shù)值存入DX中的時(shí)候,它會(huì)影響DH,DL以及EDX。
| 1 2 3 | movb $2, (%ebx) /* Move 2 into the single byte at the address stored in EBX. */ movw $2, (%ebx) /* Move the 16-bit integer representation of 2 into the 2 bytes starting at the address in EBX. */ movl $2, (%ebx) /* Move the 32-bit integer representation of 2 into the 4 bytes starting at the address in EBX. */ |
內(nèi)存操作符
X86指令中,我們可以生命靜態(tài)數(shù)據(jù)區(qū)域(類(lèi)似全局變量)。使用.data指令來(lái)聲明,緊跟在.data指令之后,使用指令.byte,?.short?以及.long來(lái)聲明1,2或者4字節(jié)數(shù)據(jù)位置,然后使用標(biāo)簽來(lái)引用之前創(chuàng)建的數(shù)據(jù)區(qū)。標(biāo)簽可以看做是內(nèi)存區(qū)域的名字,可以在之后的匯編或者連接器中使用標(biāo)簽,這就跟用名字聲明變量非常的相似,但稍微有些不同。比如,一個(gè)連續(xù)的內(nèi)存數(shù)據(jù)位置的聲明他們?cè)趦?nèi)存中的位置是連續(xù)的。
| 1 2 3 4 5 6 7 8 | .data var:.byte 64 /* Declare a byte, referred to as location var, containing the value 64. */.byte 10 /* Declare a byte with no label, containing the value 10. Its location is var + 1. */ x:.short 42 /* Declare a 2-byte value initialized to 42, referred to as location x. */ y: .long 30000 /* Declare a 4-byte value, referred to as location y, initialized to 30000. */ |
并不像高級(jí)語(yǔ)言那樣,數(shù)組可以有各種容量,并且可以使用索引訪問(wèn)。X86匯編中的數(shù)組僅僅是簡(jiǎn)單的一些內(nèi)存中接續(xù)的存儲(chǔ)單元。數(shù)組可以通過(guò)列出數(shù)值累聲明(例如第一個(gè)例子)。對(duì)于一些特別的數(shù)組,可以使用字符串;再如果一個(gè)很大的內(nèi)存需要填充0,那么可使用.zero指令。
| 1 2 3 4 5 6 7 8 | s:.long 1, 2, 3 /* Declare three 4-byte values, initialized to 1, 2, and 3. The value at location s + 8 will be 3. */ barr:.zero 10 /* Declare 10 bytes starting at location barr, initialized to 0. */ str:.string "hello" /* Declare 6 bytes starting at the address str initialized to the ASCII character values for hello followed by a nul (0) byte. */ |
內(nèi)存尋址
現(xiàn)代X86處理器最高可尋址2^32字節(jié)的內(nèi)存地址(內(nèi)存地址有32位寬)。上面的例子中,我們使用標(biāo)簽指向內(nèi)存區(qū)域,這些標(biāo)簽實(shí)際上被編譯器用實(shí)際32位地址取代。為了更進(jìn)一步支持標(biāo)簽指向內(nèi)存地址(例如常量),X86提供了一個(gè)靈活的計(jì)算和引用內(nèi)存地址的方法:兩個(gè)32位寄存器以及一個(gè)32位有符號(hào)常量相加,并且其中一個(gè)寄存器可以被2,4,8相乘。
有一點(diǎn)需要注意,當(dāng)disp/scale中使用常數(shù)的時(shí)候,這里不再需要”$”前綴。
| mov eax, 1 | movl $1, %eax |
| mov ebx, 0ffh | movl $0xff, %ebx |
| int 80h | int $0x80 |
| mov ebx, eax | mov %eax, %ebx |
| mov eax, [ecx] | movl (%ecx), %eax |
| mov eax, [ebx + 3] | movl 3(%ebx), %eax |
| mov eax, [ebx + 20h] | movl 0x20(%ebx), %eax |
| mov eax, [ebx + ecx] | movl (%ebx,%ecx), %eax |
| mov eax, [ebx + ecx*2h] | movl (%ebx,%ecx,0x2), %eax |
| mov eax, [ebx + ecx*4h-20h] | movl -0x20(%ebx,%ecx,0x4), %eax |
基本內(nèi)聯(lián)
基本內(nèi)聯(lián)匯編的格式比較簡(jiǎn)單:
| 1 | asm("assembly code"); |
例如:
| 1 2 | asm("movl %ecx, %eax"); //把ecx中的內(nèi)容移動(dòng)到eax中 __asm__("movb %bh, (%eax)") //把寄存器bh中的內(nèi)容移動(dòng)到eax指向的內(nèi)存地址 |
這里有幾點(diǎn)注意事項(xiàng):
| 1 2 3 4 | __asm__ ("movl %eax, %ebx \n\t""movl $56, %esi \n\t""movl %ecx, $label(%edx,%ebx,$4) \n\t""movb %ah, (%ebx)"); |
如果在代碼中,更改過(guò)一些寄存器并從asm返回后,則會(huì)發(fā)生一些難以預(yù)料的事情。這是因?yàn)镚CC不知道寄存器內(nèi)容的變化,所致,特別是當(dāng)編譯器進(jìn)行一些優(yōu)化時(shí)。這就需要一些擴(kuò)展功能的地方。下面來(lái)看下擴(kuò)展的asm語(yǔ)法。
擴(kuò)展的ASM
在基本內(nèi)聯(lián)匯編中,我們只有指令。但在擴(kuò)展匯編中,我們可以指定操作數(shù)。并且允許指定輸入寄存器,輸出寄存器以及改動(dòng)的寄存器列表。但不強(qiáng)制使用寄存器。格式如下:
| 1 2 3 4 5 | asm ( assembler template : output operands /* optional */: input operands /* optional */: list of clobbered registers /* optional */); |
如果沒(méi)有輸出操作符,但有輸入操作,也必須保留兩個(gè)冒號(hào),例如:
| 1 2 3 4 5 6 7 | asm ("cld \n\t" //多行指令"rep \n\t""stosl": /* 沒(méi)有輸出寄存器 */: "c" (count), "a" (fill_value), "D" (dest): "%ecx", "%edi" ); |
上段代碼意思是,把fill_value變量中的值往edi指向的內(nèi)存地址寫(xiě)入count次。也就是說(shuō),eax和edi中的內(nèi)容不再有效。再來(lái)看下面的例子:
| 1 2 3 4 5 6 7 | int a=10, b; asm ("movl %1, %%eax; \n\t" "movl %%eax, %0;":"=r"(b) /* output */:"r"(a) /* input */:"%eax" /* clobbered register */); |
這段代碼意思是,把變量a的值賦值給b。
- ‘b’是輸出操作符,%0引用它,并且’a’是輸入操作符,1%引用它。
- ‘r’是操作符的限定符。這里’r’告訴GCC可以使用任意一個(gè)寄存器來(lái)存儲(chǔ)操作符。’=’是輸出操作符的限定符,并且是只寫(xiě)的。
- 在寄存器之前有兩個(gè)’%’號(hào)。用來(lái)幫助GCC區(qū)別操作符還是寄存器。操作符只有一個(gè)’%’前綴。
換句話說(shuō),在擴(kuò)展ASM語(yǔ)法中,如果在匯編中直接使用寄存器名字而不是通過(guò)%0 %1這樣引用,則寄存器前需要兩個(gè)%限定。 - 改動(dòng)的寄存器%eax列在第三個(gè)冒號(hào)在后,告訴GCC %eax的值在匯編中有改動(dòng),所以GCC不會(huì)再用這個(gè)寄存器存儲(chǔ)其他的值。
當(dāng)asm結(jié)束時(shí),’b’會(huì)反應(yīng)更新過(guò)的數(shù)據(jù),因?yàn)樗恢付檩敵霾僮鞣Q句話說(shuō),在匯編中改變’b’的值,會(huì)被反映到匯編之外。 - 但如果一個(gè)寄存器已經(jīng)出現(xiàn)在輸出操作符列表中,那么無(wú)需再將它添加到clobber list里,如果添加了編譯的時(shí)候會(huì)報(bào)錯(cuò)。例如下面這段匯編是錯(cuò)誤的:
1 2 3 4 5 6 7 int a=10, b; asm ("movl %1, %%eax; \n\t""movl %%eax, %0;":"=b"(b) /* 明確指出使用寄存器ebx */:"r"(a) /* input */:"%eax", "%ebx" /* 編譯出錯(cuò)!! */);
編譯器模板
操作數(shù)
每個(gè) 操作數(shù)都必須包含在“”之內(nèi)。對(duì)于輸出操作數(shù),雙引號(hào)(“”)中會(huì)多一個(gè)限定符,用來(lái)決定限定符地址的模式。
如果有多個(gè)操作數(shù),他們使用逗號(hào)(,)隔開(kāi)。
每個(gè)操作數(shù)都可以通過(guò)數(shù)字來(lái)引用,按照順序一次命名。輸入和輸出操作數(shù)依次命名,第一個(gè)輸出的操作數(shù)記作0,后面依次增加。
輸出操作數(shù)必須是長(zhǎng)類(lèi)型,輸入操作數(shù)沒(méi)有這個(gè)限制。擴(kuò)展匯編最常用來(lái)調(diào)用機(jī)器指令本身,跟編譯器無(wú)關(guān)。如果輸出表達(dá)式不是一個(gè)直接地址,例如一個(gè)位閾,限定符必須是寄存器。此事,GCC會(huì)使用寄存器作為內(nèi)聯(lián)匯編的輸出,并且把寄存器的值存儲(chǔ)到輸出里。
綜上,原始輸出操作數(shù)必須是“只寫(xiě)”的;GCC假定在指令結(jié)束之前,數(shù)值都在這些操作數(shù)中,并且不需要生成。擴(kuò)展匯編也支持讀寫(xiě)操作數(shù)。
來(lái)看幾個(gè)例子:
| 1 2 3 4 | asm ("leal (%1, %1, 4), %0": "=r" (five_times_x): "r" (x) ); |
這個(gè)例子中,輸入是’x’,并且沒(méi)有指定寄存器。GCC會(huì)自己選擇一個(gè)。再同樣給輸出選擇一個(gè)寄存器。如果我們想要輸入輸出使用同一個(gè)寄存器,可以告訴GCC我們希望那種讀寫(xiě)的操作數(shù),比如:
| 1 2 3 4 | asm ("leal (%0, %0, 4), %0": "=r" (five_times_x): "0" (x) ); |
此時(shí),輸入輸出操作數(shù)會(huì)是同一個(gè)寄存器。但我們并不知道是具體那一個(gè)。如果想明確指定某一個(gè)寄存器,也有方法,比如:
| 1 2 3 4 | asm ("leal (%%ecx,%%ecx,4), %%ecx": "=c" (x): "c" (x) ); |
以上三個(gè)例子,并沒(méi)有指定任何改動(dòng)寄存器列表,為什么?前兩個(gè)例子,GCC決定使用哪個(gè)寄存器,它會(huì)知道寄存器發(fā)生的變化。在最后一個(gè)例子中,我們也沒(méi)有指定變化寄存器,因?yàn)镚CC知道值最終保存到x中,在匯編之外,它知道ecx的值了,所以沒(méi)有必要列出變化寄存器列表(clobber list),如果列上%ecx編譯就會(huì)錯(cuò)誤。
變化寄存器列表(clobber)
有些指令會(huì)改變硬件寄存器,因此必須明確指出這些改動(dòng)過(guò)的寄存器,將其列在第三個(gè)’:’之后。這是為了告訴GCC匯編使用并修改了那些寄存器。所以,GCC會(huì)知道之前被加載到這些寄存器的值已經(jīng)無(wú)效了。同時(shí)沒(méi)有必要列出放在輸入和輸出操作數(shù)中的寄存器,以?xún)?nèi)GCC知道內(nèi)聯(lián)已經(jīng)使用了他們。需要明確指出的是那些沒(méi)有明確指出的隱式使用的寄存器,那些沒(méi)有列在輸入和輸出操作數(shù)中的寄存器。
如果指令修改了內(nèi)存,需要在clobber list中添加”memory”。這樣通知GCC內(nèi)存緩存應(yīng)該失效了。同時(shí)必須添加’volatile’關(guān)鍵字,如果內(nèi)存修改并沒(méi)有列在輸入和輸出操作數(shù)中時(shí)。
我們多次可以讀寫(xiě)更改的寄存器,參考下面的例子,意思是調(diào)用子程序_foo,并且通過(guò)eax 和 ecx傳遞兩個(gè)參數(shù)給他。
注:這里的寄存器名字前是否加%,eax和%eax都是正確的!
| 1 2 3 4 5 6 7 | asm ("movl %0,%%eax; \n\t""movl %1,%%ecx; \n\t""call _foo": /* no outputs */: "g" (from), "g" (to): "eax", "ecx"); |
Volatile 關(guān)鍵字
如果熟悉內(nèi)核源碼,我們會(huì)經(jīng)常看到volatile或者volatile關(guān)鍵字在 asm或者asm之后。
如果內(nèi)聯(lián)匯編需要在它原來(lái)所在的位置處被執(zhí)行,例如不被移到循環(huán)的外面或者不被優(yōu)化掉,在asm 和 ()之間放一個(gè)volatile,像這樣:
| 1 | asm volatile ( ... : ... : ... : ...); |
如果我們添加的匯編語(yǔ)言?xún)H僅是為了計(jì)算并且沒(méi)有任何邊際效應(yīng),最好不要使用volatile關(guān)鍵字,因?yàn)関olatile會(huì)妨礙代碼優(yōu)化。
關(guān)于限定符
前面的例子中我們已經(jīng)使用了很多限定符,但還沒(méi)有具體講限定符的作用。限定符可以規(guī)定操作數(shù)是否在寄存器中,什么樣的寄存器;以及操作數(shù)是指向內(nèi)存以及內(nèi)存地址類(lèi)型;操作數(shù)是否是立即數(shù),和數(shù)字的范圍。來(lái)看下常用的限定符。
寄存器操作數(shù)限定符 ‘r’
當(dāng)使用這個(gè)限定符時(shí),操作數(shù)被存儲(chǔ)在通用寄存器中(General Purpose Registers, GPR)。舉例說(shuō)明:
| 1 | asm ("movl %%eax, %0\n" :"=r"(myval)); |
變量myval被存儲(chǔ)到及粗糙那其中,寄存器eax中的值被copy到那個(gè)寄存器中,并且myval的值會(huì)被從寄存器更新到內(nèi)存中。因?yàn)橄薅ǚ痳’,gcc會(huì)把變量保存到任何一個(gè)通用寄存器中。如果需要明確指定某一個(gè)寄存器,需要使用相對(duì)應(yīng)的限定符,如下表:
| a | %eax, %ax, %al |
| b | %ebx, %bx, %bl |
| c | %ecx, %cx, %cl |
| d | %edx, %dx, %dl |
| S | %esi, %si |
| D | %edi, %di |
內(nèi)存操作數(shù)限定符 ‘m’
當(dāng)操作數(shù)在內(nèi)存中,任何關(guān)于操作數(shù)的操作都直接訪問(wèn)內(nèi)存地址。相反,寄存器操作數(shù)是先把數(shù)據(jù)存儲(chǔ)到寄存器中,在寫(xiě)回到內(nèi)存地址中。但寄存器限定符只有當(dāng)指令明確需要或者明顯加速處理,才會(huì)存儲(chǔ)到寄存器中。當(dāng)一個(gè)C變量需要在內(nèi)聯(lián)匯編更新的時(shí)候,內(nèi)存限定符會(huì)更方便,并且,我們并不是真的需要用寄存器來(lái)存儲(chǔ)值。例如存儲(chǔ)IDTR的值到loc中:
| 1 | asm("sidt %0\n" : :"m"(loc)); |
匹配限定符(Digit)
很多情況下,一個(gè)變量就可以做輸入也可以做輸出操作數(shù),例如:
| 1 | asm ("incl %0" :"=a"(var):"0"(var)); |
來(lái)看一個(gè)簡(jiǎn)單的例子,寄存器eax及用作輸入同時(shí)用作輸出操作數(shù)。變量var作為輸入,傳值給eax,并且在完成自增后,又更新到eax中。“0”這里指代同一個(gè)限定符,第0個(gè),也就是輸出變量。也就是輸出變量var只會(huì)被存到eax中。通常下列情況可以這樣使用:
- 當(dāng)變量作為輸入,并且協(xié)會(huì)到同一個(gè)變量中。
- 沒(méi)必要把輸入和輸出分開(kāi)的時(shí)候。
匹配限定符最重要的作用是高效的使用寄存器。
其他限定符:
“g” : Any register, memory or immediate integer operand is allowed, except for registers that are not general registers.
“m” : A memory operand is allowed, with any kind of address that the machine supports in general.
“o” : A memory operand is allowed, but only if the address is offsettable. ie, adding a small offset to the address gives a valid address.
“V” : A memory operand that is not offsettable. In other words, anything that would fit the?m’ constraint but not theo’constraint.
“i” : An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time.
“n” : An immediate integer operand with a known numeric value is allowed. Many systems cannot support assembly-time constants for operands less than a word wide. Constraints for these operands should use ’n’ rather than ’i’.
下面是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(chǎn)’ or ‘d’ registers. This is primarily useful for 64-bit integer values intended to be returned with the ‘d’ register holding the most significant bits and the ‘a(chǎn)’ register holding the least significant bits.
限定修飾符
參考:https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
https://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/
http://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html
實(shí)例
First we start with a simple example. We’ll write a program to add two numbers.
| 1 2 3 4 5 6 7 8 9 10 | 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; } |
Here we insist GCC to store foo in %eax, bar in %ebx and we also want the result in %eax. The ’=’ sign shows that it is an output register. Now we can add an integer to a variable in some other way.
| 1 2 3 4 5 6 7 | __asm__ __volatile__(" lock ;\n"" addl %1,%0 ;\n": "=m" (my_var): "ir" (my_int), "m" (my_var): /* no clobber-list */); |
This is an atomic addition. We can remove the instruction ’lock’ to remove the atomicity. In the output field, “=m” says that my_var is an output and it is in memory. Similarly, “ir” says that, my_int is an integer and should reside in some register (recall the table we saw above). No registers are in the clobber list.
Now we’ll perform some action on some registers/variables and compare the value.
| 1 2 3 4 5 | __asm__ __volatile__( "decl %0; sete %1": "=m" (my_var), "=q" (cond): "m" (my_var) : "memory"); |
Here, the value of my_var is decremented by one and if the resulting value is 0 then, the variable cond is set. We can add atomicity by adding an instruction “l(fā)ock;\n\t” as the first instruction in assembler template.
In a similar way we can use “incl %0” instead of “decl %0”, so as to increment my_var.
Points to note here are that (i) my_var is a variable residing in memory. (ii) cond is in any of the registers eax, ebx, ecx and edx. The constraint “=q” guarantees it. (iii) And we can see that memory is there in the clobber list. ie, the code is changing the contents of memory.
How to set/clear a bit in a register? As next recipe, we are going to see it.
| 1 2 3 4 5 | __asm__ __volatile__( "btsl %1,%0": "=m" (ADDR): "Ir" (pos): "cc"); |
Here, the bit at the position ’pos’ of variable at ADDR ( a memory variable ) is set to 1 We can use ’btrl’ for ’btsl’ to clear the bit. The constraint “Ir” of pos says that, pos is in a register, and it’s value ranges from 0-31 (x86 dependant constraint). ie, we can set/clear any bit from 0th to 31st of the variable at ADDR. As the condition codes will be changed, we are adding “cc” to clobberlist.
Now we look at some more complicated but useful function. String copy.
| 1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
The source address is stored in esi, destination in edi, and then starts the copy, when we reach at 0, copying is complete. Constraints “&S”, “&D”, “&a” say that the registers esi, edi and eax are early clobber registers, ie, their contents will change before the completion of the function. Here also it’s clear that why memory is in clobberlist.
We can see a similar function which moves a block of double words. Notice that the function is declared as a macro.
| 1 2 3 4 5 6 7 8 9 | #define mov_blk(src, dest, numwords) \ __asm__ __volatile__ ( \"cld\n\t" \"rep\n\t" \"movsl" \: \: "S" (src), "D" (dest), "c" (numwords) \: "%ecx", "%esi", "%edi" \) |
Here we have no outputs, so the changes that happen to the contents of the registers ecx, esi and edi are side effects of the block movement. So we have to add them to the clobber list.
In Linux, system calls are implemented using GCC inline assembly. Let us look how a system call is implemented. All the system calls are written as macros (linux/unistd.h). For example, a system call with three arguments is defined as a macro as shown below.
| 1 2 3 4 5 6 7 8 9 10 | #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); \ } |
Whenever a system call with three arguments is made, the macro shown above is used to make the call. The syscall number is placed in eax, then each parameters in ebx, ecx, edx. And finally “int 0x80” is the instruction which makes the system call work. The return value can be collected from eax.
Every system calls are implemented in a similar way. Exit is a single parameter syscall and let’s see how it’s code will look like. It is as shown below.
| 1 2 3 4 5 6 | {asm("movl $1,%%eax; /* SYS_exit is 1 */xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */int $0x80" /* Enter kernel mode */); } |
The number of exit is “1” and here, it’s parameter is 0. So we arrange eax to contain 1 and ebx to contain 0 and by int $0x80, the exit(0) is executed. This is how exit works.
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的C/C++ 中嵌入汇编总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Cantor表(洛谷P1014题题解,J
- 下一篇: 【CSS3】CSS3支持的颜色表示方法大