本文提供了在 linux 平臺上使用和構(gòu)造 x86 內(nèi)聯(lián)匯編的概括性介紹。他介紹了內(nèi)聯(lián)匯編及其各種用法的基礎(chǔ)知識,提供了一些基本的內(nèi)聯(lián)匯編編碼指導(dǎo),并解釋了在 Linux 內(nèi)核中內(nèi)聯(lián)匯編代碼的一些實例。 假如您是 linux 內(nèi)核的開發(fā)人員,您會發(fā)現(xiàn)自己經(jīng)常要對與體系結(jié)構(gòu)高度相關(guān)的功能進行編碼或優(yōu)化代碼路徑。您很可能是通過將匯編語言指令插入到 C 語句的中間(又稱為內(nèi)聯(lián)匯編的一種方法)來執(zhí)行這些任務(wù)的。讓我們看一下 Linux 中內(nèi)聯(lián)匯編的特定用法。(我們將討論限制在 IA32 匯編。) [目錄]
GNU 匯編程序簡述 讓我們首先看一下 linux 中使用的基本匯編程序語法。GCC(用于 Linux 的 GNU C 編譯器)使用 AT&T 匯編語法。下面列出了這種語法的一些基本規(guī)則。(該列表肯定不完整;只包括了與內(nèi)聯(lián)匯編相關(guān)的那些規(guī)則。) 寄存器命名 寄存器名稱有 % 前綴。即,假如必須使用 eax,它應(yīng)該用作 %eax。
源操作數(shù)和目的操作數(shù)的順序 在所有指令中,先是源操作數(shù),然后才是目的操作數(shù)。這與將源操作數(shù)放在目的操作數(shù)之后的 Intel 語法不同。 mov %eax, %ebx, transfers the contents of eax to ebx.
C 表達式用作 "asm" 內(nèi)的匯編指令操作數(shù)。在匯編指令通過對 C 程序的 C 表達式進行操作來執(zhí)行有意義的作業(yè)的情況下,操作數(shù)是內(nèi)聯(lián)匯編的主要特性。 每個操作數(shù)都由操作數(shù)約束字符串指定,后面跟用括弧括起的 C 表達式,例如:"constraint" (C eXPRession)。操作數(shù)約束的主要功能是確定操作數(shù)的尋址方式。
可以在輸入和輸出部分中同時使用多個操作數(shù)。每個操作數(shù)由逗號分隔開。
在匯編程序模板內(nèi)部,操作數(shù)由數(shù)字引用。假如總共有 n 個操作數(shù)(包括輸入和輸出),那么第一個輸出操作數(shù)的編號為 0,逐項遞增,最后那個輸入操作數(shù)的編號為 n-1??偛僮鲾?shù)的數(shù)目限制在 10,假如機器描述中任何指令模式中的最大操作數(shù)數(shù)目大于 10,則使用后者作為限制。
"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 都通過存儲在寄存器中傳遞給 "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" 約束時,GCC 在這里可以自由分配任何寄存器。在我們的示例中,它選擇 %edx 來存儲 x。在讀取了 %edx 中 x 的值后,它為 y 也分配了相同的寄存器。
因為 y 是在輸出操作數(shù)部分中指定的,所以 %edx 中更新的值存儲在 -8(%ebp),堆棧上 y 的位置中。假如 y 是在輸入部分中指定的,那么即使它在 y 的臨時寄存器存儲值 (%edx) 中被更新,堆棧上 y 的值也不會更新。
因為 %eax 是在修飾列表中指定的,GCC 不在任何其它地方使用它來存儲數(shù)據(jù)。
輸入 x 和輸出 y 都分配在同一個 %edx 寄存器中,假設(shè)輸入在輸出產(chǎn)生之前被消耗。請注重,假如您有許多指令,就不是這種情況了。要確保輸入和輸出分配到不同的寄存器中,可以指定 & 約束修飾符。下面是添加了約束修飾符的示例。
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 存儲在 "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.