Windows下C++异常处理

SEH(structured exception handling)结构化异常处理

Windows提供的异常处理机制,与语言无关。SEH使用关键字: __try, __except, __finally, __leave。

  • __try语句,定义受监控的代码模块。
  • __except语句,定义的异常处理模块。
  • 执行过程:
  1. 受监控的代码模块被执行。
  2. 如果没有出现异常,控制流转入__except子句之后的代码模块中。
  3. 否则出现异常,控制流转入__except后面的表达式中,计算表达式值,再根据这个值来做相应的处理。
  4. EXCEPTION_CONTINUE_EXECUTION (–1) 异常被忽略,控制流将在异常出现的点之后,继续恢复运行。
  5. EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。
  6. EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。  
  • __finally 语法的含义就是无论如何,此句总是会执行,常用于资源释放。_finally与__try必须配对,不能同时有__except和__finally。因为两者都必须同时和__try配对使用,否则会报编译错误。
  • Windows提供了两个相关的API:
  1. LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
     1 try 
     2 { 
     3     // try block 
     4 } 
     5 except ( FilterFunction(GetExceptionInformation() ) 
     6 { 
     7     // exception handler block 
     8 } 
     9 
    10 typedef struct _EXCEPTION_POINTERS {
    11     PEXCEPTION_RECORD ExceptionRecord; 
    12     PCONTEXT ContextRecord; 
    13 } EXCEPTION_POINTERS;

    ExceptionRecord记录了异常的相关信息,如错误码、标志位等。 ContextRecord记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值,因此有了这些信息,__except模块便可以对异常错误进行很好的分类和恢复处理。不过特别需要注意的是,这两个函数只能是在__except后面的括号中的表达式作用域内有效,否则结果可能没有保证。

  2.  DWORD GetExceptionCode(VOID); 

  3. 1 try 
    2 { 
    3     // try block  
    4 } 
    5 except ( FilterFunction(GetExceptionCode() ) 
    6 { 
    7     // exception handler block 
    8 }  
  • SEH使用 RaiseException函数来引发异常
    1 VOID RaiseException(
    2     DWORD dwExceptionCode,
    3     DWORD dwExceptionFlags,
    4     DWORD nNumberOfArguments,
    5     CONST ULONG_PTR* pArguments);

C++异常处理

C++也实现了异常处理机制,即我们常见的try-catch语句

 1 try 
 2 {  
 3     包含可能抛出异常的语句;  
 4 }  
 5 catch(类型名[形参名])    // 捕获特定类型的异常  
 6 {  
 7  
 8 }  
 9 catch(类型名[形参名])    // 捕获特定类型的异常  
10 {  
11  
12 }  
13 catch(...)    // 三个点则表示捕获所有类型的异常  
14 { 
15  
16 } 

在有些情况下,我们捕获异常并不是为了对这个异常进行处理,而是修改异常的信息,然后将之抛出。C++专门提供了这样的操作方式,即我们可以直接使用throw关键字,后面无需跟随具体错误对象,这样可以将捕捉到的异常抛出。

 1 int func(int x, int y)  //定义函数  
 2 {  
 3     if(y == 0)  
 4     {  
 5         throw y;  //除数为0,抛出异常  
 6     }  
 7     return x/y;  //否则返回两个数的商  
 8 }  
 9  
10 void main()  
11 {  
12     int res;  
13     try  //定义异常  
14     {  
15         res = func(2, 3);  
16         cout << "The result of x/y is : " << res << endl;  
17         res = func(6, 0);  //出现异常,函数内部会抛出异常     
18     }  
19     catch(int)  //捕获并处理异常  
20     {  
21          cerr << "error of dividing zero.\n";  
22          exit(1);  //异常退出程序  
23     }  
24 }

   使用筛选器处理异常。当发生异常时,比如内存访问违规时,CPU硬件会发现此问题,并产生一个异常(你可以把它理解为中断),然后CPU会把代码流程切换到异常处理服务例程。操作系统异常处理服务例程会查看当前进程是否处于调试状态,如果是则通知调试器发生了异常,如果不是则操作系统会查看当前线程是否安装了异常帧链(FS[0]),如果安装了SEH(try.... catch....),则调用SEH,并根据返回结果决定是否全局展开或局部展开。如果异常链中所有的SEH都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常(二次异常)。如果还没人处理,则调用操作系统的默认异常处理代码UnhandledExceptionHandler,不过操作系统允许你Hook这个函数,就是通过SetUnhandledExceptionFilter函数来设置。大部分异常通过此种方法都能捕获,不过栈溢出、覆盖的有可能捕获不到。

1 LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
2   LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
3 );

  参数是一个函数指针,定义如下:

typedef LONG (*PTOP_LEVEL_EXCEPTION_FILTER)(
STRUCT _EXCEPTION_POINTERS *ExceptionInfo 
);
typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;

  返回值有三种:

EXCEPTION_EXECUTE_HANDLER:表明异常处理完毕,程序可以退出。

EXCEPTION_CONTINUE_EXECUTION:忽略此异常,从异常点继续运行。如果此时再发生异常,还会调用异常处理函数。

EXCEPTION_CONTINUE_SEARCH:异常没被识别,交由上一级处理函数处理。

 MiniDump文件

有了强大的SetUnhandledExceptionFilter 这个Windows API,就使得记录异常快照变得可行。只需要在通过这个API注册的异常处理函数中记录下当前上下文信息,就可以通过这些信息来分析并弥补程序上的缺陷。通常,在异常处理函数中生成MiniDump文件来供我们对异常进行分析和处理。

Microsoft提供了一个API函数,用以生成MiniDump:MiniDumpWriteDump,该API需要加载dbghelp.dll模块,并在dbghelp.h头文件里声明。

1 BOOL WINAPI MiniDumpWriteDump(
2   _In_  HANDLE hProcess,
3   _In_  DWORD ProcessId,
4   _In_  HANDLE hFile,
5   _In_  MINIDUMP_TYPE DumpType,
6   _In_  PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
7   _In_  PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
8   _In_  PMINIDUMP_CALLBACK_INFORMATION CallbackParam
9 );

有了MiniDump文件以及Release编译时生成的pdb文件,我们就可以通过VS或WinDbg等工具来分析和调试异常代码了。

转载于:https://www.cnblogs.com/biyaxiong/archive/2013/03/28/2986777.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值