汇编是指一些与处理器指令集一一对应的指令助记符的使用。以一条 C 语句为例:
1 | return 0; |
在 Intel 平台,我们最终会得到如下的汇编指令:
1 | leave |
本系列的文章主要探讨 Intel 语法。另外比较流行的,在 Unix 里大量使用的是 AT&T 语法,它和 Intel 语法有一些区别。
汇编指令的指令后面通常会跟一个参数(也称为操作数),视具体的指令而定,操作数可能是常量、内存变量或寄存器。
常量是最简单的形式,一般是在源代码中定义的。
1 | mov eax, 0x1234 |
那么十六进制数 0x1234
就是一个常量。常量简单明了,一般直接编码到指令中,除此以外几乎没有什么要额外说明的了。
寄存器与 C/C++ 中的变量类似。通用寄存器可以保存整数、偏移量、立即数、指针,或任何能用 32 位二进制表示的东西。它本质上是预分配的、物理上存在于处理器中的变量,总是位于一定范围内。它们的用法与变量还是有些差异;对寄存器而言,它们可以被反复使用,但在 C 或 C++ 程序里,定义的变量通常只用于一个目的,且不再把它用在其他地方。
IA-32 中有 8 个 32 位通用寄存器,6 个 16 位段寄存器,1 个 32 位指令指针寄存器, 1 个 32 位状态寄存器, 5 个控制寄存器, 3 个内存管理寄存器, 8 个调试寄存器,等等。在大多数情况下,我们只用到通用寄存器、指令指针寄存器、段寄存器及状态寄存器。如果处理 OS 的驱动程序之类的,则更有可能会碰到其他寄存器。
8 个 32 位通用寄存器分别是:EAX,EBX,ECX,EDX,ESI,EDI,EBP 和 ESP。这些寄存器中除了几个有专门的用处外,其他的可以随便用。例如,不少指令会默认把某些寄存器作为参数(操作数)。比如,多数字符串指令通常把 ECX 当做计数器,把 ESI 作为源指针,把 EDI 作为目的指针。此外,在某些内存模型中,有些指令默认把某些寄存器作为段的基址(这些下面很快会讲到)。最后,有些操作会涉及一些寄存器,虽然它们并没有在指令中体现出来。例如,栈操作通常会使用哪个 EBP 和 ESP 寄存器,如果它们包含的值没有映射到当前进程的地址空间里,通常会导致应用程序崩溃。