【翻译】异常和异常处理(windows平台)

翻译的不好,莫怪。

原文地址: http://crashrpt.sourceforge.net/docs/html/exception_handling.html#getting_exception_context

About Exceptions and Exception Handling

About Exception

当程序遇到一个异常或一个严重的错误时,通常意味着它不能继续正常运行并且需要停止执行。

例如,当遇到下列情况时,程序会出现异常:

程序访问一个不可用的内存地址(例如,NULL指针);

l 无限递归导致的栈溢出;

l 向一个较小的缓冲区写入较大块的数据;

l 类的纯虚函数被调用;

l 申请内存失败(内存空间不足);

l 一个非法的参数被传递给C++函数;

l C运行时库检测到一个错误并且需要程序终止执行。

有两种不同性质的异常:结构化异常(Structured Exception Handling, SEH)和类型化的C++异常。

SEH是为C语言设计的,但是他们也能够被用于C++。SEH异常由__try{}__except(){}结构来处理。SEH是VC++编译器特有的,因此如果你想要编写可移植的代码,就不应当使用SEH。

C++中类型化的异常是由try{}catch(){}结构处理的。例如(例子来自这里http://www.cplusplus.com/doc/tutorial/exceptions/):

1 #include <iostream>
 2 using namespace std;
 3  
 4 int main(){
 5     try{
 6        throw 20;
 7     }
 8     catch (int e){
 9        cout << "An exception occrred. Exception Nr. " << e << endl;
10     }
11  
12     return 0;
13 }

结构化异常处理

当发生一个SEH异常时,你通常会看到一个意图向微软发送错误报告的弹出窗口。

你可以使用RaiseException()函数自己产生一个SEH异常。

你可以在你的代码中使用__try{}__except(Expression){}结构来捕获SEH异常。程序中的main()函数被这样的结构保护,因此默认地,所有未被处理的SEH异常都会被捕获。

例如:

1 #include <Windows.h>
 2  
 3 int main(){
 4     int *p = NULL;  // pointer to NULL
 5     __try{
 6        // Guarded code
 7        *p = 13;    // causes an access violation exception;
 8     }
 9     __except(EXCEPTION_EXECUTE_HANDLER){  // Here is exception filter expression
10        // Here is exception handler
11        // Terminate program
12        ExitProcess(1);
13     }
14  
15     return 0;
16 }

每一个SEH异常都有一个与其相关联的异常码(exception code)。你可以使用GetExceptionCode()函数来获取异常码。你可以通过GetExceptionInformation()来获取异常信息。为了使用这些函数,你通常会像下面示例中一样定制自己的exception filter。

下面的例子说明了如何使用SEH exception filter。

1 int seh_filter(unsigned int code, struct _EXCEPTION_POINTERS *ep){
 2     // Generate error report
 3     // Execute exception handler
 4     return EXCEPTION_EXECUTE_HANDLER;
 5 }
 6  
 7 int main(){
 8     __try{
 9        // .. some buggy code here
10     }
11     __except(seh_filter(GetExceptionCode(), GetExceptionInformation())){
12        // Terminate program
13        ExitProcess(1);
14     }
15  
16     return 0;
17 }

__try{}__exception(){}结构是面向C语言的,但是,你可以将一个SEH异常重定向到C++异常,并且你可以像处理C++异常一样处理它。我们可以使用C++运行时库中的_set_se_translator()函数来实现。

看一个MSDN中的例子(译者注:运行此例子需打开/EHa编译选项):

1 #include <cstdio>
 2 #include <windows.h>
 3 #include <eh.h>
 4  
 5 void SEFunc();
 6 void trans_func(unsigned int, EXCEPTION_POINTERS *);
 7  
 8 class SE_Exception{
 9 private:
10     unsigned int nSE;
11 public:
12     SE_Exception(){}
13     SE_Exception(unsigned int n) : nSE(n){}
14     ~SE_Exception() {}
15     unsigned int getSeNumber(){ return nSE; }
16 };
17  
18 int main(void){
19     try{
20        _set_se_translator(trans_func);
21        SEFunc();
22     }
23     catch(SE_Exception e){
24        printf("Caught a __try exception with SE_Exception.\n");
25     }
26 }
27  
28 void SEFunc(){
29     __try{
30        int x, y=0;
31        x = 5 / y;
32     }
33     __finally{
34        printf("In finally\n");
35     }
36 }
37  
38 void trans_func(unsigned int u, EXCEPTION_POINTERS* pExp){
39     printf("In trans_func.\n");
40     throw SE_Exception();
41 }

你可能忘记对一些潜在的错误代码使用__try{}__catch(Expression){}结构进行保护,而这些代码可能会产生异常,但是这个异常却没有被你的程序所处理。不用担心,这个未被处理的SEH异常能够被unhandled Exception filter所捕获,我们可以使用SetUnhandledExceptionFilter()函数设置top-levelunhandled exception filter。

异常信息(异常发生时的CPU状态)通过EXCEPTION_POINTERS被传递给exception handler。

例如:

1 // crt_settrans.cpp
 2 // compile with: /EHa
 3 LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionPtrs){
 4     // Do something, for example generate error report
 5     //..
 6     // Execute default exception handler next
 7     return EXCEPTION_EXECUTE_HANDLER;
 8 }
 9  
10 void main(){
11     SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
12     // .. some unsafe code here
13 }

top-level SEH exception handler对进程中的每个线程都起作用,因此在你的main()函数开头调用一次就够了。

top-level SEH exception handler在发生异常的线程的上下文中被调用。这会影响异常处理函数从异常中恢复的能力。

如果你的异常处理函数位于一个DLL中,那么在使用SetUnhandledExceptionFilter()函数时就要小心了。如果你的函数在程序崩溃时还未被加载,这种行为是不可预测的。

向量化异常处理(Vectored Exception Handling)

向量化异常处理(VEH)是结构化异常处理的一个扩展,它在Windows XP中被引入。

你可以使用AddVectoredExceptionHandler()函数添加一个向量化异常处理器,VEH的缺点是它只能用在WinXP及其以后的版本,因此需要在运行时检查AddVectoredExceptionHandler()函数是否存在。

要移除先前安装的异常处理器,可以使用RemoveVectoredExceptionHandler()函数。

VEH允许查看或处理应用程序中所有的异常。为了保持后向兼容,当程序中的某些部分发生SEH异常时,系统依次调用已安装的VEH处理器,直到它找到有用的SEH处理器。

VEH的一个优点是能够链接异常处理器(chain exception handlers),因此如果有人在你之前安装了向量化异常处理器,你仍然能截获这些异常。

当你需要像调试器一样监事所有的异常时,使用VEH是很合适的。问题是你需要决定哪个异常需要处理,哪个异常需要跳过。 In program's code, some exceptions may be intentionally guarded by __try{}__except(){} construction, and handling such exceptions in VEH and not passing it to frame-based SEH handler, you may introduce bugs into application logics.

VEH目前没有被CrashRpt所使用。SetUnhandledExceptionFilter()更加适用,因为它是top-level SEH处理器。如果没有人处理异常,top-level SEH处理器就会被调用,并且你不用决定是否要处理这个异常。

CRT 错误处理

除了SEH异常和C++类型化异常,C运行库(C runtime libraries, CRT)也提供它自己的错误处理机制,在你的程序中也应该考虑使用它。

当CRT遇到一个未被处理的C++类型化异常时,它会调用terminate()函数。如果你想拦截这个调用并提供合适的行为,你应该使用set_terminate()函数设置错误处理器(error hanlder)。例如:

1 #include <iostream>
 2 void my_terminate_handler()
 3 {
 4     // Abnormal program termination (terminate() function was called)
 5     // Do something here
 6     // Finally, terminate program
 7     std::cout << "terminate.\n";
 8     exit(1);
 9 }
10  
11 int main()
12 {
13     set_terminate(my_terminate_handler);
14  
15     terminate();
16  
17     return 0;
18 }

Note:在多线程环境中,每个线程维护各自的unexpected和terminate函数。每个新线程需要安装自己的unexpected和terminate函数。因此,每个线程负责自己的unexpected和terminate处理器。

使用_set_purecall_handler()函数来处理纯虚函数调用。这个函数可以用于VC++2003及其后续版本。这个函数可以用于一个进程中的所有线程。例如(来源于MSDN):

1 // compile with: /EHa
 2 // _set_purecall_handler.cpp
 3 // compile with: /W1
 4 #include <tchar.h>
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7  
 8 class CDerived;
 9 class CBase{
10 public:
11     CBase(CDerived *derived): m_pDerived(derived) {};
12     ~CBase();
13     virtual void function(void) = 0;
14  
15     CDerived * m_pDerived;
16 };
17  
18 class CDerived : public CBase{
19 public:
20     CDerived() : CBase(this) {};   // C4355
21     virtual void function(void) {};
22 };
23  
24 CBase::~CBase(){
25     m_pDerived -> function();
26 }
27  
28 void myPurecallHandler(void){
29     printf("In _purecall_handler.");
30     exit(0);
31 }
32  
33 int _tmain(int argc, _TCHAR* argv[]){
34     //_set_purecall_handler(myPurecallHandler);
35     CDerived myDerived;
36 }

使用_set_new_handler()函数处理内存分配失败。这个函数能够用于VC++2003及其后续版本。这个函数可以用于一个进程中的所有线程。也可以考虑使用_set_new_mode()函数来为malloc()函数定义错误时的行为。例如(来自MSDN):

1 // crt_settrans.cpp
2 #include <new.h>
3 int handle_program_memory_depletion( size_t ){
4     // Your code
5 }
6 int main( void ){
7     _set_new_handler( handle_program_memory_depletion );
8     int *pi = new int[BIG_NUMBER];
9 }

在VC++2003中,你能够使用_set_security_error_handler()函数来处理缓冲区溢出错误。这个函数已经被废弃,并且从之后VC++版本的CRT中移除。

当系统函数调用检测到非法的参数时,会使用_set_invalid_parameter_handler()函数来处理这种情况。这个函数能够用于VC++2005及其以后的版本。这个函数可用于进程中的所有线程。

例子(来源于MSDN):

1 // compile with: /Zi /MTd
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <crtdbg.h>  // For _CrtSetReportMode
 5  
 6 void myInvalidParameterHandler(const wchar_t* expression,
 7                             const wchar_t* function,
 8                             const wchar_t* file,
 9                             unsigned int line,
10                             uintptr_t pReserved){
11     wprintf(L"Invalid parameter detected in function %s."
12        L" File: %s Line: %d\n", function, file, line);
13     wprintf(L"Expression: %s\n", expression);
14 }
15  
16  
17 int main( ){
18     char* formatString;
19  
20     _invalid_parameter_handler oldHandler, newHandler;
21     newHandler = myInvalidParameterHandler;
22     oldHandler = _set_invalid_parameter_handler(newHandler);
23  
24     // Disable the message box for assertions.
25     _CrtSetReportMode(_CRT_ASSERT, 0);
26  
27     // Call printf_s with invalid parameters.
28     formatString = NULL;
29     printf(formatString);
30     return 0;
31 }

C++信号处理C++ Singal Handling

C++提供了被称为信号的中断机制。你可以使用signal()函数处理信号。

Visual C++提供了6中类型的信号:

l SIGABRT Abnormal termination

l SIGFPE Floating-point error

l SIGILL Illegal instruction

l SIGINT CTRL+C signal

l SIGSEGV Illegal storage access

l SIGTERM

MSDN中说SIGILL, SIGSEGV,和SIGTERM are not generated under Windows NT并且与ANSI相兼容。但是,如果你在主线程中设置SIGSEGV signal handler,CRT将会调用它,而不是调用SetUnhandledExceptionFilter()函数设置的SHE exception handler,全局变量_pxcptinfoptrs中包含了指向异常信息的指针。

_pxcptinfoptrs也会被用于SIGFPE handler中,而在所有其他的signal handlers中,它将会被设为NULL。

当一个floating point 错误发生时,例如除零错,CRT将调用SIGFPE signal handler。然而,默认情况下,不会产生float point 异常,取而代之的是,将会产生一个NaN或无穷大的数作为这种浮点数运算的结果。可以使用_controlfp_s()函数使得编译器能够产生floating point异常。

使用raise()函数,你可以人工地产生所有的6中信号。例如:

1 #include <cstdlib>
 2 #include <csignal>
 3 #include <iostream>
 4  
 5 void sigabrt_handler(int){
 6     // Caught SIGABRT C++ signal
 7     // Terminate program
 8          std::cout << "handled.\n";
 9          exit(1);
10 }
11  
12 int main(){
13   signal(SIGABRT, sigabrt_handler);
14   // Cause abort
15   abort();   
16 }

Note:

虽然MSDN中没有详细地说明,但是你应该为你程序中的每个线程都安装SIGFPE, SIGILL和SIGSEGV signal hanlders。SIGABRT, SIGINT和SIGTERM signal hanlders对程序中的每个线程都起作用,因此你只需要在你的main函数中安装他们一次就够了。

获取异常信息 Retrieving Exception Information

译者注:这一小节不太懂,以后有时间再翻译

When an exception occurs you typically want to get the CPU state to determine the place in your code that caused the problem. You use the information to debug the problem. The way you retrieve the exception information differs depending on the exception handler you use.

In the SEH exception handler set with the SetUnhandledExceptionFilter() function, the exception information is retrieved from EXCEPTION_POINTERS structure passed as function parameter.

In __try{}__catch(Expression){} construction you retrieve exception information using GetExceptionInformation() intrinsic function and pass it to the SEH exception filter function as parameter.

In the SIGFPE and SIGSEGV signal handlers you can retrieve the exception information from the _pxcptinfoptrs global CRT variable that is declared in <signal.h>. This variable is not documented well in MSDN.

In other signal handlers and in CRT error handlers you have no ability to easily extract the exception information. I found a workaround used in CRT code (see CRT 8.0 source files, invarg.c, line 104).

The following code shows how to get current CPU state used as exception information.

1 #if _MSC_VER>=1300

2 #include <rtcapi.h>
 3 #endif
 4 
 5 #ifndef _AddressOfReturnAddress
 6 
 7 // Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
 8 #ifdef __cplusplus
 9 #define EXTERNC extern "C"
10 #else
11 #define EXTERNC
12 #endif
13 
14 // _ReturnAddress and _AddressOfReturnAddress should be prototyped before use 
15 EXTERNC void * _AddressOfReturnAddress(void);
16 EXTERNC void * _ReturnAddress(void);
17 
18 #endif 
19 
20 // The following function retrieves exception info
21 
22 void GetExceptionPointers(DWORD dwExceptionCode, 
23                           EXCEPTION_POINTERS** ppExceptionPointers)
24 {
25     // The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
26 
27     EXCEPTION_RECORD ExceptionRecord;
28     CONTEXT ContextRecord;
29     memset(&ContextRecord, 0, sizeof(CONTEXT));
30 
31 #ifdef _X86_
32 
33     __asm {
34         mov dword ptr [ContextRecord.Eax], eax
35             mov dword ptr [ContextRecord.Ecx], ecx
36             mov dword ptr [ContextRecord.Edx], edx
37             mov dword ptr [ContextRecord.Ebx], ebx
38             mov dword ptr [ContextRecord.Esi], esi
39             mov dword ptr [ContextRecord.Edi], edi
40             mov word ptr [ContextRecord.SegSs], ss
41             mov word ptr [ContextRecord.SegCs], cs
42             mov word ptr [ContextRecord.SegDs], ds
43             mov word ptr [ContextRecord.SegEs], es
44             mov word ptr [ContextRecord.SegFs], fs
45             mov word ptr [ContextRecord.SegGs], gs
46             pushfd
47             pop [ContextRecord.EFlags]
48     }
49 
50     ContextRecord.ContextFlags = CONTEXT_CONTROL;
51 #pragma warning(push)
52 #pragma warning(disable:4311)
53     ContextRecord.Eip = (ULONG)_ReturnAddress();
54     ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
55 #pragma warning(pop)
56     ContextRecord.Ebp = *((ULONG *)_AddressOfReturnAddress()-1);
57 
58 
59 #elif defined (_IA64_) || defined (_AMD64_)
60 
61     /* Need to fill up the Context in IA64 and AMD64. */
62     RtlCaptureContext(&ContextRecord);
63 
64 #else  /* defined (_IA64_) || defined (_AMD64_) */
65 
66     ZeroMemory(&ContextRecord, sizeof(ContextRecord));
67 
68 #endif  /* defined (_IA64_) || defined (_AMD64_) */
69 
70     ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
71 
72     ExceptionRecord.ExceptionCode = dwExceptionCode;
73     ExceptionRecord.ExceptionAddress = _ReturnAddress();
74 
75 
76     EXCEPTION_RECORD* pExceptionRecord = new EXCEPTION_RECORD;
77     memcpy(pExceptionRecord, &ExceptionRecord, sizeof(EXCEPTION_RECORD));
78     CONTEXT* pContextRecord = new CONTEXT;
79     memcpy(pContextRecord, &ContextRecord, sizeof(CONTEXT));
80 
81     *ppExceptionPointers = new EXCEPTION_POINTERS;
82     (*ppExceptionPointers)->ExceptionRecord = pExceptionRecord;
83     (*ppExceptionPointers)->ContextRecord = pContextRecord;  
84 }

Visual C++ Complier Flags

Visual C++编译器中有一些编译选项和异常处理有关。

在Project Properties->Configuration Properties->C/C++ ->Code Generation中可以找到这些选项。

异常处理模型Exception Handling Model

你可以为VC++编译器选择异常处理模型。选项/EHs(或者EHsc)用来指定同步异常处理模型,/EHa用来指定异步异常处理模型。可以查看下面参考小节的"/EH(Exception Handling Model)"以获取更多的信息。

Floating Point Exceptions

你可以使用/fp:except编译选项打开float point exceptions。

缓冲区安全检查Buffer Security Checks

你可以使用/GS(Buffer Security Check)选项来强制编译器插入代码以检查缓冲区溢出。缓冲区溢出指的是一大块数据被写入一块较小的缓冲区中。当检测到缓冲区溢出,CRT calls internal security handler that invokes Watson directly。

Note:

在VC++(CRT7.1)中,缓冲区溢出被检测到时,CRT会调用由_set_security_error_handler函数设置的处理器。然而,在之后的VC版本中这个函数被废弃。

从CRT8.0开始,你在你的代码中不能截获安全错误。当缓冲区溢出被检测到时,CRT会直接请求Watson,而不是调用unhandled exception filter。这样做是由于安全原因并且微软不打算改变这种行为。

更多的信息请参考如下链接

https://connect.microsoft.com/VisualStudio/feedback/details/101337/a-proposal-to-make-dr-watson-invocation-configurable

http://blog.kalmbachnet.de/?postid=75

异常处理和CRT链接Exception Handling and CRT Linkage

你的应用程序中的每个module(EXE, DLL)都需要链接CRT。你可以将CRT链接为多线程静态库(multi-threaded static library)或者多线程动态链接库(multi-threaded dynamic link library)。如果你设置了CRT error handlers,例如你设置了terminate handler, unexcepted handler, pure call handler, invalid parameter handler, new operator error handler or a signal handler,那么他们将只在你链接的CRT上运行,并且不会捕获其他CRT模块中的异常(如果存在的话),因为每个CRT模块都有它自己的内部状态。

多个工程中的module可以共享CRT DLL。这将使得被链接的CRT代码达到最小化,并且CRT DLL中的所有异常都会被立刻处理。这也是推荐使用multi-threaded CRT DLL作为CRT链接方式的原因。

异常和异常处理(windows平台)相关推荐

  1. 【厚积薄发系列】C++项目总结14—Windows平台下异常捕获不到问题分析

    问题背景: Windows平台常用的异常捕获函数有时候不起作用,特别是XP系统上,有些runtime error捕获不到,但是其他系统如win7.win8等又能正常捕获,导致产品发出去后遇到这类异常, ...

  2. 认识二进制安全与漏洞攻防技术 (Windows平台)

    二进制漏洞是指程序存在安全缺陷,导致攻击者恶意构造的数据(如Shellcode)进入程序相关处理代码时,改变程序原定的执行流程,从而实现破坏或获取超出原有的权限. 0Day漏洞 在计算机领域中,0da ...

  3. 关于错误提示:此实现不是 Windows 平台 FIPS 验证的加密算法的一部分的解决方案...

    vs上的一个项目,弄了一个aes加密算法.提示说:调用的目标异常.此实现不是 Windows 平台 FIPS 验证的加密算法的一部分.弄了好久一直不行.解决不了.最后找了半天资料终于解决了.估计下来. ...

  4. Windows平台下使用Active Directory对Subversion进行权限控制(非完美解决方案)

    Windows平台下使用Active Directory对Subversion进行权限控制(非完美解决方案) Windows平台下使用Active Directory对Subversion进行权限控制 ...

  5. 解决“此实现不是 Windows 平台 FIPS 验证的加密算法的一部分”

    异常 System.InvalidOperationException: 此实现不是 Windows 平台 FIPS 验证的加密算法的一部分.在 System.Security.Cryptograph ...

  6. 关于windows Server 服务器MD5报错,System.InvalidOperationException: 此实现不是 Windows 平台 FIPS 验证的加密算法的一部分。

    在运行.net项目时抛出以下错误: System.Reflection.TargetInvocationException: 调用的目标发生了异常. System.InvalidOperationEx ...

  7. Windows平台下的截图工具——Part One

    Windows平台下的截图工具--Part One 可能会引起歧义,标题中的PartOne单只第一部分. 按照原本打算,是只介绍一款截图软件,奈何截图工具的种类异常繁多,而平时又频繁使用到截图: 而且 ...

  8. Windows平台的容器

    虚拟化技术概述 所谓虚拟化,就是在一台计算机上同时运行多个操作系统,即在同一个物理平台上创造出多台不同机器的假象.每个运行在主机上的操作系统称为"虚拟机".不同的虚拟机之间是互相隔 ...

  9. Windows平台安装MacOS虚拟机 - VMware(结尾附视频)

    写在前面:博主是一只经过实战开发历练后投身培训事业的"小山猪",昵称取自动画片<狮子王>中的"彭彭",总是以乐观.积极的心态对待周边的事物.本人的技 ...

最新文章

  1. 【Paper】2018_多机器人领航-跟随型编队控制
  2. python cnn_使用python中pytorch库实现cnn对mnist的识别
  3. php 把数组赋给另一个数组,php将一个数组附加到另一个数组(不是array_push或+)
  4. 一小时内在本地搭建 SAP Commerce Cloud(电商云)的前后台运行环境
  5. c语言osversioninfoex,xi52qian
  6. 第三节 DOM-Element对象
  7. xshell与xftp如何实现相互连接
  8. python百度ai文字识别、代码分析_python利用百度AI实现文字识别功能
  9. java之IO整理(下)
  10. 2018年下半年软件设计师下午真题及答案解析
  11. 源地址转换、目的地址装换
  12. 图10——判断顶点u和顶点v是否存在简单路径
  13. 搞日租房的Airbnb,如何用机器学习对接上百万的房东和租客?
  14. Excel基础-数据方向
  15. 福利:工作经常用到的Mac软件整理(全)
  16. LOJ P2448 :无尽的生命 树状数组 + 离散化
  17. java编写九宫格拼图游戏_九宫格拼图游戏
  18. 1062 Talent and Virtue (25 分)(坑点分析)
  19. MATLAB用hamming窗和blackman窗来设FIR滤波器
  20. Multithreading C++ Out of Core Sotring for Massive Data|多线程C++的大规模数据外部排序

热门文章

  1. 网络运维工作是什么的?
  2. mysql mysqlhotcopy_MySQL备份工具之mysqlhotcopy
  3. ART:分享收集的关于博物馆的视频纪录片,建议喜欢艺术人们的收藏
  4. 成功解决FileNotFoundError: [Errno 2] No such file or directory: 'F:\\Program Files\\Python\\Python36\\li
  5. DL之Attention:Attention注意力机制的简介、应用领域之详细攻略
  6. 在Core WebApi中使用Swagger
  7. 程序员找工作那些事(一)幸存者偏差
  8. java基础----Runtime类的使用(一)
  9. Xcode自带工具symbolicatecrash解析Crash文件
  10. Interval query