新的异常服务程序分为两个部分,第一部分用汇编语言实现,用于保护现场以及完成一些C代码无法实现的操作,其代码如下:
Unsigned char __ExceptionEntry[]={
0x9c, //1 pushfd
0x60, //2 pushad
0x8d,0x44,0x24,0x24 //3 lea eax,[esp+9*4]
0x50, //4 push eax
0xe8,0,0,0,0, //5 call __c__ExceptionEntry
0x8d,0x64,0x24,4, //6 lea esp,[esp+4]
0xb,0xc0, //7 or eax,eax
0x74,0x11, //8 jz __call_system_handler
0x8b,0x44,0x24,0x30,//9 mov eax,dword ptr [esp+0x0c*4]
0x89,0x44,0x24,0x20,//10 mov dword ptr [esp+0x20],eax
0x61, //11 popad
0x9d, //12 popfd
0x8d,0x64,0x24,4, //13 lea esp,[esp+4]
0xca,4,0, //14 retf 4
0x61, //15 __call_system_handler: popad
0x9d, //16 popfd
0xe9,0,0,0,0 //17 jmp __system_handler
};
首先,这一部分代码为什么要使用机器码而不是汇编代码,是因为其中部分代码需要在初始化时动态修改,以机器码形式写出来便于计算偏移量。另外,为了方便起鉴在注释中使用了Intel的汇编语言格式,毕竟AT&T汇编的格式太过麻烦。
现在首先来解释一下这一段代码的作用:
第1、2行,保护现场,保存所有可能会被破坏到的寄存器。
第3行,EAX指向堆栈中的异常错误代码(关于异常状态下的堆栈内容,会在下文描述)。
第4、5行,以指向异常错误代码的指针为参数调用第二部分异常处理程序,因为在编译之前无法确定第二部分异常处理程序的地址,所以在初始化时需要将第二部分异常处理程序的地址填入0xe8之后的双字中。
第6行,第二部分异常处理程序执行完毕之后恢复调用前的堆栈。
第7、8行,确认该异常是否已经处理,如果没有处理则跳转到_call_system_handler(第15行)。
第9、10行,将异常发生时的标志寄存器的值拷贝到第1行保存的标志寄存器的位置。
第11、12行,恢复异常发生前的现场。
第13行,清除堆栈中的错误代码。注意这里绝对不能用 add esp,4,因为此时标志寄存器已经恢复到发生异常时的状态,add指令会改变标志寄存器的值,而用pop指令清除堆栈的话没有地方可以存放取出的数据,所以只能用lea指令。
第14行,以远调用方式返回被中断代码,这一句非常重要,因为是从异常处理程序返回被中断代码,所以本来应该使用IRET来返回,但是在异常可能发生在某个子程序中,因此,在返回时需要调整堆栈指针,所以这里使用带调整堆栈的RETF指令来完成这一任务,这里RETF附带的参数也需要按每次异常发生时的不同状态来修正。后面将对这里的代码和原理作一个详细的说明。
|