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