天天看點

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

0:000> k

#

# ChildEBP和RetAddr的意思在後面講解到調用規範(calling convention)的

# 時候會提到

ChildEBP RetAddr 

0019fda4 012b1464 MSVCR90D!_wtol [f:\dd\vctools\crt_bld\self_x86\crt\src\atox.c @ 55]

# 如果子產品的私有符号檔案被加載的話,那麼k指令可以顯示函數所在的源檔案

# 的名稱和行号,這個資訊在調試過程中對找源檔案幫助是很大的。

0019fe88 012b1a88 nativedebug!wmain+0x44 [e:\臨時文檔\windbg教程\nativedebug\nativedebug.cpp @ 25]

0019fed8 012b18cf nativedebug!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]

0019fee0 76761194 nativedebug!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]

# 下面的函數說明我們沒有加載到私有符号檔案,最多隻加載了公開符号檔案

# 是以有可能看不到變量的詳細資訊。如果真的要看變量的值的話,隻能通過

# 反彙編和自己的一些經驗技巧來擷取了。

0019feec 7715b3f5 kernel32!BaseThreadInitThunk+0xe

0019ff2c 7715b3c8 ntdll!__RtlUserThreadStart+0x70

0019ff44 00000000 ntdll!_RtlUserThreadStart+0x1b

當然啦,k指令的輸出太簡單了,是以windbg提供了幾個輔助指令,kp,kP, kn,kM以及其他一些輔助指令。Windbg為了節省程式員在調試中輸入過長的指令,一般都采用類似unix指令的風格,大多數都采取簡寫(擴充指令除外)。例如k是callstack的簡寫(c的發音是k),bp是breakpoint的簡寫,bu是break unresolved的意思,bm是break matches,等等。

我個人經常用的指令是kP,kn以及kM,kp/P(callstack with parameters),用來在顯示堆棧的同時,列印各個函數參數的值;

0:000> kp

# 列印每個參數的值,隻有在有私有符号檔案的情況下才有用。

0019fda4 012b1464 MSVCR90D!_wtol(wchar_t * nptr = 0x000813c2 "123432") [f:\dd\vctools\crt_bld\self_x86\crt\src\atox.c @ 55]

0019fe88 012b1a88 nativedebug!wmain(int argc = 2, unsigned short ** argv = 0x00081350)+0x44 [e:\臨時文檔\windbg教程\nativedebug\nativedebug.cpp @ 25]

0019fed8 012b18cf nativedebug!__tmainCRTStartup(void)+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]

0019fee0 76761194 nativedebug!wmainCRTStartup(void)+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]

# 沒有私有符号檔案的話,又不懂彙編的話,那就隻能放棄了

大寫的P列印的格式好看一些。

0:000> kP

0019fda4 012b1464 MSVCR90D!_wtol(

wchar_t * nptr = 0x000813c2 "123432") [f:\dd\vctools\crt_bld\self_x86\crt\src\atox.c @ 55]

0019fe88 012b1a88 nativedebug!wmain(

int argc = 2,

unsigned short ** argv = 0x00081350)+0x44 [e:\臨時文檔\windbg教程\nativedebug\nativedebug.cpp @ 25]

分析了堆棧後,一般都要去上幾層函數分析一下,以便了解目前函數被傳入錯誤參數值的原因。在windbg中,在堆棧中切換到不同的函數,需要用到.frame指令(注意前面的點号)。你需要告訴.frame指令,希望檢視哪一個函數的資訊,是以你要給.frame指令提供函數的索引值,這個索引值可以通過kn(callstack with index number)指令擷取。

0:000> kn

 # ChildEBP RetAddr 

# 注意前面黃色高亮顯示的索引号,是從0開始索引的,最近一次調用的函數的索引

# 是0

00 0019fda4 012b1464 MSVCR90D!_wtol+0x5 [f:\dd\vctools\crt_bld\self_x86\crt\src\atox.c @ 56]

01 0019fe88 012b1a88 nativedebug!wmain+0x44 [e:\臨時文檔\windbg教程\nativedebug\nativedebug.cpp @ 25]

02 0019fed8 012b18cf nativedebug!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]

03 0019fee0 76761194 nativedebug!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]

04 0019feec 7715b3f5 kernel32!BaseThreadInitThunk+0xe

05 0019ff2c 7715b3c8 ntdll!__RtlUserThreadStart+0x70

06 0019ff44 00000000 ntdll!_RtlUserThreadStart+0x1b

在上例中,假設要檢視wmain函數裡面的各個局部變量的情況,則使用1作為索引号輸入給.frame指令。

0:000> .frame 1

# 告訴你切換已經成功,顯示1所代表的函數名

01 0019fe88 012b1a88 nativedebug!wmain+0x44 [e:\臨時文檔\windbg教程\nativedebug\nativedebug.cpp @ 25]

切換了函數以後,下一步就是檢視變量的值,使用dv(display variables)指令來檢視變量資訊,這個指令相當于Visual Studio裡面的局部變量(locals)視窗。

0:000> dv

           argc = 2

           argv = 0x00081350

         result = 0

因為檢視函數在堆棧中的索引值,使用.frame指令在堆棧中切換函數,以及顯示函數的局部變量這一個操作實在是太普遍了,是以windbg提供了一個快捷指令,kM(callstack with markup)。這個指令提供了一個類似html網頁超連結的形式,供程式員在堆棧中快速切換函數并且顯示變量值,操作方式讓我想起了linux下面那個控制台界面的浏覽器lynx。下面是kM指令的輸出:

對于簡單類型,例如整型、浮點型甚至是字元串,windbg可以直接顯示出變量的值。但是對于一些複雜類型,例如數組,結構,類呀,那就需要借助另外一個指令dt(display type)了。下面的指令檢查了argv類型資訊,以及它的值。

0:000> dt argv

# unsigned short,在C和C++程式中,一般都意味着是wchar_t(寬字元)類型

# **表示是一個包含寬字元字元串(第一個星号)的數組(第二個星号,在C和C++中

# 數組和指針可以用相同的方法表示 )。

Local var @ 0x19fe94 Type unsigned short**

0x00081350

 -> 0x0008135c

 -> 0x45

既然已經知道argv是一個儲存指針的數組,下一步就是繼續檢視argv數組裡面的内容,根據前面的dv列印的結果,我們知道argc(也就是說明argv數組元素個數的參數)的值是2。在一台32位機(或者是在64位機器上調試一個32位的程式),使用dd(display by double-word)指令參看argv的記憶體,以四位元組的形式顯示,因為32位機器上,指針使用4個位元組。如果你的機器是64位機,那就需要使用dq(display by quad-word)指令以8個位元組的形式列印記憶體了。

以32位機作為例子,可以隻給dd指令你要檢視的記憶體位址(既可以是變量名,也可以直是記憶體位址),讓dd指令自己控制顯示記憶體的範圍(預設是顯示32個dword,也就是128個位元組的記憶體内容)。

0:000> dd argv

# 将argv傳給dd指令的時候, windbg是先将argv轉換成儲存

# 數組指針的位址(就是0019fe94)—畢竟數組的指針也是需要地方儲存的嘛。

# 而高亮顯示的00081350才是儲存argv數組内容的真實位址

0019fe94 00081350 00081410 667a0491 00000000

0019fea4 00000000 7ffda000 001ac17a 00000000

0019feb4 00000000 001a0000 00000000 0019fe9c

0019fec4 0000001d 0019ff1c 012b1087 67489169

0019fed4 00000000 0019fee0 012b18cf 0019feec

0019fee4 76761194 7ffda000 0019ff2c 7715b3f5

0019fef4 7ffda000 7346f628 00000000 00000000

0019ff04 7ffda000 00000000 00000000 00000000

0:000> dd 0x00081350

# 下面兩個高亮的位址,就是數組儲存的字元串指針

00081350 0008135c 000813c2 00000000 003a0045

00081360 4e34005c 658765f6 005c6863 00690057

00081370 0064006e 00670062 7a0b6559 006e005c

00081380 00740061 00760069 00640065 00620065

00081390 00670075 0044005c 00620065 00670075

000813a0 006e005c 00740061 00760069 00640065

000813b0 00620065 00670075 0065002e 00650078

000813c0 00310000 00330032 00330034 00000032

在上例中,既然我們已經知道argv數組的大小是2的話,你也可以将這個資訊提供給dd指令,告訴它你隻需要顯示argv指針所指向的記憶體的兩個元素就可以了,下面這個指令顯示了這種用法。

# L2 (Length)這個選項告訴了dd指令,隻要顯示兩個内容就足夠了。

#至于這個選項的其他用法,要在講到windbg中位址範圍的文法時才能解釋了。

0:000> dd 0x00081350 L2

00081350 0008135c 000813c2

執行了這麼多指令以後,我們終于可以看到argv[0]和argv[1]的值了,不容易呀!既然已經知道是unicode字元串,使用du(display unicode)指令就可以顯示完整的字元串内容了。

0:000> du 0008135c

# 在C++程式中,main函數的第一個參數是程式自身的完整路徑,這

# 一點跟C#程式不一樣。

0008135c "E:\臨時文檔\Windbg教程\nativedebug\Deb"

0008139c "ug\nativedebug.exe"

0:000> du 000813c2

000813c2 "123432"

但是也有很多情況下,你可能并不知道指定位址裡面儲存的内容是什麼,這個時候,建議你用dc(display double-word values and ASCII characters)指令檢視記憶體。

0:000> dc 000813c2

# dc指令的輸出是Visual studio記憶體視窗以及windbg記憶體視窗預設的顯示

# 方式

000813c2 00320031 00340033 00320033 fdfd0000 1.2.3.4.3.2.....

000813d2 ababfdfd abababab feeeabab 0000feee ................

000813e2 00000000 19310000 75b66872 13301800 ......1.rh.u..0.

000813f2 14b80008 22a40008 007566a0 008c0000 .......".fu.....

00081402 00020000 00310000 fdfd0000 14d8fdfd ......1.........

00081412 15580008 15e00008 16800008 16e80008 ..X.............

00081422 3a400008 3aa80008 3b000008 3b680008 ..@:...:...;..h;

00081432 3bf80008 3c680008 3cd80008 3d300008 ...;..h<...<..0=

最後,如果你調試的是一個非unicode程式,即是一個隻了解ASCII字元集的程式(也就是所有字元串的類型都是char),那麼在檢視字元串的時候,使用da(display ascii)而不是du指令來顯示記憶體。

本來計劃隻需要一篇文章就夠的非托管(native)程式的調試,竟然用了三篇文章的篇幅才講完!後面接着講使用windbg需要了解的基礎知識、術語等等。

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

繼續閱讀