windows中的調試
1.事後調試有兩個最基本的目標:
(1) 發現程式是在哪裡崩潰的
(2) 找出導致程式崩潰的原因
2.按優先選擇順序排列的調試技術:
(1) 使用調試版本進行本地調試
(2) 使用帶有偵錯符號的發布版進行本地調試
(3) 使用調試版本進行遠端偵錯
(4) 使用帶有偵錯符號的發布版本進行遠端偵錯
(5) 使用 Dr.Watson 記錄檔進行事後調試
(6) 使用崩潰對話方塊的資訊進行事後調試
3.傳回型別為BOOL型的Windows API函數返回的值不一定是0或1,所以在編寫帶有調試功能的C++程式時,將傳回值與TRUE進行比較是一件很冒險的事情,應避免這樣做。傳回型別為HANDLE的Windows API函數在有錯誤發生時,通常返回一個空的控制代碼,或返回INVALID_HANDLE_VALUE(值為-1)。傳回型別為LONG或DWORD的Windows API函數則通常返回0或-1。如果一個API函數不可能有錯誤發生,它就返回VOID。可以通過GetLastError得到相應的錯誤碼。
4.在Visual C++中通過在觀察視窗中輸入"@ERR, hr",可以監控GetLastError的傳回值。
5.如果你希望在錯誤資訊中顯示錯誤碼,可以通過FormatMessage API函數將錯誤碼轉換到文字格式設定。
6.永遠不要重定位其他程式會使用的第三方DLL
7.最佳化版本通常不將EBP作為堆棧基址指標使用。這種最佳化類型稱為幀指標省略(FPO)
8.因為最佳化版本可能不使用堆棧基址指標,如果函數原型聲明不一致,就很可能會在函數返回時導致崩潰。
9.使用調試反組譯碼視窗查看你的原始碼如何被轉換成彙編代碼
10.建立最有用的對應檔,通常使用/MAPINFO:LINES和/MAPINFO:EXPORTS工程選項。將你發布的程式的所有模組的對應檔歸檔。
11.使用Visual C++名字解析工具(Undname)將混合名字轉換到原始名字。
Undname ?RandomException@@YGXHHHH@Z
輸出:?RandomException@@YGXHHHH@Z == RandomException
-f選項顯示出整個函數原型
Undname -f ?RandomException@@YGXHHHH@Z
輸出:?RandomException@@YGXHHHH@Z == void __stdcall RandomException
11.調試版本的運行時刻函數庫對記憶體的分配作了跟蹤,並允許使用者檢查記憶體流失;在剛分配的記憶體裡寫上0XCD的位元組模式,這有助於發現使用未被初始化資料的錯誤;在被釋放的記憶體裡寫上0XCD的位元組模式,這有助於發現使用已被釋放的記憶體;在緩衝區的兩邊分配了四位元組的保護資料,並用0XFD的位元組模式作初始化,來檢查寫記憶體的上溢出和下溢出;在每個記憶體配置的地方對原始碼檔案名稱和行號作了記錄,這有助於使用者在原始碼中對記憶體配置進行定位。所以,調試版本能發現多種的記憶體錯誤。
12.幀指標的省略(FPO)隱藏了函數原型不匹配的錯誤,這僅在調試版本中函數返回時會導致崩潰。
13.在調試版本中所有的變數都是volatile的。而在發布版本中如果一個變數未設定成volatile,那麼便會有一個與最佳化相關的錯誤。如果時多線程,那程式出問題的可能性非常大。
15.發布版本中的變數最佳化問題:
void StackAttack () {
int optimizedOut1,optimizedOut2;
TCHAR bugsText[16], *bugs=_T("This function has bugs!");
_tcscpy(bugsText,bugs);
}
在這個函數中,bugsText緩衝區的長度不能接收bugs字串。不必要的變數optimizedOut1和optimizedOut2在調試版本中會保護堆棧內容不被破壞,但這些變數在發布版本中會被去掉。導致的結果是,緩衝區的溢出會破壞堆棧的函數返回地址,從而發布版的程式會崩潰,而在調試版中不會。一般的,被最佳化的變數不會這麼明顯。
12.最好為你的可執行程式建立偵錯符號,並將得到的PDB文檔存檔,即使程式屬於發布版本。
13.為程式的某個版本建立偵錯符號,要對程式所對應的Visual C++項目做如下的設定:
(1) 開啟工程設定對話方塊,在Setting...對話方塊中選擇所需的版本(如:Win32 Release)。
(2) 在工程式控制制樹裡,通過單擊根節點選擇整個工程。
(3) 在C/C++標籤裡選擇General類。在調試資訊裡,如果是發布版本則選擇Program Database,如果是調試版本則選擇Program Database for Edit and Continue(注意:編輯繼續選項與最佳化串連不相容,而且它還增大可執行檔的長度,從而不適合於發布版本)。
(4) 在Link標籤裡選擇Debug類。然後選擇Debug info和Microsoft format選項。記住不要選擇Separate types選項,這樣所有的調試資訊才會被合并到單獨的一個PDB檔案中。另外,如果你需要做事後調試的對應檔時,記住要選擇Generate Mapfile選項。
(5) 對於發布版本,選擇Link標籤,在Project Options對話方塊的最後加上“/OPT:REF”。這個選項使得不被引用的函數和資料不會出現在可執行檔中,從而避免了檔案無畏的增大。對於調試版本不要使用這個選項,因為它會關閉增量連結(incremential linking)
(6) 使用Rebuild All命令重新編譯整個工程。
注意:如果你發現帶有偵錯符號的可執行檔比不帶調試符的可執行檔大許多,很有可能你忘記了加上/OPT:REF連結選項。
14.為了進一步控制調試,要使用反組譯碼代碼視窗進行代碼的調試
15.最好不要使用Separate types選項,除非你要在一個非常慢的電腦上編連非常大的工程。
16.可以在觀察視窗中顯示TIB(thread information block)結構,使用@TIB,並在程式中添加如下代碼:
#ifdef _DEBUG
#include "tib.h"
PTIB pTIB
#endif
在觀察視窗中通過pTIB=@TIB來查看TIB內容。
17.使用Autoexp.dat
18.在Windows2000裡設定系統調用斷點:
(1) 確定包含API函數的模組。
findstr MessageBox win32api.csv
(2) 確定模組對應的偵錯符號已裝載
(3) 確定真正的函數名
dumpbin -symbols user32.dbg | findstr MessageBox
返回“_MessageBoxA@16”。如果偵錯符號沒有被裝載,使用命令:
dumpbin -exports user32.dll | findstr MessageBox
返回“MessageBoxA”,注意,“MessageBox”只會被前置處理器看到,它會將名字轉換為“MessageBoxA”或“MessageBoxW”,“A”代表ANSI,而“W”代表寬字元或Unicode。
(4) 在斷點對話方塊裡設定斷點。如果偵錯符號被裝載了,輸入
{,,user32.dll}_MessageBoxA@16
如果偵錯符號沒有被裝載,輸入
{,,user32.dll}MessageBoxA
如果偵錯符號沒有被裝載,你還需要在選項對話方塊的Debug標籤裡設定Load COFF&Export選項。這個選項允許你在沒有偵錯符號的情況下,在輸出函數上設定斷點。
註:如果沒有findstr.exe工具,可以使用Visual C++ 的 Find in Files命令。
19.如果傳回值不大於32位,可在觀察視窗鍵入“@EAX”查看,如果傳回值長為64位,其低32位會放在EAX中,高32位放在EDX中。如果傳回值大於64位,會在EAX中放入指向傳回值的指標,可以通過在觀察視窗中進行類型轉換,例如:若返回一個CRect,則可以鍵入“(CRect*)@EAX”顯示結果,或在記憶體視窗的Address欄中直接鍵入EAX查看傳回值。
20.使用API函數GetAsyncKeyState幫你調試WM_MOUSEMOVE訊息。
21.使用Spy++調試與訊息有關的問題。
22.使用回調協助你調試Windows代碼:回調允許你進入Windows看它在做些什麼。
23.PostMessage和SendMessage的區別: PostMessage只負責將訊息放到訊息佇列中,不確定何時及是否處理SendMessage要等到受到訊息處理的返回碼(DWord類型)後才繼續
24.可以使用X86的記憶體對齊規則來確定一個指標是否有效。堆棧、堆的指標都是雙字對齊的,所以,它們的最後一位應該是十六進位的0、4、8、C。當X86指令可以是任何大小,所以指令指標的最後一位可以是任何數字。函數是16位元組對齊,所以函數指標的最後一位應該總是零。
25.要發現對已被釋放的記憶體的訪問操作,一定要將被釋放的指標置為空白。