A.关于堆栈
jmp指令:不影响堆栈
call指令:影响堆栈,对于短调用来说,call指令执行时会将下一条指令的eip压栈,到ret指令时,这个eip会被从堆栈中弹出。
对于长转移;还会将cs压栈
B.call指令堆栈示意图
假设函数foo(param1,param2,param3)
1.短调用时堆栈示意图
2.短调用返回时堆栈示意图
3.长调用时堆栈示意图
4.长调用返回时堆栈示意图(通过带参数的ret指令)
5.有特权级变换的转移及返回时的堆栈变化
转移时示意图
返回时示意图
由于堆栈可能会发生变化,所以intel提供了将堆栈A的诸多内容复制到堆栈b中。这里我们只涉及到连个堆栈,而事实上,由于每个任务
最多在都可能在4个特权级之间转移,所以每个任务实际上需要4个堆栈。原有的ss和一个esp就不能满足我们的需要,这是就要使用到
TSS(Task-State Stack),一个数据结构,包含多个字段
6.32位TSS结构如下
TSS字段偏移4到27的3个ss和3个esp,当发生堆栈切换时,内层的ss和esp就是从TSS里面取得.
C.转移过程概述
call过程(低特权->高特权)
1.根据目标代码段的DPL(新的CPL)从TSS中选择应该切换至哪个ss和esp
2.从TSS中取得新的ss和esp.在这个过程中如果发现ss、esp或者TSS界限错误都会导致无效TSS异常(#TS)
3.对ss描述符进行检验,如果发生错误同样产生#TS异常
4.暂时性地保存当前ss和esp的值
5.加载新的ss和esp
6.将刚刚保存的ss和esp的值压入堆栈
7.从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的数目有调用门中Param Count(最多31个参数,大于31可以使用结构指针)
一项来决定。如果Param Count 为0的话,将不复制参数
8.将当前的cs和eip压栈
9.加载调用门中指定的新的cs和eip,开始执行被调用的过程
ret过程(高特权->低特权)
1.检查保存的cs中的RPL以判断返回时是否要交换特权等级
2.加载被调用者堆栈上的cs和eip(此时会进行代码段描述符和选择子类型和特权级检验)
3.如果ret指令含有参数,则增加esp的值以跳过参数,然后esp将指向被保存过的ss和esp.注意,ret的参数必须对应调用门中的param count
4.加载ss和esp,切换到调用者堆栈,被调用者的ss和esp被丢弃。这里会进行你个ss描述符、esp以及ss段描述的检验
5.如果ret指令含有参数,增加esp的值以跳过参数(此时已经在调用者堆栈中)
6.检查ds、es、fs、gs的值,如果其中哪一个寄存器指向的段的DPL小于CPL(此规则不使用与一致代码段),那么一个空描述符会被加载到该
寄存器