preface
在逐渐深入底层的时候,汇编真的十分重要,它是一门直接操作硬件的语言,可以清楚的知道每一步指令过后 CPU 干了什么事,做到精准打击。在逆向中,学好汇编也是非常重要的,否则连题目都看不懂,这里我就来复习一下关于汇编的一些基础知识
x86汇编 和 x64汇编
x86 是由 Intel 公司开发的一款 32 位架构,也称作 IA-32 和 i386,其汇编叫做 x86 汇编,最初的时候,AMD 和 Intel 均支持这种指令。
到了后来,CPU 迈向了 64 位,就有个问题摆在 CPU 厂商面前,是向下兼容还是制造一套全新的指令集?AMD 公司抢先 Intel 设计出兼容 x86 的 CPU,称为 AMD64 计划,而 Intel 设计了一款全新的处理器,没有兼容 x86,叫做 IA-64(安腾CPU),但是并没有什么效果,基本所有人都会选择向下兼容的 AMD64 ,最终导致了 IA-64 基本处于淘汰状态,Intel 无奈跟着 AMD64 的脚步,只能又开发向下兼容的架构,为了历史名称方便,把它叫做 x86-64 架构,也叫 x64,其实跟 AMD64 是一回事。
虽然现在的个人电脑基本全都是 x86-64 架构了,但是还是要了解一下这些历史,甚至王爽老师的著名教材《汇编语言》还用的是 16 位的 8086 CPU,只要懂了一种架构,迁移到其他架构下也不会很难。
两种架构下的寄存器
x86
在 8086 时代,寄存器都是 16 位的,进入了 80386 时代,由于 IA-32,一些寄存器变成了 32 位,但是依然可以向下兼容,例如 eax 里面就可以单独调用 16 位的 ax 寄存器。下面介绍一些常见的寄存器:
x86 架构下有 4 个 32 位的通用寄存器,分别是:eax,ebx,ecx,edx,
这几个寄存器可以当作通用的寄存器来使用,尽管有一些寄存器是被大家约定俗成有附加用途的,例如函数的返回值存储在 eax 中。
下面是这四个寄存器的位分布图,可以看到,这些寄存器依然保留了 16 位的寄存器 ax, bx, cx, dx,因此还是可以像 8086 汇编一样使用他们,eax 的低十六位可以作为 ax ,ax 又可以当作 ah 和 al 使用
其中,这四个 16 位的寄存器也是有约定俗成的用法的:
ax 被用作累加器(accumulate register),进行加法操作后的结果会存储在 ax 中
bx 被用作基寄存器(base register),因为他可以用来索引寻址
cx 被用作计数器(counting register),一般存储循环的变量,如 for 循环中的 i
dx 被用作数据寄存器(data register),一般和 ax 相互配合进行乘除操作
有三个指针寄存器 ebp,esp,eip,
这三个寄存器是跟指针操作有关的,同样的,在学习 16 位汇编的时候我们学过 ip,sp,bp,在这里是一样的,这三个寄存器的低十六位就是 ip,sp,bp,下面介绍下这三个 16 位指针寄存器的一般作用
ip 存放的是将要执行的指令的地址(instruction pointer)
sp 存放的是栈顶的地址(stack pointer)
bp 存放的是栈底的地址(base pointer)
以及两个索引寄存器 esi,edi,
esi 通常在内存操作指令中作为 “源地址指针” 使用,edi 通常在内存操作指令中作为 “目的地址指针” 使用,他们两最早是叫做变址寄存器,一般用来存放存储单元的偏移量,来实现各种寻址方式。
再介绍一下几个段寄存器,在 8086 时代,数据总线是 16 位的,而地址总线是 20 位,显然一个 16 位的寄存器无法表示一个 20 位的地址,因此产生了另外一种 16 位长的寄存器叫段寄存器 cs,ds,es,ss,采用 实际地址 = 地址寄存器 段寄存器 << 4(x16)
的方法来表示一个地址,比如:
CS = 0x2000
IP = 0x1000
;CS:IP 表示用CS和IP共同表示的地址
CS:IP = (ES << 4) IP
= (0x2000 << 4) 0x1000
= 0x20000 0x1000
= 0x21000
TODO
还有一个挺重要的寄存器叫做 FLAG 寄存器
pic
如果你学了计系3,你应该会明白,这里其实是硬件实现与软件实现的问题。硬件上来说,push 指令和pop指令用到了栈,是直接从esp寻址的,所以esp只能用来存放栈的地址。其他寄存器都 是软件实现的,所以他们具体用来做什么都可以随便改(即使常用的ebp也可以不拿来当作栈底指 针,直接当作通用寄存器用也没关系。)。
x86-64
x86-64 架构下新增了 8 个通用寄存器,并且寄存器的位长