三、栈
三、*栈
1. 栈的概念
栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。
2. CPU栈机制
入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)。
从程序化的⻆度来讲,应该有一个标记,这个标记一直指示着栈顶。(SS:SP)
- 栈从栈底(高位)开始存入数据,从栈顶(低位)开始复制数据转移出寄存器。
3. SS和SP
CPU 如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU 从SS 和SP 中得到栈顶的地址。
push ax 的执行,由以下两步完成。
(1)SP=SP-2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2)将ax中的内容送入SS:SP 指向的内存单元处,SS:SP此时指向新栈顶。
pop ax的执行过程和push ax刚好相反,由以下两步完成。
(1)将SS:SP 指向的内存单元处的数据送入ax 中;
(2)SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
- push指令先指定空位置,再存入数据
- pop指令先存入数据到寄存器中,再改变SS:SP位置
- pop 操作前的栈顶元素依然存在,但是,它己不在栈中。当再次执行 push 等入栈指令后,SS:SP移至上一个内存单元,并在里面写入新的数据,它将被覆盖。
故磁盘格式化并没有清除数据,只是把“指针”复位了而已,下次写入只是覆盖原有数据,可以复原。
4. 栈溢出
将 10010H~1001FH当作栈空间,该栈空间容量为16字节(8字),初始状态为空,SS=1000H、SP=0020H,SS:SP 指向10020H;
在执行8次push ax 后,向栈中压入8个字,栈满,SS: SP 指向 10010H;
再次执行 push ax: sp=sp- 2,SS:SP 指向 1000EH,栈顶超出了栈空间,ax 中的数据送入1000 EH 单元处,将栈空间外的数据覆盖
上面描述了执行push、pop 指令时,发生的栈顶超界问题。可以看到,当栈满的时候再使用push 指令入栈,或栈空的时候再使用pop 指令出栈,都将发生栈顶超界问题(栈溢出)。
栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。
PS: 如果是故意造成栈溢出的,就可以利用此特性将一些程序中的受保护数据取出(例如用户登录密码等信息),所以栈溢出也是PWN攻击的一种方式。
8086CPU只知道栈项在何处(由SS:SP指示) ,而不知道我们安排的栈空间有多大。这点就好像CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道要执行的指令有多少。从这两点上我们可以看出8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令是哪一条。
5. 栈段
我们可以将长度为N( ≤ 64KB) 的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来用,从而定义了一个栈段。比如,我们将 10010H~1001FH 这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一个栈段。
如果将 10000H~1FFFFH 这段空间当作栈段,SS=1000H,栈空间为64KB,栈最底部的字单元地址为1000:FFFE。任意时刻,SS:SP 指向栈顶单元,当栈中只有一个元素的时候,S S=1000H,SP=FFFEH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2.
SP原 来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。
换一个⻆度看,任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以sS:SP 只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE ,所以栈空时,SP=0000H.
段的总结
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。
不管我们如何安排,CPU将内存中的某段内容当作代码,是因CSIP 指向了那里:CPU将某段内存当作栈,是因为SS;SP 指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU 按我们的安排行事。要非常清楚 CPU 的工作机理,才能在控制 CPU 按照我们的安排运行的时候做到游刃有余。