GCC嵌入式ASM快速指南

时间:2022-03-23 07:02:09

本文从Inline assembly for x86 in Linux中摘译。

Table of Contents

  • 1 简要GNU汇编语法
    • 1.1 寄存器命名
    • 1.2 来源和目的的顺序
    • 1.3 操作数的尺寸
    • 1.4 直接操作数
    • 1.5 直接内存引用
  • 2 内联汇编
    • 2.1 内存操作数约束
    • 2.2 匹配约束
    • 2.3 常见内联汇编用例
      • 2.3.1 “asm”和寄存器约束“r”
      • 2.3.2 指定寄存器约束
      • 2.3.3 匹配约束
      • 2.3.4 内存操作数约束
      • 2.3.5 clobbered约束
  • 3 相关资源

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列表中。