作者:vivek
翻译:ssword
原文:http://sig9.com/articles/att-syntax
本文粗谈一下gas(1)的汇编语法,即AT&T风格汇编。初次接触很可能会觉得它别扭,不过若有其他汇编语言的基础,稍事了解即可快速上手。我假设你熟悉INTEL风格汇编——也就是INTEL手册中的那种风格。方便起见,我就用NASM(Netwide Assembler)来做比对。
gas属于GNU Binary Utilities(binutils),也是GCC的一个后端。对编写较长的汇编程序而言它并非首选,不过对于类Unix系统的内核级hacking,它就无可替代了。选择AT&T风格使得gas饱受争议,人们总说它只是GCC的后端,而对开发者不友好。INTEL风格汇编的教众也认为,它在可读性及编译上几乎是令人窒息。尽管如此,有一点必须了解:很多操作系统都选择了gas作为底层代码的汇编器。
基本形式
AT&T汇编程序的结构与其他汇编大同小异,伪指令、标签、指令—即最多带三个操作数的助记符。要说AT&T汇编的不同,最显眼的地方就是它操作数的顺序。
例如,一个简单的数据移动指令在INTEL风格下边是这个样子:
mnemonic destination, source
在AT&T风格下边则是这样:
mnemonic source, destination
一部分人(包括我)觉得这种格式更贴切。接下来说说AT&T风格指令中的操作数。
寄存器
每个IA-32架构寄存器的名字必须以’%'作前缀,如%al,%bx,%ds,%cr0,等等。
如上的mov指令就是把一个16位寄存器ax中的值移动到另一个16位寄存器bx中。
字面量
每个字面量必须以’$'为前缀。 例如:
mov $100, %bx
mov $A, %al
第一个指令是把值100移动到寄存器bx中,第二个指令是把一个字节A移动到AL寄存器中。下面这个指令就是错误的:
怎么说呢,这条指令是要把寄存器bx的值移动给一个字面量,显然不靠谱。
内存寻址
在AT&T风格中,内存引用起来是这个格式:
segment-override:signed-offset(base,index,scale)
按你寻址的需求,其中的部分可以省略
注意下,基地址及偏移中的数不带前缀’$'。拿几个例子和对应的NASM风格做个比较应该好些:
GAS memory operand NASM memory operand
------------------ -------------------
100 [100]
%es:100 [es:100]
(%eax) [eax]
(%eax,%ebx) [eax+ebx]
(%ecx,%ebx,2) [ecx+ebx*2]
(,%ebx,2) [ebx*2]
-10(%eax) [eax-10]
%ds:-10(%ebp) [ds:ebp-10]
实例:
mov %ax, 100
mov %eax, -100(%eax)
第一个指令是把寄存器AX中的值移动到数据段寄存器(默认)偏移100的内存位置,第二个指令是把寄存器eax中的值移动到[eax-100]。
操作数大小
有时指明操作数的大小是必须的,尤其是移动字面量到内存。例如这个指令:
这里只说了把值10移动到内存偏址100处,而没有说值的大小。在NASM中,这通过给操作数后面跟个byte/word/dword之类的关键词来指明。而在AT&T风格里,是通过指令中b/w/l之类的后缀指明。如:
把值为10的一个字节移动到内存地址[ex:eax],另如:
把值为10的一个长整数移动到同一位置。
再几个例子:
movl $100, %ebx
pushl %eax
popw %ax
控制流程
jmp,call,ret等指令可以转移程序的执行位置。在同一代码段中跳转,是近距跳转(near)。若是跳转到不同的代码段,就是远程跳转(far)。可用的跳转地址可以来自相对偏移(label)、寄存器、内存以及段偏移指针。相对偏移通过label指明,如下:
使用寄存器或者内存的值做地址的操作数必须加个前缀’*'。若是远程跳转,必须加个’l'作前缀,如‘ljmp’,‘lcall’等等。例如:
GAS syntax NASM syntax
========== ===========
jmp *100 jmp near [100]
call *100 call near [100]
jmp *%eax jmp near eax
jmp *%ecx call near ecx
jmp *(%eax) jmp near [eax]
call *(%ebx) call near [ebx]
ljmp *100 jmp far [100]
lcall *100 call far [100]
ljmp *(%eax) jmp far [eax]
lcall *(%ebx) call far [ebx]
ret retn
lret retf
lret $0x100 retf 0x100
段偏移指针按下面的格式指明:
例如:
记住这些很快就能上手了。要了解gas的更多细节,不妨参阅这个文档。