Windbg Step 2 分析程序堆栈实战
#include <tchar.h>
#ifdef _UNICODE
#define _ttol _wtol
#else
#define _ttol atol
#endif
void Usage()
{
#ifdef _UNICODE
wprintf(L"[Usage]: nativedebug.exe <digital numbers>\n");
#else
printf("[Usage]: nativedebug.exe <digital numbers>\n");
#endif
}
int _tmain(int argc, _TCHAR* argv[])
{
int result = 0;
if ( argc != 2 )
{
Usage();
return -1;
}
result = _ttol(argv[1]);
#ifdef _UNICODE
wprintf(L"%s * %s = %d\n", argv[1], argv[1], result * result);
wprintf(L"Press any key to exit ...\n");
_getwch();
#else
printf("%s * %s = %d\n", result * result);
printf("Press any key to exit ...\n");
_getch();
#endif
return 0;
}
编译,用Windbg分析。
1. 设置断点,打开源文件,直接在result = _ttol(argv[1]);按F9
或者设置_wtol和atol的断点:
因为代码中有:
#ifdef _UNICODE
# define _ttol _wtol
#else
# define _ttol atol
#endif
而宏是在编译期间就被编译器扩展,并不会被加到符号文件中去,因此如果你试图使用bp命令在_ttol入口设置断点的话,是会失败的。因此你可以使用类似下面的通配符来查找正确的函数名:
x MSVCR90D!*tol×
然后用bm *tol*给所有含有tol的函数都设置断点;
然后用bl查看断点列表,用bc 2-6 清除,用bd 2-6禁用 第二个到6个断点
2. lm查看loaded Modules
lm
start end module name
01330000 0134b000 MyApp C (private pdb symbols) E:\ProLab\WindbgFirst\Debug\MyApp.pdb
59bc0000 59ce4000 MSVCR90D (deferred)
75100000 75200000 kernel32 (deferred)
76750000 76796000 KERNELBASE (deferred)
77500000 77680000 ntdll (pdb symbols) c:\websymbols\wntdll.pdb\ACE318E6A2F44F23A6CC5628F10A7DDC2\wntdll.pdb
我们发现MSVCR90D的pdb并没有被加载,是因为程序还没运行到,
3. 按F11
没有跳到源代码!
运行:src.path C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src
4. lm
lm
start end module name
01330000 0134b000 MyApp C (private pdb symbols) E:\ProLab\WindbgFirst\Debug\MyApp.pdb
598d0000 599f4000 MSVCR90D (private pdb symbols) c:\websymbols\msvcr90d.i386.pdb\EBEA784C96244F1E8F8D35E0391C898D1\msvcr90d.i386.pdb
75100000 75200000 kernel32 (deferred)
76750000 76796000 KERNELBASE (deferred)
77500000 77680000 ntdll (pdb symbols) c:\websymbols\wntdll.pdb\ACE318E6A2F44F23A6CC5628F10A7DDC2\wntdll.pdb
5. 查看堆栈 k / kp / kP / kn
k比较简单,kp能看到各个函数的输入参数,kP比kp看起来更舒服,kn(callstack with index number)
在windbg中,在堆栈中切换到不同的函数,需要用到.frame命令(注意前面的点号)。
# ChildEBP RetAddr
00 0021fa0c 01341464 MSVCR90D!_wtol [f:\dd\vctools\crt_bld\self_x86\crt\src\atox.c @ 55]
01 0021faf0 01341a88 MyApp!wmain+0x44 [e:\prolab\windbgfirst\windbgfirst\windbgfirst.cpp @ 29]
02 0021fb40 013418cf MyApp!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
03 0021fb48 75113677 MyApp!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]
04 0021fb54 77539d42 kernel32!BaseThreadInitThunk+0xe
05 0021fb94 77539d15 ntdll!__RtlUserThreadStart+0x70
06 0021fbac 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> .frame 01
01 0021faf0 01341a88 MyApp!wmain+0x44 [e:\prolab\windbgfirst\windbgfirst\windbgfirst.cpp @ 29]
0:000> dv
argc = 0n2
argv = 0x000d1b90
result = 0n0
6. dv(display variables)
切换了函数以后,下一步就是查看变量的值,使用dv命令来查看变量信息,这个命令相当于Visual Studio里面的局部变量(locals)窗口。
7. kM
.frame的方式比较复杂,因此windbg提供了一个快捷命令,kM(callstack
with markup)。这个命令提供了一个类似html网页超链接的形式,供程序员在堆栈中快速切换函数并且显示变量值,
# ChildEBP RetAddr
00 0021fa0c 01341464 MSVCR90D!_wtol+0x5
01 0021faf0 01341a88 MyApp!wmain+0x44
02 0021fb40 013418cf MyApp!__tmainCRTStartup+0x1a8
03 0021fb48 75113677 MyApp!wmainCRTStartup+0xf
04 0021fb54 77539d42 kernel32!BaseThreadInitThunk+0xe
05 0021fb94 77539d15 ntdll!__RtlUserThreadStart+0x70
06 0021fbac 00000000 ntdll!_RtlUserThreadStart+0x1b
8. dt
对于简单类型,例如整型、浮点型甚至是字符串,windbg可以直接显示出变量的值。但是对于一些复杂类型,例如数组,结构,类呀,那就需要借助另外一个命令dt(display
type)了
Local var @ 0x21fafc Type wchar_t**
0x000d1b90
unsigned short,在C和C++程序中,一般都意味着是wchar_t(宽字符)类型
**表示是一个包含宽字符字符串的数组
9. dd(display by double-word)
下一步就是继续查看argv数组里面的内容,根据前面的dv打印的结果,我们知道argc(也就是说明argv数组元素个数的参数)的值是2。在一台32位机(或者是在64位机器上调试一个32位的程序),使用dd命令参看argv的内存,以四字节的形式显示,如果是64位,使用dq(display
by quad-word)命令以8个字节的形式打印内存。
dd默认是显示32个dword,也就是128个字节的内存内容。
# 将argv传给dd命令的时候, windbg是先将argv转换成保存
# 数组指针的地址(就是0021fafc)—毕竟数组的指针也是需要地方保存的嘛。
# 而高亮显示的00081350才是保存argv数组内容的真实地址
dd argv0021fafc 000d1b90 000d1c30 164b267e 00000000
0021fb0c 00000000 7efde000 00da7a64 00000000
0021fb1c 00000000 00220000 00000000 0021fb04
0021fb2c 00000069 0021fb84 01341087 175eb66e
0021fb3c 00000000 0021fb48 013418cf 0021fb54
0021fb4c 75113677 7efde000 0021fb94 77539d42
0021fb5c 7efde000 76a0918c 00000000 00000000
0021fb6c 7efde000 00000000 00000000 00000000
继续分析argv数组内容
既然我们已经知道argv数组的大小是2的话,你也可以将这个信息提供给dd命令,告诉它你只需要显示argv指针所指向的内存的两个元素就可以了
10. dd 000d1b90 L2
000d1b90 000d1b9c 000d1be8
11. 执行了这么多命令以后,我们终于可以看到argv[0]和argv[1]的值了,不容易呀!既然已经知道是unicode字符串,使用du(display
unicode)命令就可以显示完整的字符串内容了。
000d1b9c "E:\ProLab\WindbgFirst\Debug\MyAp"
000d1bdc "p.exe"
0:000> du
000d1be8 "226"
发现连续执行du,windbg会默认去取下一个地址的内容,呈现出来.
但是也有很多情况下,你可能并不知道指定地址里面保存的内容是什么,这个时候,建议你用dc(display
double-word values and ASCII characters)命令查看内存。
000d1b90 000d1b9c 000d1be8 00000000 003a0045 ............E.:.
000d1ba0 0050005c 006f0072 0061004c 005c0062 \.P.r.o.L.a.b.\.
000d1bb0 00690057 0064006e 00670062 00690046 W.i.n.d.b.g.F.i.
000d1bc0 00730072 005c0074 00650044 00750062 r.s.t.\.D.e.b.u.
000d1bd0 005c0067 0079004d 00700041 002e0070 g.\.M.y.A.p.p...
000d1be0 00780065 00000065 00320032 00000036 e.x.e...2.2.6...
000d1bf0 fdfdfdfd abababab abababab feeefeee ................
000d1c00 00000000 00000000 31dcc3ca 1800157c ...........1|...
如果你调试的是一个非unicode程序,即是一个只理解ASCII字符集的程序(也就是所有字符串的类型都是char),那么在查看字符串的时候,使用da(display ascii)而不是du命令来显示内存