----------------------------------------------------------------------------------------------------------------------------
#include
#include
#include
jmp_buf mark;
void test1()
{
char* p1, *p2, *p3, *p4;
p1 = malloc(10);
if (!p1) longjmp(mark, 1);
p2 = malloc(10);
if (!p2)
{
// 这里虽然可以释放资源,
// 但是程序员很容易忘记,也容易出错
free(p1);
longjmp(mark, 1);
}
p3 = malloc(10);
if (!p3)
{
// 这里虽然可以释放资源
// 但是程序员很容易忘记,也容易出错
free(p1);
free(p2);
longjmp(mark, 1);
}
p4 = malloc(10);
if (!p4)
{
// 这里虽然可以释放资源
// 但是程序员很容易忘记,也容易出错
free(p1);
free(p2);
free(p3);
longjmp(mark, 1);
}
// do other job
free(p1);
free(p2);
free(p3);
free(p4);
}
void test()
{
char* p;
p = malloc(10);
// do other job
test1();
// do other job
// 这里的资源可能得不到释放
free(p);
}
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
char* p;
p = malloc(10);
// do other job
test1();
// do other job
// 这里的资源可能得不到释放
free(p);
}
else
{
printf("捕获到一个异常/n");
}
}
呵呵!这是不是很多在Windows平台上做开发的程序员朋友们都用过。很熟悉吧!但是这里其实有一个认识上的误区,大多数程序员,包括很多计算机书籍中,都把SEH机制与__try,__except,__finally,__leave异常模型等同起来。这种提法是不对的,至少是不准确的。因为SHE狭义上讲,只能算是Window系列操作系统所提供的一种异常处理机制;而无论是__try,__except,__finally,__leave异常模型,还是try,catch,throw异常模型,它们都是VC所实现并提供给开发者的。其中__try,__except,__finally,__leave异常模型主要是提供给C程序员使用;而try,catch,throw异常模型,它则是提供给C++程序员使用,而且它也遵循C++标准中异常模型的定义。这多种异常机制之间的关系如下图所示:
typedef struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
DWORD handler;
}EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION;
#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;
}
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
UINT_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
//seh.c
// 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;
}
本文所讲到的异常处理机制,它就是狭义上的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!
-----------------------------------------------------------------------------------------------------------------------
//seh-test.c
#include
void main()
{
puts("hello");
// 定义受监控的代码模块
__try
{
puts("in try");
}
//定义异常处理模块
__except(1)
{
puts("in except");
}
puts("world");
}
// 例程1
// 平面的线性结构
#include
void main()
{
puts("hello");
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}
// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}
puts("world");
}
// 例程2
// 分层的嵌套结构
#include
void main()
{
puts("hello");
__try
{
puts("in try");
// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}
}
__except(1)
{
puts("in except");
}
puts("world");
}
// 例程3
// 分层的嵌套在__except模块中
#include
void main()
{
puts("hello");
__try
{
puts("in try");
}
__except(1)
{
// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}
puts("in except");
}
puts("world");
}
对查找匹配恰当的异常处理模块的过程等几条规则翻译如下:
1. 受监控的代码模块被执行(也即__try定义的模块代码);
2. 如果上面的代码执行过程中,没有出现异常的话,那么控制流将转入到__except子句之后的代码模块中;
3. 否则,如果出现异常的话,那么控制流将进入到__except后面的表达式中,也即首先计算这个表达式的值,之后再根据这个值,来决定做出相应的处理。这个值有三种情况,如下:
EXCEPTION_CONTINUE_EXECUTION (–1) 异常被忽略,控制流将在异常出现的点之后,继续恢复运行。
EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。
EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。
上面的规则其实挺简单的,很好理解。当然,这个规则也非常的严谨,它能很好的满足开发人员的各种需求,满足程序员对异常处理的分类处理的要求,它能够给程序员提供一个灵活的控制手段。
其中比较特殊的就是__except关键字后面跟的表达式,它可以是各种类型的表达式,例如,它可以是一个函数调用,或是一个条件表达式,或是一个逗号表达式,或干脆就是一个整型常量等等。例如代码如下:
// seh-test.c
// 异常处理模块的查找过程演示
#include
int seh_filer()
{
return 0;
}
void test()
{
__try
{
int* p;
puts("test()函数的try块中");
// 下面将导致一个异常
p = 0;
*p = 45;
}
// 注意,__except关键字后面的表达式是一个函数表达式
// 而且这个函数将返回0,所以控制流进入到上一层
// 的try-except语句中继续查找
__except(seh_filer())
{
puts("test()函数的except块中");
}
}
void main()
{
puts("hello");
__try
{
puts("main()函数的try块中");
// 注意,这个函数的调用过程中,有可能出现一些异常
test();
}
// 注意,这个表达式是一个逗号表达式
// 它前部分打印出一条message,后部分是
// 一个常量,所以这个值也即为整个表达式
// 的值,因此系统找到了__except定义的异
// 常处理模块,控制流进入到__except模块里面
__except(puts("in filter"), 1)
{
puts("main()函数的except块中");
}
puts("world");
}
#include
void main()
{
int j, zero;
puts("hello");
__try
{
puts("main()函数的try块中");
zero = 0;
j = 10;
// 下面将导致一个异常
j = 45 / zero;
// 注意,异常出现后,程序控制流又恢复到了这里
printf("这里会执行到吗?值有如何呢?j=%d /n", j);
}
// 注意,这里把zero变量赋值为1,试图恢复错误,
// 当控制流恢复到原来异常点时,避免了异常的再次发生
__except(puts("in filter"), zero = 1, -1)
{
puts("main()函数的except块中");
}
puts("world");
}
呵呵!厉害吧!要知道C++异常处理模型可没有这样的能力。但是请注意,一般这项功能不能轻易采用,为什么呢?因为它会导致不稳定,再看下面一个示例,代码如下:
#include
void main()
{
int* p, a;
puts("hello");
__try
{
puts("main()函数的try块中");
// 下面将导致一个异常
p = 0;
*p = 45;
printf("这里会执行到吗?值有如何呢?p=%d /n", *p);
}
// 注意,这里把p指针赋了一个合法的值,也即说,
// 当控制流恢复到原来异常点时,异常将不会再次发生
__except(puts("in filter"), p = &a, -1)
{
puts("main()函数的except块中");
}
puts("world");
}
LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
DWORD GetExceptionCode(VOID);
typedef struct _EXCEPTION_POINTERS { // exp
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
#include
#include
int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
printf("存储保护异常/n");
return 1;
}
else return 0;
}
int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
printf("被0除异常/n");
return 1;
}
else return 0;
}
void main()
{
puts("hello");
__try
{
__try
{
int* p;
// 下面将导致一个异常
p = 0;
*p = 45;
}
// 注意,__except模块捕获一个存储保护异常
__except(exception_access_violation_filter(GetExceptionInformation()))
{
puts("内层的except块中");
}
}
// 注意,__except模块捕获一个被0除异常
__except(exception_int_divide_by_zero_filter(GetExceptionInformation()))
{
puts("外层的except块中");
}
puts("world");
}
呵呵!感觉不错,大家可以在上面的程序基础之上改动一下,让它抛出一个被0除异常,看程序的运行结果是不是如预期那样。
最后还有一点需要阐述,在C++的异常处理模型中,有一个throw关键字,也即在受监控的代码中抛出一个异常,那么在SEH异常处理模型中,是不是也应该有这样一个类似的关键字或函数呢?是的,没错!SEH异常处理模型中,对异常划分为两大类,第一种就是上面一些例程中所见到的,这类异常是系统异常,也被称为硬件异常;还有一类,就是程序中自己抛出异常,被称为软件异常。怎么抛出呢?还是Windows提供了的API函数,它的声明如下:
VOID RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception flag
DWORD nNumberOfArguments, // number of arguments in array
CONST DWORD *lpArguments // address of array of arguments
);
#include
#include
int seh_filer(int code)
{
switch(code)
{
case EXCEPTION_ACCESS_VIOLATION :
printf("存储保护异常,错误代码:%x/n", code);
break;
case EXCEPTION_DATATYPE_MISALIGNMENT :
printf("数据类型未对齐异常,错误代码:%x/n", code);
break;
case EXCEPTION_BREAKPOINT :
printf("中断异常,错误代码:%x/n", code);
break;
case EXCEPTION_SINGLE_STEP :
printf("单步中断异常,错误代码:%x/n", code);
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
printf("数组越界异常,错误代码:%x/n", code);
break;
case EXCEPTION_FLT_DENORMAL_OPERAND :
case EXCEPTION_FLT_DIVIDE_BY_ZERO :
case EXCEPTION_FLT_INEXACT_RESULT :
case EXCEPTION_FLT_INVALID_OPERATION :
case EXCEPTION_FLT_OVERFLOW :
case EXCEPTION_FLT_STACK_CHECK :
case EXCEPTION_FLT_UNDERFLOW :
printf("浮点数计算异常,错误代码:%x/n", code);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO :
printf("被0除异常,错误代码:%x/n", code);
break;
case EXCEPTION_INT_OVERFLOW :
printf("数据溢出异常,错误代码:%x/n", code);
break;
case EXCEPTION_IN_PAGE_ERROR :
printf("页错误异常,错误代码:%x/n", code);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION :
printf("非法指令异常,错误代码:%x/n", code);
break;
case EXCEPTION_STACK_OVERFLOW :
printf("堆栈溢出异常,错误代码:%x/n", code);
break;
case EXCEPTION_INVALID_HANDLE :
printf("无效句病异常,错误代码:%x/n", code);
break;
default :
if(code & (1<<29))
printf("用户自定义的软件异常,错误代码:%x/n", code);
else
printf("其它异常,错误代码:%x/n", code);
break;
}
return 1;
}
void main()
{
puts("hello");
__try
{
puts("try块中");
// 注意,主动抛出一个软异常
RaiseException(0xE0000001, 0, 0, 0);
}
__except(seh_filer(GetExceptionCode()))
{
puts("except块中");
}
puts("world");
}
上面的程序很简单,这里不做进一步的分析。我们需要重点讨论的是,在__except模块中如何识别不同的异常,以便对异常进行很好的分类处理。毫无疑问,它当然是通过GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误代码,实际也即是DwExceptionCode字段。异常错误代码在winError.h文件中定义,它遵循Windows系统下统一的错误代码的规则。每个DWORD被划分几个字段,如下表所示: