最近学习了X86汇编,其实无论是古老的8086还是现在i3/5/7/9,Xeon3/5,在最基本原理上,都是相通的,只是CPU位数,寻址空间,寄存器个数,指令集的扩充等方面有所不同,对于学习,8086永不过时。
1.内存中字的存储
8086CPU中,用16位寄存器来存储一个字,高8位存放高字节,低8位存放低字节。在内存中存储时,由于内存单元是字节单元,一个单元存放一个字节,那么一个字(2字节,16位)应该用两个连续的存储单元(内存地址)来存储,低字节存放在低地址,高字节存放在高地址,这就是我们所说的小端序,大端序与之相反。 字单元:存放一个字型数据(16位)的内存单元,它由两个连续内存单元组成。 8086CPU不支持将数据直接存入段寄存器(DS),需要先将数据存放到通用寄存器,然后再MOV到段寄存器。 “[address]”做为一个整体表示一个内存单元,中括号中的数字表示内存单元的偏移地址。如: 假设DS中为1000H,那么: MOV Al, [0] ;将10000H内存地址中的数据存入AX MOV [0], Al ;将AX中的数据存入内存地址10000H eg.1读取10000H中内存单元的内容 mov bx, 1000H mov ds, bx mov al, [0] eg.2将al中的数据存入10000H内存地址中 mov bx, 1000H mov ds, bx mov [0], al
字的传送: mov bx, 1000H mov ds, bx mov ax, [0] ;1000:0处的数据存入AX寄存器 mov [0], ax ;AX寄存器中的值存入1000:0
2.指令学习:mov、add、sub
(1)mov 指令格式: mov 寄存器, 数据 mov 寄存器,寄存器 mov 寄存器,内存单元 mov 内存单元,寄存器 mov 段寄存器,寄存器 mov 寄存器,段寄存器 mov 段寄存器,内存单元 (2)add指令格式: add 寄存器, 数据 add 寄存器,寄存器 add 寄存器,内存单元 add 内存单元,寄存器 (3)sub指令格式: sub 寄存器, 数据 sub 寄存器,寄存器 sub 寄存器,内存单元 sub 内存单元,寄存器
3.数据段
前面讲过段的概念,也讲过代码段,那么我们知道指令和数据都是二进制形式,既然有代码段,那么也应该有数据段。 我们可以将一组长度为N、地址连续、起始地址为16倍数的内存单元当做专门存储数据的内存段,即:数据段。如:123B0H~123B9H这段内存空间做数据段,那么它的段地址为123BH,长度为10个字节。 相比于代码段有代码段寄存器:CS,那么数据段也有数据段寄存器:DS 数据段的数据访问:可以用ds作为数据段的地址,再根据需要访问数据段中的具体单元 如:123B0H~123B9H作为一个数据段 mov ax, 123BH mov ds, ax ;设置段地址 mov al, 0 ;清零al寄存器,保存累加结果 add al, [0] ;累加数据段第0偏移地址 add al, [1] ;累加数据段第1偏移地址 add al, [2] ;累加数据段第2偏移地址
4.栈
8086CPU提供操作把一段内存以栈的方式进行操作,入栈指令:push,出栈指令:pop。 (1)push 指令格式: push 寄存器 push 段寄存器 push 内存地址 (2)pop指令格式: pop 寄存器 pop 段寄存器 pop 内存地址 eg1. push ax ;把ax寄存器中的数据存入栈 eg2. pop ax ;从栈顶取出数据存入ax
8086CPU的入栈和出栈操作都可以以字为单位进行。 8086CPU提供段寄存器SS和寄存器SP,栈顶的段地址存放在SS,偏移地址粗放在SP,任意时刻:SS:SP指向栈顶元素。push和pop指令被执行时,CPU从SS和SP中取到栈顶地址 push ax的执行可分2部分完成: (1)SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶。因为栈地址是从高地址向低地址移动使用的。如:栈地址10000H~1000FH,栈底是1000FH,栈顶是10000H,初始时SS是1000H,SP是0010H,栈为空,由于此时要压入数据,SP=SP-2,则SP变成了000EH。 (2)将ax中的内容存入SS:SP指向的内存单元,即:执行push ax后,第一个数据被压入1000EH地址单元,SSH和SP的值不变,等待下一次入栈。 总结:入栈是从高地址向低地址方向增长,此外:任意时刻SS:SP指向栈顶元素 pop ax的执行过程与入栈相反,也可分2部分完成: (1)将SS:SP指向的内存单元处的数据送入ax中 (2)SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。 总结:出栈是从低地址高地址方向减少的,此外:细心读者可以看一下,出栈后,栈内数据并不清零,也就是垃圾数据,所以有时候我们依然能在某些时候读到正确数据,但是千万别侥幸,因为栈随时会增长,覆盖这些数据,更危险的是,此时入果向该地址写入数据,那么程序将立即崩溃。
栈顶越界和栈底越界的问题:栈底越界,我们会读到栈外的数据;栈顶越界,我们会向超出栈空间外的内存空间写入数据,这是非常危险的。但是8086CPU并不提供栈保护机制,这需要程序员自己来完成。
栈段:我们可以将长度为N的一组地址连续、起始地址为16倍数的内存单元,当做栈空间来使用,从而定义一个栈段,但是这仅仅是一种安排,CPU并不会由于这种安排,就在执行pop和push指令时,自动为将我们定义的栈空间当做栈空间访问,需要我们通过SS:SP来控制。此外,pop和push本质上是一种特殊的内存访问指令,pop和push修改的是SP。