目的:发生异常时候,我们需要知道异常发生在哪个位置,通过解析栈的信息可以知道出现异常的位置
这需要搞清楚函数之间调用时候通过栈传参的细节
结合王道的ppt(补一下底层的基础知识)
王道基础知识
call指令的作用
ret指令的作用
栈是倒过来的由高地址向低地址发展
函数调用切换栈帧
函数调用传递参数
gcc编译器将每个栈帧大小设置为16B的整数倍(当前函数的栈帧除外),因此栈帧内可能出现空闲未使用的区域。
通常将局部变量集中存储在栈帧底部区域
通常将调用参数集中存储在栈帧顶部区域
栈帧最底部一定是上一层栈帧基址(ebp旧值)
栈帧最顶部一定是返回地址(当前函数的栈帧除外)
函数调用的例子
王道这些解析我感觉是最通俗易懂的
回归工程
异常处理函数
咱们这个异常处理函数,需要知道是哪条指令出了异常,然后再作下一步处理(打印出来之类的,像编辑器提示你哪句代码错了也是依靠下面的方法)
我们需要拿到EIP栈里面的内容,为了使判错更加精准,一般也需要用到通用寄存器里面的值
我们怎么拿到寄存器的值呢?
两种方案
方案一:
要这么多形参,太复杂了,放弃
方案二:
因为这些值都是挨在一起的,所以搞个结构体,结构体的值都是挨一起的,所以能一一对应,这个设计很巧妙
//irq.h
typedef struct _exception_frame_t {
// 结合压栈的过程,以及pusha指令的实际压入过程
int gs, fs, es, ds; //push压入的
int edi, esi, ebp, esp, ebx, edx, ecx, eax; //pusha压入的
// int num;
// int error_code; //错误码暂时不加
int eip, cs, eflags;//硬件自动压入的
}exception_frame_t;
//irq.c
void do_handler_unknown (exception_frame_t * frame) {
do_default_handler(frame, "Unknown exception.");
}
咱们传参给函数do_handler_unknown,do_handler_unknown函数会去找调用do_handler_unknown函数的函数的栈顶附件,找到那个函数传过来的实参
exception_handler_unknown函数call了do_handler_unknown函数,相当于exception_handler_unknown调用了do_handler_unknown,那么do_handler_unknown就去exception_handler_unknown函数栈的栈顶位置找传过来的参数,我们自己压入那个结构体的起始地址,如下图
因为栈的地址是由高地址往低地址发展,所以定义结构体时候要把GS段寄存器放第一位,然后这个结构体的起始地址就是GS的地址(就像数组的第一个元素的地址可以当数组的起始地址),在没有执行 call do_handler_unknown之前,还是处于exception_handler_unknown函数栈里面,所以压入esp栈顶指针
exception_handler_unknown:
// 保存所有寄存器
pusha // 保存通用寄存器
// 保存段寄存器
push %ds
push %es
push %fs
push %gs
push %esp
call do_handler_unknown
add $(1*4), %esp // 丢掉esp,以保护下面pop的正确性,没有用pop指令,这块代码需要重用,见下图
// 恢复保存的寄存器
pop %gs
pop %fs
pop %es
pop %ds
popa
iret //中断返回指令,所以不能用c语言去写
call指令执行结束回到函数exception_handler_unknown(),esp指向的是形参,并不是部分寄存器的值(见图10:函数栈帧内容)
调试结果
看看结构体能不能正确兜住这些寄存器的值
在这个位置打断点
监控这个结构体,发现这个结构体中eip的值是0x00010267
去除0异常那个代码那里看看反汇编的代码
确实是10267
但是我发现在这个断点处的cpu寄存器信息并不是10267
因为此时此刻执行的是do_handler_unknown函数,并没有异常
看网上解释说eip就是PC的值