先分析《自己动手写操作系统》中的部分程序
//初始化中断向量表(见protect.c文件)
init_idt_desc(INT_VECTOR_COPROC_ERR,DA_386IGate, copr_error, PRIVILEGE_KRNL);
//INT_VECTOR_COPROC_ERR =16
//中断处理函数的定义
copr_error:
push 0xFFFFFFFF
push 16
jmp exception
//exception函数定义
exception:
call exception_handler
add esp, 4*2 //让栈顶指向EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS
//exception_handler函数定义
void exception_handler(int vec_no, int err_code,int eip, int cs, int eflags)
{
}
核心问题:汇编语句call exception_handler发生前后堆栈的变化情况
背景知识:如果中断或异常发生时没有特权级变换,那么eflags,cs,eip将依次被压入堆栈,如果有出错码的话,出错码将在最后被压栈。有特权级变换的情况下同样会发生堆栈切换,此时,ss和esp将被压入内层堆栈,然后是eflags,cs,eip,出错码(如果有的话)。对于不同中断号,不总会有出错码,可以参考《自己动手写操作系统》P89的表3.8
分析堆栈过程:
当CPU捕获到16号中断后,首先将eflags,cs,eip压入堆栈
随后程序来到中断处理函数copr_error处,又将出错码和16依次压入堆栈
然后程序跳转到exception函数处,执行call exception_handler函数,此时CPU自动将call exception_handler语句的下一条语句(add esp, 4*2)的地址压栈,此时堆栈情况如下:
| ||
Addr of (add esp, 4*2) | ||
16 | ||
出错码(如果有的话) | ||
Eip | ||
Cs | ||
Eflags |
程序跳转到exception_handler处,虽然是C函数,但是底层汇编实现代码的头两句一定是:
push ebp//将调用exception_handler的点处堆栈基址保存,方便返回
mov ebp, esp//将栈顶指针保存到ebp,方便索引栈中的参数
exception_handler函数虽然有5个参数,都会根据ebp加减偏移量在堆栈中找到,但是并不会有出栈的操作,这点谨记!
call exception_handler执行过程中,堆栈中会存在Addr of (add esp, 4*2)这一项,而当call exception_handler执行结束后,开始执行add esp, 4*2之前,堆栈中的Addr of (add esp, 4*2)这一项会自动弹出。
所以add esp,4*2执行结束后,会将esp指针指到eip地址处