为了保持和gcc的输出的兼容性,as支持AT&T System V/386汇编语法,它和Intel语法有相当大的差别。强调这个是因为几乎所有的80386文档只使用Intel语法。两者之间的显著区别是:
● AT&T的立即数有前缀'$',Intel的立即数没有前缀(Intel 'push 4'为AT&T 'push Ŭ')。AT&T的寄存器有前缀'%',Intel的寄存器没有前缀。AT&T的绝对跳转(和相对PC的跳转相反)jump/call操作数有前缀'*',Intel没有前缀。
● AT&T和Intel语法的源和目的操作数的顺序相反。Intel的'add eax, 4'等效于AT&T的'addl Ŭ, %eax'。使用'source, dest'规范的目标是为了和以前的Unix汇编器保持兼容。
● AT&T语法中的内存操作数的宽度是根据操作码名称的最后一个字符决定的。操作码后缀'b','w',和'l'指定了byte(8-bit),word(16-bit),和long(32-bit)的内存引用。Intel语法则对内存操作数(不是操作码)加前缀:'byte ptr','word ptr'和'dword ptr'。这样,Intel 'mov al, byte ptr foo'等效于AT&T的'movb foo, $al'。
● AT&T中的long jumps和calls的立即形式为'lcall/ljmp $section, $offset';Intel语法为'call/jmp far section:offset'。同样,AT&T的远程返回指令为'lret $stack-adjust',而Intel格式为'ret far stack-adjust'。
● AT&T汇编器不提供对多节(multiple section)程序的支持。Unix风格的系统希望所有的程序都是单节的。
■ 操作码命名(opcode naming)
操作码前有一个字符的后缀,指定了操作数的宽度。字母'b','w',和'l'指定了byte,word,和long型的操作数。如果指令中没有后缀并且不包含内存操作数,那么as将基于目标寄存器操作数(指令中的最后一个寄存器)填充这个缺少的后缀。所以,'mov %ax, %bx'等效于'movw %ax, %bx';同样,'mov ũ, %bx'等效于'movw ũ, %bx'。注意这个特点不和AT&T的Unix汇编器兼容,后者假定缺少的后缀为long型操作数宽度。(这个不兼容型并不影响编译器的输出,因为编译器总是显式地指定操作码后缀。)
AT&T和Intel中的操作码的格式几乎全部一样,但有一个例外。AT&T的符号扩展(sign extend)和零扩展(zero exten)指令需要指定2个宽度,一个宽度用来指定sign/zero扩展的from,另一个用来零扩展to。在AT&T语法中使用2个操作码后缀。符号扩展和零扩展的基本名称是'movs...'和'movz...'(Intel格式为'movsx'和'movzx')。操作码后缀加在这个基本名称后,from在之前。所以,AT&T语法中'movxbl %al, %edx'意思为”move sign extend from %al to %edx“。所以可能的后缀有'bl'(从byte到long),'bw'(从byte到word),和'wl'(从word到long)。
Intel风格的指令:
● 'cbw' - 符号扩展byte '%al'到word '%ax',
● 'cwde' - 符号扩展word '%ax'到long '%eax',
● 'cwd' - 符号扩展word '%ax'到long '%dx:%ax',
● 'cdq' - 符号扩展dword '%eax'到quad '%edx:%eax',
在AT&T中分别叫'cbtw','cwtl','cwtd',和'cltd'。
远程call/jump指令在AT&T中分别为'lcall'和'ljmp',而Intel的格式为'call far'和'jump far'。
■ 寄存器命名(register naming)
寄存器操作数总有前缀'%'。80386的寄存器包括:
● 8个32-bit寄存器'%eax'(accumulator),'%ebx','%ecx','%edx','%edi','%esi','%ebp'(<I>frame</I> pointer),和'%esp'(stack pointer)。
● 8个低16-bit的以上寄存器:'%ax','%bx','%cx','%dx','%di','%si','%bp',和'%sp'。
● 6个节寄存器'%cs'(代码节),'%ds'(数据节),'%ss'(堆栈节),'%es', '%fs',和'%gs'。
● 3个处理器控制寄存器'%cr0','%cr2',和'%cr3'。
● 6个调试寄存器'%db0','%db1','%db2','%db3','%db6',和'%db7'。
● 2个测试寄存器'%tr6'和'%tr7'。
● 8个浮点寄存器栈'%st'或等效的'%st(0)','%st(1)','%st(2)','%st(3)','%st(4)','%st(5)','%st(6)',和'%st(7)'。
■ 操作码前缀(opcode prefixes)
操作码前缀用于修改以下操作码。它们用于重复字符串指令,提供节覆盖(section overrides),执行总线锁定操作,以及给出操作数和地址的宽度(对于通常的32-bit操作数,可以使用一个”操作数宽度“操作码前缀,来指定16-bit的操作数)。操作码前缀通常占据一行,没有操作数,并且必须直接位于它们所作用于的指令之前。例如'scas'(字符串扫描)指令可以这样重复:
repne
scas
下面列出操作码前缀:
● 节覆盖前缀'cs','ds','ss','es','fs','gs'。
● 操作数/地址宽度前缀'data16'和'addr16',将32-bit的操作数/地址改变为16-bit的操作数/地址。注:16-bit寻址模式(即8086和80286寻址模式)还没有支持。
● 总线锁定前缀'lock',在执行它修饰的指令时禁止中断。(只对特定指令有效,参考80386指令手册。)
● 等待协处理器指令'wait',等待协处理器完成当前指令。对于80386/80387组合这不再需要。
● 'rep','repe',和'repne'前缀用来修饰字符串指令,使它们重复'%ecx'次。
注:操作码前缀(1字节):
0xF0 - LOCK;
0xF2 - REPNE/REPNZ(只用于字符串指令);
0xF3 - REP(只用于字符串指令);
0xF3 - REPE/REPZ(只用于字符串指令);
0xF3 - Streaming SIMD扩展指令;
段前缀:
0x2E - CS段覆盖;
0x36 - SS段覆盖;
0x3E - DS段覆盖;
0x26 - ES段覆盖;
0x64 - FS段覆盖;
0x65 - GS段覆盖。
操作数宽度覆盖:0x66
地址宽度覆盖: 0x67
■ 内存引用(memory references)
Intel语法中间接内存引用的形式为:
section:[base + index*scale + disp]
等效的AT&T语法为:
section:disp(base, index, scale)
其中base和index分别为可选的、32-bit的基址和索引寄存器;disp为可选的偏移(displacement),scale的取值为1,2,4,和8,乘以index计算操作数的地址。如果不指定scale,scale取值为1。section指定了内存操作数的可选的节寄存器,它可以覆盖缺省的节寄存器(参考80386手册中的节寄存器的缺省值)。注意AT&T语法中的节覆盖必须有'%'前缀。如果指定的节覆盖碰巧和缺省的节寄存器相同,那么as并不输出任何节寄存器覆盖前缀。所以,可以使用节寄存器覆盖来强调给定的内存操作数的节寄存器。
下面是Intel和AT&T的内存引用的例子:
AT&T:'-4(%ebp)',Intel:'[ebp-4]';
base是'%ebp',disp为'-4'。使用缺省的节(%ss)。index和scale缺省。
AT&T:'foo(,%eax,4)',Intel:'[foo + eax*4]';
index为%eax(scale为4),disp为'foo'。节寄存器缺省为%ds。
AT&T:'foo(,1)',Intel:'[foo]';
使用foo指向的值当作内存操作数。注意base和index都缺省,但只有一个',',这是一个语法例外。
AT&T:'%gs:foo',Intel:'gs:foo'。
绝对的call和jump的操作数必须有前缀'*'。如果不指定前缀,as选择PC(program counter)相对寻址。
任何有内存操作数的指令必须指定它的宽度(byte,word,long),使用操作码后缀('b','w','l')。
■ 跳转指令的处理(handling of jump instrucion)
跳转指令总是使用最小可能的偏移(displacement)进行优化。处理方法是,如果目标地址足够近,那么使用字节(8-bit)偏移的跳转。如果字节偏移不够,那么使用long(32-bit)偏移。不支持word(16-bit)偏移跳转(即在跳转指令前加'addr16'操作码前缀),这是因为80386坚持,如果使用word偏移那么%eip将被掩码为16-bit。
注意'jcxz','jecxz','loop','loopz','loope','loopnz'和'loopne'指令只使用byte偏移,所以如果你使用这些指令(gcc不使用),你会得到一个错误消息(以及不正确的代码)。AT&T对这个问题的解决是扩展'jcxz foo'为:
jcxz cx_zero
jmp cx_nonzero
cx_zero:
jmp foo
cx_nonzero:
■ 浮点(floating point)
(omitted)
■ 写16-bit代码(wrting 16-bit code)
GAS除了支持”纯“32-bit i386代码外,还提供对实模式和16-bit保护模式代码段的有限支持。