在*nix阵营中,AT&T形式的汇编是更普遍的,例如gas还有gcc的内联汇编都是这种,它与我们在汇编课上熟悉Intel形式的汇编(比如,MASM支持的那些)的不同点主要在以下几方面:
1. 操作数的顺序是反的。比如movl %eax, %ebx的意思是把eax的值赋值给ebx
2. 寄存器名字前面要加%,立即操作数前面要加$。比如movl $0×1984, %eax
3. 指令加后缀b (8bit), w (16bit), l (32bit)指示操作数大小,这一条并不强制,因为汇编器基本可以根据操作数自行推断,但建议加上。比如movb %al, %bl
4. 间接寻址,用圆括号不用方括号,并且有一个很恶心的格式 imm32(base,index,scale)。例如,-4(%ebx,%eax,8)等价于Intel的[ebx+eax*8-4]
下面说GCC的内联汇编,乍一看上去很简单,只要加个asm(或者__asm__)就可以了,例如 asm(”nop”); 但是麻烦的在于让其和上下文的C代码匹配上。内联汇编的格式如下:
asm ( assembler template
: output operands (optional)
: input operands (optional)
: list of clobbered registers (optional)
);
后面的三个可选项就是负责指定相应的“接口”。看这样一段简单的代码,作用是把局部变量a的值赋值给b:
{
int a=10, b;
asm ("movl %1, %%eax; movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax"); /* clobbered register */
}
这里我们直接写类似”movl a, %eax; movl %eax, b”是不行的,因为汇编代码里不知道a和b是啥东西,所以需要在output和input这两部分指定“约束”。“约束”的格式是一个字符串后面跟一个括号里的C表达式,比如”r”(a)表示a这个变量必须放在某个寄存器中。字符串里可以包含的字符及意义:
a eax
b ebx
c ecx
d edx
S esi
D edi
q eax, ebx, ecx, edx
r eax, ebx, ecx, edx, esi, edi
m 内存
i 立即数
数字0/1... 表示引用之前出现过的
可以有多个约束,用逗号隔开。在output部分需要前面加一个等号。上面代码里%0, %1这些就是和下面output, input两部分指明的约束是对应的,最先出现的”=r”(b)对应%0,然后出现的”r”(a)对应%1。有时候我们需要在input里指明output中已经出现过的某个约束,这时候就应该用”0″, “1″这种了。另外,注意现在表示寄存器时要加两个百分号(%%eax)了。
最后的clobbered register是指明这段代码里可能会用到的寄存器,意思是告诉gcc,这些寄存器里的值可能已经让我修改了,你不能假定它们还有效。出现在input和output里的寄存器会自动被认为是clobbered的,无须再写出。
这个东西实在是很乱很难说清楚,上面说的也只是最常用的一部分内容。最后用几段典型代码展示一下:
下面这段等价于var=var+1,注意input部分的”0″的使用
asm ("incl %0" :"=a"(var):"0"(var));
下面这段是把count个字节从src拷贝到dst
asm ("cld\n
rep\n
movsb"
: /* no output */
:"S"(src), "D"(dst), "c"(count));
最后来个货真价实的,传说中的Linux内核代码中的memcpy
static inline void * __memcpy(void * to, const void * from, size_t n)
{
int d0, d1, d2;
__asm__ __volatile__(
"rep ; movsl\n\t"
"testb $2,%b4\n\t"
"je 1f\n\t"
"movsw\n"
"1:\ttestb $1,%b4\n\t"
"je 2f\n\t"
"movsb\n"
"2:"
: "=&c" (d0), "=&D" (d1), "=&S" (d2)
:"0" (n/4), "q" (n),"1" ((long) to),"2" ((long) from)
: "memory");
return (to);
}
简单来说上面那段代码的意思是一开始用movsl四字节复制,等发现快到头了,再根据剩余的情况用movsw和movsb。细节不解释了,仔细分析一下还是很好玩的