未处理异常和C++异常——Windows核心编程学习手札之二十五
未處理異常和C++異常
——Windows核心編程學(xué)習(xí)手札之二十五
當(dāng)一個(gè)異常過濾器返回EXCEPTION_CONTINUE_SEARCH標(biāo)識符時(shí)是告訴系統(tǒng)繼續(xù)上溯調(diào)用樹,尋找另外的異常過濾器,但當(dāng)每個(gè)過濾器都返回EXCEPTION_CONTINUE_SEARCH標(biāo)識符,出現(xiàn)了所謂“未處理異常”。
每個(gè)線程開始執(zhí)行,實(shí)際是利用kernel32.dll中的一個(gè)函數(shù)來調(diào)用BaseProcessStart或BaseThreadStart,這兩個(gè)函數(shù)實(shí)際是一樣的,區(qū)別在于一個(gè)函數(shù)用于進(jìn)程的主線程(primary thread):
?????? VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnStartAddr){
???????????????????? __try{
?????????????????????????????????? ExitThread((pfnStartAddr)());
???????????????????? }
???????????????????? __except(UnhandledExceptionFilter(GetExceptionInfomation())){
??????????????????????????? ExitProcess(GetExceptionCode());
???????????????????? }
??????????? //note:we never get here
?????? }
另一個(gè)函數(shù)用于進(jìn)程的所有輔助線程(Secondary thread):
?????? VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr,PVOID pvParam){
???????????????????? __try{
?????????????????????????????????? ExitThread((pfnStartAddr)( pvParam));
???????????????????? }
???????????????????? __except(UnhandledExceptionFilter(GetExceptionInfomation())){
??????????????????????????? ExitProcess(GetExceptionCode());
???????????????????? }
??????????? //note:we never get here
?????? }
這兩個(gè)函數(shù)都包含一個(gè)SHE框架,每個(gè)函數(shù)都有一個(gè)try塊,并從這個(gè)try塊里調(diào)用主線程或輔助線程的進(jìn)入點(diǎn)函數(shù)。當(dāng)線程引發(fā)一個(gè)異常時(shí),所有過濾器都返回EXCEPTION_CONTINUE_SEARCH時(shí),將會自動調(diào)用一個(gè)由系統(tǒng)提供的特殊過濾器函數(shù):
?????? LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);
這個(gè)函數(shù)負(fù)責(zé)顯示一個(gè)消息框,指出有一個(gè)進(jìn)程的線程存在未處理的異常,并且能讓用戶結(jié)束或調(diào)試這個(gè)進(jìn)程。在消息框的異常描述之后,提供用戶兩個(gè)選擇:
1)選擇“ok”按鈕,將導(dǎo)致UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH,這將引起全局展開,所有的finally塊都要執(zhí)行,然后BaseProcessStart或BaseThreadStart中的處理程序執(zhí)行。這兩個(gè)處理程序都是ExitProcess,退出進(jìn)程,退出的代碼是異常代碼,這是進(jìn)程的線程結(jié)束了進(jìn)程本身不是操作系統(tǒng),意味這程序員可以控制這種行為并改變它。
2)選擇“Cancel”按鈕,UnhandledExceptionFilter試圖加載一個(gè)調(diào)試程序,并將這個(gè)調(diào)試程序掛接在進(jìn)程上,通過將調(diào)試程序附在進(jìn)程上,可以檢查全局、局部和靜態(tài)變量的狀態(tài),設(shè)置斷點(diǎn),檢查調(diào)用樹,重新啟動進(jìn)程,以及調(diào)試一個(gè)進(jìn)程可以做的任何事情。
注意:上面是在用戶方式(user-mode)下的程序開發(fā),對于運(yùn)行在內(nèi)核方式(kernel-mode)出現(xiàn)未處理異常,未處理異常是在操作系統(tǒng)中或更可能在設(shè)備驅(qū)動程序中,而不是在應(yīng)用程序中,這樣的未處理異常表示一個(gè)嚴(yán)重的程序錯(cuò)誤(bug)!如果一個(gè)低級虛擬內(nèi)存函數(shù)產(chǎn)生一個(gè)異常,系統(tǒng)查找是否有內(nèi)核方式異常過濾器準(zhǔn)備處理這個(gè)異常,如果系統(tǒng)找不到,則異常是未處理的,如果一個(gè)未處理異常發(fā)生在內(nèi)核方式,讓系統(tǒng)繼續(xù)運(yùn)行是不安全的,所以系統(tǒng)在這種情況下不會調(diào)用UnhandledExceptionFilter函數(shù),而是顯示所謂的藍(lán)屏死機(jī)(Blue Screen of Death),顯示畫屏切換到只包含文本的藍(lán)屏視頻方式,并且計(jì)算機(jī)被停機(jī)(balt),顯示的文本告訴是哪個(gè)設(shè)備驅(qū)動程序被加載,并且該模塊中包含引發(fā)未處理異常的代碼。
當(dāng)程序員選擇cancel按鈕時(shí),告訴UnhandledExceptionFilter函數(shù)對進(jìn)程進(jìn)行調(diào)試,隨時(shí)將調(diào)試程序連接到任何進(jìn)程的能力稱為即時(shí)調(diào)試(Just-in-time Debugging)。在內(nèi)部,UnhandledExceptionFilter調(diào)用調(diào)試程序,需要查看下面的注冊表子關(guān)鍵字:
?????? HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/WindowsNT/
CurrentVersion/AeDebug
該關(guān)鍵字里,有一個(gè)Debugger的數(shù)值,安裝Visual Studio時(shí)被設(shè)置成下面的值:
?????? C:/Program Filese/Microsoft Visual Studio/Common/MSDev98/Bin/msdev.exe
?????????????????????????? -p %ld? –e %ld
在windows98中,這些值不存放在注冊表中,而是存放在Win.ini文件中。
這個(gè)值告訴系統(tǒng)要將哪個(gè)程序(這里是msdev.exe)作為調(diào)試程序運(yùn)行,當(dāng)然也可以選擇其他調(diào)試程序。UnhandledExceptionFilter還在命令行里向調(diào)試程序傳遞兩個(gè)參數(shù),第一個(gè)參數(shù)是被調(diào)試進(jìn)程ID;第二個(gè)參數(shù)規(guī)定一個(gè)可繼承的手工復(fù)位事件,這個(gè)事件是由UnhandledExceptionFilter按無信號狀態(tài)建立的,廠商必須實(shí)現(xiàn)自己的調(diào)試程序,這樣才能認(rèn)識指定進(jìn)程ID和事件句柄的-p和-e選項(xiàng)。
在進(jìn)程ID和事件句柄都合并到這個(gè)串中后,UnhandledExceptionFilter通過調(diào)用CreateProcess來執(zhí)行調(diào)試程序,這是,調(diào)試程序進(jìn)程開始運(yùn)行并檢查它的命令行參數(shù),如果存在-p選項(xiàng),調(diào)試程序取得進(jìn)程ID,并通過調(diào)用DebugActiveProcess將自身掛接到該進(jìn)程上:
?????? BOOL DebugActiveProcess(DWORD dwProcessID);
一旦調(diào)試程序完成自身的掛接,操作系統(tǒng)將被調(diào)試者(debuggee)的狀態(tài)通報(bào)給調(diào)試程序。在調(diào)試程序完全初始化后,要再檢查它的命令行,找-e選項(xiàng),如該項(xiàng)存在,調(diào)試程序取得相應(yīng)事件句柄并調(diào)用SetEvent。
另外,不必在調(diào)試進(jìn)程之前等待異常的出現(xiàn),可以隨時(shí)將一個(gè)調(diào)試程序連接在任何進(jìn)程上,只需運(yùn)行MSDEV –p PID,其中PID是要調(diào)試的進(jìn)程的ID,實(shí)際在Windows2000的Task Manager里選擇一個(gè)進(jìn)程,并選擇debug菜單,就將引起Task Manager去查看前面的注冊表子關(guān)鍵字,調(diào)用CreateProcess,并傳遞所選定的進(jìn)程ID作為參數(shù),在這里,Task Manage為事件句柄傳送0值。
如果異常發(fā)生時(shí),不想在屏幕上顯示消息框,有下面幾個(gè)方法:
1)強(qiáng)制進(jìn)程終止運(yùn)行
為防止UnhandledExceptionFilter顯示異常消息框,可以調(diào)用SetErrorMode函數(shù),并向它傳遞一個(gè)SEM_NOGPFAULTERRORBOX標(biāo)識符:
?????? UNINT SetErrorMode(UINT fuErrorMode);
當(dāng)調(diào)用UnhandledExceptionFilter函數(shù)來處理異常時(shí),看到已經(jīng)設(shè)置了這個(gè)標(biāo)志,就立即返回EXCEPT_EXECUTE_HANDLER,這將導(dǎo)致全局展開并執(zhí)行BaseProcessStart或BaseThreadStart中的處理程序,該處理程序結(jié)束進(jìn)程。
2)包裝一個(gè)線程函數(shù)
在主線程進(jìn)入點(diǎn)函數(shù) (main、wmain、WinMain、wWinMain)的整個(gè)內(nèi)容安排一個(gè)try-except塊,保證異常過濾器的結(jié)果值總是EXCEPT_EXECUTE_HANDLER,這樣就保證異常總能得到處理,防止系統(tǒng)再調(diào)用UnhandledExceptionFilter函數(shù)。缺點(diǎn)是只能捕捉主線程中發(fā)生的異常,而其他輔助線程則不能覆蓋。
3)包裝所有線程函數(shù)
Windows提供了SetUnhandledExceptionFilter函數(shù)可以按SHE格式包裝所有線程函數(shù)。
?????? PTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
??????????????????????????? PTOP_LEVEL_EXCEPTION_FILTER pTopLevelExceptionFilter);
在進(jìn)程調(diào)用這個(gè)函數(shù)后,進(jìn)程的任何線程發(fā)生一個(gè)未處理異常,就會導(dǎo)致調(diào)用程序自己的異常過濾器,需要將這個(gè)過濾器地址作為參數(shù)傳遞給SetUnhandledExceptionFilter。過濾器函數(shù)原型是:
?????? LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);
3)自動調(diào)用調(diào)試程序
在設(shè)置調(diào)試程序的注冊表同一個(gè)關(guān)鍵字里,有個(gè)Auto值,該值用來規(guī)定UnhandledExceptionFilter是應(yīng)該顯示消息框還是僅啟動調(diào)試程序,如果Auto設(shè)置為1,UnhandledExceptionFilter就不顯示消息框向用戶報(bào)告異常,而是立即調(diào)用調(diào)試程序,如設(shè)置為0,就按照上面所說的。
UnhandledExceptionFilter函數(shù)的內(nèi)部執(zhí)行情況:
1)如果發(fā)生一個(gè)存取違規(guī)并且是由于試圖寫內(nèi)存(相對于讀內(nèi)存)引起的,系統(tǒng)要查看是不是要修改一個(gè)exe模塊或dll模塊中的資源,默認(rèn)下資源是只讀的,試圖修改資源會引起存取異常。然而16位Windows允許修改資源,從兼容性考慮,32位和64位也應(yīng)允許修改資源,所以當(dāng)想要修改資源時(shí),UnhandledExceptionFilter調(diào)用VirtualProtect,將資源頁上的保護(hù)改成PAGE_READWRITE,并返回EXCEPTION_CONTINUE_EXECUTION;
2)如果已經(jīng)調(diào)用了SetUnhandledExceptionFilter指定自己的過濾器,UnhandledExceptionFilter就調(diào)用指定的過濾器,如果自己的過濾器返回EXCEPTION_EXECUTE_HANDLER或EXECEPTION_CONTINUE_EXECUTION,UnhandledExceptionFilter就將這個(gè)值返回給系統(tǒng),如果沒有設(shè)置自己的未處理異常過濾器,或自己的未處理異常過濾器返回EXCEPTION_CONTINUE_SEARCH,則進(jìn)入第3步;
3)如果進(jìn)程是在調(diào)試程序下運(yùn)行,就返回EXCEPTION_CONTINUE_SEARCH。調(diào)試程序顯示一個(gè)消息框并運(yùn)行調(diào)試進(jìn)程(IsDebuggerPresent函數(shù)用來確定一個(gè)進(jìn)程是否正在被調(diào)試);
4)如果進(jìn)程中一個(gè)線程以SEM_NOGPFAULTERRORBOX標(biāo)志為參數(shù)調(diào)用SetErrorCode,UnhandledExceptionFilter就返回EXCEPTION_EXECUTE_HANDLER;
5)如果進(jìn)程在一個(gè)作業(yè)job里并且作業(yè)的限制信息設(shè)定了JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION標(biāo)志,則UnhandledExceptionFilter返回EXCEPTION_EXECUTE_HANDLER;
6)UnhandledExceptionFilter查閱注冊表并取出Auto值,如果是1,到第7步,如是0,則向用戶顯示一個(gè)消息框;
7)UnhandledExceptionFilter產(chǎn)生調(diào)試程序,首先調(diào)用CreateEvent建立一個(gè)無信號的、手工復(fù)位的事件,這個(gè)事件的句柄可繼承,然后從注冊表中取出Debugger值,調(diào)用sprintf把它粘帖到進(jìn)程ID(通過調(diào)用GetCurrentProcessID函數(shù)得到)和事件句柄里。STARTUPINFO的lpDesktop成員也設(shè)置成”Winsta0//Default”,這樣調(diào)試程序就出現(xiàn)在交互式桌面上。
8)當(dāng)調(diào)試程序完成初始化,就設(shè)置事件句柄,這將喚醒UnhandledExceptionFilter中的線程,這樣進(jìn)程就在調(diào)試程序下運(yùn)行,UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH。
SHE(結(jié)構(gòu)化異常處理)可用于任何編程語言的操作系統(tǒng)設(shè)施,而C++異常只能用于編寫C++代碼。如果是編寫C++程序,應(yīng)使用C++異常處理而不是結(jié)構(gòu)化異常處理,理由是C++異常處理是語言的一部分,編譯器知道C++類對象是什么,也就是說編譯器能夠自動生成代碼來調(diào)用C++對象析構(gòu)函數(shù),保證對象的清除。
不過,Microsoft Visual C++編譯器也利用了操作系統(tǒng)的SHE實(shí)現(xiàn)了C++異常處理,所以當(dāng)建立一個(gè)C++的try塊時(shí),編譯器就生成一個(gè)SHE的__try塊,一個(gè)C++的catch測試變成一個(gè)SHE異常過濾器,并且catch中的代碼變成SHE的__except塊中的代碼,實(shí)際上,C++的throw語句,編譯器生成windows對應(yīng)的RaiseException函數(shù)的調(diào)用,用于throw語句的變量傳遞給RaiseException作為附加的參數(shù),如上,C++異常在內(nèi)部是由SHE實(shí)現(xiàn)的。
正常情況下,C++異常處理不能使程序從硬件異常中恢復(fù),硬件違規(guī)就是存取違規(guī)或零作除數(shù)這種異常,但microsoft已經(jīng)對其編譯器增加了這種支持能力。
總結(jié)
以上是生活随笔為你收集整理的未处理异常和C++异常——Windows核心编程学习手札之二十五的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 异常处理程序和软件异常——Windows
- 下一篇: 窗口消息——Windows核心编程学习手