1、 指令操作数的顺序是先源后目的,与Intel指令的先目的后源的顺序相反;
2、 寄存器操作数总是以'%'作为前缀。
3、 立即数前加前缀$;
4、 操作码加后缀以指明操作数的长度,这些后缀有b(8位)、w(16位)、l(32位);
例:
AT&T Intel
moww %bx, %ax // mov ax, bx
xorl %eax, %eax // xor eax, eax
movw $1, %ax // mov ax,1
movb X, %ah // mov ah, byte ptr X
movw X, %ax // mov ax, word ptr X
movl X, %eax // mov eax, X
根据指令中指定的寄存器操作数补上相应的后缀字符。所以,下面两个指令是等价的(这是Gas的特性,而真正的AT&T的Unix汇编程序会将将没有字长后缀的指令的操作数字长全部当作32位):
5、 大部分指令的操作码都与Intel指令相同,只有下面几个是例外:
movsSD // movsx ,短到长,高位补符号位
movzSD // movzx ,短到长,高位补0
这里,S 是代表源操作数长度的后缀,D 是代表目的操作数长度的后缀。如:
movswl %ax, %ecx // movsx ecx, ax
cbtw // cbw ,将AL中的一个字节扩充为一个字(AX),
// AH是AL的符号位
cwtl // cwde ,将AX中的一个字扩充为一个双字(EAX),
// EAX的高位是AX的符号位
cwtd // cwd ,将AX中的一个字扩充为两个字(DX:AX)
// DX是AX的符号位
cltd // cdq ,将EAX中的一个双字扩充为两个双字
// (EDX:EAX),EDX是EAX的符号位
lcall $S,$O // call far S:O
ljmp $S,$O // jmp far S:O
lret $V // ret far V
6、 操作码的前缀(如rep)要单独写一行,不要和它修饰的指令(如movsb、stosb)写在同一行上;
7、 内存间接寻址的格式不同
AT&T Intel
disp(base, index, scale) [base + index*scale + disp]
例:
movl 4(%ebp), %eax // mov eax, [ebp+4]
addl (%eax,%eax,4), %ecx // add ecx, [eax + eax*4]
movb $4, %fs:(%eax) // mov fs:eax, 4
movl _array(,%eax,4), %eax // mov eax, [4*eax + array]
movw _array(%ebx,%eax,4), %cx // mov cx, [ebx + 4*eax + array])
8、 局部标号可以用数字,而且可以重复。在以这些标号为目的的转移指令上,标号要带上后缀,b表示向前,f表示向后。
例:
orw %bx,%bx
jz 1f
1:
movl $0x101000,%eax
movl %eax,%cr3 /* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
jmp 1f /* flush the prefetch-queue */
1:
movl $1f,%eax
jmp *%eax /* make sure eip is relocated */
1:
9、 实模式下的语法与Intel指令语法基本相同;可以用上述格式的汇编单独写程序(有许多宏定义和它特有的文件格式),而后用gcc/gas将其汇编成目标代码。在linux中,这种形式的代码主要集中在启动部分。
__asm__ __volatile__ (
asm statements
: outputs
: inputs
: registers-modified
);
asm statements是一组AT&T格式的汇编语言语句,每个语句一行,由/n分隔各行。所有的语句都被包裹在一对双引号内。其中使用的寄存器前面要加两个%%做前缀;转移指令多是局部转移,因此多使用数字标号。
inputs指明程序的输入参数,每个输入参数都括在一对圆括号内,各参数用逗号分开。每个参数前加一个用双引号括起来的标志,告诉编译器把该参数装入到何处。可用的标志有:“g”:让编译器决定如何装入它;“a”:装入到ax/eax;“b”:装入到bx/ebx;“c”:装入到cx/ecx;“d”:装入到dx/edx;“D”:装入到di/edi;“S”:装入到si/esi;“q”:a、b、c、d寄存器等;“r”:任一通用寄存器;“i”:整立即数;“p”:有效内存地址;“=”:输出;“+”:既是输入又是输出;“&”:改变其值之前写;“%”:与下一个操作数之间可互换;“#”:忽略其后的字符,直到逗号;“*”:当优先选择寄存器时,忽略下面的字符;“0~9”:指定一个操作数,它既做输入又做输出。通常用“g”。
outputs指明程序的输出位置,通常是变量。每个输出变量都括在一对圆括号内,各个输出变量间用逗号隔开。每个输出变量前加一个标志,告诉编译器从何处输出。可用的标志与输入参数用的标志相同,只是前面加“=”。如“=g”。输出操作数必须是左值,而且必须是只写的。如果一个操作数即做输出又做输入,那么必须将它们分开:一个只写操作数,一个输入操作数。输入操作数前加一个数字限制(0~9),指出输出操作数的序号,告诉编译器它们必须在同一个物理位置。两个操作数可以是同一个表达式,也可以是不同的表达式。
registers-modified告诉编译器程序中将要修改的寄存器。每个寄存器都用双引号括起来,并用逗号隔开。如“ax”。如果汇编程序中引用了某个特定的硬件寄存器,就应该在此处列出这些寄存器,以告诉编译器这些寄存器的值被改变了。如果汇编程序中用某种不可预测的方式修改了内存,应该在此处加上“memory”。这样以来,在整个汇编程序中,编译器就不会把它的值缓存在寄存器中了。
__volatile__是可选的,它防止编译器修改该段汇编语句(重排序、重组、删除等)。
输入参数和输出变量按顺序编号,先输出后输入,编号从0开始。程序中用编号代表输入参数和输出变量(加%做前缀)。对操作数进行操作时也允许明确指出是对哪一个字节操作,此时%与序号之间插入一个“b”表示最低字节,插入一个“h”表示次低字节。
输入、输出、寄存器部分都可有可无。如有,顺序不能变;如无,应保留“:”,除非不引起二意性。
int i=0, j=1, k=0;
__asm__ __volatile__("
pushl %%eax/n //asm statement
movl %1, %%eax/n //asm statement
addl %2, %%eax/n //asm statement
movl %%eax, %0/n //...
popl %%eax" //...
: "=g" (k) //outputs
: "g" (i), "g" (j) //inputs
: "ax", "memory" //registers modified
);