目前很多开源基于x86处理器环境的C/C++,Objective-C/C++编译器所带的汇编器使用AT&T格式。AT&T汇编针对x86指令集与其它处理器(比如ARM、Blackfin等)有所不同,它与Intel自定义的汇编格式有比较大的偏差。GCC的汇编器能支持Intel语法特性,可以参考我前面的博文来获悉如何使用。不过对于最新的LLVM2.0,又开始弃用Intel语法特性了。因此不管怎么说了解AT&T汇编语法还是有些好处的,呵呵。
首先,AT&T汇编在对于存储器访问上,如果要指定访问字节的宽度,是通过加后缀来实现的,而Intel则是在存储器操作数之前加访问宽度限定词,比如:
(Intel)mov dword ptr [edx], eax (AT&T)movl %eax, (%edx)
大部分指令对于AT&T使用以下后缀:
b 字节(8位),对应于Intel的byte ptr
w 字(16位),对应于Intel的word ptr
l 双字(实际是表示long,32位),对应于Intel的dword ptr
q 四字(64位),对应于Intel的qword ptr
然而,如果是x87浮点指令的话用以下后缀:
s 单精度浮点(short,32位),对应于Intel的dword ptr,如果x87关于存储访问的指令后缀缺省,则默认为后缀s。
l 双精度浮点(long,64位),对应于Intel的qword ptr。
t 扩展双精度浮点(twelve-byte,96位(由于四字节对齐需要,实际精度是80位)),对应于Intel的tbyte ptr。
然后,像FIADD这指令,在AT&A汇编格式中,fiadds表示操作数是short访存数据类型;而fiaddl表示操作数是int访存数据类型。由于操作数是整数类型的存储器寻址,所以后缀s表示short,而l则表示long(不管是32位模式还是64位模式都是32位长)。
其次,AT&T格式中,指令后面的操作数顺序与Intel格式是相反的。AT&T是源操作数在前,目的操作数在后:
(Intel)mov rax, rbx (AT&T)mov %rbx, %rax
(Intel)mov eax, cs:var (AT&T)movl %cs:var, %eax
(Intel)pinsrw xmm8, [rdi], 1 (AT&T)pinsrw $1, (%rdi), %xmm8
(Intel)pshuflw xmm8, xmm9, 0a1H (AT&T)pshuflw $0xa1, %xmm9, %xmm8
再次,AT&T与Intel语法之间的寻址表示也不同:
(Intel)sub eax, [ebx + ecx * 4h - 20h] (AT&T)subl -0x20(%ebx, %ecx, 0x4), %eax
上面表示缩放因子以及偏移量的立即数前不需要加$符号。
这个可以总结为:Intel的寻址方式若表示为[<base register> + <index register> * <scale> + <offset>],则AT&T表示为:<offset>(<base register>, <index register>, <scale>)
最后,AT&T与Intel之间有些指令的表示也不同:
在Intel指令表示中,如果是带符号扩展的MOV指令是用movsx表示,而在AT&T中用movs;而Intel中零扩展MOV指令是movzx,而在AT&T中是用movz表示,比如:
(Intel)movzx rax, byte ptr [rsi + 3] (AT&T)movzb 3(%rsi), %rax
(Intel)movsx rax, word ptr [rsi + 4] (AT&T)movsw 4(%rsi), %rax
而MOVS指令在Intel指令集中是被废弃的字符串操作指令。
而在AT&T中也能通过REP指令来重复操作存储器拷贝工作,比如在Apple LLVM汇编器中,可以这么写:
// 第一个参数在%rdi,第二个参数在%rsi,第三个参数在%rdx
_MyMemCpy:
mov %rdx, %rcx
rep movsb
ret