本文提供了在 linux 平台上使用和构造 x86 内联汇编的概括性介绍。他介绍了内联汇编及其各种用法的基础知识,提供了一些基本的内联汇编编码指导,并解释了在 Linux 内核中内联汇编代码的一些实例。 假如您是 linux 内核的开发人员,您会发现自己经常要对与体系结构高度相关的功能进行编码或优化代码路径。您很可能是通过将汇编语言指令插入到 C 语句的中间(又称为内联汇编的一种方法)来执行这些任务的。让我们看一下 Linux 中内联汇编的特定用法。(我们将讨论限制在 IA32 汇编。) [目录]
GNU 汇编程序简述 让我们首先看一下 linux 中使用的基本汇编程序语法。GCC(用于 Linux 的 GNU C 编译器)使用 AT&T 汇编语法。下面列出了这种语法的一些基本规则。(该列表肯定不完整;只包括了与内联汇编相关的那些规则。) 寄存器命名 寄存器名称有 % 前缀。即,假如必须使用 eax,它应该用作 %eax。
源操作数和目的操作数的顺序 在所有指令中,先是源操作数,然后才是目的操作数。这与将源操作数放在目的操作数之后的 Intel 语法不同。 mov %eax, %ebx, transfers the contents of eax to ebx.
C 表达式用作 "asm" 内的汇编指令操作数。在汇编指令通过对 C 程序的 C 表达式进行操作来执行有意义的作业的情况下,操作数是内联汇编的主要特性。 每个操作数都由操作数约束字符串指定,后面跟用括弧括起的 C 表达式,例如:"constraint" (C eXPRession)。操作数约束的主要功能是确定操作数的寻址方式。
可以在输入和输出部分中同时使用多个操作数。每个操作数由逗号分隔开。
在汇编程序模板内部,操作数由数字引用。假如总共有 n 个操作数(包括输入和输出),那么第一个输出操作数的编号为 0,逐项递增,最后那个输入操作数的编号为 n-1。总操作数的数目限制在 10,假如机器描述中任何指令模式中的最大操作数数目大于 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 的值复制为 "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 */
当使用 "r" 约束时,GCC 在这里可以自由分配任何寄存器。在我们的示例中,它选择 %edx 来存储 x。在读取了 %edx 中 x 的值后,它为 y 也分配了相同的寄存器。
因为 y 是在输出操作数部分中指定的,所以 %edx 中更新的值存储在 -8(%ebp),堆栈上 y 的位置中。假如 y 是在输入部分中指定的,那么即使它在 y 的临时寄存器存储值 (%edx) 中被更新,堆栈上 y 的值也不会更新。
因为 %eax 是在修饰列表中指定的,GCC 不在任何其它地方使用它来存储数据。
输入 x 和输出 y 都分配在同一个 %edx 寄存器中,假设输入在输出产生之前被消耗。请注重,假如您有许多指令,就不是这种情况了。要确保输入和输出分配到不同的寄存器中,可以指定 & 约束修饰符。下面是添加了约束修饰符的示例。
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.