VC調(diào)試器高級(jí)應(yīng)用----高級(jí)斷點(diǎn)篇
一.高級(jí)斷點(diǎn)語法
高級(jí)斷點(diǎn)語法由兩部分組成:1.上下文部分.2.位置,表達(dá)式,變量或Windows消息條件.
用函數(shù),源文件和二進(jìn)制模塊來指定上下文,上下文的表示方法:
{[函數(shù)],[源文件],[二進(jìn)制模塊]}
必須指定唯一的,足夠的上下文信息才能獲取斷點(diǎn)位置.如在TEST.CPP的20行設(shè)一位置斷點(diǎn),語法為:{,TEST.CPP,}.20,如A.DLL或B.DLL都使用了該行,又只想在B.DLL的調(diào)用中觸發(fā),則必須使用:{,TEST.CPP,B.DLL}.20.
VC調(diào)試器中可直接輸入上下文語法:Breakpoints對(duì)話框的Location選項(xiàng)卡BreakAt編輯框中.更容易的方法是使用BreatAt框右的箭頭打開菜單,選擇Advanced項(xiàng),然后在Context框中輸入斷點(diǎn)的相應(yīng)信息.
如想在一個(gè)絕對(duì)地址上中斷,直接在BreakAt框中輸入地址就行.
二.任何函數(shù)上快速中斷
將函數(shù)名輸入BreadAt框中.如果是C++代碼,同時(shí)還需要類限定符.支持重載了的函數(shù),調(diào)試器會(huì)列出所有滿足條件的函數(shù)供選擇,如輸入時(shí)提供足夠的信息,完全可略過選擇過程.如輸入:"CString::operator=(const char *)"可唯一確定要中斷的函數(shù).
三.在系統(tǒng)或DLL輸出的函數(shù)中設(shè)置斷點(diǎn)
在程序中從DLL輸入的函數(shù)中設(shè)置一個(gè)斷點(diǎn)可能是毫無作用的,調(diào)試器需要知道在何處可以找到該函數(shù)上下文信息,同時(shí),函數(shù)名取決于是否加載了DLL的符號(hào).只有在W2K以上版本中才能在系統(tǒng)DLL中設(shè)置斷點(diǎn)--原因在于其它系統(tǒng)沒有提供邊寫入邊復(fù)制保護(hù)的功能,若一定要啟用這種方法,必須要有COFF(Common Object File Format),并在調(diào)試器中輸出啟動(dòng)的裝載----在Options對(duì)話框的Debug頁,將Load COFF & Exports選中.
VC調(diào)試器用分級(jí)的符號(hào)信息法,完整的符號(hào)的級(jí)別高于不太完整的.PDB(Program Database)文件具有所有可能的源碼行,函數(shù),變量和類型信息,優(yōu)先級(jí)便高于COFF/DBG文件,后者只有公用函數(shù)符號(hào),而COFF/DBG文件高于輸出名稱,輸入的名稱是一種偽符號(hào).
調(diào)試時(shí),如DEBUG窗口輸出:裝載DLL的符號(hào),則說明符號(hào)已被裝入;否則說明沒有裝載DLL的符號(hào).
沒有裝入符號(hào)時(shí),使用的位置字符串是DLL輸出的名稱,可能用DUMPBIN程序查看這個(gè)名稱:DUMPBIN /EXPORTS DLLname.例:在LoadLibraryA中設(shè)置中斷:"{,,Kernel32.dll}LoadLibraryA".
如裝入了符號(hào),則要根據(jù)輸出函數(shù)和調(diào)用協(xié)議來計(jì)算函數(shù)名.如上例,LoadLibraryA使用__stdcall調(diào)用協(xié)議,據(jù)該協(xié)議,函數(shù)名以下劃線為前綴,所跟有進(jìn)棧的字節(jié)數(shù)為后綴的@號(hào).一般說來,參數(shù)個(gè)數(shù)*4,就是參數(shù)占用棧空間的總字節(jié)數(shù),LoadLibary的名稱便是:_LoadLibraryA@4,故最后的語法是:或"{,,Kernel32.dll}_LoadLibraryA@4"
附:常用的調(diào)用協(xié)議
1、__stdcall調(diào)用約定相當(dāng)于16位動(dòng)態(tài)庫中經(jīng)常使用的PASCAL調(diào)用約定。在32位的VC++5.0中PASCAL調(diào)用約定不再被支持(實(shí)際上它已被定義為__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall調(diào)用約定。兩者實(shí)質(zhì)上是一致的,即函數(shù)的參數(shù)自右向左通過棧傳遞,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧,但不同的是函數(shù)名的修飾部分(關(guān)于函數(shù)名的修飾部分在后面將詳細(xì)說明)。
_stdcall是Pascal程序的缺省調(diào)用方式,通常用于Win32 Api中,函數(shù)采用從右到左的壓棧方式,自己在退出時(shí)清空堆棧。VC將函數(shù)編譯后會(huì)在函數(shù)名前面加上下劃線前綴,在函數(shù)名后加上"@"和參數(shù)的字節(jié)數(shù)。
2、C調(diào)用約定(即用__cdecl關(guān)鍵字說明)按從右至左的順序壓參數(shù)入棧,由調(diào)用者把參數(shù)彈出棧。對(duì)于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護(hù)的(正因?yàn)槿绱耍瑢?shí)現(xiàn)可變參數(shù)的函數(shù)只能使用該調(diào)用約定)。另外,在函數(shù)名修飾約定方面也有所不同。
_cdecl是C和C++程序的缺省調(diào)用方式。每一個(gè)調(diào)用它的函數(shù)都包含清空堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會(huì)比調(diào)用_stdcall函數(shù)的大。函數(shù)采用從右到左的壓棧方式。VC將函數(shù)編譯后會(huì)在函數(shù)名前面加上下劃線前綴。是MFC缺省調(diào)用約定。
3、__fastcall調(diào)用約定是“人”如其名,它的主要特點(diǎn)就是快,因?yàn)樗峭ㄟ^寄存器來傳送參數(shù)的(實(shí)際上,它用ECX和EDX傳送前兩個(gè)雙字(DWORD)或更小的參數(shù),剩下的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧),在函數(shù)名修飾約定方面,它和前兩者均不同。
_fastcall方式的函數(shù)采用寄存器傳遞參數(shù),VC將函數(shù)編譯后會(huì)在函數(shù)名前面加上"@"前綴,在函數(shù)名后加上"@"和參數(shù)的字節(jié)數(shù)。
4、thiscall僅僅應(yīng)用于“C++”成員函數(shù)。this指針存放于CX寄存器,參數(shù)從右到左壓。thiscall不是關(guān)鍵詞,因此不能被程序員指定。
5、naked call采用1-4的調(diào)用約定時(shí),如果必要的話,進(jìn)入函數(shù)時(shí)編譯器會(huì)產(chǎn)生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數(shù)時(shí)則產(chǎn)生代碼恢復(fù)這些寄存器的內(nèi)容。naked call不產(chǎn)生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。
關(guān)鍵字 __stdcall、__cdecl和__fastcall可以直接加在要輸出的函數(shù)前,也可以在編譯環(huán)境的Setting...C/C++ Code Generation項(xiàng)選擇。當(dāng)加在輸出函數(shù)前的關(guān)鍵字與編譯環(huán)境中的選擇不同時(shí),直接加在輸出函數(shù)前的關(guān)鍵字有效。它們對(duì)應(yīng)的命令行參數(shù)分別為/Gz、/Gd和/Gr。缺省狀態(tài)為/Gd,即__cdecl。
要完全模仿PASCAL調(diào)用約定首先必須使用__stdcall調(diào)用約定,至于函數(shù)名修飾約定,可以通過其它方法模仿。還有一個(gè)值得一提的是WINAPI宏,Windows.h支持該宏,它可以將出函數(shù)翻譯成適當(dāng)?shù)恼{(diào)用約定,在WIN32中,它被定義為__stdcall。使用WINAPI宏可以創(chuàng)建自己的APIs。
四.位置斷點(diǎn)修飾符
1.跳躍計(jì)數(shù).
功能是執(zhí)行斷點(diǎn)但不在斷點(diǎn)處停止,直到執(zhí)行完了一個(gè)特定的次數(shù)為止.
使用中首先設(shè)置一個(gè)標(biāo)準(zhǔn)的位置斷點(diǎn),打開BreadPoint對(duì)話框,選中該斷點(diǎn),單擊Condition,然后在彈出的對(duì)話框最下面的編輯控件中輸入次數(shù).
只有當(dāng)程序全速運(yùn)行時(shí),未執(zhí)行的循環(huán)次數(shù)才有用.單步執(zhí)行跨過斷點(diǎn)時(shí)不會(huì)更新跳躍計(jì)數(shù).
例:已知循環(huán)可能崩潰,但不清楚在哪次循環(huán)時(shí),輸入遠(yuǎn)遠(yuǎn)大于總循環(huán)次數(shù)的跳躍計(jì)數(shù)修飾符,則在崩潰時(shí)可打開Breakpoint框,其中將列出還未執(zhí)行的循環(huán)次數(shù),與總次數(shù)相減就可得已執(zhí)行的次數(shù).
2.條件表達(dá)式.
只有表達(dá)式為真時(shí)觸發(fā).Breakpoint框Condition按鈕,選第一個(gè)編輯框,輸入表達(dá)式即可.規(guī)則:
.只可使用C類型比較運(yùn)算符.
.表達(dá)式中不能調(diào)用任何函數(shù).
.表達(dá)式中不能包含任何宏值.
表達(dá)式為@TIB=Thread Infomation Block Linear Address,則程序只在該特定線程中才會(huì)中斷.例:線程@TIB地址值為0E000,則輸入"@TIB==0xE000",則在切換到該線程時(shí)中斷.對(duì)W98,可用@FS=thread specific value.
如在某特定錯(cuò)誤后中斷,則可用@ERR,如"@ERR=2"表示在最后錯(cuò)誤為ERROR_FILE_NOT_FOUND.除@CLK外,所有可在WATCH窗口中使用的偽寄存器均可用于條件表達(dá)式.
條件表達(dá)式可與跳躍斷點(diǎn)組合使用.
3.變量更改
在變量更改時(shí)中斷程序.只有當(dāng)位置斷點(diǎn)執(zhí)行時(shí)才能檢查變量.常用用調(diào)用棧高層的函數(shù)中發(fā)現(xiàn)出錯(cuò),需要深入調(diào)用棧,壓縮范圍找出根源時(shí).
添加時(shí)在Breakpoint框第一個(gè)編輯框中輸入變量名(可以是指針指向聽對(duì)象:*p),在第二個(gè)編輯框中輸入要查看的項(xiàng)目數(shù)量.
五.全局表達(dá)式和條件斷點(diǎn).
調(diào)試器可監(jiān)控某一地址和該地址上的1,2或4字節(jié)的內(nèi)容.如可用硬件調(diào)試寄存器,則不影響速度;否則程序?qū)尾綀?zhí)行ASM指令并在每一步中檢查條件,這將嚴(yán)重影響程序運(yùn)行速度.
總共有4個(gè)調(diào)試寄存器.硬件調(diào)試寄存器不能處理超過1個(gè)雙字長的引用.確保利用硬件調(diào)試寄存器的最好方法是使用表達(dá)式和數(shù)據(jù)更改位置的實(shí)際地址值.例如:g_szGlobal是全局?jǐn)?shù)組指針,地址為0x5000,則在Breakpoint對(duì)話框中DATA選項(xiàng)卡中將表達(dá)式斷點(diǎn)設(shè)為"*(char*))0x5000=='G'",但如果寫為"WO(0x5000)=='G',則用不到硬件調(diào)試寄存器,會(huì)單步執(zhí)行每條指令.
與全局表達(dá)式斷點(diǎn)類似,使用變量的16進(jìn)制地址給定長指針計(jì)算地址,并將要查看的單元數(shù)設(shè)為1,則全局變量斷點(diǎn)可發(fā)揮最付佳功效.如上例要在變量改動(dòng)時(shí)中斷,則輸入:"*(long*)0x5000".
六.WINDOWS消息斷點(diǎn).
Breakpoint框的Message頁.需要指定一個(gè)窗口過程,注意:MFC世界中AfxWndProc是多數(shù)窗口的一個(gè)窗口過程,所以總會(huì)在該斷