Windbg教程-调试非托管程序的基本命令中
前面的文章調試非托管程序的基本命令上講到如何在windbg里面啟動一個程序并且加載調試符號文件。一旦符號文件加載完畢以后,就可以進行調試了,例如設置斷點,查看堆棧信息等等。
?
因為是剛剛啟動程序(main函數還沒有機會執行),可以查看源代碼了解要設置斷點的地方。設置斷點可以使用bp、bu和bm來做,其中bp可以根據函數名、指令地址以及源代碼文件地址來設置斷點。
?
bp命令是在設置斷點過程用的比較多的一個命令,下面的表格演示了它的簡單用法:
| 命令格式 | 示例 | 說明 |
| bp?函數名 | bp Usage | 在函數Usage的入口中斷程序的執行。 |
| bp?指令地址 | bp 010113c0 | 在執行地址在010113c0的指令前中斷程序的執行。 |
| bp `源文件地址` | bp `nativedebug.cpp:21` | 在源代碼nativedebug.cpp的第21行設置斷點,請注意符號“`”(感嘆號鍵左邊的反引號)。 ? ? |
???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
如果你有源代碼的話,通過windbg的菜單“File”—“Open Source File”打開源文件,找到相應的代碼行,按下鍵盤的F9就可以設置斷點了(當然前提條件是你已經設置好正確的符號文件,符號文件請參考文章Visual Studio調試之符號文件)。
?
看起來好像沒有什么特別的,只不過是設置斷點的方法比Visual studio復雜一些罷了,不過在windbg中,bp等命令允許在觸發斷點的時候執行一系列的調試命令。例如中斷程序后,打印堆棧,保存內存文件然后退出,或者執行一個小的調試命令腳本程序等等,這個過程與visual studio里面的跟蹤斷點(Trace Point)非常相像,當然操作起來稍微復雜一些(visual studio的跟蹤斷點的用法請參考文章Visual Studio調試之斷點技巧篇)。在windbg中設置觸發斷點執行其他命令的方法會在后續的文章里面講到。
?
例如在調試本文的示例程序(示例程序在文章調試非托管程序的基本命令上里面),可以執行以下的命令:
bp Usage
#
#?沒有輸出結果,正所謂沒有消息就是好消息,如果斷點成功設置,
#?windbg不會顯示任何信息。
#
?
如果在設置斷點時,出現類似下面的消息:
?
bp UsageA
#
#?輸出結果
#
Bp expression 'UsageA' could not be resolved, adding deferred bp
?
那么有兩個檢查步驟,第一是檢查符號文件是否正確加載,第二步是檢查設置斷點的函數名是否真的存在于程序當中。
?
第一步,檢查符號文件是否正確加載,可以使用lm命令查看已加載模塊的詳細信息,例如在上面的例子中,我們相信UsageA命令應該在模塊nativedebug.exe中,可以執行下面的命令來查看nativedebug模塊的詳細信息(請注意模塊名是緊跟在vm選項后面的,沒有空格,沒有后綴名,也沒有蛀牙):
?
lm vmnativedebug
#
#?輸出結果
#
start??? end??????? module name
#?注意下面這一行里面的private pdb symbols,說明我們已經加載了正確的符號文件。
#?至于private的含義,會在以后的文章里面講到。
01000000 0101b000?? nativedebug C (private pdb symbols)?D:\Debuggers\sym\nativedebug.pdb\E873A517513C4CC9BA5C805D1A709F206\nativedebug.pdb
??? Loaded symbol image file: nativedebug.exe
# Image path指明了模塊加載的路徑,在64位機器上調試程序的時候,
#這個信息是蠻有用的?。因為你需要知道一些系統模塊是在system32還是
# SysWow64文件夾里加載的。
??? Image path: nativedebug.exe
??? Image name: nativedebug.exe
??? Timestamp:??????? Sat Feb 20 20:05:20 2010 (4B7FD000)
??? CheckSum:???????? 00000000
??? ImageSize:??????? 0001B000
??? Translations:???? 0000.04b0 0000.04e4 0409.04b0 0409.04e4
?
順便說一下,因為nativedebug是我們自己編譯的,有一些版本方面的信息在編譯的時候沒有加進去。如果你查看一個Windows自帶的模塊的詳細信息的話,你可能會看到類似下面的輸出:
?
lm vmntdll
#
#?輸出結果
#
start??? end??????? module name
775f0000 7772c000?? ntdll????? (pdb symbols)?????????D:\Debuggers\sym\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb
??? Loaded symbol image file: ntdll.dll
??? Image path: ntdll.dll
??? Image name: ntdll.dll
??? Timestamp:??????? Tue Jul 14 09:09:47 2009 (4A5BDADB)
??? CheckSum:???????? 0014033F
ImageSize:????????0013C000
#?模塊的版本號,如果你的程序象微軟的產品那樣有多個版本,而且需要對多個
#?版本提供技術支持的話,下面的信息對于找到正確版本的符號文件非常非常非常
#?重要。
??? File version:???? 6.1.7600.16385
??? Product version:?6.1.7600.16385
File flags:???????0 (Mask 3F)
#?模塊要求的子系統
??? File OS:????????? 40004 NT Win32
??? File type:??????? 2.0 Dll
??? File date:??????? 00000000.00000000
??? Translations:???? 0409.04b0
??? CompanyName:????? Microsoft Corporation
??? ProductName:????? Microsoft? Windows? Operating System
??? InternalName:???? ntdll.dll
??? OriginalFilename: ntdll.dll
ProductVersion:???6.1.7600.16385
#?下面只顯示了已發布的產品的信息,版本號已經在前面的注釋里介紹過了。
# win7_rtm的意思是當前的模塊是從win7_rtm這個源代碼分支里編譯出來的。
#?版本分支的概念在團隊軟件產品開發過程中是一個平常的做法,大部分版本
#?控制軟件都支持代碼分支的做法。這個過程解釋起來有點復雜,現在你需要
#?知道的是,如果你現在工作的公司沒有采取版本分支的做法,那么祝賀你,
#?至少在尋找符號文件的過程里,你會比較輕松(不需要考慮分支的影響),
#?雖然會在后面發布高質量的軟件產品你的團隊會死的比較難看。
#?如果你工作的公司正在采取版本分支的做法的話,那么你一定要在正確的分支
#?下尋找對應版本的符號文件,否則你會死的很難看。
#
#?另外,下面一行的輸出里還有一個重要的信息沒有顯示,那就是模塊是否為調試版
#?,還是發布版。與軟件分支一樣,如果考慮進去,也是一樣無法加載到正確的
#?符號文件的。
#
#?如果使用類似微軟的方法編譯軟件,會在后面的文章中講到。
??? FileVersion:????? 6.1.7600.16385 (win7_rtm.090713-1255)
??? FileDescription:?NT Layer DLL
#?這個嘛,地球人都知道。
????LegalCopyright:?? ? Microsoft Corporation. All rights reserved.
?
既然知道符號文件已經被正確加載,那么下一步就是確認設置的函數名是否存在于模塊中,可以使用x命令來檢查符號文件保存的名字信息—就是函數名呀,全局變量名之類的信息。如果直接調用x命令,windbg會顯示模塊里面所有的名字。一般都是使用x加上一個匹配模式來查找指定的名字在模塊中是否已定義。比如,為了檢查UsageA這個名字在nativedebug.exe模塊中是否已定義,可以執行下面的命令來查看(感嘆號前面是告訴x命令要在哪一個模塊中查找名字,感嘆號后面就是要查找的名字):
?
x nativedebug!UsageA
#
#?輸出結果—沒有輸出結果
#
?
如果x沒有找到指定的名字,就不會輸出任何信息,否則,會有類似下面的輸出:
?
x nativedebug!Usage
#
#?輸出結果,前面的地址是函數入口在內存中的地址,而后面則顯示了函數的聲明信息。
#
010113c0 nativedebug!Usage (void)
?
X命令允許你在查找過程中使用通配符進行匹配,例如,在我們的示例程序中,被用來執行轉換的“函數”_ttol不是一個真實的函數,而是一個宏。下面是這個宏的定義:
?
#ifdef?_UNICODE
#?? define?_ttol?????? _wtol
#else
#?? define?_ttol?????? atol
#endif
?
而宏是在編譯期間就被編譯器擴展,并不會被加到符號文件中去,因此如果你試圖使用bp命令在_ttol入口設置斷點的話,是會失敗的。因此你可以使用類似下面的通配符來查找正確的函數名:
x MSVCR90D!*tol*
#
#?輸出結果(注意黃色高亮的名字)
#
65cd1bb0 MSVCR90D!__STRINGTOLD (struct _LDOUBLE *, char **, char *, int)
65d47c80 MSVCR90D!_ld12told (struct _LDBL12 *, struct _LDOUBLE *)
65cd1900 MSVCR90D!_atoldbl (struct _LDOUBLE *, char *)
65cd6790 MSVCR90D!_wcstol_l (wchar_t *, wchar_t **, int, struct localeinfo_struct *)
65cd4400 MSVCR90D!strtol (char *, char **, int)
65d4bac0 MSVCR90D!__mtold12 (char *, unsigned int, struct _LDBL12 *)
65cd5030 MSVCR90D!_tolower_l (int, struct localeinfo_struct *)
65cd6300 MSVCR90D!wcstol (wchar_t *, wchar_t **, int)
65ca0d50 MSVCR90D!atol (char *)
65cd4940 MSVCR90D!_strtol_l (char *, char **, int, struct localeinfo_struct *)
65ca12d0 MSVCR90D!_wtol (wchar_t *)
65d4a980 MSVCR90D!__wstrgtold12_l (struct _LDBL12 *, wchar_t **, wchar_t *, int, int, int, int, struct localeinfo_struct *)
65d544e0 MSVCR90D!_ftol (void)
65cd5010 MSVCR90D!_tolower (int)
65cd5210 MSVCR90D!tolower (int)
65ca12f0 MSVCR90D!_wtol_l (wchar_t *, struct localeinfo_struct *)
65d48fd0 MSVCR90D!__dtold (struct _LDOUBLE *, double *)
65ce3c30 MSVCR90D!_mbctolower_l (unsigned int, struct localeinfo_struct *)
65d48dc0 MSVCR90D!__STRINGTOLD_L (struct _LDOUBLE *, char **, char *, int, struct localeinfo_struct *)
65cd17f0 MSVCR90D!_atoldbl_l (struct _LDOUBLE *, char *, struct localeinfo_struct *)
65ce3d80 MSVCR90D!_mbctolower (unsigned int)
65ca0d70 MSVCR90D!_atol_l (char *, struct localeinfo_struct *)
65d38670 MSVCR90D!__lc_strtolc (struct tagLC_STRINGS *, char *)
65cdd8e0 MSVCR90D!CPtoLCID (int)
65d47d40 MSVCR90D!__strgtold12_l (struct _LDBL12 *, char **, char *, int, int, int, int, struct localeinfo_struct *)
65c6109c MSVCR90D!_imp__FileTimeToLocalFileTime = <no type information>
?
在上面的輸出,可以看到atol和_wtol在msvcr90d.dll這個模塊中都定義了,而我們現在不是很確定當時程序編譯的時候,_UNICODE這個宏是否被定義了。因此我們即可以采用一個笨方法,就是使用bp命令在atol和_wtol兩個函數入口上都設置斷點,運行看看到底程序會中斷在哪一個函數上。
?
或者,可以使用bm命令,bm命令相當于bp命令的擴展,允許用戶使用一個通配符設置斷點。例如:
?
bm *tol*
?
#
#?輸出結果?– Windbg會在所有匹配的函數入口上設置斷點。
#?很多,的確很多,因此請慎用bm命令。
#
?4: 65cd1bb0 @!"MSVCR90D!__STRINGTOLD"
?5: 65d47c80 @!"MSVCR90D!_ld12told"
?6: 65cd1900 @!"MSVCR90D!_atoldbl"
?7: 65cd6790 @!"MSVCR90D!_wcstol_l"
?8: 65cd4400 @!"MSVCR90D!strtol"
?9: 65d4bac0 @!"MSVCR90D!__mtold12"
?10: 65cd5030 @!"MSVCR90D!_tolower_l"
?11: 65cd6300 @!"MSVCR90D!wcstol"
?12: 65ca0d50 @!"MSVCR90D!atol"
?13: 65cd4940 @!"MSVCR90D!_strtol_l"
?14: 65ca12d0 @!"MSVCR90D!_wtol"
?15: 65d4a980 @!"MSVCR90D!__wstrgtold12_l"
?16: 65d544e0 @!"MSVCR90D!_ftol"
?17: 65cd5010 @!"MSVCR90D!_tolower"
?18: 65cd5210 @!"MSVCR90D!tolower"
?19: 65ca12f0 @!"MSVCR90D!_wtol_l"
?20: 65d48fd0 @!"MSVCR90D!__dtold"
?21: 65ce3c30 @!"MSVCR90D!_mbctolower_l"
?22: 65d48dc0 @!"MSVCR90D!__STRINGTOLD_L"
?23: 65cd17f0 @!"MSVCR90D!_atoldbl_l"
?24: 65ce3d80 @!"MSVCR90D!_mbctolower"
?25: 65ca0d70 @!"MSVCR90D!_atol_l"
?26: 65d38670 @!"MSVCR90D!__lc_strtolc"
?27: 65cdd8e0 @!"MSVCR90D!CPtoLCID"
?28: 65d47d40 @!"MSVCR90D!__strgtold12_l"
?
設置好斷點后,可以使用bl命令(breakpoint list)來查看已經設置好的斷點:
?
bl
#
#?輸出結果
#?第一列是斷點的編號;
#?第二列,e表示(enabled),u表示(unresolved),因此如果那一列的值為e,則說明
#?斷點是啟用狀態,如果為d表示(disabled),則表示禁用狀態。如果有u,則基本上
#?說明這個斷點是沒有設置成功的,雖然windbg會在后續加載每一個模塊的時候,都嘗試
#?根據那個名字設置斷點;
#?后面幾列,放在后面的文章講。
#
?0 e 010113c0???? 0001 (0001)?0:**** nativedebug!Usage
?1 eu???????????? 0001 (0001) (UsageA)
#?這個斷點沒有設置正確
?2 eu???????????? 0001 (0001) (`22`)
?4 e 65cd1bb0???? 0001 (0001)?0:**** MSVCR90D!__STRINGTOLD
?5 e 65d47c80???? 0001 (0001)?0:**** MSVCR90D!_ld12told
?6 e 65cd1900???? 0001 (0001)?0:**** MSVCR90D!_atoldbl
?7 e 65cd6790???? 0001 (0001)?0:**** MSVCR90D!_wcstol_l
?8 e 65cd4400???? 0001 (0001)?0:**** MSVCR90D!strtol
?9 e 65d4bac0???? 0001 (0001)?0:**** MSVCR90D!__mtold12
10 e 65cd5030?????0001 (0001)?0:**** MSVCR90D!_tolower_l
11 e 65cd6300?????0001 (0001)?0:**** MSVCR90D!wcstol
12 e 65ca0d50?????0001 (0001)?0:**** MSVCR90D!atol
13 e 65cd4940?????0001 (0001)?0:**** MSVCR90D!_strtol_l
14 e 65ca12d0?????0001 (0001)?0:**** MSVCR90D!_wtol
15 e 65d4a980?????0001 (0001)?0:**** MSVCR90D!__wstrgtold12_l
16 e 65d544e0?????0001 (0001)?0:**** MSVCR90D!_ftol
17 e 65cd5010?????0001 (0001)?0:**** MSVCR90D!_tolower
18 e 65cd5210?????0001 (0001)?0:**** MSVCR90D!tolower
19 e 65ca12f0?????0001 (0001)?0:**** MSVCR90D!_wtol_l
20 e 65d48fd0?????0001 (0001)?0:**** MSVCR90D!__dtold
21 e 65ce3c30?????0001 (0001)?0:**** MSVCR90D!_mbctolower_l
22 e 65d48dc0?????0001 (0001)?0:**** MSVCR90D!__STRINGTOLD_L
23 e 65cd17f0?????0001 (0001)?0:**** MSVCR90D!_atoldbl_l
24 e 65ce3d80?????0001 (0001)?0:**** MSVCR90D!_mbctolower
25 e 65ca0d70?????0001 (0001)?0:**** MSVCR90D!_atol_l
26 e 65d38670?????0001 (0001)?0:**** MSVCR90D!__lc_strtolc
27 e 65cdd8e0?????0001 (0001)?0:**** MSVCR90D!CPtoLCID
28 e 65d47d40?????0001 (0001)?0:**** MSVCR90D!__strgtold12_l
?
在上面的輸出中,可以看到斷點1和2是無效的斷點,因此可以使用bc(breakpoint clear)這個命令刪除掉這兩個斷點:
?
bc 1
#
#?沒有輸出結果—沒有消息就是好消息
#
bc 2
#
#?沒有輸出結果—沒有消息就是好消息
#
?
因此在前面的bm命令中,設置了太多的斷點,為了避免在不必要的函數上中斷,我們既可以使用bc命令將它們刪掉,也可以使用bd(breakpoint disabled)命令將其禁用。因為命令實在太多,所以我們可以使用一個小技巧—使用一個范圍來禁用一批斷點:
?
bd 4-10
#
#?沒有輸出結果—沒有消息就是好消息,
#?這個命令將從斷點4到斷點10的所有斷點都禁用了。
#
?
bd和bc命令的語法是一樣的,既可以根據指定的范圍禁用或刪除一批斷點,也可以根據指定的通配符來操作一批斷點,還可以使用一種稀奇古怪的語法來操作斷點(這個稀奇古怪的語法會在后面的文章中講到)。
設置好斷點以后,可以繼續進程的運行了,斷點觸發以后,我們才能查看進程的堆棧以及一些變量的數據。這些內容放在下一篇文章調試非托管程序的基本命令下講解。
總結
以上是生活随笔為你收集整理的Windbg教程-调试非托管程序的基本命令中的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windbg教程-调试非托管程序的基本命
- 下一篇: 使用PowerDbg自动化Windbg调