1. CPU的栈操作的支持:
1) 现代CPU都提供栈的功能,即提供栈的访问功能,指令有push和pop等;
2) 8086CPU对栈的操作(push、pop等)都是以字为单位的,即16位,因此不得在栈操作中使用非16位的寄存器,如AL等,否则将会报错;
2. 用SS:SP定义一个内存栈:
1) 和前面利用DS寄存器定义一个数据段一样,栈同样也是存在于用户的内存区的(即当前程序的内存区);
2) 栈段段基由SS寄存器定义(即Stack Segment),而SP这个偏移指针则用来指示栈顶,因为栈是后进先出的,因此初始化时SP大于SS,并且入栈是SP变小出栈SP变大,因此栈是一个反向扩增的内存区域,栈顶是低地址,栈底是高地址;
3) 定义栈(即对SS:SP进行初始化):初始化时设SS = N, SP = M,则栈底 = M - 2,栈顶 = N,总共可以放(M - N) / 2个字(注意!栈操作的单位是字);
!注意:栈也只是内存空间中的一部分,只不过它具有特殊的访问方式而已(通过push和pop方式访问);
3. push和pop的执行过程(举例的形式):
1) 对于push ax:先sp -= 2再将ax压入ss:sp处,从图形上看就是sp先往下退两格再塞ax(向下地址变小);
2) 对于pop ax:先将ss:sp处的字赋给ax,然后sp += 2,从图形上看就是先取值然后sp往上进两格(栈底在上面,栈顶在下面,栈是一个反向扩充的内存区);
!注意:弹栈并不意味着弹掉的空间清零,只是不去管它(即不维护了),将来可能会被覆盖掉;
4. 越界问题:
1) CPU在硬件层面上并没有提供任何越界警告的机制,即当用于多次push和pop之后sp超出上面所述的N ~ M - 2的范围;
2) 越界的危害,越界后可能访问到其它正在执行的程序或者操作系统的内核,如果不小心对其中的数据进行修改则后果不堪设想,可能会造成死机崩溃;
3) 越界攻击:是一种黑客常用的手段,比如一般用户的敏感信息如密码等不得直接访问,黑客就会在密码段旁边的空间设定一个栈,然后不停pop(pop不会修改数据,而push会修改数据,因此就用pop),越界到密码段,然后获取密码,这当然只是一个例子,越界攻击还可以做其它意想不到的事;
5. push和pop指令的具体使用:
1) 同样,这里只介绍操作对象是寄存器的情况,当操作对象是内存单元时比较麻烦,这里的规则并不适用;
2) 对于push,除了ip不能push以外其它所有寄存器都可以push,意思就是把push后面寄存器中的内容压栈;
3) 对于pop,除了cs和ip不能作为操作数以外其余寄存器都可以,意思就是将栈顶的元素弹至pop后面指定的寄存器中保存;
4) 示例:
; 定义一个栈 mov ax, 1000H mov ss, ax mov sp, 10H ; 对ax和bx初始化 mov ax, 1AH mov bx, 2BH ; 备份ax和bx到栈中 push ax push bx ; 破坏(清空)ax和bx sub ax, ax sub bx, bx ; 从栈中恢复数据(注意后进先出的顺序) pop bx pop ax ; 顺序弄错就成交换ax和bx的值了 pop ax pop bx!注意,可以直接对sp使用传送指令!
6. 关于Debug的T命令中断问题的介绍:
a. 在使用命令D、U、E、A时,其中的段基地址可以直接使用寄存器来代替,这使得调试更加方便和快捷;
b. 例如:D DS:1000、A CS:00等;
当用用T命令执行修改SS寄存器(必须是SS寄存器)的命令时将会一次性执行两条指令,即包修改SS寄存器的下一条指令也同时执行了,因此将会看不见Debug停留在修改SS寄存器指令的下一条指令,如下图所示:
!这是因为当T遇到修改SS寄存器的指令时会产生中断,当然现在可以先不管这个问题,这个问题会在以后解释,但不过在用Debug调试的时候一定会因为遇到这个问题而感到非常郁闷;
**同样这种中断也会使栈中出现意想不到的数据:
当2000:0~10的范围设为栈,但是由于T命令遇到修改ss而产生中断而使栈中的内容不为0,这就说明了中断处理过程中使用了ss:sp定义的栈并产生了数据,这点要非常注意,虽然中断返回后ss:sp被重新设成了初值(这就意味着这并不影响中断返回后栈的使用),但有些情况下需要避免这种情况,特别是在操作系统初始化的时候就需要内核栈不被任何数据占用,这就意味着内核初始化时不得调用任何函数(函数会产生中断),因此所有函数都必须使用内联或宏替换进行代码嵌入!(貌似扯远了。。。)