Windbg局部变量显示不正确
假设有一段如下程序,在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
嗯? 这个局部变量显示的值完全不对啊。。。。。。
Windbg显示正确的局部变量
于是我就怀疑是不是.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
之后查看局部变量不正确? 在文章开始前我配置Optimization
为/Od
,可是还有一个Whole Program Optimization
选项没有配置为No
。 在配置完这两个优化选项之后,再调用.frame 2
,可以正常查看局部变量了,而不再需要调用.frame /c 2
.
那如果出现了文中所碰到的情况,如果对Windows 32位的函数栈比较熟悉的,还可以直接通过堆栈的内容,查看到真实的局部变量(前提是这些局部变量存放在栈上)。
对于用默认的Release编译出来的32位程序,因为被优化,当用Windbg调试或者分析Dump时,查看局部变量,使用dv /V /t
很可能得不到正确的结果,要查看真实的局部变量值还得结合函数汇编和函数调用栈信息去查看。