当源代码证明不是这样时,调试变量如何为 NULL
How can debugged variables be NULL when source code proves otherwise
我正在调试完整的内存转储 (procdump -ma ...
),并且正在调查调用堆栈,对应于以下源代码:
unsigned int __stdcall ExecutionThread(void* pArg)
{
__try
{
BOOL bRunning = TRUE;
CInternalManagerObject* pInternalManagerObject = (CInternalManagerObject*) pArg;
pInternalManagerObject->Init();
CInternaStartlManagerObject* pInternaStartlManagerObject = pInternalManagerObject->GetInternaStartlManagerObject();
while(bRunning)
{
bRunning = pInternalManagerObject->Poll(pInternaStartlManagerObject);
if (CSLGlobal::IsValidHandle(_Module.m_hNeverEvent))
WaitForSingleObject(_Module.m_hNeverEvent, 15);
} <<<<<<<<<<<<<<<<============== here is the call stack pointer
pInternalManagerObject->DeInit();
如您所见,pArg
正在被转换然后被使用,因此 pArg
不可能成为 NULL
,但这正是 watch-window 告诉我。最重要的是,内部变量似乎是未知的(也如watch-window中所述)。
观看-window内容:
pArg 0x0000000000000000 void *
bRunning identifier "bRunning" is undefined
pInternalManagerObject identifier "pInternalManagerObject" is undefined
我可以理解 bRunning
被优化掉了,因为这个变量不再使用了,但这对 pInternalManagerObject
来说是不正确的,它仍然在下面的行中使用。
符号似乎加载正常。
我正在使用 Visual Studio Professional 2017,版本 15.8.8 查看此内容。
有没有人知道可能导致这种奇怪行为的原因以及我可以做些什么来获得具有正确内部变量值的转储?
生成汇编代码的问题后编辑
生成的程序集是:
27:
28: unsigned int __stdcall ExecutionThread(void* pArg)
29: {
00007FF69C7A1690 48 89 5C 24 08 mov qword ptr [rsp+8],rbx
00007FF69C7A1695 48 89 74 24 10 mov qword ptr [rsp+10h],rsi
00007FF69C7A169A 57 push rdi
00007FF69C7A169B 48 83 EC 20 sub rsp,20h
00007FF69C7A169F 48 8B F9 mov rdi,rcx
30: __try
31: {
32: BOOL bRunning = TRUE;
00007FF69C7A16A2 BB 01 00 00 00 mov ebx,1
33: CInternalManagerObject* pInternalManagerObject = (CInternalManagerObject*) pArg;
34:
35: pInternalManagerObject->Init();
00007FF69C7A16A7 E8 64 EA FD FF call CInternalManagerObject::Init (07FF69C780110h)
36:
37: CBaseManager* pBaseManager = pInternalManagerObject->GetBaseManager();
00007FF69C7A16AC 48 8B CF mov rcx,rdi
00007FF69C7A16AF E8 0C E9 FD FF call CInternalManagerObject::GetBaseManager (07FF69C77FFC0h)
00007FF69C7A16B4 48 8B F0 mov rsi,rax
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16B7 48 8B CF mov rcx,rdi
38:
39: while(bRunning)
00007FF69C7A16BA 85 DB test ebx,ebx
00007FF69C7A16BC 74 2E je ExecutionThread+5Ch (07FF69C7A16ECh)
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16BE 48 8B D6 mov rdx,rsi
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16C1 E8 7A ED FD FF call CInternalManagerObject::Poll (07FF69C780440h)
00007FF69C7A16C6 8B D8 mov ebx,eax
42:
43: if (CSLGlobal::IsValidHandle(_Module.m_hNeverEvent))
00007FF69C7A16C8 48 8D 0D C1 13 0E 00 lea rcx,[_Module+550h (07FF69C882A90h)]
00007FF69C7A16CF E8 3C F2 FB FF call __Skyline_Global::CSLGlobal::IsValidHandle (07FF69C760910h)
00007FF69C7A16D4 85 C0 test eax,eax
00007FF69C7A16D6 74 12 je ExecutionThread+5Ah (07FF69C7A16EAh)
44: WaitForSingleObject(_Module.m_hNeverEvent, 15);
00007FF69C7A16D8 BA 0F 00 00 00 mov edx,0Fh
00007FF69C7A16DD 48 8B 0D AC 13 0E 00 mov rcx,qword ptr [_Module+550h (07FF69C882A90h)]
00007FF69C7A16E4 FF 15 16 0B 08 00 call qword ptr [__imp_WaitForSingleObject (07FF69C822200h)]
45: }
00007FF69C7A16EA EB CB jmp ExecutionThread+27h (07FF69C7A16B7h)
46:
47: pInternalManagerObject->DeInit();
00007FF69C7A16EC E8 FF E7 FD FF call CInternalManagerObject::DeInit (07FF69C77FEF0h)
48: }
我想这意味着 pArg
的正确值可以在寄存器 RDI
中找到。
Register
window 给了我以下信息:
RAX = 0000000000000000
RBX = 0000000000000001
RCX = 0000000000000000
RDX = 0000000000000000
RSI = 00000072A1E83220
RDI = 00000072A14A9990
...
查看上述位置的内存,我看到十六进制值如下:
0x00000072A14A9990 98 59 82 9c f6 7f 00 00 01 00 00 00 00 00 08 00 28 d2 28 62 f9 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 0e 78 a2 72 00 ˜Y.œö...........(Ò(bù...................................P.x¢r.
0x00000072A14A99CE 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 5c 07 00 00 00 00 00 00 d0 07 00 02 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..ÿÿÿÿ............\.......Ð.......ÿÿÿÿÿÿÿÿÿÿÿÿ................
0x00000072A14A9A0C 00 00 00 00 d0 07 00 02 00 00 00 00 38 59 82 9c f6 7f 00 00 f0 90 60 a2 72 00 00 00 00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 59 82 9c f6 7f 00 00 00 00
这是否意味着 pArg
确实不是 NULL
? (对不起,我没有汇编调试经验)
Does this mean that pArg is not NULL indeed?
不,这不是那个意思; pArg
为空。手表 window 告诉你,寄存器告诉你。
As you can see, pArg is being typecasted and then being used, so it's
impossible for pArg to be NULL.
这是不正确的;那不是演员所做的。如果变量为 null,则转换的结果将为 null。
https://en.cppreference.com/w/c/language/cast
I suppose this means that the correct value of pArg can be found in
register RDI.
没有; pArg
安装到 rcx
上; mov
从右到左工作。
mov rcx,rdi
RCX = 0000000000000000
https://c9x.me/x86/html/file_module_x86_id_176.html
I can understand bRunning being optimised away, as this variable is not used anymore, but this is not correct for pInternalManagerObject,
which is still used in the following line.
我的猜测是,当程序计数器位于函数的第一行时,您已经观察到手表 window。 bRunning
和 pInternalManagerObject
超出范围。 (尽管它们可能会由于优化而被剥离)。请注意,如果变量被剥离,即使使用它,您也看不到它。
想法
- 防御性编程。调用
assert
(或代码库使用的任何断言宏)以便在取消引用之前检查 pArg
(或任何其他指针)的值。如果这是您可以在生产中合理看到的错误,请更进一步:记录意外行为并提前退出该功能。 http://www.cplusplus.com/reference/cassert/assert/
- KISS:在这种情况下,我会赞扬任何愿意获得 "hands dirty" 的人,只是没有必要开始破解反汇编。在这种情况下,答案就在那里。 https://en.wikipedia.org/wiki/KISS_principle
- 此外,如果问题的措辞更易于阅读,您会在 SO 上得到更好的回应。记住在进入代码之前解释你在做什么以及问题是什么。解释您面临的故障(以及任何错误输出),并提出问题。 https://whosebug.com/help/how-to-ask
我正在调试完整的内存转储 (procdump -ma ...
),并且正在调查调用堆栈,对应于以下源代码:
unsigned int __stdcall ExecutionThread(void* pArg)
{
__try
{
BOOL bRunning = TRUE;
CInternalManagerObject* pInternalManagerObject = (CInternalManagerObject*) pArg;
pInternalManagerObject->Init();
CInternaStartlManagerObject* pInternaStartlManagerObject = pInternalManagerObject->GetInternaStartlManagerObject();
while(bRunning)
{
bRunning = pInternalManagerObject->Poll(pInternaStartlManagerObject);
if (CSLGlobal::IsValidHandle(_Module.m_hNeverEvent))
WaitForSingleObject(_Module.m_hNeverEvent, 15);
} <<<<<<<<<<<<<<<<============== here is the call stack pointer
pInternalManagerObject->DeInit();
如您所见,pArg
正在被转换然后被使用,因此 pArg
不可能成为 NULL
,但这正是 watch-window 告诉我。最重要的是,内部变量似乎是未知的(也如watch-window中所述)。
观看-window内容:
pArg 0x0000000000000000 void *
bRunning identifier "bRunning" is undefined
pInternalManagerObject identifier "pInternalManagerObject" is undefined
我可以理解 bRunning
被优化掉了,因为这个变量不再使用了,但这对 pInternalManagerObject
来说是不正确的,它仍然在下面的行中使用。
符号似乎加载正常。
我正在使用 Visual Studio Professional 2017,版本 15.8.8 查看此内容。
有没有人知道可能导致这种奇怪行为的原因以及我可以做些什么来获得具有正确内部变量值的转储?
生成汇编代码的问题后编辑
生成的程序集是:
27:
28: unsigned int __stdcall ExecutionThread(void* pArg)
29: {
00007FF69C7A1690 48 89 5C 24 08 mov qword ptr [rsp+8],rbx
00007FF69C7A1695 48 89 74 24 10 mov qword ptr [rsp+10h],rsi
00007FF69C7A169A 57 push rdi
00007FF69C7A169B 48 83 EC 20 sub rsp,20h
00007FF69C7A169F 48 8B F9 mov rdi,rcx
30: __try
31: {
32: BOOL bRunning = TRUE;
00007FF69C7A16A2 BB 01 00 00 00 mov ebx,1
33: CInternalManagerObject* pInternalManagerObject = (CInternalManagerObject*) pArg;
34:
35: pInternalManagerObject->Init();
00007FF69C7A16A7 E8 64 EA FD FF call CInternalManagerObject::Init (07FF69C780110h)
36:
37: CBaseManager* pBaseManager = pInternalManagerObject->GetBaseManager();
00007FF69C7A16AC 48 8B CF mov rcx,rdi
00007FF69C7A16AF E8 0C E9 FD FF call CInternalManagerObject::GetBaseManager (07FF69C77FFC0h)
00007FF69C7A16B4 48 8B F0 mov rsi,rax
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16B7 48 8B CF mov rcx,rdi
38:
39: while(bRunning)
00007FF69C7A16BA 85 DB test ebx,ebx
00007FF69C7A16BC 74 2E je ExecutionThread+5Ch (07FF69C7A16ECh)
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16BE 48 8B D6 mov rdx,rsi
40: {
41: bRunning = pInternalManagerObject->Poll(pBaseManager);
00007FF69C7A16C1 E8 7A ED FD FF call CInternalManagerObject::Poll (07FF69C780440h)
00007FF69C7A16C6 8B D8 mov ebx,eax
42:
43: if (CSLGlobal::IsValidHandle(_Module.m_hNeverEvent))
00007FF69C7A16C8 48 8D 0D C1 13 0E 00 lea rcx,[_Module+550h (07FF69C882A90h)]
00007FF69C7A16CF E8 3C F2 FB FF call __Skyline_Global::CSLGlobal::IsValidHandle (07FF69C760910h)
00007FF69C7A16D4 85 C0 test eax,eax
00007FF69C7A16D6 74 12 je ExecutionThread+5Ah (07FF69C7A16EAh)
44: WaitForSingleObject(_Module.m_hNeverEvent, 15);
00007FF69C7A16D8 BA 0F 00 00 00 mov edx,0Fh
00007FF69C7A16DD 48 8B 0D AC 13 0E 00 mov rcx,qword ptr [_Module+550h (07FF69C882A90h)]
00007FF69C7A16E4 FF 15 16 0B 08 00 call qword ptr [__imp_WaitForSingleObject (07FF69C822200h)]
45: }
00007FF69C7A16EA EB CB jmp ExecutionThread+27h (07FF69C7A16B7h)
46:
47: pInternalManagerObject->DeInit();
00007FF69C7A16EC E8 FF E7 FD FF call CInternalManagerObject::DeInit (07FF69C77FEF0h)
48: }
我想这意味着 pArg
的正确值可以在寄存器 RDI
中找到。
Register
window 给了我以下信息:
RAX = 0000000000000000
RBX = 0000000000000001
RCX = 0000000000000000
RDX = 0000000000000000
RSI = 00000072A1E83220
RDI = 00000072A14A9990
...
查看上述位置的内存,我看到十六进制值如下:
0x00000072A14A9990 98 59 82 9c f6 7f 00 00 01 00 00 00 00 00 08 00 28 d2 28 62 f9 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 0e 78 a2 72 00 ˜Y.œö...........(Ò(bù...................................P.x¢r.
0x00000072A14A99CE 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 5c 07 00 00 00 00 00 00 d0 07 00 02 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..ÿÿÿÿ............\.......Ð.......ÿÿÿÿÿÿÿÿÿÿÿÿ................
0x00000072A14A9A0C 00 00 00 00 d0 07 00 02 00 00 00 00 38 59 82 9c f6 7f 00 00 f0 90 60 a2 72 00 00 00 00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 59 82 9c f6 7f 00 00 00 00
这是否意味着 pArg
确实不是 NULL
? (对不起,我没有汇编调试经验)
Does this mean that pArg is not NULL indeed?
不,这不是那个意思; pArg
为空。手表 window 告诉你,寄存器告诉你。
As you can see, pArg is being typecasted and then being used, so it's impossible for pArg to be NULL.
这是不正确的;那不是演员所做的。如果变量为 null,则转换的结果将为 null。
https://en.cppreference.com/w/c/language/cast
I suppose this means that the correct value of pArg can be found in register RDI.
没有; pArg
安装到 rcx
上; mov
从右到左工作。
mov rcx,rdi
RCX = 0000000000000000
https://c9x.me/x86/html/file_module_x86_id_176.html
I can understand bRunning being optimised away, as this variable is not used anymore, but this is not correct for pInternalManagerObject, which is still used in the following line.
我的猜测是,当程序计数器位于函数的第一行时,您已经观察到手表 window。 bRunning
和 pInternalManagerObject
超出范围。 (尽管它们可能会由于优化而被剥离)。请注意,如果变量被剥离,即使使用它,您也看不到它。
想法
- 防御性编程。调用
assert
(或代码库使用的任何断言宏)以便在取消引用之前检查pArg
(或任何其他指针)的值。如果这是您可以在生产中合理看到的错误,请更进一步:记录意外行为并提前退出该功能。 http://www.cplusplus.com/reference/cassert/assert/ - KISS:在这种情况下,我会赞扬任何愿意获得 "hands dirty" 的人,只是没有必要开始破解反汇编。在这种情况下,答案就在那里。 https://en.wikipedia.org/wiki/KISS_principle
- 此外,如果问题的措辞更易于阅读,您会在 SO 上得到更好的回应。记住在进入代码之前解释你在做什么以及问题是什么。解释您面临的故障(以及任何错误输出),并提出问题。 https://whosebug.com/help/how-to-ask