假设有一段如下程序,在VS2005中,选择Release配置项,为了避免将测试程序中的局部变量被优化,关闭工程属性中C/C++->Optimization中的/Od编译选项,然后编译生成一个32位的可执行程序。
#include <windows.h> #include <tchar.h> #include <stdio.h> int main() { int iValue = 5; char *pStr = "hello world!"; char *pError = (char*)0x5; printf("%s %d\n", pStr, iValue); return 0; }用Windbg 启动这个程序,并且运行g命令,程序会在printf调用时产生异常,可以看看异常时候的函数调用栈:
0:000> kv ChildEBP RetAddr Args to Child 0018fed0 00401082 0040c030 0040b354 00000000 testforme!_output_l+0x7f4 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\output.c @ 1648] 0018ff14 00409581 0040b354 00000005 0040b344 testforme!printf+0x73 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\printf.c @ 63] 0018ff38 00401298 00000001 037f1b18 037f1b40 testforme!main+0x31 (CONV: cdecl) [d:\vsproject\testforme\testforme\test.cpp @ 10] 0018ff88 7647338a 7efde000 0018ffd4 77969902 testforme!__tmainCRTStartup+0x15f (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] 0018ff94 77969902 7efde000 7171b5ba 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo]) 0018ffd4 779698d5 004012ef 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 0018ffec 00000000 004012ef 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])函数调用栈和异常不是我们本文所关心的,本文所关心的是在这种情况下,如何去查看main函数中的局部变量的值呢? 如下,先通过kn查看栈帧的编号, 然后调用.frame命令切换栈帧到main函数, 最后调用dv /V /t查看局部变量。
0:000> kn # ChildEBP RetAddr 00 0018fed0 00401082 testforme!_output_l+0x7f4 [f:\sp\vctools\crt_bld\self_x86\crt\src\output.c @ 1648] 01 0018ff14 00409581 testforme!printf+0x73 [f:\sp\vctools\crt_bld\self_x86\crt\src\printf.c @ 63] 02 0018ff38 00401298 testforme!main+0x31 [d:\vsproject\testforme\testforme\test.cpp @ 10] 03 0018ff88 7647338a testforme!__tmainCRTStartup+0x15f [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] 04 0018ff94 77969902 kernel32!BaseThreadInitThunk+0xe 05 0018ffd4 779698d5 ntdll!__RtlUserThreadStart+0x70 06 0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000> .frame 2 02 0018ff38 00401298 testforme!main+0x31 [d:\vsproject\testforme\testforme\test.cpp @ 10] 0:000> dv /V /t 0018ff08 <virtual frame 18ff14>-0x000c char * pStr = 0x00402320 "???" 0018ff0c <virtual frame 18ff14>-0x0008 char * pError = 0x2e8b86c5 "--- memory read error at address 0x2e8b86c5 ---" 0018ff10 <virtual frame 18ff14>-0x0004 int iValue = 0n0嗯? 这个局部变量显示的值完全不对啊。。。。。。
于是我就怀疑是不是.frame命令用错了,接着查看了.frame的帮助文档,并且看到这个参数/c, 大意是将设置指定的栈帧作为当前本地override上下文。
/c Sets the specified frame as the current local override context. This action allows a user to access the nonvolatile registers for any function in the call stack.半信半疑的使用了下.frame /c 2, 并且查看了局部变量,终于显示正确了。
0:000> .frame /c 2 02 0018ff38 00401298 testforme!main+0x31 [d:\vsproject\testforme\testforme\test.cpp @ 10] eax=00000005 ebx=00000020 ecx=7ffffffe edx=00000073 esi=00000000 edi=00000005 eip=00409581 esp=0018ff1c ebp=0018ff38 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202 testforme!main+0x31: 00409581 83c410 add esp,10h 0:000> dv /V /t 0018ff2c <virtual frame 18ff38>-0x000c char * pStr = 0x0040b344 "hello world!" 0018ff30 <virtual frame 18ff38>-0x0008 char * pError = 0x00000005 "--- memory read error at address 0x00000005 ---" 0018ff34 <virtual frame 18ff38>-0x0004 int iValue = 0n5可是这个时候你运行kn会发现frame 0 和 frame 1不会显示了,这时你可以调用.cxr回复到出现异常时候的上下文。
0:000> kn *** Stack trace for last set context - .thread/.cxr resets it # ChildEBP RetAddr 02 0018ff38 00401298 testforme!main+0x31 [d:\vsproject\testforme\testforme\test.cpp @ 10] 03 0018ff88 7647338a testforme!__tmainCRTStartup+0x15f [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] 04 0018ff94 77969902 kernel32!BaseThreadInitThunk+0xe 05 0018ffd4 779698d5 ntdll!__RtlUserThreadStart+0x70 06 0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000> .cxr Resetting default scope 0:000> kn # ChildEBP RetAddr 00 0018fed0 00401082 testforme!_output_l+0x7f4 [f:\sp\vctools\crt_bld\self_x86\crt\src\output.c @ 1648] 01 0018ff14 00409581 testforme!printf+0x73 [f:\sp\vctools\crt_bld\self_x86\crt\src\printf.c @ 63] 02 0018ff38 00401298 testforme!main+0x31 [d:\vsproject\testforme\testforme\test.cpp @ 10] 03 0018ff88 7647338a testforme!__tmainCRTStartup+0x15f [f:\sp\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327] 04 0018ff94 77969902 kernel32!BaseThreadInitThunk+0xe 05 0018ffd4 779698d5 ntdll!__RtlUserThreadStart+0x70 06 0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b为啥使用了.frame /c 2就可以查看正确的局部变量了呢?难道编译器做了什么优化,导致Windbg在调用.frame 2之后查看局部变量不正确? 在文章开始前我关闭了/Od选项,可是还有一个Whole Program Optimization选项没有关闭。 于是同时关闭完这两个优化选项之后,可以在调用.frame 2后正常的查看局部变量了,而不再需要调用.frame /c 2.
那如果出现了文中所碰到的情况,如果对Windows 32位的函数栈比较熟悉的,还可以直接通过堆栈的内容,查看到真实的局部变量(前提是这些局部变量存放在栈上)。
对于用默认的Release编译出来的32位程序,因为被优化,当用Windbg调试或者分析Dump时,查看局部变量,使用dv /V /t很可能得不到正确的结果,要查看真实的局部变量值还得结合函数汇编和函数调用栈信息去查看。
