您的位置:首页 > 其它

Windbg 教程-调试非托管程序的基本命令下

2010-11-02 08:50 309 查看
http://www.cnblogs.com/killmyday/archive/2010/03/14/1685331.html

前面的文章
调试非托管程序的基本命令中

讲到如何使用
windbg在程序中设置断点,既然断点已经设置好了,下一步就是直接执行程序,程序中断以后,第一件事情就是查看堆栈。在
windbg中查看堆栈使用
k命令就可以,在最新的
windbg中(记不得从哪个版本开始了),如果你加载了
sos.dll,
k命令也可以显示托管程序部分的堆栈信息。下面是程序在
_ttol函数入口处中断时的堆栈输出:

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

ChildEBP RetAddr

#

#
打印每个参数的值,只有在有私有符号文件的情况下才有用。

#

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]

#

#
没有私有符号文件的话,又不懂汇编的话,那就只能放弃了

#

0019feec 7715b3f5 kernel32!BaseThreadInitThunk+0xe

0019ff2c 7715b3c8 ntdll!__RtlUserThreadStart+0x70

0019ff44 00000000 ntdll!_RtlUserThreadStart+0x1b

大写的
P
打印的格式好看一些。

0:000> kP

ChildEBP RetAddr

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]

0019feec 7715b3f5 kernel32!BaseThreadInitThunk+0xe

0019ff2c 7715b3c8 ntdll!__RtlUserThreadStart+0x70

0019ff44 00000000 ntdll!_RtlUserThreadStart+0x1b

分析了堆栈后,一般都要去上几层函数分析一下,以便了解当前函数被传入错误参数值的原因。在
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(d

isplay double-word values and ASCII c

haracters)
命令查看内存。

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
需要了解的基础知识、术语等等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: