本文提供了在 linux 平臺(tái)上使用和構(gòu)造 x86 內(nèi)聯(lián)匯編的概括性介紹。他介紹了內(nèi)聯(lián)匯編及其各種用法的基礎(chǔ)知識(shí),提供了一些基本的內(nèi)聯(lián)匯編編碼指導(dǎo),并解釋了在 Linux 內(nèi)核中內(nèi)聯(lián)匯編代碼的一些實(shí)例。 假如您是 linux 內(nèi)核的開(kāi)發(fā)人員,您會(huì)發(fā)現(xiàn)自己經(jīng)常要對(duì)與體系結(jié)構(gòu)高度相關(guān)的功能進(jìn)行編碼或優(yōu)化代碼路徑。您很可能是通過(guò)將匯編語(yǔ)言指令插入到 C 語(yǔ)句的中間(又稱為內(nèi)聯(lián)匯編的一種方法)來(lái)執(zhí)行這些任務(wù)的。讓我們看一下 Linux 中內(nèi)聯(lián)匯編的特定用法。(我們將討論限制在 IA32 匯編。) [目錄](méi)
GNU 匯編程序簡(jiǎn)述 讓我們首先看一下 linux 中使用的基本匯編程序語(yǔ)法。GCC(用于 Linux 的 GNU C 編譯器)使用 AT&T 匯編語(yǔ)法。下面列出了這種語(yǔ)法的一些基本規(guī)則。(該列表肯定不完整;只包括了與內(nèi)聯(lián)匯編相關(guān)的那些規(guī)則。) 寄存器命名 寄存器名稱有 % 前綴。即,假如必須使用 eax,它應(yīng)該用作 %eax。
源操作數(shù)和目的操作數(shù)的順序 在所有指令中,先是源操作數(shù),然后才是目的操作數(shù)。這與將源操作數(shù)放在目的操作數(shù)之后的 Intel 語(yǔ)法不同。 mov %eax, %ebx, transfers the contents of eax to ebx.
C 表達(dá)式用作 "asm" 內(nèi)的匯編指令操作數(shù)。在匯編指令通過(guò)對(duì) C 程序的 C 表達(dá)式進(jìn)行操作來(lái)執(zhí)行有意義的作業(yè)的情況下,操作數(shù)是內(nèi)聯(lián)匯編的主要特性。 每個(gè)操作數(shù)都由操作數(shù)約束字符串指定,后面跟用括弧括起的 C 表達(dá)式,例如:"constraint" (C eXPRession)。操作數(shù)約束的主要功能是確定操作數(shù)的尋址方式。
"asm" 和寄存器約束 "r" 讓我們先看一下使用寄存器約束 r 的 "asm"。我們的示例顯示了 GCC 如何分配寄存器,以及它如何更新輸出變量的值。 int main(void) { int x = 10, y; asm ("movl %1, %%eax; "movl %%eax, %0;" :"=r"(y) /* y is output operand */ :"r"(x) /* x is input operand */ :"%eax"); /* %eax is clobbered register */ }
在該例中,x 的值復(fù)制為 "asm" 中的 y。x 和 y 都通過(guò)存儲(chǔ)在寄存器中傳遞給 "asm"。為該例生成的匯編代碼如下:
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%edx /* x=10 is stored in %edx */ #APP /* asm starts here */ movl %edx, %eax /* x is moved to %eax */ movl %eax, %edx /* y is allocated in edx and updated */ #NO_APP /* asm ends here */ movl %edx,-8(%ebp) /* value of y in stack is updated with the value in %edx */
當(dāng)使用 "r" 約束時(shí),GCC 在這里可以自由分配任何寄存器。在我們的示例中,它選擇 %edx 來(lái)存儲(chǔ) x。在讀取了 %edx 中 x 的值后,它為 y 也分配了相同的寄存器。
因?yàn)?y 是在輸出操作數(shù)部分中指定的,所以 %edx 中更新的值存儲(chǔ)在 -8(%ebp),堆棧上 y 的位置中。假如 y 是在輸入部分中指定的,那么即使它在 y 的臨時(shí)寄存器存儲(chǔ)值 (%edx) 中被更新,堆棧上 y 的值也不會(huì)更新。
輸入 x 和輸出 y 都分配在同一個(gè) %edx 寄存器中,假設(shè)輸入在輸出產(chǎn)生之前被消耗。請(qǐng)注重,假如您有許多指令,就不是這種情況了。要確保輸入和輸出分配到不同的寄存器中,可以指定 & 約束修飾符。下面是添加了約束修飾符的示例。
int main(void) { int x = 10, y; asm ("movl %1, %%eax; "movl %%eax, %0;" :"=&r"(y) /* y is output operand, note the & constraint modifier. */ :"r"(x) /* x is input operand */ :"%eax"); /* %eax is clobbered register */ }
以下是為該示例生成的匯編代碼,從中可以明顯地看出 x 和 y 存儲(chǔ)在 "asm" 中不同的寄存器中。
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%ecx /* x, the input is in %ecx */ #APP movl %ecx, %eax movl %eax, %edx /* y, the output is in %edx */ #NO_APP movl %edx,-8(%ebp)
#define rdtscll(val) __asm__ __volatile__ ("rdtsc" : "=A" (val)) The generated assembly looks like this (if val has a 64 bit memory space). #APP rdtsc #NO_APP movl %eax,-8(%ebp) /* As a result of A constraint movl %edx,-4(%ebp) %eax and %edx serve as outputs */ Note here that the values in %edx:%eax serve as 64 bit output.