使用CDB和NTSD開始調試旅程

介紹

  在軟件開發和維護的過程中,調試是最有具有價值的技能之一,它幾乎被用在產品生命週期的每個階段。首先,開發人員很明顯會遇到很多錯誤。這些錯誤多種多樣,有邏輯錯誤、語法錯誤、編譯器方面的錯誤等等。其次,當測試更多的高級的情況或當軟件在和其他環境交互的時候,質量保證部門人員可能會遇到困難。最後,產品發佈之後,公司必須對它提供一些支持。顧客拿到軟件產品之後,調試並沒有結束,錯誤通常都會升級,並被返回公司等待調試。

 

這篇教程的目的

  這篇教程僅僅是關於調試的一個介紹。這篇是”教程 #1”,如果反饋好的話,我會繼續寫。有許多複雜的調試技術以及問題,以至於我們不知道從哪裏開始着手。這篇教程從最初開始幫助你瞭解調試是怎麼一回事。我希望把高級調試的世界展現給初級和中級程序員,而不是簡單的重新編譯,或者簡單的MessageBoxPrintf調試。

 

調試器和操作系統

  你可以到下面這個網站下載最新的微軟提供的調試器。

http://www.microsoft.com/whdc/ddk/debugging

 

CDBNTSDWindbg

  這篇文章討論的情況一般都是Windows2000以及更高版本。我們將要談論的這三個調試器是CDBNTSDWinDbgWindows2000以及更高版本一般系統內都內置NTSD!所以,如果你想快速調試,就不需要另外再安裝軟件了。

  那麼這又有什麼不一樣呢?文檔上說”NTSD不需要控制檯支持,而CDB需要”。這是真的,NTSD確實不需要,而CDB需要。然而,我發現還有更多的不同之處。第一,老版本的NTSD不支持PDB符號文件,它們只支持DBG符號文件!我還發現NTSD不支持符號服務器,而CDB支持。老版本的NTSD不能創建內存dump,還有其他一些問題,比如NTSD只支持2種斷點命令。NTSD相對於CDB的一個優勢就是它不需要控制檯。

  當你正在調試用戶態服務或者登陸到系統之前的進程時,不需要控制檯窗口這個特點是非常重要的。如果沒有用戶登陸到系統,你就不能創建控制檯窗口。你可以設置一個命令選項-d,讓NTSD和已經連接上的內核調試器通信(CDB也有相同的選項)。這樣就能通過內核調試器來調試系統啓動期間的進程。當你已經可以用內核調試器調試進程,用戶態調試器能給你更多的靈活性。這已經超出了介紹章節的範圍,只要消化這個概念就行了。

  除了很少的一些差異,WinDbgCDB幾乎一樣。WinDbgGUI程序,CDB是控制檯程序,這是第一個不同。WinDbg還支持內核調試和源碼級調試。

 

Visual C++調試器

  我不使用這個調試器,並且我不推薦使用它。第一個原因就是它非常消耗資源。它加載的非常慢,並且包含了很多沒用的東西,以至於成爲累贅。第二個原因就是安裝完這個調試器後,你需要重啓,我一般都是在沒有安裝調試器的機器上工作。並且VC++非常大,安裝費時。

 

Windows 9x/ME

  在Windows 9x/ME的機器上,我們該怎麼辦呢?你可以使用WinDbg。對所有系統,和調試有關的API都是一樣的,因此Windbg能運行在Windows 9x/ME上。我唯一的擔心就是WinDbg會檢測當前系統是否Windows 9x並且會不允許調試。我最近發現事實上不會這樣。剩下的問題就是,最新的WinDbgMSI安裝包,並且不允許安裝在Windows 9x系統上。我們可以在NT系統上安裝,然後共享這個目錄或者拷貝到CD上共享。這會有一些影響,比如若NT9x系統放置的數據在內存的不同地方,你就不能使用所有的!xxx命令。那麼符號能使用嗎?是的,PDB可以使用。但是當設置了ba r1 xxxxx之後,單步走非常慢。這篇文章的內容不包括Windows 9x/ME

 

 

設置環境

  開始調試前,這是非常重要的一步。把系統配置成你喜歡的狀態,並且包含所有你需要的工具。

 

符號和符號服務器

  符號是調試操作中很重要的一步。你可以從微軟的一個地址下載相應操作系統的所有符號。問題是,你需要很大的硬盤空間,並且不如你在一臺機器上調試許多操作系統,這可能會非常麻煩。

  爲了適應這個需要,微軟提供了”符號服務器”這個功能。這會幫助你得到正確的符號。符號服務器的地址http://msdl.microsoft.com/download/symbols。如果你把符號路徑設置爲這個地址,調試器將會自動你需要的系統符號。你自己的程序的符號需要你自己設置。

 

映像文件執行選項

  這是註冊表中的一個位置,當一個程序開始運行,這個註冊表位置會自動將調試器附加到程序。這個註冊表位置如下:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

  在這個鍵下,你只要創建一個子鍵,取名爲你想要調試的程序,比如”myapplication.exe”。如果你以前沒有使用過這個功能,可能會有一個默認鍵值”Your Application Here”或類似的值,重命名它即可。

  這個鍵下的一個值是”Debugger”,你可以在這裏設置需要開啓的調試器,”Your Application Here”下面默認的這個值爲”ntsd -d”。你不能使用這個,除非已經有內核調試器附加在系統上,所以要去掉”-d”選項。

注意:使用”-d”選項,並且當前沒有內核調試器附加在系統上,每次啓動程序都有可能導致系統鎖死。必須非常小心。如果內核調試器已經設置好了,你可以使用”g”命令解鎖系統。

  另外一個值爲”GlobalFlags”。這是另外一個可以用於調試的工具,然而它超出了本篇的範圍。如果你想知道更多,可以看”gflags.exe”。

 

內核調試設置

  如果需要內核調試,首先你需要以調試模式啓動系統。雖然在系統屬性裏面可以用GUI的方式設置,我還是建議直接編輯boot.ini文件。在C:\盤找到boot.ini文件,它是一個隱藏的系統文件。

小心:不正確的編輯這個文件將會讓你無法啓動。

  這個啓動文件內容類似下面:

timeout=30

default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

[operating systems]

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=

"Microsoft Windows XP Professional" /fastdetect

 

我將複製Operating Systems下面的第一行:

 

timeout=30

default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

[operating systems]

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=

    "Microsoft Windows XP Professional" /fastdetect

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=

   "Microsoft Windows XP Professional" 

   /fastdetect /debug /debugport=COM1 /baudrate=115200

複製的這行包含了你的設置。/debug,然後是/debugport=port,最後是/baudrate=baudrate。調試端口是你需要使用的串行端口,這是你需要設置的硬件,你還需要另外一臺機器來完成設置。除了使用COM端口,你還可以使用IEEE 1394,這個速度會更快一些。

當你再次啓動後,選擇”Debugger Enabled”選項來啓動調試模式。

(其實我們一般都用虛擬機來完成這部分的設置,具體可以在論壇上搜,有很多教程。)

 

環境變量

  我一般會把_NT_SYMBOL_PATH環境變量設置爲微軟的符號服務器和我自己的符號目錄。你可以在     系統屬性->高級->環境變量  裏面設置這個值。

 

默認調試器

  當有任何崩潰出現在系統上,將會調用缺省調試器。默認情況下,它被設置爲”Doctor Watson”。這個註冊表鍵在如下位置: 

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug

我會把”Auto”設爲1”Debugger”設爲需要的調試器。

 

彙編

  我十分推薦你學習彙編語言。這篇教程不會給你展示源碼級調試,因爲我從來不這樣做並且我也不會。源碼級調試帶來的問題是,你不會總是有源代碼,並且有時候從源代碼看不出問題在哪。如果你瞭解系統的組成,你可以很容易的逆向系統來找到你需要的信息,這個源碼級調試不能帶給你的。

  我討厭源碼級調試的另外一個原因是,如果源碼和符號不符合,調試器將會給你錯誤的信息。這意味着如果你爲你的項目創建了多個版本,你不得不找到和你正在調試的程序符合的版本。

 

 

讓我們開始吧

  這篇教程是第一部分,如果你們喜歡,我們寫更多,並且會更深入。這篇將會解決兩個簡單的用戶態編程問題。

 

發佈版本的符號

  首先,怎樣爲發佈版程序創建符號呢?很簡單,創建一個make文件。

我一般使用的編譯選項如下:

/nologo /MD /W3 /Oxs /Zi /I "..\..\inc" /D "WIN32" /D "_WINDOWS" 

/Fr$(OBJDIR)\\ /Fo$(OBJDIR)\\ /Fd$(OBJDIR)\\ /c

我一般使用的鏈接選項如下:

 

/nologo /subsystem:console 

  /out:$(TARGETDIR)\$(TARGET)/pdb:<YourProjectName>.pdb 

  /debug /debugtype:both 

/LIBPATH:"..\..\..\bin\lib"

這將會爲你的工程創建.pdb文件。當然,根據VC++7的介紹,他們已經放棄使用.DBG(因此/debugtype:both在這個編譯器上可能會出現錯誤).DBG.PDB的簡單版本並且它不包含源碼信息,精確的符號察看。它甚至不包含參數或其他一些東西。如果你還在使用這樣的編譯器,你需要做下面的工作:

rebase -b 0x00100000 -x $(TARGETDIR) -a $(TARGETDIR)\$(TARGET)

-b選項後面是重定位的新的可執行文件的內存地址。如果你使用默認的Visual Studio方式創建文件,那可能會比這個更小一點,不過,你不會獲得符號。產生的代碼是一樣的,並且會有相應的優化。不同的是,這些文件會更有用,不論你在哪裏使用,你都能得到符號信息。

  記住,最棒的調試時機總是在你還沒有重新生成可執行文件之前。一旦你不得不重新生成可執行文件,你必須知道,你已經改變了這個文件在內存中的位置。你也可能改變了文件執行的速度。如果你需要重現這個錯誤,這將是非常重要的!如果需要4天才能引起這個錯誤,那該怎麼辦呢?如果能的話,最好在發生的時候就去處理它。

 

簡單的Access Violation錯誤

  我們來看一個簡單的”Access Violation”錯誤,這很常見。解決這個問題可以分爲三步。

1. 誰觸發了這個訪問?哪個模塊?

2. 它要訪問哪裏?這塊內存在哪?

3. 爲什麼它要訪問這塊內存?它要幹什麼?

這些是一般情況下解決這個問題的方法,其中第2條又是最重要的。然而,解決13的問題可以幫助解決2的問題。

  我創建了一個簡單的崩潰的程序。我已經把我的默認調試器設置爲CDB,並且我運行了這個程序。我也爲這個可執行文件創建了符號並把_NT_SYMBOL_PATH設置爲微軟的符號服務器。

當我們運行這個程序,我們將會看到下面的情況:

C:\programs\DirectX\Games\src\Games\temp\bin>temp

 

Microsoft (R) Windows Debugger  Version 6.3.0005.1

Copyright (c) Microsoft Corporation. All rights reserved.

 

*** wait with pending attach

Symbol search path is: 

  SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

 

Executable search path is:

ModLoad: 00400000 00404000   C:\programs\DirectX\Games\src\Games\temp\bin\temp.e

xe

ModLoad: 77f50000 77ff7000   C:\WINDOWS.0\System32\ntdll.dll

ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll

ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll

ModLoad: 77dd0000 77e5d000   C:\WINDOWS.0\system32\ADVAPI32.DLL

ModLoad: 78000000 78086000   C:\WINDOWS.0\system32\RPCRT4.dll

(ee8.c38): Access violation - code c0000005 (!!! second chance !!!)

eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8

eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

MSVCRT!_output+0x18:

77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??

0:000>

第一個要注意的就是,這個錯誤發生在MSVCRT.DLL當中。這很明顯,因爲調試器給我們顯示了類似的信息,格式爲<module>!<nearest symbol>+offset。這意味着最近的符號是_output,並且我們運行到了裏面的+18h處。因此我們假設自己正處於_output函數當中。

(ee8.c38): Access violation - code c0000005 (!!! second chance !!!)

eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8

eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

MSVCRT!_output+0x18:

77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??

0:000>

如果我們想驗證這個信息,我們應該怎麼做?

<0:000> x *!

start    end        module name

00400000 00404000   temp         (deferred)

77c10000 77c63000   MSVCRT       (pdb symbols)  

                                 c:\symbols\msvcrt.pdb\3D6DD5921\msvcrt.pdb

77dd0000 77e5d000   ADVAPI32     (deferred)

77e60000 77f46000   kernel32     (deferred)

77f50000 77ff7000   ntdll        (deferred)

78000000 78086000   RPCRT4       (deferred)

這個命令展示所有模塊的列表以及它們的開始和結束位置。我們的錯誤地址是77c3f10b77c10000<=77c3f10b<=77c63000,因此可以確認錯誤發生在MSVCRT。下面我們來確定這個地址在哪。

有幾種方法可以完成,我們可以反彙編代碼,找出這個地址,還可以看棧回溯。首先我們來看看_output函數的反彙編代碼。

0:000> u MSVCRT!_output

MSVCRT!_output:

77c3f0f3 55               push    ebp

77c3f0f4 8bec             mov     ebp,esp

77c3f0f6 81ec50020000     sub     esp,0x250

77c3f0fc 33c0             xor     eax,eax

77c3f0fe 8945d8           mov     [ebp-0x28],eax

77c3f101 8945f0           mov     [ebp-0x10],eax

77c3f104 8945ec           mov     [ebp-0x14],eax

77c3f107 8b450c           mov     eax,[ebp+0xc]

0:000> u

MSVCRT!_output+0x17:

77c3f10a 53               push    ebx

77c3f10b 8a18             mov     bl,[eax]

即使你不懂彙編,你也會發現一些東西。首先,我們可以發現這個內存地址在EAX當中。它是CPU的一個寄存器,但是我們可以把它看作一個變量。環繞EAX[]符號相當於C語言中的*MyPointer。這意味着我們正在引用EAX指向的地址。那麼EAX又是怎麼來的?EAX來自[EBP+0Ch],你可以把它看作DWORD *EBP,EAX=EBP[3].這是因爲在彙編語言中,沒有類型。EAX是一個32位寄存器,EBP+12相當於一個DWORD指針加3

下面我們可以看到MOV EBPESPESP是棧指針。參數都是壓入棧中,返回地址和局部變量都是在棧中。ESP指向棧的位置。在內存中,一個C函數調用約定的存放如下:

[Parameter n]

...

[Parameter 2]

[Parameter 1]

[Return Address]

並且,我們看到了PUSH  EBPPUSH就是把某個東西壓入棧,因此我們在棧中保存了EBP以前的值。此時,棧的狀態如下:

[Parameter n]

...

[Parameter 2]

[Parameter 1]

[Return Address]

[Previous EBP]

然後我們又把EBP設爲ESP,我們可以把它看作是一個指針,棧就相當於一個DWORD類型的數組。因此,棧中各個變量與EBP的對應如下:

[Parameter n]     ==  [EBP + n*4 + 4] (The formula)

...

[Parameter 2]     ==  [EBP + 12]

[Parameter 1]     ==  [EBP + 8]

[Return Address]  ==  [EBP + 4]

[Previous EBP]    ==  [EBP + 0]

對於我們這個例子來說,我們的變量是_output的第二個參數。那麼,下面該怎麼辦呢?我們來反彙編調用函數!我們知道EBP+4指向返回地址,或者我們也可以得到棧回溯。

0:000> kb

ChildEBP RetAddr  Args to Child

0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18

0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35

0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f

0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3

0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23

0:000>

“KB”命令能得到棧回溯。我們不會總是得到完整的棧回溯,我們會在更加深入的教程中講解這點。在這篇簡單的教程裏,我們假定我們得到了完整的棧回溯。我們注意到,這個函數是printf,並且printf調用_output。我們來反彙編printf,注意我們不用每次都反彙編整個函數,將其分割成幾段即可。有時,我們能從棧回溯中很簡單就找到錯誤發生點,而這些函數也都非常簡單,我們可以非常輕鬆的跟蹤他們。

0:000> u MSVCRT!_output

MSVCRT!_output:

77c3f0f3 55               push    ebp

77c3f0f4 8bec             mov     ebp,esp

77c3f0f6 81ec50020000     sub     esp,0x250

77c3f0fc 33c0             xor     eax,eax

77c3f0fe 8945d8           mov     [ebp-0x28],eax

77c3f101 8945f0           mov     [ebp-0x10],eax

77c3f104 8945ec           mov     [ebp-0x14],eax

77c3f107 8b450c           mov     eax,[ebp+0xc]

0:000> u

MSVCRT!_output+0x17:

77c3f10a 53               push    ebx

77c3f10b 8a18             mov     bl,[eax]

77c3f10d 33c9             xor     ecx,ecx

77c3f10f 84db             test    bl,bl

77c3f111 0f8445070000     je      MSVCRT!_output+0x769 (77c3

77c3f117 56               push    esi

77c3f118 57               push    edi

77c3f119 8bf8             mov     edi,eax

0:000> u MSVCRT!printf

MSVCRT!printf:

77c3e658 6a10             push    0x10

77c3e65a 68e046c177       push    0x77c146e0

77c3e65f e8606effff       call    MSVCRT!_SEH_prolog (77c354

77c3e664 bea0acc577       mov     esi,0x77c5aca0

77c3e669 56               push    esi

77c3e66a 6a01             push    0x1

77c3e66c e8bdadffff       call    MSVCRT!_lock_file2 (77c394

77c3e671 59               pop     ecx

0:000> u

MSVCRT!printf+0x1a:

77c3e672 59               pop     ecx

77c3e673 8365fc00         and     dword ptr [ebp-0x4],0x0

77c3e677 56               push    esi

77c3e678 e8c7140000       call    MSVCRT!_stbuf (77c3fb44)

77c3e67d 8945e4           mov     [ebp-0x1c],eax

77c3e680 8d450c           lea     eax,[ebp+0xc]

77c3e683 50               push    eax

77c3e684 ff7508           push    dword ptr [ebp+0x8]

0:000> u

MSVCRT!printf+0x2f:

77c3e687 56               push    esi

77c3e688 e8660a0000       call    MSVCRT!_output (77c3f0f3)

_output的第二個參數是[EBP+8],並且有PUSH  EBPMOV  EBPESP,因此這和我之前說的情況一樣。但是也不總是這樣的,要視具體情況而定,我們慢慢再深入講解。

因此,我們可以確定printf的第一個參數在內存中的哪個地方,並且printf是我們的程序發出的調用。從錯誤信息,我們知道EAX0,因此我們在對一個空指針解引用。

77c3f10b 8a18    mov bl,[eax]   ds:0023:00000000=??

下面是我們寫的代碼:

int main(int argc, char *argv[])

 {  

  char *TheLastParameter[100];

 

  sprintf(*TheLastParameter, "The last parameter is %s", argv[argc]);

  printf(*TheLastParameter);

 

  return 0;

 }

這段代碼有很多問題,然後因爲空指針,錯誤發生在printf。奇怪的是,並沒有在sprintf()處出現錯誤。那麼,我們該怎樣只用KB命令解決這個問題呢?

0:000> kb

ChildEBP RetAddr  Args to Child

0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18

0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35

0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f

0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3

0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23

0:000>

我們有符號和棧回溯,第一個參數是0,並且我們調用了這個函數。這是很簡單的情況,我會試着講述一些技巧,讓你發現問題的所在之處。知道棧是如何組成的,在內存中的狀態,這對你找出問題所在之處非常有幫助,因爲僅僅一個”kb”命令不會讓你一眼就能看出問題。

 

不按預期運行的程序

  這也是一個很常見的錯誤。你運行了程序,但是你並沒有看到正確的輸出,或者總是有一些錯誤信息。這個常見的問題有可能非常容易解決,也可能很複雜。那麼解決這種問題的步驟如何呢?

1. 什麼東西沒有工作? 

2. 哪些API或模塊可能會和這個問題有關?

3. 什麼原因導致這些API不正常的工作?

這些步驟不是必須的。下面讓我們來看一個例子,關於打開文件的。

  HANDLE hFile;

  DWORD dwWritten;

  hFile = CreateFile("c:\MyFile.txt", GENERIC_READ, 

                       0, NULL, OPEN_EXISTING, 0, NULL);

 

  if(hFile != INVALID_HANDLE_VALUE)

  {

   WriteFile(hFile, "Test", strlen("Test"), &dwWritten, NULL);

   CloseHandle(hFile);

  }

這是我們的代碼。你可能會想使用GetLastError(),然後重新編譯,並將錯誤打印出來。但是,你並不需要這樣做,在這個例子中非常簡單。下面我們來看看,打開調試器,並且會停在這個函數。有符號的幫助,一切都很簡單,CreateFile是一個導出的符號,我們總是能斷在這個函數上。

C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp

 

Microsoft (R) Windows Debugger  Version 6.3.0005.1

Copyright (c) Microsoft Corporation. All rights reserved.

 

CommandLine: temp

Symbol search path is: 

    SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

 

Executable search path is:

ModLoad: 00400000 00404000   temp.exe

ModLoad: 77f50000 77ff7000   ntdll.dll

ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll

ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll

(2a0.94): Break instruction exception - code 80000003 (first chance)

eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48

eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

ntdll!DbgBreakPoint:

77f75a58 cc               int     3

0:000> bp temp!main

0:000> g

我們在main函數上下一個斷點,然後使用”g”命令讓其運行。當我們到達斷點,用”p”命令單步走,直到CreateFile函數。

Breakpoint 0 hit

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main:

00401000 51               push    ecx

0:000> p

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x1:

00401001 56               push    esi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x2:

00401002 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x3:

00401003 33ff             xor     edi,edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x5:

00401005 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x6:

00401006 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x7:

00401007 6a03             push    0x3

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x9:

00401009 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0xa:

0040100a 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0xb:

0040100b 6800000080       push    0x80000000

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x10:

00401010 6810304000       push    0x403010

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x15:

00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3

2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476

0:000> p

eax=ffffffff ebx=7ffdf000 ecx=77f939e3 edx=00000002 esi=00000000 edi=00000000

eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286

temp!main+0x1b:

0040101b 8bf0             mov     esi,eax

在調用CreateFile函數之後,EAX中將存儲返回值。我們注意到其值爲ffffffff,也就是”Invalid Handle Value”,我們還想知道GetLastError的值,它存儲在fs:34這個位置。FSTEB選擇子,我們可以把它dump出來。

0:000> dd fs:34

0038:00000034  00000002 00000000 00000000 00000000

0038:00000044  00000000 00000000 00000000 00000000

0038:00000054  00000000 00000000 00000000 00000000

0038:00000064  00000000 00000000 00000000 00000000

0038:00000074  00000000 00000000 00000000 00000000

0038:00000084  00000000 00000000 00000000 00000000

0038:00000094  00000000 00000000 00000000 00000000

0038:000000a4  00000000 00000000 00000000 00000000

CDB還有一種更快速的方式能做到這點,!gle:

0:000> !gle

LastErrorValue: (Win32) 0x2 (2) - The system cannot find the file specified.

LastStatusValue: (NTSTATUS) 0xc0000034 - Object Name not found.

0:000>

找不到對應的文件。那麼問題出在哪呢?我們需要進一步調試,我們來看看傳遞給CreateFile的參數。

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x10:

00401010 6810304000       push    0x403010

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x15:

00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA 

   (00402004)]{kernel32!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476

幸運的是,這是內存中的一個常量,它不太可能會改變,因爲我們並沒有運行到離CreateFile太遠。

然後,我們可以使用”da”,”dc”,“du”命令。”da”命令打印出ANSI字符串,”du”打印出Unicode字符串,”dc”和”dd”類似,不過它是打印出所有的字符,包括不可顯示的。我們知道這是一個ANSI字符串,”da”命令:

0:000> da 403010

00403010  "c:MyFile.txt"

0:000>

我們看到這是錯誤的字符串,我們應該用c:\\MyFile.txt來代替它。

因此,我們找到並更正這個錯誤,但是,我們還是不能寫入。我們再進一步調試。

重新載入一遍。

C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp

 

Microsoft (R) Windows Debugger  Version 6.3.0005.1

Copyright (c) Microsoft Corporation. All rights reserved.

 

CommandLine: temp

Symbol search path is: 

  SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

 

Executable search path is:

ModLoad: 00400000 00404000   temp.exe

ModLoad: 77f50000 77ff7000   ntdll.dll

ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll

ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll

(80c.c94): Break instruction exception - code 80000003 (first chance)

eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48

eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202

ntdll!DbgBreakPoint:

77f75a58 cc               int     3

0:000> bp temp!main

0:000> g

Breakpoint 0 hit

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main:

00401000 51               push    ecx

0:000> p

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x1:

00401001 56               push    esi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x2:

00401002 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x3:

00401003 33ff             xor     edi,edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x5:

00401005 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x6:

00401006 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x7:

00401007 6a03             push    0x3

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x9:

00401009 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0xa:

0040100a 57               push    edi

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0xb:

0040100b 6800000080       push    0x80000000

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x10:

00401010 6810304000       push    0x403010

0:000>

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000

eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x15:

00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3

2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476

0:000>

eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=00000000 edi=00000000

eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293

temp!main+0x1b:

0040101b 8bf0             mov     esi,eax

0:000> p

走到這裏,我們看到EAX是一個有效的handle,繼續。

eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=0040101d esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293

temp!main+0x1d:

0040101d 83feff           cmp     esi,0xffffffff

0:000>

eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401020 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x20:

00401020 741b             jz      temp!main+0x3d (0040103d)            

0:000>

eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401022 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x22:

00401022 8d442408         lea     eax,[esp+0x8]     ss:0023:0012ff4c=00322cf8

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401026 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x26:

00401026 57               push    edi

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401027 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x27:

00401027 50               push    eax

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401028 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x28:

00401028 6a04             push    0x4

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=0040102a esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x2a:

0040102a 6820304000       push    0x403020

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=0040102f esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x2f:

0040102f 56               push    esi

0:000>

eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000

eip=00401030 esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213

temp!main+0x30:

00401030 ff1500204000 call dword ptr [temp!_imp__WriteFile (00402000)]{kernel32!

WriteFile (77e7f13a)} ds:0023:00402000=77e7f13a

0:000> p

eax=00000000 ebx=7ffdf000 ecx=77e7f1c9 edx=00000015 esi=000007e8 edi=00000000

eip=00401036 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc

cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246

temp!main+0x36:

00401036 56               push    esi

我們調用了WriteFile並且EAX==0,這意味着調用返回錯誤,我們來看看其他變量。

第二個參數是對的,長度爲4:

0:000> da 403020

00403020  "Test"

4個參數代表寫入的地字節數,爲0

0:000> dd 012ff4c

0012ff4c  00000000 00401139 00000001 00322470

0012ff5c  00322cf8 00403000 00403004 0012ffa4

0012ff6c  0012ff94 0012ffa0 00000000 0012ff98

0012ff7c  00403008 0040300c 00000000 00000000

0012ff8c  7ffdf000 00000001 00322470 00000000

0012ff9c  8053476f 00322cf8 00000001 0012ff84

0012ffac  e1176590 0012ffe0 00401200 004020c0

0012ffbc  00000000 0012fff0 77e814c7 00000000

下面我們檢查一下GetLastError的值。

0:000> !gle

LastErrorValue: (Win32) 0x5 (5) - Access is denied.

LastStatusValue: (NTSTATUS) 0xc0000022 - {Access Denied}  

              A process has requested access to an object, 

              but has not been granted those access rights.

0:000>

拒絕訪問?怎麼會這樣!檢查一下,我們只以READ權限打開這個文件,沒有WRITE權限,這就是問題所在。然後我們在代碼中修改這個錯誤。

hFile = CreateFile("c:\\MyFile.txt", GENERIC_READ, 

        0, NULL, OPEN_EXISTING, 0, NULL);

 

 

總結

  這僅僅是對基本調試技術的一個介紹。例子都很簡單,但是其中展示的這些技術都是很有價值的。這是調試教程的第一篇,如果有人感興趣,我們繼續補上一些更加高級的內容。

  對有些人來說,這篇教程可能會很簡單,對其他人來說又可能太難了。你不可能一天就變成一個熟練的調試專家,這是需要聯繫的。我建議大家,即使是很簡單的問題,也要試着去用調試器解決。聯繫得越多,工具使用的越熟練,你將會學習到更多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章