CPU中的主要结构是运算器、控制器与寄存器,这些器件通过CPU的内部总线相连,其中运算器负责信息处理,寄存器负责信息存储,控制器控制各种器件进行工作,内部总线连接各种器件,在它们之间进行数据的传送。对于汇编程序员来说,主要部件是寄存器,因为只有寄存器是我们可以编程直接操作的。不同的CPU架构不同,8086CPU共有14个寄存器,分别是AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW,今天我们先来学习基础的通用寄存器,其余的寄存器在用到时会给予说明。

一、通用寄存器
   AX、BX、CX和DX是四个通用寄存器,通常用来存放一般性的数据,后面的分析都在8086CPU中进行。每个通用寄存器是16位,即一次可以处理一个字(2个字节)的数据,但是为了与8086CPU之前的CPU相容,也支持一个字节的寄存器,即AH与AL,类似的还有BH和BL等,CPU操作时会降AH和AL当作独立的寄存器,运算进位时会直接丢弃,因为CPU认为只有一个8位的寄存器而已:
逆向初学【二】:汇编入门-寄存器-LMLPHP

二、物理地址
   CPU访问内存需要知晓存储单元的地址,由于8086CPU地址总线为20位,但是寄存器却只有16位,即一次处理的地址最多只有2的16次幂,远小于2的20次幂。为了弥补这个问题,8086CPU采用了一种特殊的方式通过16位的寄存器来构造20位的访问地址,简单来说,即:段地址*16+偏移地址。从计算的位数上来说,段地址和偏移地址存储在CPU寄存器中,都是16位的;段地址*16,即整体在右边添加4个二进制位,成为了20位;20位与16位相加,得到了20位的实际内存地址。这里虽然是8086CPU中的方法,但是其实确实现在所有CPU中的实际寻址算法,即一个基地址加上一个偏移量得到一个实际地址。由此我们可以得到段的概念,内存本身不分段,但是由于CPU的特殊寻址方式,我们可以将内存看作最大64KB的一个个段(16位的最大值正好是64KB),因此我们可以在汇编中人为的指定段的起始和结束。当然,这里的段地址和偏移量实际上存储在CPU的寄存器中,比如获取指令的CS:IP,其中CS为代码段寄存器,其中存储代码段的基地址,IP寄存器则存储着当前要执行的指令的指针,即一个偏移量,因此CS:IP指定了接下来CPU要执行的指令的位置。这里需要说明的是,内存中的数据对于CPU来说都是二进制位,能够区分数据和指令的唯一标准就是指令曾经或者正在被CS:IP指定;每执行完一条指令,IP会累加上条指令的长度,从而指向下调指令。
逆向初学【二】:汇编入门-寄存器-LMLPHP
   我们是否可以修改控制CS:IP的值呢?答案是肯定的,只不过我们不能使用mov等传送指令,而应当使用jmp这类转移指令,基本的用法是:
-1. 修改CS:IP: jmp 2AE3:3    执行后:CS=2AE3H, IP=0003H;
-2. 仅修改IP:jmp ax(ie. move ip, ax)即用寄存器中的值修改IP;

三、内存访问
   CPU访问内存除了获取指令,还要获取数据,那么数据部分如何定位的呢?同指令的CS:IP一样,8086CPU使用DS:[...]来获取内存数据地址,其中段寄存器存储内存数据段的基地址,而[...]表示一个内存偏移量指向的内存单元,如[0]表示偏移量为0的内存单元,这里使用时要注意,8086CPU不支持直接对DS传送值,因此mov ds, 1000H是非法的,正确地是通过寄存器来实现,即:mov ax, 1000H; mov ds, ax;
   这部分我们要学习基本的汇编指令,如mov、sub、add等,都可以操作寄存器,操作完之后将数据放入第一个参数表示的寄存器中。CPU中的内存数据一种特殊的结果就是栈,即只能从一端读写数据的结构,其基本指令是push ax;将寄存器ax的值入栈;和pop ax;从栈中取出栈顶元素放入寄存器ax中;下面是mov\add\sub命令的一个简单示例:
逆向初学【二】:汇编入门-寄存器-LMLPHP
   然后我们来看看PUSH命令的执行过程:
逆向初学【二】:汇编入门-寄存器-LMLPHP
   然后是POP命令:
逆向初学【二】:汇编入门-寄存器-LMLPHP
   可以看出,栈的操作关键是栈顶位置的确定,因此CPU专门使用SS:SP来获取当前内存中栈顶的位置。值得一提的是,CPU本身并没有对栈的大小进行检查,因此实际中会出现栈顶越界的问题(上限超出-PUSH;下限超出-POP),这也就要求我们必须人为进行检查,否则就会出现程序的漏洞。
09-23 13:01