天天看點

Windbg教程-調試非托管程式的基本指令中

因為是剛剛啟動程式(main函數還沒有機會執行),可以檢視源代碼了解要設定斷點的地方。設定斷點可以使用bp、bu和bm來做,其中bp可以根據函數名、指令位址以及源代碼檔案位址來設定斷點。

bp指令是在設定斷點過程用的比較多的一個指令,下面的表格示範了它的簡單用法:

指令格式

示例

說明

bp 函數名

bp Usage

在函數Usage的入口中斷程式的執行。

bp 指令位址

bp 010113c0

在執行位址在010113c0的指令前中斷程式的執行。

bp `源檔案位址`

bp `nativedebug.cpp:21`

在源代碼nativedebug.cpp的第21行設定斷點,請注意符号“`”(感歎号鍵左邊的反引号)。

#

# 沒有輸出結果,正所謂沒有消息就是好消息,如果斷點成功設定,

# 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

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指令的文法是一樣的,既可以根據指定的範圍禁用或删除一批斷點,也可以根據指定的通配符來操作一批斷點,還可以使用一種稀奇古怪的文法來操作斷點(這個稀奇古怪的文法會在後面的文章中講到)。

本文轉自 donjuan 部落格園部落格,原文連結:http://www.cnblogs.com/killmyday/archive/2010/02/28/1675135.html   ,如需轉載請自行聯系原作者