全部博文(668)
分类:
2008-08-07 09:02:42
为了更进一步认识SEH机制,更深刻的理解SEH与__try,__except,__finally,__leave异常模型机制的区别。本篇文章特别对狭义上的SEH进行一些极为细致的讲解。
SEH设计思路
上图中,注意其中绿线部分所连成链表数据结构,这就是用户应用程序向操作系统注册的一系列的监控函数。如果某个函数中声明了异常处理机制,那么在函数帧栈中将分配一个数据结构体(EXCEPTION_REGISTRATION),这个数据结构体有点类似与局部变量的性质,它包含两个字段,其中一个是指向监控函数的指针(handler function address);另一个就是链表指针(previous EXCEPTION_REGISTRATION)。特别需要注意的是,并不是每个函数帧栈中都有EXCEPTION_REGISTRATION数据结构。另外链表头指针被保存到FS:[0]中,这样无论是操作系统,还是应用程序都能够很好操纵这个链表数据体变量。
EXCEPTION_REGISTRATION的定义如下:
typedef struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
DWORD handler;
}EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION;
通过一个简单例子,来理解SEH机制
也许上面的论述过于抽象化和理论化了,还是看一个简单的例子吧!这样也很容易来理解SEH的工作机制原来是那么的简单。示例代码如下:
//seh.c
#include
#include
#include
typedef struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
DWORD handler;
}EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION;
// 异常监控函数
EXCEPTION_DISPOSITION myHandler(
EXCEPTION_RECORD *ExcRecord,
void * EstablisherFrame,
CONTEXT *ContextRecord,
void * DispatcherContext)
{
printf("进入到异常处理模块中\n");
printf("不进一步处理异常,程序直接终止退出\n");
abort();
return ExceptionContinueExecution;
}
int main()
{
DWORD prev;
EXCEPTION_REGISTRATION reg, *preg;
// 建立异常结构帧(EXCEPTION_REGISTRATION)
reg.handler = (DWORD)myHandler;
// 把异常结构帧插入到链表中
__asm
{
mov eax, fs:[0]
mov prev, eax
}
reg.prev = (EXCEPTION_REGISTRATION*) prev;
// 注册监控函数
preg = ®
__asm
{
mov eax, preg
mov fs:[0], eax
}
{
int* p;
p = 0;
// 下面的语句被执行,将导致一个异常
*p = 45;
}
printf("这里将不会被执行到.\n");
return 0;
}
上面的程序运行结果如下:
通过上面的演示的简单例程,现在应该非常清楚了Windows操作系统提供的SEH机制的基本原理和控制流转移的具体过程。另外,这里分别详细介绍一下exception_handler回调函数的各个参数的涵义。其中第一个参数为EXCEPTION_RECORD类型,它记录了一些与异常相关的信息。它的定义如下:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
UINT_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
第二个参数为PEXCEPTION_REGISTRATION类型,既当前的异常帧指针。第三个参数为指向CONTEXT数据结构的指针,CONTEXT数据结构体中记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值,这一点有点类似于setjmp函数的作用。第四个参数DispatcherContext,它也是一个指针,表示调度的上下文环境,这个参数一般不被用到。
最后再来看一看exception_handler回调函数的返回值有何意义?它基本上有两种返回值,一种就是返回ExceptionContinueExecution,表示异常已经被恢复了,程序可以正常继续执行。另一种就是ExceptionContinueSearch,它表示当前的异常回调处理函数不能有效处理这个异常错误,系统将会根据EXCEPTION_REGISTRATION数据链表,继续查找下一个异常处理的回调函数。上面的例程的详细分析如下图所示:
来一个稍微复杂一点例子,来更深入理解SEH机制
现在,相信大家已经对SEH机制,既有了非常理性的理解,也有非常感性的认识。实际上,从用户角度上来分析,SEH机制确是比较简单。它首先是用户注册一系列的异常回调函数(也即监控函数),操作系统为每个线程维护一个这样的链表,每当程序中出现异常的时候,操作系统便获得控制权,并纪录一些与异常相关的信息,接着系统便依次搜索上面的链表,来查找并调用相应的异常回调函数。
说到这里,也许朋友们有点疑惑了?上一篇文章中讲述到,“无论是__try,__except,__finally,__leave异常模型机制,或是try,catch,throw方式的C++异常模型,它们都是在SEH基础上来实现的”。但是从这里看来,好像上面描述的SEH机制与try,catch,throw方式的C++异常模型不太相关。是的,也许表面上看起来区别是比较大的,但是SEH机制,它的的确确是上面讲到的其它两种异常处理模型的基础。这一点,在深入分析C++异常模型的实现时,会再做详细的叙述。这里为了更深入理解SEH机制,主人公阿愚设计了一个稍微复杂一点例子。它仍然只有SEH机制,没有__try,__except,__finally,__leave异常模型的任何影子,但是它与真实的__try,__except,__finally,__leave异常模型的实现却有几分相似之处。
// seh.c
#include
#include
typedef struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
DWORD handler;
}EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION;
#define SEH_PROLOGUE(pFunc_exception) \
{ \
DWORD pFunc = (DWORD)pFunc_exception; \
_asm mov eax, FS:[0] \
_asm push pFunc \
_asm push eax \
_asm mov FS:[0], esp \
}
#define SEH_EPILOGUE() \
{ \
_asm pop FS:[0] \
_asm pop eax \
}
void printfErrorMsg(int ex_code)
{
char msg[20];
memset(msg, 0, sizeof(msg));
switch (ex_code)
{
case EXCEPTION_ACCESS_VIOLATION :
strcpy(msg, "存储保护异常");
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
strcpy(msg, "数组越界异常");
break;
case EXCEPTION_BREAKPOINT :
strcpy(msg, "断点异常");
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO :
case EXCEPTION_INT_DIVIDE_BY_ZERO :
strcpy(msg, "被0除异常");
break;
default :
strcpy(msg, "其它异常");
}
printf("\n");
printf("%s,错误代码为:0x%x\n", msg, ex_code);
}
EXCEPTION_DISPOSITION my_exception_Handler(
EXCEPTION_RECORD *ExcRecord,
void * EstablisherFrame,
CONTEXT *ContextRecord,
void * DispatcherContext)
{
int _ebp;
printfErrorMsg(ExcRecord->ExceptionCode);
printf("跳过出现异常函数,返回到上层函数中继续执行\n");
printf("\n");
_ebp = ContextRecord->Ebp;
_asm
{
// 恢复上一个异常帧
mov eax, EstablisherFrame
mov eax, [eax]
mov fs:[0], eax
// 返回到上一层的调用函数
mov esp, _ebp
pop ebp
mov eax, -1
ret
}
// 下面将绝对不会被执行到
exit(0);
return ExceptionContinueExecution;
}
EXCEPTION_DISPOSITION my_RaiseException_Handler(
EXCEPTION_RECORD *ExcRecord,
void * EstablisherFrame,
CONTEXT *ContextRecord,
void * DispatcherContext)
{
int _ebp;
printfErrorMsg(ExcRecord->ExceptionCode);
printf("跳过出现异常函数,返回到上层函数中继续执行\n");
printf("\n");
_ebp = ContextRecord->Ebp;
_asm
{
// 恢复上一个异常帧
mov eax, EstablisherFrame
mov eax, [eax]
mov fs:[0], eax
// 返回到上一层的调用函数
mov esp, _ebp
pop ebp
mov esp, ebp
pop ebp
mov eax, -1
ret
}
// 下面将绝对不会被执行到
exit(0);
return ExceptionContinueExecution;
}
void test1()
{
SEH_PROLOGUE(my_exception_Handler);
{
int zero;
int j;
zero = 0;
// 下面的语句被执行,将导致一个异常
j = 10 / zero;
printf("在test1()函数中,这里将不会被执行到.j=%d\n", j);
}
SEH_EPILOGUE();
}
void test2()
{
SEH_PROLOGUE(my_exception_Handler);
{
int* p;
p = 0;
printf("在test2()函数中,调用test1()函数之前\n");
test1();
printf("在test2()函数中,调用test1()函数之后\n");
printf("\n");
// 下面的语句被执行,将导致一个异常
*p = 45;
printf("在test2()函数中,这里将不会被执行到\n");
}
SEH_EPILOGUE();
}
void test3()
{
SEH_PROLOGUE(my_RaiseException_Handler);
{
// 下面的语句被执行,将导致一个异常
RaiseException(0x999, 0x888, 0, 0);
printf("在test3()函数中,这里将不会被执行到\n");
}
SEH_EPILOGUE();
}
int main()
{
printf("在main()函数中,调用test1()函数之前\n");
test1();
printf("在main()函数中,调用test1()函数之后\n");
printf("\n");
printf("在main()函数中,调用test2()函数之前\n");
test2();
printf("在main()函数中,调用test2()函数之后\n");
printf("\n");
printf("在main()函数中,调用test3()函数之前\n");
test3();
printf("在main()函数中,调用test3()函数之后\n");
return 0;
}
上面的程序运行结果如下:
在main()函数中,调用test1()函数之前
被0除异常,错误代码为:0xc0000094
跳过出现异常函数,返回到上层函数中继续执行
在main()函数中,调用test1()函数之后
在main()函数中,调用test2()函数之前
在test2()函数中,调用test1()函数之前
被0除异常,错误代码为:0xc0000094
跳过出现异常函数,返回到上层函数中继续执行
在test2()函数中,调用test1()函数之后
存储保护异常,错误代码为:0xc0000005
跳过出现异常函数,返回到上层函数中继续执行
在main()函数中,调用test2()函数之后
在main()函数中,调用test3()函数之前
其它异常,错误代码为:0x999
跳过出现异常函数,返回到上层函数中继续执行
在main()函数中,调用test3()函数之后
Press any key to continue
总结
本文所讲到的异常处理机制,它就是狭义上的SEH,虽然它很简单,但是它是Windows系列操作系统上其它所有异常处理模型实现的奠基石。有了它就有了基本的物质保障,
另外,通常一般所说的SEH,它都是指在本篇文章中所阐述的狭义上的SEH机制基础之上,实现的__try,__except,__finally,__leave异常模型,因此从下一篇文章中,开始全面介绍__try,__except,__finally,__leave异常模型,实际上,它也即广义上的SEH。此后所有的文章内容中,如没有特别注明,SEH机制都表示__try,__except,__finally,__leave异常模型,这也是为了与try,catch,throw方式的C++异常模型相区分开。
朋友们!有点疲劳了吧!可千万不要放弃,继续到下一篇的文章中,可要知道,__try,__except,__finally,__leave异常模型,它可以说是最优先的异常处理模型之一,甚至比C++的异常模型还好,功能还强大!即便是JAVA的异常处理模型也都从它这里继承了许多优点,所以不要错过呦,Let’s go!