本文将对AT&T汇编进行简单的介绍,如果要了解详细情况,请查阅GNU as的参考手册(http://sourceware.org/binutils/docs-2.16/as/index.html)。
刚开始接触AT&T汇编的时候,很多人都有点犯糊涂。但是如果你有过编写汇编程序的经验,只要记住几个要点,对它应该还是比较容易让手。在以下的介绍中,我将使用NASM语法与之对照。
GNU as是GNU编译器的一个支持(后端)程序。也许as并不适合编写大型的汇编程序,但无论如何,它是当代unix族操作系统的一个重要部件,尤其是编写内核时。因为它语法有点晦涩,所以,经常被人诟病,认为它不“友好”,过分地强调作为GCC的一个后端程序。如果你以前只有Intel汇编语法背景,你学习起来会稍微感到沉闷。但不管怎样,现在很多操作系统的底层代码就是用as汇编实现的(比如linux),如果你有兴趣研究linux内核代码的话,最好还是具备一点AT&T汇编的知识。
基本格式:
AT&T汇编程序的结构跟其它汇编语言类似,由directives, labels, instructions组成,助记符最多可以跟随三个操作数。与其它汇编语言相比,最大的区别在于操作数的顺序,比如Intel汇编语法的传送指令通常是:
mnemonic destination,source
然而,在AT&T汇编中,通常是:
mnemonic source,destination
也即源操作数在左边,目的操作数在右边。
接下来介绍AT&T汇编在x86架构下操作数的类型:
1.寄存器
所有寄存器前面都要加前缀%。比如%al,%bx,%cr0。
move %ax,%bx
上面指令将移动寄存器ax的值到寄存器bx。
2.立即数
所有立即数必须冠以前缀$,比如:
move $100,%bx
mov $A,%al
上面第一条指令将把100放入寄存器ax中;第二条指令把A的ascii码放入al寄存器。
3.内存地址
在AT&T汇编语法中,内存通过下面方式表达:
segment-override:sigend-offset(base,index,scale)
比如:(注意offset和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]
示例:
move %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) #写入一个字节
movl $10,%es:(%eax) #写入一个双字
其余一些例子:
movl $100, %ebx
pushl %eax
popw %ax
控制转移指令:
jmp, call, ret这些指令用于控制程序从某条指令跳到另外的指令处。它又分为近跳转(在同一段内)和远跳转(不同段内)。跳转地址可以用相对偏移(label),寄存器,内存操作数,Segment-offset指针表示:
1.相对偏移(label)
label1:
...
jmp label1
2.寄存器或内存操作数
必须添加前缀*。如果是远跳转,还要对跳转指令加上l前缀,例子:
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
3.Segment-offset
形如:
jmp $segment, $offset
示例:
jmp $0x10, $0x100000
这些就是AT&T汇编最基本的东西,应该很容易。把上面这些牢记在心中,看懂GAS的汇编代码不是太难。如果想阅览linux内核底层源码的话,再了解一下GCC内嵌汇编格式,就可以开始了~~