异常和异常处理(windows平台)
【翻譯】異常和異常處理(windows平臺)
翻譯的不好,莫怪。
原文地址:?http://crashrpt.sourceforge.net/docs/html/exception_handling.html#getting_exception_context
About Exceptions and Exception Handling
?
About Exception
?
當(dāng)程序遇到一個異常或一個嚴(yán)重的錯誤時,通常意味著它不能繼續(xù)正常運行并且需要停止執(zhí)行。
例如,當(dāng)遇到下列情況時,程序會出現(xiàn)異常:
??程序訪問一個不可用的內(nèi)存地址(例如,NULL指針);
l?無限遞歸導(dǎo)致的棧溢出;
l?向一個較小的緩沖區(qū)寫入較大塊的數(shù)據(jù);
l?類的純虛函數(shù)被調(diào)用;
l?申請內(nèi)存失敗(內(nèi)存空間不足);
l?一個非法的參數(shù)被傳遞給C++函數(shù);
l?C運行時庫檢測到一個錯誤并且需要程序終止執(zhí)行。
?
有兩種不同性質(zhì)的異常:結(jié)構(gòu)化異常(Structured Exception Handling, SEH)和類型化的C++異常。
SEH是為C語言設(shè)計的,但是他們也能夠被用于C++。SEH異常由__try{}__except(){}結(jié)構(gòu)來處理。SEH是VC++編譯器特有的,因此如果你想要編寫可移植的代碼,就不應(yīng)當(dāng)使用SEH。
C++中類型化的異常是由try{}catch(){}結(jié)構(gòu)處理的。例如(例子來自這里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?}
?
結(jié)構(gòu)化異常處理
當(dāng)發(fā)生一個SEH異常時,你通常會看到一個意圖向微軟發(fā)送錯誤報告的彈出窗口。
你可以使用RaiseException()函數(shù)自己產(chǎn)生一個SEH異常。
你可以在你的代碼中使用__try{}__except(Expression){}結(jié)構(gòu)來捕獲SEH異常。程序中的main()函數(shù)被這樣的結(jié)構(gòu)保護(hù),因此默認(rèn)地,所有未被處理的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異常都有一個與其相關(guān)聯(lián)的異常碼(exception code)。你可以使用GetExceptionCode()函數(shù)來獲取異常碼。你可以通過GetExceptionInformation()來獲取異常信息。為了使用這些函數(shù),你通常會像下面示例中一樣定制自己的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(){}結(jié)構(gòu)是面向C語言的,但是,你可以將一個SEH異常重定向到C++異常,并且你可以像處理C++異常一樣處理它。我們可以使用C++運行時庫中的_set_se_translator()函數(shù)來實現(xiàn)。
看一個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){}結(jié)構(gòu)進(jìn)行保護(hù),而這些代碼可能會產(chǎn)生異常,但是這個異常卻沒有被你的程序所處理。不用擔(dān)心,這個未被處理的SEH異常能夠被unhandled Exception filter所捕獲,我們可以使用SetUnhandledExceptionFilter()函數(shù)設(shè)置top-levelunhandled exception filter。
異常信息(異常發(fā)生時的CPU狀態(tài))通過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對進(jìn)程中的每個線程都起作用,因此在你的main()函數(shù)開頭調(diào)用一次就夠了。
top-level SEH exception handler在發(fā)生異常的線程的上下文中被調(diào)用。這會影響異常處理函數(shù)從異常中恢復(fù)的能力。
如果你的異常處理函數(shù)位于一個DLL中,那么在使用SetUnhandledExceptionFilter()函數(shù)時就要小心了。如果你的函數(shù)在程序崩潰時還未被加載,這種行為是不可預(yù)測的。
向量化異常處理(Vectored Exception Handling)
向量化異常處理(VEH)是結(jié)構(gòu)化異常處理的一個擴(kuò)展,它在Windows XP中被引入。
你可以使用AddVectoredExceptionHandler()函數(shù)添加一個向量化異常處理器,VEH的缺點是它只能用在WinXP及其以后的版本,因此需要在運行時檢查AddVectoredExceptionHandler()函數(shù)是否存在。
要移除先前安裝的異常處理器,可以使用RemoveVectoredExceptionHandler()函數(shù)。
VEH允許查看或處理應(yīng)用程序中所有的異常。為了保持后向兼容,當(dāng)程序中的某些部分發(fā)生SEH異常時,系統(tǒng)依次調(diào)用已安裝的VEH處理器,直到它找到有用的SEH處理器。
VEH的一個優(yōu)點是能夠鏈接異常處理器(chain exception handlers),因此如果有人在你之前安裝了向量化異常處理器,你仍然能截獲這些異常。
當(dāng)你需要像調(diào)試器一樣監(jiān)事所有的異常時,使用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處理器就會被調(diào)用,并且你不用決定是否要處理這個異常。
CRT?錯誤處理
除了SEH異常和C++類型化異常,C運行庫(C runtime libraries, CRT)也提供它自己的錯誤處理機(jī)制,在你的程序中也應(yīng)該考慮使用它。
當(dāng)CRT遇到一個未被處理的C++類型化異常時,它會調(diào)用terminate()函數(shù)。如果你想攔截這個調(diào)用并提供合適的行為,你應(yīng)該使用set_terminate()函數(shù)設(shè)置錯誤處理器(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:在多線程環(huán)境中,每個線程維護(hù)各自的unexpected和terminate函數(shù)。每個新線程需要安裝自己的unexpected和terminate函數(shù)。因此,每個線程負(fù)責(zé)自己的unexpected和terminate處理器。
?
使用_set_purecall_handler()函數(shù)來處理純虛函數(shù)調(diào)用。這個函數(shù)可以用于VC++2003及其后續(xù)版本。這個函數(shù)可以用于一個進(jìn)程中的所有線程。例如(來源于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()函數(shù)處理內(nèi)存分配失敗。這個函數(shù)能夠用于VC++2003及其后續(xù)版本。這個函數(shù)可以用于一個進(jìn)程中的所有線程。也可以考慮使用_set_new_mode()函數(shù)來為malloc()函數(shù)定義錯誤時的行為。例如(來自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()函數(shù)來處理緩沖區(qū)溢出錯誤。這個函數(shù)已經(jīng)被廢棄,并且從之后VC++版本的CRT中移除。
當(dāng)系統(tǒng)函數(shù)調(diào)用檢測到非法的參數(shù)時,會使用_set_invalid_parameter_handler()函數(shù)來處理這種情況。這個函數(shù)能夠用于VC++2005及其以后的版本。這個函數(shù)可用于進(jìn)程中的所有線程。
例子(來源于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++提供了被稱為信號的中斷機(jī)制。你可以使用signal()函數(shù)處理信號。
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相兼容。但是,如果你在主線程中設(shè)置SIGSEGV signal handler,CRT將會調(diào)用它,而不是調(diào)用SetUnhandledExceptionFilter()函數(shù)設(shè)置的SHE exception handler,全局變量_pxcptinfoptrs中包含了指向異常信息的指針。
_pxcptinfoptrs也會被用于SIGFPE handler中,而在所有其他的signal handlers中,它將會被設(shè)為NULL。
當(dāng)一個floating point?錯誤發(fā)生時,例如除零錯,CRT將調(diào)用SIGFPE signal handler。然而,默認(rèn)情況下,不會產(chǎn)生float point?異常,取而代之的是,將會產(chǎn)生一個NaN或無窮大的數(shù)作為這種浮點數(shù)運算的結(jié)果。可以使用_controlfp_s()函數(shù)使得編譯器能夠產(chǎn)生floating point異常。
使用raise()函數(shù),你可以人工地產(chǎn)生所有的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中沒有詳細(xì)地說明,但是你應(yīng)該為你程序中的每個線程都安裝SIGFPE, SIGILL和SIGSEGV signal hanlders。SIGABRT, SIGINT和SIGTERM signal hanlders對程序中的每個線程都起作用,因此你只需要在你的main函數(shù)中安裝他們一次就夠了。
獲取異常信息?Retrieving Exception Information
譯者注:這一小節(jié)不太懂,以后有時間再翻譯
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++編譯器中有一些編譯選項和異常處理有關(guān)。
在Project Properties->Configuration Properties->C/C++ ->Code Generation中可以找到這些選項。
異常處理模型Exception Handling Model
你可以為VC++編譯器選擇異常處理模型。選項/EHs(或者EHsc)用來指定同步異常處理模型,/EHa用來指定異步異常處理模型。可以查看下面參考小節(jié)的"/EH(Exception Handling Model)"以獲取更多的信息。
Floating Point Exceptions
你可以使用/fp:except編譯選項打開float point exceptions。
緩沖區(qū)安全檢查Buffer Security Checks
你可以使用/GS(Buffer Security Check)選項來強(qiáng)制編譯器插入代碼以檢查緩沖區(qū)溢出。緩沖區(qū)溢出指的是一大塊數(shù)據(jù)被寫入一塊較小的緩沖區(qū)中。當(dāng)檢測到緩沖區(qū)溢出,CRT calls internal security handler that invokes Watson directly。
Note:
在VC++(CRT7.1)中,緩沖區(qū)溢出被檢測到時,CRT會調(diào)用由_set_security_error_handler函數(shù)設(shè)置的處理器。然而,在之后的VC版本中這個函數(shù)被廢棄。
從CRT8.0開始,你在你的代碼中不能截獲安全錯誤。當(dāng)緩沖區(qū)溢出被檢測到時,CRT會直接請求Watson,而不是調(diào)用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
你的應(yīng)用程序中的每個module(EXE, DLL)都需要鏈接CRT。你可以將CRT鏈接為多線程靜態(tài)庫(multi-threaded static library)或者多線程動態(tài)鏈接庫(multi-threaded dynamic link library)。如果你設(shè)置了CRT error handlers,例如你設(shè)置了terminate handler, unexcepted handler, pure call handler, invalid parameter handler, new operator error handler or a signal handler,那么他們將只在你鏈接的CRT上運行,并且不會捕獲其他CRT模塊中的異常(如果存在的話),因為每個CRT模塊都有它自己的內(nèi)部狀態(tài)。
多個工程中的module可以共享CRT DLL。這將使得被鏈接的CRT代碼達(dá)到最小化,并且CRT DLL中的所有異常都會被立刻處理。這也是推薦使用multi-threaded CRT DLL作為CRT鏈接方式的原因。
總結(jié)
以上是生活随笔為你收集整理的异常和异常处理(windows平台)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 正则表达式,解决要么有要有没有,但必须开
- 下一篇: OpenGL 坐标系统(Perspect