做编译器的代码生成器部分的时候一定会使用到汇编代码,特在此总结一些简单的汇编语言知识点。以能看懂常见的汇编代码为目的,不做深入探究。
学习一种的汇编语言,必须了解这种 CPU 的寄存器、寻址方式以及各种指令。我们就先从寄存器开始着手吧。
通用寄存器
32位x86架构的CPU有8个通用寄存器:
通用寄存器 段寄存器
- AX: Accumulator register (累加器 CS 代码段)
 
- CX: Counter register (计数器 SS 堆栈段)
 
- DX: Data register (数据 ES 附加段)
 
- BX: Base register (基址 DS 数据段)
 
指针寄存器 堆栈寄存器
- SP: Stack pointer register (栈指针,指向栈顶)
 
- BP: Stack base pointer register (基址指针,指向当前stack frame的底部)
 
- SI: Source index register (源索引指针)
 
- DI: Destination index register (目的索引指针)
 
以上名称对应的寄存器均为16位,在32位模式下,上面的寄存器名称前需要加E,64位模式下需要加R。例如,访问32位的AX寄存器需要使用名称EAX,而访问64位(x86_64)的需要使用RAX。
另外,AX, BX, CX, DX这四个通用寄存器的高8位和低8位还可以分成两个单独的寄存器使用,名称分别是XH和XL(X=A,B,C,D)。
下面是RAX寄存器的示意图:
1 2 3 4 5 6 7 8 9 10 11
   | +---------------------------------------------------------------+ | 8bits | 8bits | 8bits | 8bits | 8bits | 8bits | 8bits | 8bits | |---------------------------------------------------------------| |                              RAX                              | |---------------------------------------------------------------| |                               |              EAX              | |---------------------------------------------------------------| |                                               |      AX       |  |---------------------------------------------------------------| |                                               |  AH   |   AL  | +---------------------------------------------------------------+
   | 
 
x86_64添加了8个新的通用寄存器,且引入了新的命名规则,详情可参考这里。
其他常用寄存器
- EFLAGS: 32位寄存器,用于保存指令结果和处理器状态,具体说明可参考这里。
 
- IP: 指令寄存器,用于保存要执行的指令,和通用寄存器类似,32位模式下用EIP,64位模式下用RIP。
 
语法
x86汇编语言主要有两个语法分支: AT&T和Intel。我们平常接触到的GNU系的工具(包括GCC,OBJDUMP等)都是使用AT&T语法,若无特别说明,下面的汇编代码例子也是如此。
| 操作 | 
AT&T | 
Intel | 
注释 | 
| 寄存器前缀 | 
%eax | 
eax | 
 | 
| 立即数前缀 | 
$5 | 
5 | 
 | 
| 指令后缀 | 
movl | 
mov | 
操作数长度4 | 
| 参数次序 | 
movl $5, %eax | 
mov eax, 5 | 
R[eax] = 5 | 
| 取址1 | 
var | 
[var] | 
变量 | 
| 取址2 | 
0x8(%eax) | 
[eax + 0x8] | 
偏移 | 
| 取址3 | 
arr(, %eax, 4) | 
[eax * 4 + arr] | 
数组 | 
指令
mov
拷贝数据
1 2 3 4
   | # R[eax] = 5 movl $5, %eax # var = 5 movl %eax, var
   | 
 
cmp
比较操作符,并将结果暂存于EFLAGS寄存器。
跳转指令
跳转指令可以修改IP寄存器的值,以实现控制流转换。
- jmp (jump):无条件跳转到对应的label地址处。
 
- je (jump if equal): 如果上一次cmp指令结果是相等的,跳转到label处。
 
- jne (jump if not equal):和je相反,如果上一次cmp指令结果不相等,跳转到label处。
 
- jl (jump if less):如果上一次cmp指令结果小于条件成立,跳转到label处。
 
- jle (jump if less or equal):如果上一次cmp指令结果小于等于条件成立,跳转到label处。
 
- jg (jump if greater):如果上一次cmp指令结果大于条件成立,跳转到label处。
 
- jge (jump if greater or equal):如果上一次cmp指令结果大于等于条件成立,跳转到label处。
 
在这里查看更多跳转指令
call ret
- call: 将下一条指令的地址入栈,然后跳转到proc地址处。
 
- ret: 将栈顶的数据出栈并载入IP寄存器(即跳转回call的下一条指令处)。
 
enter leave
- enter: 新建一个stack frame,相当于:
 
1 2
   | push %ebp mov  %esp, %ebp
   | 
 
- leave: 销毁当前stack frame,恢复上一个stack frame,相当于:
 
运算指令
1 2
   | # R[ebx] += R[eax] add %eax, %ebx
   | 
 
1 2
   | # R[ebx] -= R[eax] sub %eax, %ebx
   | 
 
其他常用指令
- lea (Load effective address):
 
1 2
   | # R[eax] = R[ebx] - 8 lea -0x8(%ebx), %eax
   | 
 
注意和mov指令的区别:
1 2
   | # mem[R[eax]] = R[ebx] - 8 mov -0x8(%ebx), %eax
   | 
 
引用:http://blog.atime.me/note/asm-summary.html
参阅:https://en.wikipedia.org/wiki/Control_flow