本文从Inline assembly for x86 in Linux中摘译。 Table of Contents
1 简要GNU汇编语法1.1 寄存器命名寄存器名字前面应该加上%前缀。比如要使用eax,那么应该写为%eax。 1.2 来源和目的的顺序在任何一条指令中,总是先写来源,然后再写目的。这与Intel汇编语法正好相反。 movl %eax, %ebx -- transfers the contents of eax to ebx 1.3 操作数的尺寸根据操作数的长度是字节、字或者长整形,指令一般需要加上b、w或者l后缀。这不是强制要求的,GCC会根据操作数的尺寸自动加上合适后缀。但是手工加上后缀一方面加强了可读性,另一方面消除了GCC猜错的可能。 movb %al, %blmovw %ax, %bxmovl %eax, %ebx 1.4 直接操作数直接操作数需要加上$前缀。 movl $0xffff, %eax 1.5 直接内存引用使用()去完成直接内存引用。 movb (%esi), %al -- will transfer the byte in the memory pointed by esi to al 2 内联汇编GCC提供了一种特殊结构去创建联汇编,格式如下: asm ( assembler template : output operands : input operands : list of clobbered registers ); 模板由汇编指令构成。input operands是用来作为汇编指令输入操作数的C表达式。output operands依此类推。 asm ("movl %%cr4, %0\n" :"=r"(cr3val)) a %eaxb %ebxc %ecxd %edxS %esiD %edi 2.1 内存操作数约束当操作数位于内存中时,所有操作都会直接在内存位置执行。内存操作数约束的作用是当一个C变量需要被内联汇编操作时,程序员不需要显式地把它放到寄存器中。如: asm ("sidt %0\n" : :"m"(loc)); 2.2 匹配约束在某些情况下,一个变量可能会同时输入和输出操作数。这样的情况可以使用匹配约束来完成。 asm ("incl %0" :"=a"(var):"0"(var)); 在我们的匹配约束例子中,%eax同时作为输入和输出操作数。var先被读入到%eax,操作完成后结果被存回var。“0”指定了和0号输出变量同样约束。即是说,它指定了var的输出应该只被放到%eax。这类约束可以在如下的情况使用:
使用匹配约束可以有效地使用寄存器。 2.3 常见内联汇编用例下面的例子展示常见的用法。 2.3.1 “asm”和寄存器约束“r”int main(void) { int x = 10, y; asm ("movl %1, %%eax;" "movl %%eax, %0;" :"=r"(y) :"r"(x) :"%eax"); /* y is output operand */ /* x is input operand */ /* eax is clobbered register */ return 0; } 生成的汇编代码如下: main: pushl %ebp movl %esp, %ebp subl $8, %esp movl $10, -4(%ebp) movl -4(%ebp), %edx /* x=10 is stored in %edx */,#APP movl %edx, %eax /* x is moved to eax */ movl %eax, %edx /* y is allocated in edx and updated */,#NO_APP movl %edx, -8(%ebp) /* value of y in the stack is updated with the value in edx */ “r”指明GCC可以采用任意的寄存器。在上例中,edx被先后用于存放x和y。 由于eax在clobbered list中,GCC不会使用它去存放数据。 在上例中,edx被先后用于输入和输出,这里有一个前提就是输入数据总是在输出之前就已经失效了。但是实际情况中有很多指令并不总是这样做的,所以我们可以加上“&”修饰符让输入输出使用不同的寄存器。 int main(void){ int x = 10, y; asm ("movl %1, %%eax;" "movl %%eax, %0;" :"=r"(y) /* y is output operand, note the "&" modifier */ :"r"(x) /* x is input operand */ :"%eax");/* eax is clobbered register */ return 0;} 生成下面代码: main: pushl %ebp movl %esp, %ebp subl $8, %esp movl $10, -4(%ebp) movl -4(%ebp), %ecx /* x=10 is stored in %ecx */,#APP movl %ecx, %eax movl %eax, %edx /* output is in %edx */,#NO_APP movl %edx, -8(%ebp) 2.3.2 指定寄存器约束下面例子中,cpuid的输入由eax指定,输出到eax、ebx、ecx、edx四个寄存器中。 asm ("cpuid" :"=a"(_eax), "=b"(_ebx), "=c"(_ecx), "=d"(_edx) :"a"(op)); 然后生成如下的代码(这里假设eax等变量存放栈上): movl -20(%ebp),%eax /* store 'op' in %eax -- input */,#APP cpuid,#NO_APP movl %eax,-4(%ebp) /* store %eax in _eax -- output */ movl %ebx,-8(%ebp) /* store other registers in movl %ecx,-12(%ebp) respective output variables */ movl %edx,-16(%ebp) strcpy可以用下面的方法实现: asm ("cld\n rep\n movsb" : /* no input */ :"S"(src), "D"(dst), "c"(count)); 2.3.3 匹配约束拿系统调用的代码做例子: ,#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \{ \long __res; \__asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \ "d" ((long)(arg3)),"S" ((long)(arg4))); \__syscall_return(type,__res); \} 通过使用匹配约束“0”,系统调用号被放入到eax中。 2.3.4 内存操作数约束考虑如下的原子减一操作: __asm__ __volatile__( "lock; decl %0" :"=m"(counter) :"m"(counter)); 会生成如下的汇编: ,#APP lock decl -24(%ebp),#NO_APP 上面的例子中,变量在内存中直接被减一。假如使用“r”的话,操作就没有原子性了。 2.3.5 clobbered约束考虑如下的memcpy实现: asm ("movl $count, %%ecx; up: lodsl; stosl; loop up;" : :"S"(src), "D"(dst) :"%ecx", "%eax"); lodsl和stosl隐式地使用eax,ecx被显式地加载到寄存器中。除非我们用clobbered约束告诉GCC这两个寄存器是可用的,它不会再使用它们去存放其他数据。esi和edi没有加入到clobbered列表中,因为它们已经出现在了输入操作数列表中。准则就是,例如一个寄存器在asm中使用了(无论是显式还是隐式),而又没有出现在输入、输出操作数列表中,那么它就必须出现在clobbered列表中。 |