简介AT&T风格汇编

fleuria » 17 Feb 2010

作者: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 %ax, %bx

如上的mov指令就是把一个16位寄存器ax中的值移动到另一个16位寄存器bx中。

字面量

每个字面量必须以'$'为前缀。 例如:

mov $100,   %bx
mov $A, %al

第一个指令是把值100移动到寄存器bx中,第二个指令是把一个字节A移动到AL寄存器中。下面这个指令就是错误的:

mov %bx,    $100

怎么说呢,这条指令是要把寄存器bx的值移动给一个字面量,显然不靠谱。

内存寻址

在AT&T风格中,内存引用起来是这个格式:

segment-override:signed-offset(base,index,scale)

按你寻址的需求,其中的部分可以省略

%es:100(%eax,%ebx,2)

注意下,基地址及偏移中的数不带前缀'$'。拿几个例子和对应的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]。

操作数大小

有时指明操作数的大小是必须的,尤其是移动字面量到内存。例如这个指令:

mov $10,    100

这里只说了把值10移动到内存偏址100处,而没有说值的大小。在NASM中,这通过给操作数后面跟个byte/word/dword之类的关键词来指明。而在AT&T风格里,是通过指令中b/w/l之类的后缀指明。如:

movb    $10,    %es:(%eax)

把值为10的一个字节移动到内存地址[ex:eax],另如:

movl    $10,    %es:(%eax)

把值为10的一个长整数移动到同一位置。

再几个例子:

movl    $100, %ebx
pushl   %eax
popw    %ax

控制流程

jmp,call,ret等指令可以转移程序的执行位置。在同一代码段中跳转,是近距跳转(near)。若是跳转到不同的代码段,就是远程跳转(far)。可用的跳转地址可以来自相对偏移(label)、寄存器、内存以及段偏移指针。相对偏移通过label指明,如下:

label1:
    .
    .
  jmp   label1

使用寄存器或者内存的值做地址的操作数必须加个前缀'*'。若是远程跳转,必须加个'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

段偏移指针按下面的格式指明:

jmp $segment, $offset

例如:

jmp $0x10, $0x100000

记住这些很快就能上手了。要了解gas的更多细节,不妨参阅这个文档

hosted on github, and powered by jekyll. (rss)