Windows系统程序设计之结构化异常处理
生活随笔
收集整理的這篇文章主要介紹了
Windows系统程序设计之结构化异常处理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
標 題:
【原創】Windows系統程序設計之結構化異常處理
作 者: 北極星2003
時 間: 2006-09-20,20:21:28
鏈 接: http://bbs.pediy.com/showthread.php?t=32222
目錄:
一、??SEH的概念、特性
二、??SEH的基本使用方法
1、??結束異常程序
(1)try塊的自然退出與非自然退出
(2)finally塊的清理功能及對程序結構的影響
(3)關鍵字__leave
2、??異常處理程序
(1)異常處理的基本流程
(2)異常過濾器
(3)全局展開
(4)暫停全局展開
3、??未處理異常(頂層異常處理)
三、??SEH相關數據結構的介紹
1、??EXCEPTION_POINTER結構
2、??EXCEPTION_RECORD結構
3、??EXCEPTOIN_REGISTRATION結構
4、異常處理鏈結構圖
四、??VC++編譯器級SEH的具體實現
1、??VC擴展異常幀
2、??VC異常幀堆棧布局
3、??兩個實例程序:顯示異常幀信息
4、??實例分析及特性介紹
5、??VC中的定層異常處理
6、??VC搜索異常處理程序流程
五、參考資料
--------------------------------------------------------------------------
?
前言:
????對于這片文章應該是我寫的最認真的一篇了,斷斷續續地寫了將近一個月,從最初的復習性的回顧,收集更多資料,反復地整理思路,查閱Windows源碼中的相關的源碼,設計文章的整體框架,到每一個部分的詳細設計,包括流程圖的設計。每一個過程都進展的并不順利,由于時間的關系每星期只能有2~3次的大段時間的在電腦面前,所以思路一直在被打斷,整理文章寫的并不流暢。有些思路或許已經不知道遺忘在哪個角落,但我也盡量把相關方面的知識點都講到。本文的重點在于SEH原理方面的介紹,從單純的使用角度來看,比較簡單,但如果適當的使用SEH機制,這在很大程度上與使用者對SEH的理解程度有很大關系,因而對于具體的實現,文中只介紹重點及需要注意的地方,具體使用方法可參見參考文獻[1]。
一、??SEH的概念、特性
??SEH,結構化異常處理,是作為一種系統機制引入到操作系統中的,本身與語言無關。在我們自己的程序中使用SEH可以讓我們集中精力開發關鍵功能,而把程序中所可能出現的異常進行統一的處理,使程序顯得更加簡潔且增加可讀性。
??當在程序中使用SEH時,就變成編譯器相關的。其所造成的負擔主要由編譯程序來承擔,例如編譯程序會產生一些表(table)來支持SEH的數據結構,還會提供回調函數。
二、??SEH的基本使用方法
1、??結束異常程序
??一個結束異常程序能確保調用和執行一個代碼塊,對應與具體的實現,結束處理程序的結構如下所示:
????__try?{
??????//?受保護的代碼
????}
????__finally?{
??????//?結束處理程序
????}
(1)try塊的自然退出與非自然退出
??try塊可能會因為return,goto,異常等非自然退出,也可能會因為成功執行而自然退出。但不論try塊是如何退出的,finally塊的內容都會被執行。??請看下面兩個程序:
? ?
??通過使用結束處理程序,可以避免return語句的過早執行。當retrun?試圖退出try塊時,編譯程序要保證finally塊中的代碼首先被執行。這事實上就是一個局部展開的過程,當從try塊的過早退出強制控制轉移到finally塊時,都將引起局部展開。
(2)finally塊的清理功能及對程序結構的影響
??寫過軟件的朋友一般都有這樣一個影響:在編碼的過程中需要加入需要檢測,檢測功能是否成功執行,若成功的話執行這個,不成功的話需要作一些額外的清理工作,例如釋放內存,關閉句柄等。如果檢測不是很多的話,倒沒什么影響;但若又許多檢測,且軟件中的邏輯關系比較復雜時,往往需要化很大精力來實現繁蕪的檢測判斷。結果就會使程序看起來結構比較復雜,大大降低程序的可讀性,而且程序的體積也不斷增大。
??事實上可以用SEH?來解決,把一些相關函數的清理代碼都放在finally塊,只需要在其中加一些適當的判斷,不需要回到每個可能失敗的地方添加清理代碼。下面的FunSampleA函數是一個常規的函數,FunSampleB引入了SEH結束處理程序機制:
? ?
??這兩個函數的功能是一樣的。可以看到在FunSampleA中的清理函數(CloseHandle)到處都是,而在FunSampleB中的清理函數則全部集中在finally塊,如果在閱讀代碼時只需看try塊的內容即可了解程序流程。這兩個函數本身都很小,可以細細體會下這兩個函數的區別。
(3)關鍵字__leave
??在try塊中使用__leave關鍵字會使程序跳轉到try塊的結尾,從而自然的進入finally塊。
??對于上例中的FunSampleB,try塊中的3個return完全可以用__leave來替換。兩者的區別是用return會引起try過早退出系統會進行局部展開而增加系統開銷,若使用__leave就會自然退出try塊,開銷就小的多。
??
??但有一種情況下必須使用__leave而不能使用return,即當finally塊后還需要執行一定的功能,如下所示:
?
?
2、異常處理程序
異常處理程序能在程序發生異常時進行相應的處理,對應與具體的實現,異常處理程序的結構如下所示:
????__try?{
??????//?受保護的代碼
????}
????__except?(?/*異常過濾器exception?filter*/?)?{
??????//?異常處理程序exception?handler
????}
(1)異常處理的基本流程(注:此流程圖來源于參考資料[1])
?? ?
(2)異常過濾器
異常過濾器只有三個可能的值(定義在Windows的Excpt.h中):
EXCEPTION_EXECUTE_HANDLER
EXCEPTION_CONTINUE_SERCH
EXCEPTION_CONTINUE_EXECUTION
下面是兩種基本的使用方法:
方式一:直接使用過濾器的三個返回值之一
__try?{
???……
}
__except?(?EXCEPTION_EXECUTE_HANDLER?)?{
???……
}
方式二:自定義過濾器
__try?{
???……
}
__except?(?MyFilter(?GetExceptionCode()?)?)
{
???……
}
LONG?MyFilter?(?DWORD?dwExceptionCode?)
{
??if?(?dwExceptionCode?==?EXCEPTION_ACCESS_VIOLATION?)
????return?EXCEPTION_EXECUTE_HANDLER?;
??else
????return?EXCEPTION_CONTINUE_SEARCH?;
}
(3)全局展開
??首先來看一下全局展開的基本流程(此流程圖來源于參考資料[1]):
? ?
??接下來看一個全局展開的實例(源碼GlobalUnwindSample.cpp):
代碼: #include?<iostream.h> #include?<windows.h>static?unsigned?int?nStep?=?1?;void?Function_B?() {int?x,?y?=?0?;__try?{x?=?5?/?y?;??//?引發異常}__finally{cout?<<?"Step?"?<<?nStep++?<<?"?:?執行Function_B的finally塊的內容"?<<?endl?;} }void?Function_A?(?) {__try?{Function_B?()?;}__finally{cout?<<?"Step?"?<<?nStep++?<<?"?:?執行Function_A的finally塊的內容"?<<?endl?;} }long?MyExcepteFilter?(?) {cout?<<?"Step?"?<<?nStep++?<<?"?:?執行main的異常過濾器"?<<?endl?;return?EXCEPTION_EXECUTE_HANDLER?; }int?main?() {__try?{Function_A?()?;}__except?(?MyExcepteFilter()?){cout?<<?"Step?"?<<?nStep++?<<?"?:?執行main的except塊的內容"?<<?endl?;}return?0?; }/*輸出結果: Step?1?:?執行main的異常過濾器 Step?2?:?執行Function_B的finally塊的內容 Step?3?:?執行Function_A的finally塊的內容 Step?4?:?執行main的except塊的內容 */ ??這個程序的執行流程如下所示:
? ?
(4)暫停全局展開
??如果程序中出現異常,且已經找到過濾器值為EXCEPTION_EXECUTE_HANDLER所對應的try塊,此時系統會進行全局展開,在正常情況下,系統會執行該try塊以內的所有finally過程,然后再執行該try塊對應的異常處理過程。但如果在某個finally中放入一個return,就可以阻止全局展開。
3、未處理異常(頂層異常處理)
??當軟件中出現異常,而在你的程序中沒有相應的異常處理程序,此時就形成了未處理異常。此時系統會彈出異常提示對話框,并可以結束進程。
??顯示異常對話框是這個功能具體是在UnhandledExceptionFilter中實現的,在啟動進程、線程時,系統會安裝一個最頂層的異常處理try-except結構,如下所示:
?
??BaseProcessStart用于進程的主線程,而BaseThreadStart用于其他線程。當異常產生時,如果程序中沒有相應的異常處理程序或者全都返回EXCEPTION_CONTINUE_SEARCH時,就會自動調用UnhandleExceptionFilter。
三、??SEH相關數據結構的介紹
1、EXCEPTION_POINTER結構
?
2、EXCEPTION_RECORD結構
?
3、EXCEPTOIN_REGISTRATION結構
??
4、異常處理鏈結構圖
??
四、VC++編譯器級SEH的具體實現
1、VC擴展異常幀
?
?
2、VC異常幀堆棧布局
?
3、兩個實例程序:顯示異常幀信息(詳見源碼ShowExcptFrame1.cpp,ShowExcptFrame2.cpp)
對上面的結構不熟悉也沒關系,下面通過實例來加深理解。
代碼: // //?顯示ScopeTable信息 // void?SEHShowScopeTable(?PVC_EXCEPTION_REGISTRATION?pVCExcRec?) {printf(?"Frame:?%08X??Handler:?%08X??Prev:?%08X??Scopetable:?%08X\n",?\pVCExcRec,?pVCExcRec->handler,?pVCExcRec->prev,??pVCExcRec->scopetable?);PSCOPETABLE?pScopeTableEntry?=?pVCExcRec->scopetable;for?(?int?i?=?0;?i?<=?pVCExcRec->trylevel;?i++?){printf(?"????scopetable[%u]?PrevTryLevel:?%08X??""filter:?%08X??__except:?%08X\n",?i,pScopeTableEntry->previousTryLevel,pScopeTableEntry->lpfnFilter,pScopeTableEntry->lpfnHandler?);pScopeTableEntry++;}printf(?"\n"?); }???// //顯示異常幀信息 // void?SEHShowExcptFrames(?) {PVC_EXCEPTION_REGISTRATION??pVCExcRec;//?取得異常幀鏈首地址,保存在pVCExcRec__asm???mov?eax,?FS:[0]__asm???mov?[pVCExcRec],?EAX//?遍歷異常幀鏈while?(?(unsigned)pVCExcRec?!=?0xFFFFFFFF?){SEHShowScopeTable(?pVCExcRec?);pVCExcRec?=?(PVC_EXCEPTION_REGISTRATION)(pVCExcRec->prev);}??????? } 嵌套異常結構示例一:
?
輸出結果:
?
??
嵌套異常結構示例二:
?
輸出結果:
?
4、實例分析及特性介紹(比較分析,注意結構示例與結果之間的聯系)
??[1]??上面這個兩個結果中各有4個異常幀。
??[2]??他們的后兩條異常幀都是一致的,變化的只是前兩個異常幀
??[3]??都是兩重函數調用,main?Function,每一重調用都以為著增加一個異常幀,其中的第一個幀代表最內層異常塊,即Function函數的異常幀(相對于顯示異常幀信息的函數來說),第二幀代表main函數的異常幀。
??[4]??scopetable域的項數與該函數中的異常塊數目相關,示例一中的Function函數中有三個嵌套的異常塊,因而第一幀scopetable中有三項;示例二中的Function函數中有四個異常塊,因而第二幀scoptable中有四項。
??[5]?首先來看示例一,Function函數中的三個異常塊是嵌套的,所以在scopetable[2]中的PrevTryLevel=1,表示當前異常塊在幀中的索引為2,與它相鄰的外層異常塊的索引為1,這個異常塊的異常過濾器地址為filter:00401204,而異常處理回調函數的地址為__except:?00401207;因而在示例一的第一幀scopetable中的三項形成一個鏈表(這也比較好理解:三個異常塊嵌套)。再來看示例二,還是看第一幀,自己分析下,是否形成了兩個鏈表。到這里,對于scoptable中的PrevTryLevel的意義應該清楚了。如果對于這部分還不是很理解的話可以通過增加或修改程序中異常塊、或者再增加幾層函數調用,然后觀察分析輸出結果。
??[6]??不知道大家有沒有發現EXCEPTION_REGISTRATION結構中有個handler,而在SCOPETABLE中也有個lpfnHandler(即上面例子輸出結果中的__except項),這兩個都是異常處理回調函數的地址,它們之間到底有什么聯系和區別?示例中前三幀的handler地址都是004014B0,why?
??這個也不難回答,scopetable中的lpfnHandler是用于用戶自定義的異常處理函數,而EXCEPTION_REGISTRATION的handler是用于系統默認的異常處理程序,即通常說的頂層異常處理回調函數。
??[7]??分析到這里,我只分析了4幀中的兩幀,至于后兩幀的作用,這一點將會在后文中講解。
5、VC中的頂層異常處理
??是否還記得在介紹第二部分的未處理異常時提到的BaseProcessStart,BaseThreadStart這兩個函數。在啟動主/次線程時,系統會把整個線程置于一個異常塊中,即頂層異常處理體。在上文顯示異常幀的兩個例子中的第四幀正是來源于此。
??在默認情況下,VC程序的入口點由運行時庫函數mainCRTStartup和WinMainainCRTStartup來實現(如果對這方面不熟悉的話,請參見參考資料[5]),其中又一次對將要調用的用戶程序入口main,WinMain(默認情況)置于異常處理體中,在上文顯示異常幀的兩個例子中的第三幀來自這里。
對于這兩個入口函數,CRT中有相應的源碼,大致結構經簡化如下所示:
?
??
??現在可以來總結下,VC編譯器中默認情況(即我們的程序不顯示的引入SEH)下的異常塊結構示例:
?
??其中的BaseProcessStart,WinMainCRTStartup這兩個函數并非固定,需要是視具體情況而定,這個只是用做示例。
??到這里,對于上小節中的遺留下來的關于第三和第四個異常幀的問題已經解決了。
6、VC搜索異常處理程序流程
??在介紹第三部分SEH相關數據結構時,已經介紹了異常處理鏈的結構,但針對VC中的異常處理,異常幀的擴展包括scopetable結構的引入使得支持嵌套的異常結構,因而當出現異常時尋找相應的異常處理程序的過程相對來說變的更加復雜。VC搜索異常處理程序流程如下所示:
?
?
【參考文獻】
[1].?Windows核心編程?Jeffrey?Richter著?機械工業出版社
[2].?A?Crash?Course?on?the?Depths?of?Win?32®?Structured?Exception?Handling
http://www.microsoft.com/msj/0197/Exception/Exception.aspx
[3].?Windows?2000?內部揭密??機械工業出版社
[4].?Windows異常處理流程??SoBeIt
???http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2471
[5].?在VC中編譯、運行程序的小知識點
???http://fmddlmyy.home4u.china.com/text3.html
作 者: 北極星2003
時 間: 2006-09-20,20:21:28
鏈 接: http://bbs.pediy.com/showthread.php?t=32222
目錄:
一、??SEH的概念、特性
二、??SEH的基本使用方法
1、??結束異常程序
(1)try塊的自然退出與非自然退出
(2)finally塊的清理功能及對程序結構的影響
(3)關鍵字__leave
2、??異常處理程序
(1)異常處理的基本流程
(2)異常過濾器
(3)全局展開
(4)暫停全局展開
3、??未處理異常(頂層異常處理)
三、??SEH相關數據結構的介紹
1、??EXCEPTION_POINTER結構
2、??EXCEPTION_RECORD結構
3、??EXCEPTOIN_REGISTRATION結構
4、異常處理鏈結構圖
四、??VC++編譯器級SEH的具體實現
1、??VC擴展異常幀
2、??VC異常幀堆棧布局
3、??兩個實例程序:顯示異常幀信息
4、??實例分析及特性介紹
5、??VC中的定層異常處理
6、??VC搜索異常處理程序流程
五、參考資料
--------------------------------------------------------------------------
?
前言:
????對于這片文章應該是我寫的最認真的一篇了,斷斷續續地寫了將近一個月,從最初的復習性的回顧,收集更多資料,反復地整理思路,查閱Windows源碼中的相關的源碼,設計文章的整體框架,到每一個部分的詳細設計,包括流程圖的設計。每一個過程都進展的并不順利,由于時間的關系每星期只能有2~3次的大段時間的在電腦面前,所以思路一直在被打斷,整理文章寫的并不流暢。有些思路或許已經不知道遺忘在哪個角落,但我也盡量把相關方面的知識點都講到。本文的重點在于SEH原理方面的介紹,從單純的使用角度來看,比較簡單,但如果適當的使用SEH機制,這在很大程度上與使用者對SEH的理解程度有很大關系,因而對于具體的實現,文中只介紹重點及需要注意的地方,具體使用方法可參見參考文獻[1]。
一、??SEH的概念、特性
??SEH,結構化異常處理,是作為一種系統機制引入到操作系統中的,本身與語言無關。在我們自己的程序中使用SEH可以讓我們集中精力開發關鍵功能,而把程序中所可能出現的異常進行統一的處理,使程序顯得更加簡潔且增加可讀性。
??當在程序中使用SEH時,就變成編譯器相關的。其所造成的負擔主要由編譯程序來承擔,例如編譯程序會產生一些表(table)來支持SEH的數據結構,還會提供回調函數。
二、??SEH的基本使用方法
1、??結束異常程序
??一個結束異常程序能確保調用和執行一個代碼塊,對應與具體的實現,結束處理程序的結構如下所示:
????__try?{
??????//?受保護的代碼
????}
????__finally?{
??????//?結束處理程序
????}
(1)try塊的自然退出與非自然退出
??try塊可能會因為return,goto,異常等非自然退出,也可能會因為成功執行而自然退出。但不論try塊是如何退出的,finally塊的內容都會被執行。??請看下面兩個程序:
? ?
??通過使用結束處理程序,可以避免return語句的過早執行。當retrun?試圖退出try塊時,編譯程序要保證finally塊中的代碼首先被執行。這事實上就是一個局部展開的過程,當從try塊的過早退出強制控制轉移到finally塊時,都將引起局部展開。
(2)finally塊的清理功能及對程序結構的影響
??寫過軟件的朋友一般都有這樣一個影響:在編碼的過程中需要加入需要檢測,檢測功能是否成功執行,若成功的話執行這個,不成功的話需要作一些額外的清理工作,例如釋放內存,關閉句柄等。如果檢測不是很多的話,倒沒什么影響;但若又許多檢測,且軟件中的邏輯關系比較復雜時,往往需要化很大精力來實現繁蕪的檢測判斷。結果就會使程序看起來結構比較復雜,大大降低程序的可讀性,而且程序的體積也不斷增大。
??事實上可以用SEH?來解決,把一些相關函數的清理代碼都放在finally塊,只需要在其中加一些適當的判斷,不需要回到每個可能失敗的地方添加清理代碼。下面的FunSampleA函數是一個常規的函數,FunSampleB引入了SEH結束處理程序機制:
? ?
??這兩個函數的功能是一樣的。可以看到在FunSampleA中的清理函數(CloseHandle)到處都是,而在FunSampleB中的清理函數則全部集中在finally塊,如果在閱讀代碼時只需看try塊的內容即可了解程序流程。這兩個函數本身都很小,可以細細體會下這兩個函數的區別。
(3)關鍵字__leave
??在try塊中使用__leave關鍵字會使程序跳轉到try塊的結尾,從而自然的進入finally塊。
??對于上例中的FunSampleB,try塊中的3個return完全可以用__leave來替換。兩者的區別是用return會引起try過早退出系統會進行局部展開而增加系統開銷,若使用__leave就會自然退出try塊,開銷就小的多。
??
??但有一種情況下必須使用__leave而不能使用return,即當finally塊后還需要執行一定的功能,如下所示:
?
?
2、異常處理程序
異常處理程序能在程序發生異常時進行相應的處理,對應與具體的實現,異常處理程序的結構如下所示:
????__try?{
??????//?受保護的代碼
????}
????__except?(?/*異常過濾器exception?filter*/?)?{
??????//?異常處理程序exception?handler
????}
(1)異常處理的基本流程(注:此流程圖來源于參考資料[1])
?? ?
(2)異常過濾器
異常過濾器只有三個可能的值(定義在Windows的Excpt.h中):
EXCEPTION_EXECUTE_HANDLER
EXCEPTION_CONTINUE_SERCH
EXCEPTION_CONTINUE_EXECUTION
下面是兩種基本的使用方法:
方式一:直接使用過濾器的三個返回值之一
__try?{
???……
}
__except?(?EXCEPTION_EXECUTE_HANDLER?)?{
???……
}
方式二:自定義過濾器
__try?{
???……
}
__except?(?MyFilter(?GetExceptionCode()?)?)
{
???……
}
LONG?MyFilter?(?DWORD?dwExceptionCode?)
{
??if?(?dwExceptionCode?==?EXCEPTION_ACCESS_VIOLATION?)
????return?EXCEPTION_EXECUTE_HANDLER?;
??else
????return?EXCEPTION_CONTINUE_SEARCH?;
}
(3)全局展開
??首先來看一下全局展開的基本流程(此流程圖來源于參考資料[1]):
? ?
??接下來看一個全局展開的實例(源碼GlobalUnwindSample.cpp):
代碼: #include?<iostream.h> #include?<windows.h>static?unsigned?int?nStep?=?1?;void?Function_B?() {int?x,?y?=?0?;__try?{x?=?5?/?y?;??//?引發異常}__finally{cout?<<?"Step?"?<<?nStep++?<<?"?:?執行Function_B的finally塊的內容"?<<?endl?;} }void?Function_A?(?) {__try?{Function_B?()?;}__finally{cout?<<?"Step?"?<<?nStep++?<<?"?:?執行Function_A的finally塊的內容"?<<?endl?;} }long?MyExcepteFilter?(?) {cout?<<?"Step?"?<<?nStep++?<<?"?:?執行main的異常過濾器"?<<?endl?;return?EXCEPTION_EXECUTE_HANDLER?; }int?main?() {__try?{Function_A?()?;}__except?(?MyExcepteFilter()?){cout?<<?"Step?"?<<?nStep++?<<?"?:?執行main的except塊的內容"?<<?endl?;}return?0?; }/*輸出結果: Step?1?:?執行main的異常過濾器 Step?2?:?執行Function_B的finally塊的內容 Step?3?:?執行Function_A的finally塊的內容 Step?4?:?執行main的except塊的內容 */ ??這個程序的執行流程如下所示:
? ?
(4)暫停全局展開
??如果程序中出現異常,且已經找到過濾器值為EXCEPTION_EXECUTE_HANDLER所對應的try塊,此時系統會進行全局展開,在正常情況下,系統會執行該try塊以內的所有finally過程,然后再執行該try塊對應的異常處理過程。但如果在某個finally中放入一個return,就可以阻止全局展開。
3、未處理異常(頂層異常處理)
??當軟件中出現異常,而在你的程序中沒有相應的異常處理程序,此時就形成了未處理異常。此時系統會彈出異常提示對話框,并可以結束進程。
??顯示異常對話框是這個功能具體是在UnhandledExceptionFilter中實現的,在啟動進程、線程時,系統會安裝一個最頂層的異常處理try-except結構,如下所示:
?
??BaseProcessStart用于進程的主線程,而BaseThreadStart用于其他線程。當異常產生時,如果程序中沒有相應的異常處理程序或者全都返回EXCEPTION_CONTINUE_SEARCH時,就會自動調用UnhandleExceptionFilter。
三、??SEH相關數據結構的介紹
1、EXCEPTION_POINTER結構
?
2、EXCEPTION_RECORD結構
?
3、EXCEPTOIN_REGISTRATION結構
??
4、異常處理鏈結構圖
??
四、VC++編譯器級SEH的具體實現
1、VC擴展異常幀
?
?
2、VC異常幀堆棧布局
?
3、兩個實例程序:顯示異常幀信息(詳見源碼ShowExcptFrame1.cpp,ShowExcptFrame2.cpp)
對上面的結構不熟悉也沒關系,下面通過實例來加深理解。
代碼: // //?顯示ScopeTable信息 // void?SEHShowScopeTable(?PVC_EXCEPTION_REGISTRATION?pVCExcRec?) {printf(?"Frame:?%08X??Handler:?%08X??Prev:?%08X??Scopetable:?%08X\n",?\pVCExcRec,?pVCExcRec->handler,?pVCExcRec->prev,??pVCExcRec->scopetable?);PSCOPETABLE?pScopeTableEntry?=?pVCExcRec->scopetable;for?(?int?i?=?0;?i?<=?pVCExcRec->trylevel;?i++?){printf(?"????scopetable[%u]?PrevTryLevel:?%08X??""filter:?%08X??__except:?%08X\n",?i,pScopeTableEntry->previousTryLevel,pScopeTableEntry->lpfnFilter,pScopeTableEntry->lpfnHandler?);pScopeTableEntry++;}printf(?"\n"?); }???// //顯示異常幀信息 // void?SEHShowExcptFrames(?) {PVC_EXCEPTION_REGISTRATION??pVCExcRec;//?取得異常幀鏈首地址,保存在pVCExcRec__asm???mov?eax,?FS:[0]__asm???mov?[pVCExcRec],?EAX//?遍歷異常幀鏈while?(?(unsigned)pVCExcRec?!=?0xFFFFFFFF?){SEHShowScopeTable(?pVCExcRec?);pVCExcRec?=?(PVC_EXCEPTION_REGISTRATION)(pVCExcRec->prev);}??????? } 嵌套異常結構示例一:
?
輸出結果:
?
??
嵌套異常結構示例二:
?
輸出結果:
?
4、實例分析及特性介紹(比較分析,注意結構示例與結果之間的聯系)
??[1]??上面這個兩個結果中各有4個異常幀。
??[2]??他們的后兩條異常幀都是一致的,變化的只是前兩個異常幀
??[3]??都是兩重函數調用,main?Function,每一重調用都以為著增加一個異常幀,其中的第一個幀代表最內層異常塊,即Function函數的異常幀(相對于顯示異常幀信息的函數來說),第二幀代表main函數的異常幀。
??[4]??scopetable域的項數與該函數中的異常塊數目相關,示例一中的Function函數中有三個嵌套的異常塊,因而第一幀scopetable中有三項;示例二中的Function函數中有四個異常塊,因而第二幀scoptable中有四項。
??[5]?首先來看示例一,Function函數中的三個異常塊是嵌套的,所以在scopetable[2]中的PrevTryLevel=1,表示當前異常塊在幀中的索引為2,與它相鄰的外層異常塊的索引為1,這個異常塊的異常過濾器地址為filter:00401204,而異常處理回調函數的地址為__except:?00401207;因而在示例一的第一幀scopetable中的三項形成一個鏈表(這也比較好理解:三個異常塊嵌套)。再來看示例二,還是看第一幀,自己分析下,是否形成了兩個鏈表。到這里,對于scoptable中的PrevTryLevel的意義應該清楚了。如果對于這部分還不是很理解的話可以通過增加或修改程序中異常塊、或者再增加幾層函數調用,然后觀察分析輸出結果。
??[6]??不知道大家有沒有發現EXCEPTION_REGISTRATION結構中有個handler,而在SCOPETABLE中也有個lpfnHandler(即上面例子輸出結果中的__except項),這兩個都是異常處理回調函數的地址,它們之間到底有什么聯系和區別?示例中前三幀的handler地址都是004014B0,why?
??這個也不難回答,scopetable中的lpfnHandler是用于用戶自定義的異常處理函數,而EXCEPTION_REGISTRATION的handler是用于系統默認的異常處理程序,即通常說的頂層異常處理回調函數。
??[7]??分析到這里,我只分析了4幀中的兩幀,至于后兩幀的作用,這一點將會在后文中講解。
5、VC中的頂層異常處理
??是否還記得在介紹第二部分的未處理異常時提到的BaseProcessStart,BaseThreadStart這兩個函數。在啟動主/次線程時,系統會把整個線程置于一個異常塊中,即頂層異常處理體。在上文顯示異常幀的兩個例子中的第四幀正是來源于此。
??在默認情況下,VC程序的入口點由運行時庫函數mainCRTStartup和WinMainainCRTStartup來實現(如果對這方面不熟悉的話,請參見參考資料[5]),其中又一次對將要調用的用戶程序入口main,WinMain(默認情況)置于異常處理體中,在上文顯示異常幀的兩個例子中的第三幀來自這里。
對于這兩個入口函數,CRT中有相應的源碼,大致結構經簡化如下所示:
?
??
??現在可以來總結下,VC編譯器中默認情況(即我們的程序不顯示的引入SEH)下的異常塊結構示例:
?
??其中的BaseProcessStart,WinMainCRTStartup這兩個函數并非固定,需要是視具體情況而定,這個只是用做示例。
??到這里,對于上小節中的遺留下來的關于第三和第四個異常幀的問題已經解決了。
6、VC搜索異常處理程序流程
??在介紹第三部分SEH相關數據結構時,已經介紹了異常處理鏈的結構,但針對VC中的異常處理,異常幀的擴展包括scopetable結構的引入使得支持嵌套的異常結構,因而當出現異常時尋找相應的異常處理程序的過程相對來說變的更加復雜。VC搜索異常處理程序流程如下所示:
?
?
【參考文獻】
[1].?Windows核心編程?Jeffrey?Richter著?機械工業出版社
[2].?A?Crash?Course?on?the?Depths?of?Win?32®?Structured?Exception?Handling
http://www.microsoft.com/msj/0197/Exception/Exception.aspx
[3].?Windows?2000?內部揭密??機械工業出版社
[4].?Windows異常處理流程??SoBeIt
???http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2471
[5].?在VC中編譯、運行程序的小知識點
???http://fmddlmyy.home4u.china.com/text3.html
總結
以上是生活随笔為你收集整理的Windows系统程序设计之结构化异常处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sniffer Pro 教程
- 下一篇: C++DES加解密