WinDbg
WinDbg支持以下三種類型的命令:
· 常規命令,用來調試進程
· 點命令,用來控制調試器
· 擴展命令,可以添加叫WinDbg的自定義命令,一般由擴展dll提供這些命令
PDB文件
PDB文件是由鏈接器產生的程序數據庫文件。私有PDB文件包含私有和公有符號,源代碼行,類型,本地和全局變量信息。公有PDB文件不包含類型,本地變量和源代碼行信息,且只包含共有成員的調試信息。
Dump文件
利用Dump工具,你可以獲得進程的快照信息。一個mini-dump包含當前進程的所有線程,線程棧信息和已加載模塊信息。一個full-dump包括更多信息,如堆信息。
使用WinDbg調試
1. 啟動WinDbg
要用WinDbg(x86)調式32位程序,用WinDbg(x64)調試64位程序。
2. 使用幫助
任何時候都可以使用!help命令來獲取幫助,查看命令的使用方法。
3. 設置SymbolFile Path,指定了符號庫,我們才能看到詳細的類型信息
SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
WinDbg會將微軟的符號庫下載指定的本地目錄中,以上設置可以使用以下命令來實現:
.symfixc:\symbols,此命令表示要連接到Microsoft服務器下載調試符號文件,符號文件將被下載到c:\symbols目錄中。
還可以使用.sympath命令來顯示當前的符號路徑設置。
4. 重新加載符號
如果進入調試之后才指定的符號路徑,需要使用命令來重新加載符號
.reload
5. 加載調試擴展
調試擴展定義了很多命令來調試.net程序。使用調式非托管代碼的命令來調試.net程序是非常困難的。下面的命令加載了用於調試.net程序的sos模塊。
a. 對於.Net Runtime 2.0
.loadby sos mscorwks
b. 對於.Net Runtime 4.0
.loadby sos clr
sos是一個dll,定義了很多針對.net assembly的調試命令,sos.dll針對不同的runtime有不同的版本。如果使用.load命令,需要為sos.dll指定完整路徑。如果使用.loadby命令,則表示要在mscorwks.dll和clr.dll的同一目錄下查找sos。對於.net程序,基本上都會加載mscorwks.dll(2.0)和clr.dll(4.0)。
6. 查看線程
a. 包括托管線程和非托管線程
~
b. 查看托管線程
!threads
c. 顯示有關托管線程池的信息,包括隊列中工作請求的數目、完成端口線程的數目和計時器的數目。
!ThreadPool
d. 切換線程(中間的數字表示線程號)
~0s
e. 查看線程棧, 僅提供托管代碼的堆棧跟蹤。
!clrstack [-a] [-l] [-p] [-n]
-p 選項顯示托管函數的參數。
-l 選項將顯示有關幀中的局部變量的信息。若SOSdebugging extension 未能檢索本地名稱,因此,本地名稱的輸出格式<local address>= <value>。
-a (all) 選項是一個表示 -l 和 -p 的組合的快捷方式。
-n 選項禁止顯示源文件名和行號。
在基於x64 和IA-64 的平台上,SOS調試擴展不顯示過渡幀。
f. 查看線程棧,只能正常顯示非托管部分
k
g. 顯示線程棧全部信息,包括托管和非托管部分
!dumpstack [-EE] [-n] [top stack [bottom stack]]
-EE 選項使DumpStack 命令僅顯示托管函數。使用top 和bottom 參數可限制x86 平台上顯示的堆棧幀。
-n 選項禁止顯示源文件名和行號。如果調試器已指定選項
顯示所有線程棧信息
h. 對一個進程中的所有線程運行 DumpStack 命令
!EEStack [-short] [-EE]
將-EE 選項直接傳遞給DumpStack 命令。-short 參數將輸出限制為以下類型的線程:
· 已獲取鎖的線程。
· 己停止運行以允許垃圾回收的線程。
· 當前在托管代碼中的線程。
i. 顯示所有線程的堆棧
~*e !clrstack
j. 顯示在當前堆棧的邊界內找到的所有托管對象
!dumpstackobjects [-verify] [top stack [bottom stack]] 或 !dso
7. 查看堆中的所有對象信息,包括類型信息,個數,大小等
!dumpheap –stat
a. 指定對象類型,如果需要結果准確,需要使用全名稱,類型名稱是大小寫敏感的
!dumpheap -type FlowThrottle –stat
b. 以上命令輸出的第一列是mt(method table)信息,表示類型對象的地址。我們可以使用此信息來明確指定我們感興趣的對象在堆中的信息。
!dumpheap -mt 000007feee769c00 -stat
注意,如果不指定-stat,那么輸出每個對象的信息,使用-stat,將會看到統計信息。
c. 使用如下命令只輸出對象地址
!dumpheap -mt 000007feee769c00 -short
d. -short 選項將輸出限制為只是每個對象的地址。這使您方便地以管道方式將輸出從該命令轉移到另一個調試器命令,以便自動化。
e. -min 選項忽略小於 size 參數指定的大小(單位為字節)的對象。-max 選項忽略大於size 參數指定的大小(單位為字節)的對象。
8. 顯示有關內部公共語言運行時數據結構所使用的進程內存的信息。
EEHeap [-gc][-loader]
-gc 和-loader 選項將此命令的輸出限制為垃圾回收器或加載程序數據結構。有關垃圾回收器的信息列出了托管堆中每個段的范圍。如果指針落在由-gc 給出的段范圍內,則該指針是一個對象指針。
9. 打印對象信息,指定任何有效的對象地址,就能查看該對象的內容
!dumpobj <address>或者 !do<address>
輸出的對象信息中有每個屬性的地址信息,所以可以繼續使用!do命令來打印屬性指向的對象信息。
!dumpobj –nofields<address>或者!do <address>
nofields選項指示不要輸出對象字段信息,當對象是string類型時,該選項就非常有用。
10. 顯示有關指定地址處的值類字段的信息。
DumpVC<MethodTable address> <Address>
MethodTable 參數使DumpVC 命令可以正確解釋字段。值類不使用方法表作為它的第一個字段。
11. 輸出多個對象信息
.foreach (myobj{!dumpheap -mt 008f4104-short}) {!do ${myobj}}
以上命令指示,對於堆中所有類型為008f4104的對象,依次調用!do命令。
12. 打印數組信息
!dumparray [-start<startIndex>] [-length <length>] [-details] [-nofields] <arrayobject address>或者!da
· -start 選項指定開始顯示元素的起始索引。
· -length 選項指定要顯示的元素數量。
· -details 選項使用 DumpObj 和 DumpVC 格式顯示元素的詳細信息。
· -nofields 選項可阻止顯示數組。此選項只有在指定了-detail 選項之后才可用。
下面的命令顯示在地址 00ad28d0 處的數組內容。顯示從第二個元素開始,連續顯示五個元素。
!dumparray -start2 -length 5 -detail 00ad28d0
13. 輸出app domain信息
!dumpdomain
枚舉在指定的 AppDomain 對象地址內加載的每個Assembly 對象。若在調用DumpDomain 命令時不提供任何參數,則將列出進程中的所有AppDomain 對象。
14. 輸出程序集信息
!dumpassembly
15. 打印方法表
!dumpmt [-MD]<MethodTable address>
顯示有關指定地址處的方法表的信息。指定-MD 選項將顯示與對象一起定義的所有方法的列表。每個托管對象都包含一個方法表指針。
16. 打印EEClass結構,其中可以看到類型靜態變量信息
!dumpclass<EEClass address>
顯示有關與類型關聯的 EEClass 結構的信息。DumpClass命令顯示靜態字段值,但不顯示非靜態字段值。使用DumpMT、DumpObj,Name2EE 或Token2EE 命令獲取EEClass 結構的地址。
17. 顯示異常
a. 使調試器在引發指定異常時停止,但在引發其他異常時繼續運行。
!StopOnException [-derived] [-create | -create2]<Exception> <Pseudo-register number>
-derived 選項用於捕獲指定異常以及從指定異常派生的每個異常。
b. 顯示當前活動線程上的最后一個異常
!PrintException [-nested] [-lines] [<Exception objectaddress>] 或 !PE
顯示從指定地址處的Exception 類派生的任何對象的字段並設置這些字段的格式。如果不指定地址,PrintException命令將顯示在當前線程上引發的最后一個異常。
-nested 選項顯示有關嵌套異常對象的詳細信息。
-lines 選項顯示源信息(如果可用)。
c. 顯示發生在所有線程上的最后的異常
~*e !pe
18. 調試GC相關信息
a. 顯示有關對指定地址處的對象的引用(或根)的信息。
!GCRoot [-nostacks] <Object address>
GCRoot 命令將檢查整個托管堆和句柄表以查找其他對象內的句柄和堆棧上的句柄。然后,在每個堆棧中搜索對象的指針,同時還搜索終結器隊列。此命令無法確定堆棧根是有效的還是已丟棄。使用CLRStack 和U 命令可對本地或參數值所屬的幀進行反匯編,以便確定堆棧根是否仍在使用中。
-nostacks 選項將搜索限制為垃圾回收器句柄和 Freachable 對象。
b. 顯示所有已進行終結注冊的對象。
!FinalizeQueue [-detail] | [-allReady] [-short]
-detail 選項顯示有關需要清理的任何 SyncBlocks 的額外信息以及有關等待清理的任何RuntimeCallableWrappers (RCW) 的額外信息。這兩種數據結構都由終結器線程在運行時進行緩存和清理。
-allReady 選項顯示所有准備終止的對象,無論它們已被垃圾回收標記成這樣,還是將被下一個垃圾回收標記。在“准備終止”列表中的對象為不再為根的可終止對象。此選項可能耗費大量資源,因為它驗證可終止隊列中的所有對象是否仍然為根對象。
-short 選項將輸出限制為每個對象的地址。如果與-allReady 一起使用,則將枚舉具有不再為根的終結器的所有對象。如果單獨使用,則將列出終結和“准備終結”隊列中的所有對象。
19. 設置斷點
!bpmdSystem.Windows.Forms.dllSystem.Windows.Forms.MessageBox.Show
第一個參數是dll文件名,第二個是完整的方法名。
20. 查看所有斷點列表
bl
21. 釋放當前斷點, 讓程序繼續運行。讓程序運行到斷點后WinDBG會自動停下來。
g
22. 顯示公共語言運行時版本。
!eeversion
23. 清除屏幕信息,該命令還你一個清潔的屏幕
.cls
24. 退出當前調試
q
//斷點相關
bp + 地址設置斷點
bl 顯示已經設定的斷點
bu + 地址設置斷點,但是這種類型斷點再下一次啟動時被記錄
bc 清除斷點
對於斷點范圍,可以用*匹配,-表示一個范圍,表達多個可用,號隔開
程序入口偽寄存器
WinDbg里有個偽寄存器叫$exentry,里面記錄了程序的入口點。所以我們只要在命令輸入欄里輸入
bp $exentry
(bp就是用來下斷點的命令,詳細用法可以參考WinDbg的幫助文檔)
//調試符號
ld kernerl32 //加載kernerl32模塊的符號
lm m k* //顯示已經加載的,以k開頭的模塊
ln //顯示最近操作過的模塊名
dt dbg2 //檢測模塊
[[[[[[[[[[[[]]]]]]]]]]]]
x kernerl32!k* 顯示模塊kernerl32中所有以k開頭的函數
dv 顯示局部變量值
dv /i/t/v 顯示局部變量的類型,值相關信息。
x <module>!* / ? 顯示指定模塊的符號
x argc 查看變量argc的值。
dt argc 查看變量值
dt _PEB 7ffdd00 將內存地址7ffdd00開始的內容以PEB結構的方式顯示出來。
dd 12000 L4 查看地址12000 后面的四個字
dds 12000 L100 查看堆棧上地址12000開始,后面的100個dword的內容,如果有調試符號,會將符號顯示。此方法來追蹤堆棧。(先看ebp,再用此方法)
dd ebp + 4, 返回地址, ebp + 8 第一個參數
[[[[[[[[[]]]]]]]]]
.kill 殺死調試進程
.restart 重新調試
[[[[[]]]]]]]]]]]]]]
k 顯示調用堆棧
,kn加序號而已。
kb 顯示前三個參數。第一個參數ebp+8;第二個ebp+0x0C;第三個ebp+0x10;dd ebp+0x14是第四個參數
kp 顯示函數參數類型,數值
kp f f開關顯示相鄰棧基之差,從而可以推斷出棧的健康狀況。
[[[[[[[[[[]]]]]]]]]]
| 顯示進程
~顯示線程
~0 s 切換到 0號線程
[[[[[[[[[]]]]]]]]]
dv 顯示函數參數&局部變量,注意,dv是跟棧幀相關的,對不同的棧幀顯示不同的局部變量。
@1, kn 顯示所有棧幀
@2, .frame選擇想要查看的棧幀
@3, dv /i/v/t顯示該棧幀里局部變量信息
@3, dv /i /V /t 顯示變量基於棧幀的地址
如果沒有私有符號,dv是不能顯示變量信息的。
vc 生成的調試符號*.pdb windbg不認識,需要設置為c++/General/DebugInfo= C7 compatible
=====
sympath + c:\nasm 添加符號搜索路徑
.sympath 顯示符號搜索路徑
//顯示一定范圍內存
!db L 32 : results in 32 bytes being displayed (as hexadecimal bytes),
//查看pe信息
!dh [Options] Address : 查看模塊pe信息
!dh -f : display file headers
!dh -s : section headers
!dh -a : all header informations
查看結構體成員
dt nt!_EPROCESS
查看當前的irql
!irql
查看Verifier 檢測統計信息
!verifier
查看某個內存地址屬於那一個模塊
!pool 地址
!lmi Address : 查看模塊的主要信息
!pcr 可以查看當前執行的線程及irql, 等信息
//
Why doesn't the WinDBG command !irql always return the correct IRQL for my target?
[Answer by Jake Oshins, jakeo_at_windows_dot_microsoft_dot_com. Workaround provided by James Antognini, antognini_at_mindspring_dot_nospam_dot_com, 27 August 2003]
!irql currently only produces useful results on a crashdump, not a live system. To retrieve the current IRQL on a live system you should instead use the !pcr command.
!processfield:列出EPROCESS的成員
該命令前的!號,意味着它來自於調試器的擴展模塊―kdextx86.dll。該命令可顯示內核用來代表一個進程的EPROCESS結構(該結構並沒有正式的說明文檔)的成員及其偏移量。
盡管該命令僅列出了成員的偏移量,但你也能很容易的猜出其正確的類型。例如,LockEvent位於0x70處,其下一個成員的偏移量為0x80。則該成員占用了16個字節,這與KEVENT結構非常類似。
!threadfields:列出ETHREAD成員
這是kdextx86.dll提供的另一個強大的選項。和!processfields類似,它列出未文檔化的ETHREAD結構的成員及其偏移量。內核使用它表示一個線程.
//進程信息
!tep
!peb ,顯示peb(進程信息)
//顯示相關
dt ntdll!*teb* 列出匹配通配符的結構名
dt -v -r ntdll!_TEB
列出結構_TEB的成員信息
//顯示變量地址
r $peb 顯示模塊peb的地址
//查看錯誤信息
!gle
//設置斷點的技巧
可以直接把斷點設在: kernel32!BaseProcessStart
1), 先用lm 顯示所有已經加載的模塊
2), dt our_exe_name!*main* //在我們的程序模塊中搜索包含main的地址(注意:如果未加載symbol是不能顯示的!)
3), 如果存在,在our_exe_name!*main 處設置斷點
=======
Command SoftICE OllyDbg
Run F5 F9
Step Into F11 F7
Step Over F10 F8
Set Break Point F8 F2
搜索內存
5、查找字符串
在步驟1我們運行程序時就記錄了提示注冊錯誤的字符串“Wrong Serial, try again!”,現在我們就要在內存找到該字符串的位置。
輸入命令
s –a 00400000 L53000 “Wrong”
該命令的意思是以ASCII碼形式在內存地址00400000往后53000個字節搜索字符串“Wrong”。
s,就是要調用查找的命令
-a,指定使用ASCII碼的形式查找
00400000,指定要開始尋找的內存地址。
L53000,說明要在00400000往后的53000字節搜索。這個數值和00400000都可以從Stud_PE獲得。00400000是程序的裝入地址,而53000是映像的大小,也就是程序載入內存后占用的內存大小。使用這兩個數值,基本上可以搜索到程序使用的整個內存范圍。
“Wrong”,就不用多解釋了,就是我們要尋找的字符串。不過WinDbg不支持模糊搜索,所以這里輸入的字符串必定要完全正確。
內存訪問斷點
6、下內存訪問斷點
WinDbg中,ba命令代表Break On Access,即訪問時中斷。
我們在命令行輸入:
ba r 1 0044108c
命令的意思是在內存0044108c的位置下字節的讀斷點。命令中各元素的含義可以參考幫助文檔,這里不啰嗦。
輸入bl,查看斷點使用情況:
地址運算
? 0x33 + 0x44
運行后將得到計算和
3.查看和修改數據
調試中不可避免的要查看和修改數據
查看內存:
db/dw/dd/dq [Address] 字節/字/雙字/四字方式查看數據
da/du [Address] ASCII字符串/Unicode字符串方式查看指定地址
其它常用的如查看結構
dt nt!_EPROCESS
dt nt!_EPROCESS 89330da0 (把0x89330da0作為對象指針)
修改內存:
eb/ew/ed/eq/ef/ep Address [Values]
字節/字/雙字/四字/浮點數/指針/
ea/eu/eza/ezu Address [Values]
ASCII字符串/Unicode字符串/以NULL結尾的ASCII字符串/以NULL結尾的Unicode字符串
搜索內存:
s -[b/w/d/q/a/u] Range Target
搜索字節/字/雙字/四字/ASCII字符串/Unicode字符串
2.斷點
斷點之於調試當然是非常重要的
常用命令:
bp [Address]or[Symbol] 在指定地址下斷
可以使用地址或符號,如
bp 80561259(Windbg默認使用16進制)
bp MyDriver!GetKernelPath
bp MyDriver!GetKernelPath+0x12
bp [Address] /p eprocess 僅當當前進程為eprocess時才中斷
這個很常用,比如你bp nt!NtTerminateProcess,但是只想在某一進程觸發此斷點時才斷下來,那就加上這個參數吧,因為內核中的代碼是各個進程共用的,所以此命令很實用
bp [Address] /t ethread 僅當當前線程為ethread時才中斷,用法跟/p參數類似
bu [Address]or[Symbol] 下一個未解析的斷點(就是說這個斷點需要延遲解析)
這個也很常用,比如我們的驅動名為MyDriver.sys,那么在驅動加載之前下斷bu MyDriver!DriverEntry,
然后加載這個驅動時就可以斷在驅動入口,並且這個是不需要調試符號支持的
bl 列出所有斷點,L=List
bc[id] 清除斷點,c=Clear,id是bl查看時的斷點編號
bd[id] 禁用斷點,d=Disable,id即斷點編號
be[id] 啟用斷點,e=Enable,id為斷點編號
windbg u只能顯示幾行怎么多顯示 加 l 后面跟數字茹 u NtOpenProcess l100
u NtOpenKey NtOpenkey+50 (顯示50字節) 要查看的地址+要顯示的字節
條件斷點(condition breakpoint)的是指在上面3種基本斷點停下來后,執行一些自定義的判斷。
在基本斷點命令后加上自定義調試命令,可以讓調試器在斷點觸發停下來后,執行調試器命令。每個命令之間用分號分割。
語法格式如:
0:000> bp Address "j (Condition) 'OptionalCommands'; 'gc' "
0:000> bp Address ".if (Condition) {OptionalCommands} .else {gc}"
這兩條是等價的.
當然
.if
{
}
.else
{
}
更好理解.
0:000> bp `mysource.cpp:143` "j (poi(MyVar)>0n20) ''; 'gc' "
0:000> bp `mysource.cpp:143` ".if (poi(MyVar)>0n20) {} .else {gc}" 使用POI這里用的不是[] 而是()
若MyVar大於20則不stop,
否則stop下來進行調試.
MyVar符號表示符號所在的內存地址,而不是符號的數值,相當於C語言中的 &操作符的作用。Windbg命令poi的作用是取這個地址上的值,相當於C語言中的*操作符.因此這里取得MyVar的值.
偽寄存器,幫助保存調試的中間信息
考慮這樣的情況,如果要記錄某一個函數被執行了多少次,應該怎么做?簡單的做法就是修改代碼,在對應的函數入口做記錄。可是,如果要記錄的函數是系統API呢?
設置寄存器 條件斷點
當eax內的值為0xa3時斷點Sop. 沒問題,Hah.
0:000> bp mydriver!myFunction "j @eax = 0xa3 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xa3 {} .else {gc}"
但以下就不一定了,當eax中人值為0xc0004321時,
不一定會斷下來.
為什么呢?
原因是內核態時,MASM會對EAX中的值進行符號擴展.
那么0xc0004321 會變成0xFFFFFFFFc0004321
這樣當然斷不下來啦。
0:000> bp mydriver!myFunction "j @eax = 0xc0004321 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xc0004321 {} .else {gc}"
如何處理呢?看看下面就知道了.
0:000> bp mydriver!myFunction "j (@eax & 0x0`ffffffff) = 0x0`c0004321 '';'gc'"
0:000> bp mydriver!myFunction ".if (@eax & 0x0`ffffffff) = 0x0`c0004321 {} .else {gc}"
爽吧,高位清0!
下面的命令可以統計VirtualAllocEx被執行了多少次:
bp /1 /c @$csp @$ra;g
bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf /"function executes: %d times /",@$t0;.echo;g"
這里用到的$t0就是Windbg提供的偽寄存器。可以用來存儲中間信息。這里用它來存儲函數執行的次數。r命令可以用來查看,修改寄存器(CPU寄存器和Windbg的偽寄存器都有效)的值。隨便挑一個繁忙的進程,用這個命令設定斷點后觀察:
0:009> bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf
/"function executes: %d times /",@$t0;.echo;g"
0:009> g
function executes: 1 times
function executes: 2 times
function executes: 3 times
function executes: 4 times
…
哈哈,這確實是一個好方法.