windows核心编程-第一章 对程序错误的处理
第一章-對程序錯誤的處理
? ? 在開始介紹Microsoft Windows 的特性之前,必須首先了解 Wi n d o w s的各個函數是如何進行錯誤處理的。
當調用一個Wi n d o w s函數時,它首先要檢驗傳遞給它的的各個參數的有效性,然后再設法執行任務。如果傳遞了一個無效參數,或者由于某種原因無法執行這項操作,那么操作系統就會返回一個值,指明該函數在某種程度上運行失敗了。表 1 - 1列出了大多數Wi n d o w s函數使用的返回值的數據類型。
?
? ? 一個Wi n d o w s函數返回的錯誤代碼對了解該函數為什么會運行失敗常常很有用。 M i c r o s o f t公司編譯了一個所有可能的錯誤代碼的列表,并且為每個錯誤代碼分配了一個 3 2位的號碼。
從系統內部來講,當一個Wi n d o w s函數檢測到一個錯誤時,它會使用一個稱為線程本地存儲器(thread-local storage) 的機制,將相應的錯誤代碼號碼與調用的線程關聯起來( 線程本地存儲器將在第2 1章中介紹) 。這將使線程能夠互相獨立地運行,而不會影響各自的錯誤代碼。當函數返回時,它的返回值就能指明一個錯誤已經發生。若要確定這是個什么錯誤,請調用G e t L a s t E r r o r函數:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DWORD GetLastError();
? ? 該函數只返回線程的3 2位錯誤代碼。當你擁有3 2位錯誤代碼的號碼時,必須將該號碼轉換成更有用的某種對象。 Wi n E r r o r. h頭文件包含了M i c r o s o f t公司定義的錯誤代碼的列表。下面顯示了該列表的某些內容,使你能夠看到它的大概樣子:
? ? ?如你所見,每個錯誤都有3種表示法:一個消息I D(這是你可以在源代碼中使用的一個宏,以便與G e t L a s t E r r o r的返回值進行比較) ,消息文本(對錯誤的英文描述)和一個號碼(應該避免使用這個號碼,可使用消息I D) 。請記住,這里只顯示了Wi n E r r o r. h頭文件中的很少一部分內容,整個文件的長度超過2 1 0 0 0行。
? ? 當Wi n d o w s函數運行失敗時,應該立即調用G e t L a s t E r r o r函數。如果調用另一個Wi n d o w s函數,它的值很可能被改寫。
? ? 注意 G e t L a s t E r r o r能返回線程產生的最后一個錯誤。如果該線程調用的Wi n d o w s函數運行成功,那么最后一個錯誤代碼就不被改寫,并且不指明運行成功。有少數Wi n d o w s函數并不遵循這一規則,它會更改最后的錯誤代碼;但是 Platform SDK文檔通常指明,當函數運行成功時,該函數會更改最后的錯誤代碼。
Wi n d o w s 9 8 許多Windows 98的函數實際上是用M i c r o s o f t公司的1 6位Windows 3.1產品產生的1 6位代碼來實現的。這種比較老的代碼并不通過 G e t L a s t E r r o r之類的函數來報告錯誤,而且M i c r o s o f t公司并沒有在Windows 98中修改1 6位代碼,以支持這種錯誤處理方式。對于我們來說,這意味著Windows 98中的許多Wi n 3 2函數在運行失敗時不能設置最后的錯誤代碼。該函數將返回一個值,指明運行失敗,這樣你就能夠發現該函數確實已經運行失敗,但是你無法確定運行失敗的原因。
? ? 有些Wi n d o w s函數之所以能夠成功運行,其中有許多原因。例如,創建指明的事件內核對象之所以能夠取得成功,是因為你實際上創建了該對象,或者因為已經存在帶有相同名字的事件內核對象。你應搞清楚成功的原因。為了將該信息返回, M i c r o s o f t公司選擇使用最后錯誤代碼機制。這樣,當某些函數運行成功時,就能夠通過調用 G e t L a d t E r r o r函數來確定其他的一些信息。對于具有這種行為特性的函數來說, Platform SDK文檔清楚地說明了G e t L a s t E r r o r函數可以這樣使用。請參見該文檔,找出C r e a t e E v e n t函數的例子。
? ? 進行調試的時候,監控線程的最后錯誤代碼是非常有用的。 在Microsoft Visual studio 6.0中,M i c r o s o f t的調試程序支持一個非常有用的特性,即可以配置 Wa t c h窗口,以便始終都能顯示線程的最后錯誤代碼的號碼和該錯誤的英文描述。通過選定 Wa t c h窗口中的一行,并鍵入“@ e r r, h r” ,就能夠做到這一點。觀察圖1 - 1,你會看到已經調用了C r e a t e F i l e函數。該函數返回I N VA L I D _ H A N D L E _ VA L U E(- 1)的H A N D L E,表示它未能打開指定的文件。但是Wa t c h窗口向我們顯示最后錯誤代碼(即如果調用 G e t L a s t E r r o r函數,該函數返回的錯誤代碼)是0 x 0 0 0 0 0 0 0 2。該Wa t c h窗口又進一步指明錯誤代碼2是指“系統不能找到指定的文件。 ”你會發現它與Wi n E r r o r. h頭文件中的錯誤代碼2所指的字符串是相同的。
?
OK,然后我就照著做,但是發現了一個尷尬的事情,我在vs2012上找不大watch窗口,搜索了下,說是在調試->窗口->監視(Debug->Window->Watch),然后我就去找,但是并沒有找到,一開始看到的是這個:
?
額...后來無意中發現了,其實是當你點擊運行的時候,那個地方才會出現Watch的選項:
?
然后就是我在vs2012中寫了下上面說的那個:
?
然后又在網上找了一些常用技巧:
1.eg: int *p = new int[100];
如果只查看p的話,只能看到一個結果??梢允褂?#xff1a;p,n 查看n個結果。
?
2.格式化數據和表達式賦值語句.
常用變量格式化符(表達式的值后跟逗號,接格式化符,如”(int)0xFFFF,d”):
d,I:有符號的十進制數.
u ?:無符號的十進制數.
o ?:無符號的八
x,X:十六進制數.
l,h:d,i,u,o,x,X的長前綴或短前綴.
f ?:有符號浮點數.
e ?:有符號的科學計數法.
g ?:有符號的浮點或有符號的科學計數法,用其中較短的一個.
c ?:單字符.
s ?:字符串.
su :雙字節字符串.
st :雙字節字符串或ANSI字符串,取決于AUTOEXP.DAT中的Unicode String設置.
hr :Windows類標記.
wm :Windows消息碼.
?
常用內存轉儲對象的格式化符(用法同變量格式化符):
ma :64個ASCII碼字符.
m ?:以16進制書寫的16字節,后跟16個ASCII字符.
mb :以16進制書寫的16字節,后跟16個ASCII字符.
mw :8個字長.
md :4個雙精度字.
mq :4個四倍字長的字.
mu :2字節字符(Unicode標準).
# ?:將指針擴展到指定的數值數目的內存存儲單元上.(#代表一個數字)
?
WATCH窗口允許重新設置數據變量的格式,
如:可用BY,DW表達式來定位指針的偏移量;
可用&和*運算符,且兩運算符都可直接操作內存地址;
甚至可用上下說明符明確指定變量的上下文.
總之,所有格式化方法和指定方法在WATCH窗口都有效
?
WATCH窗口是一個完整的表達式求值程序,可以在其中查看任何條件語句.
?
表達式中可用的偽寄存器(可當普通變量進行查看):
@ERR:最后一個錯誤值,GetLastError API返回相同的值.
@TIB:當前線程的線程信息塊.(調試器不能處理”FS:0″格式).
@CLK:時鐘寄存器.
@EAX,@EBX,@ECX,@EDX,@ESI,@EDI,@DIP,@ESP,@EBP,@EFL
????:Intel CPU寄存器.
@CS,@DS,@ES,@SS,@FS,@GS
????:Intel CPU段寄存器.
@ST0,@ST1,@ST2,@ST3,@ST4,@ST5,@ST6,@ST7
????:Intel CPU浮點寄存器.
?
3.適時編碼
?
許多時候只想對兩斷點間的執行時間有個大致印象,可用@CLK得出兩斷點間所需執行時間(包括調試器占用的時間).
需要輸入兩個@CLK觀察符,第一個是@CLK,第二個是@CLK=0.第二個的目的是重新運行時將定時器清0.
時間以微秒為單位,大多數情況下需要格式化為毫秒:”@CLK/1000,d”.
?
4.在WATCH窗口中調用函數
?
大多數情況下用于執行專門編寫的校驗數據結構,保證數據的相關性的函數.在釋放構件中,從未調用過的函數不會被鏈接,因此不必擔
?
心這類函數會對影響發布構件.
如函數沒有參數,也要求使用括號”()”,調用時像用普通函數一樣傳送參數.WATCH右邊將顯示函數返回值.
這里有些限制:
1.只能在一個單線程上下文中執行函數.如是多線程程序,將函數輸入到WATCH窗口中檢查結果后應立即從WATCH窗口清除,否則,如調
?
試函數在第二個線程上下文中執行,會立即終止第二個線程的運行.
2.調試函數必須在20秒內執行.如執行過程中出現異常,程序會在調試器中中止.
3.(常識)只對數據驗證進行內存讀取,如有問題,調用OutputDebugString類的函數.如更改內存或調用API函數—-盡管這是可能的,但
?
無法預知可能會發生什么.
只要在WATCH窗口中重新計算表達式,已輸入WATCH窗口的調試函數就會執行:
.程序處于運行狀態并觸發某一斷點時.
.單步調試某一代碼行或某一指令時.
.在WATCH窗口左邊編輯完成調試函數的文本并按下回車時.
.在運行程序時出現異常情況,并讓你返回調試器中時.
使用調試函數的建議:輸入調試函數并查看值后,立即從WATCH窗口清除;只為最關鍵的數據結構編寫調試函數;不要更改個別結構的轉
?
儲內像.
?
5.自動擴展自己的類型
?
常見的自動擴展是RECT,輸入RECT型的變量后直接顯示其中的某些數據成員的值.
自定義類型擴展時,只需將自己的類型入口加入<VS Common>\MSDev98\Bin目錄的AUTOEXP.DAT文件中.
例:
擴展CreateProcess()所用到的PROCESS_INFORMATION結構
(1).檢查調試器將該類型識別為什么.將PROCESS_INFORMATION變量輸入WATCH窗口,右擊變量,選擇Properties,在這里它被標注為
?
_PROCESS_INFORMATION類型.
(2).打開AUTOEXP.DAT文本文件,加入擴展入口.語法如下:
Type=[text]<member[format]>
本例中要查看hProcess和hThread值,故輸入:
_PROCESS_INFORMATION=hProcess=<hProcess,X> hThread=<hThread,X>
其中X表示以16進制查看.有個特殊的格式化符<,t>,用于通知調試器輸入最易派生類型的類型名.如B派生至A,只有B有自動擴展規則,
?
則B的自動擴展將會是后面跟隨著類A的自動擴展規則的類型名B.
?
6.Set Next Statement命令
?
可以在調試時從菜單運行,但也可在WATCH窗口中直接設置EIP寄存器—-小心,可能很容易摧毀程序.在最優化的釋放構件中,最安全的
?
方法是在Disassembly窗口中使用該命令.如代碼在堆棧上創建了臨時變量,更要多加小心.
最常用的情況是:在出問題的函數前設置一個斷點,檢查進入的參數,單步調試整個函數;如問題不是重復的,使用Set Next Statement
?
設置返回到斷點的執行點,并更改參數.這樣可在一個調試會話中測試多個假設,節省測試時間,但它不能用于所有場合,因為函數執行
?
會破壞其狀態.另一個常用地點是測試時填充數據結構,如表和數組,可用它輸入額外的數據并查看代碼如何處理–當某些數據條件難于復制時更為方便.
????Visual studio還配有一個小的實用程序,稱為Error Lookup??梢允褂?/span>Error Lookup
將錯誤代碼的號碼轉換成相應文本描述(見圖1 - 2) 。如果在編寫的應用程序中發現一個錯誤,可能想要向用戶顯示該錯誤的文本描述。Wi n d o w s提供了一個函數,可以將錯誤代碼轉換成它的文本描述。該函數稱為 F o r m a t -M e s s a g e,顯示如下:
? ? F o r m a t M e s s a g e函數的功能實際上是非常豐富的,在創建向用戶顯示的字符串信息時,它是首選函數。該函數之所以有這樣大的作用,原因之一是它很容易用多種語言進行操作。該函數能夠檢測出用戶首選的語言(在Regional Settings Control Panel小應用程序中設定) ,并返回相應的文本。當然,首先必須自己轉換字符串,然后將已轉換的消息表資源嵌入你的 . e x e文件或D L L模塊中,然后該函數會選定正確的嵌入對象。 E r r o r S h o w示例應用程序(本章后面將加以介紹)展示了如何調用該函數,以便將M i c r o s o f t公司定義的錯誤代碼轉換成它的文本描述。
? ? 有些人常常問我,M i c r o s o f t公司是否建立了一個主控列表,以顯示每個 Wi n d o w s函數可能返回的所有錯誤代碼??上?#xff0c;回答是沒有這樣的列表,而且 M i c r o s o f t公司將永遠不會建立這樣的一個列表。因為在創建系統的新版本時,建立和維護該列表實在太困難了。建立這樣一個列表存在的問題是,你可以調用一個 Wi n d o w s函數,但是該函數能夠在內部調用另一個函數,而這另一個函數又可以調用另一個函數,如此類推。由于各種不同的原因,這些函數中的任何一個函數都可能運行失敗。有時,當一個函數運行失敗時,較高級的函數對它進行恢復,并且仍然可以執行你想執行的操作。為了創建該主控列表, M i c r o s o f t公司必須跟蹤每個函數的運行路徑,并建立所有可能的錯誤代碼的列表。這項工作很困難。而且,當創建系統的新版本時,這些函數的運行路徑還會改變。
1.1 定義自己的錯誤代碼
前面已經說明 Wi n d o w s函數是如何向函數的調用者指明發生的錯誤,你也能夠將該機制用于自己的函數。比如說,你編寫了一個希望其他人調用的函數,你的函數可能因為這樣或那樣的原因而運行失敗,你必須向函數的調用者說明它已經運行失敗。
若要指明函數運行失敗,只需要設定線程的最后的錯誤代碼,然后讓你的函數返回FA L S E、I N VA L I D _ H A N D L E _ VA L U E、N U L L或者返回任何合適的信息。若要設定線程的最后錯誤代碼,只需調用下面的代碼:
?
請將你認為合適的任何 3 2位號碼傳遞給該函數。嘗試使用 Wi n E r r o r. h中已經存在的代碼,圖1-2 Error Lookup窗口只要該代碼能夠正確地指明想要報告的錯誤即可。如果你認為 Wi n E r r o r. h中的任何代碼都不能正確地反映該錯誤的性質,那么可以創建你自己的代碼。錯誤代碼是個 3 2位的數字,劃分成表
1-2 所示的各個域。
表1-2 錯誤代碼的域
?
這些域將在第2 4章中詳細講述。現在,需要知道的重要域是第 2 9位。M i c r o s o f t公司規定,他們建立的所有錯誤代碼的這個信息位均使用 0。如果創建自己的錯誤代碼,必須使 2 9位為1。這樣,就可以確保你的錯誤代碼與M i c r o s o f t公司目前或者將來定義的錯誤代碼不會發生沖突。
來一個自定義的例子:SetLastError((1<<29) + (1<<30) * 0 + 1);
1.2 ErrorShow示例應用程序
????E r r o r S h o w應用程序“01 ErrorShow. e x e”(在清單1 - 1中列出)展示了如何獲取錯誤代碼的文本描述的方法。該應用程序的源代碼和資源文件位于本書所附光盤上的 0 1 - E r r o r S h o w目錄下。一般來說,該應用程序用于顯示調試程序的 Wa t c h窗口和Error Lookup程序是如何運行的。當啟動該程序時,就會出現
如圖1 - 3所示的窗口。
?
可以將任何錯誤代碼鍵入該編輯控件。當單擊 Look up按鈕時,在底部的滾動窗口中就會顯示該錯誤的文本描述。該應用程序唯一令人感興趣的特性是如何調用F o r m a t M e s s a g e函數。下面是使用該函數的方法:
?
????第一個代碼行用于從編輯控件中檢索錯誤代碼的號碼。然后,建立一個內存塊的句柄并將它初始化為N U L L。F o r m a t M e s s a g e函數在內部對內存塊進行分配,并將它的句柄返回給我們。當調用F o r m a t M e s s a g e函數時,傳遞了F O R M AT _ M E S S A G E _ F R O M _ S Y S T E M標志。該標志告訴F o r m a t M e s s a g e函數,我們想要系統定義的錯誤代碼的字符串。還傳遞了 F O R M AT _M E S S A G E _ A L L O C AT E _ B U F F E R標志,告訴該函數為錯誤代碼的文本描述分配足夠大的內存塊。該內存塊的句柄將在 h l o c a l變量中返回。第三個參數指明想要查找的錯誤代碼的號碼,第四個參數指明想要文本描述使用什么語言。
? ? 如果F o r m a t M e s s a g e函數運行成功,那么錯誤代碼的文本描述就位于內存塊中,將它拷貝到對話框底部的滾動窗口中。如果F o r m a t M e s s a g e函數運行失敗,設法查看N e t M s g . d l l模塊中的消息代碼,以了解該錯誤是否與網絡有關。使用 N e t M s g . d l l模塊的句柄,再次調用F o r m a t M e s s a g e函數。你會看到,每個 D L L(或. e x e)都有它自己的一組錯誤代碼,可以使用Message Compiler(M C . e x e)將這組錯誤代碼添加給該模塊,并將一個資源添加給該模塊。這就是Visual Studio的Error Lookup工具允許你用M o d u l e s對話框進行的操作。
總結
以上是生活随笔為你收集整理的windows核心编程-第一章 对程序错误的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows-DLL注入
- 下一篇: Intel汇编语言程序设计学习-第五章