Heisenbug:WinApi程序在某些计算机上崩溃

9
请帮忙!我真的很绝望。我的程序是一个小型个人笔记管理器(可以在谷歌上搜索“cintanotes”)。在某些电脑上(当然,我没有拥有它们中的任何一台),它在启动后会崩溃并显示未处理的异常。除了这些电脑倾向于使用AMD CPU外,没有什么特别之处。
环境:Windows XP,Visual C++ 2005/2008,原始WinApi。
以下是关于此“Heisenbug”的确定内容:
1)崩溃仅发生在Release版本中。
2)只要我删除所有与GDI相关的内容,崩溃就会消失。
3)BoundChecker没有抱怨。
4)编写日志表明,崩溃发生在本地int变量的声明上!那怎么可能?内存损坏?
非常感谢您的任何想法!
更新:我已经成功在“有问题”的PC上调试了该应用程序。结果如下:
“CintaNotes.exe中的0x0044a26a处出现未处理的异常:0xC000001D:非法指令。”
代码中断在:
0044A26A cvtsi2sd xmm1,dword ptr [esp+14h]
因此,问题似乎在于“代码生成/启用增强指令集”编译器选项。它被设置为“/arch:SSE2”,并在不支持SSE2的机器上崩溃。我将此选项设置为“未设置”,错误就消失了。谢天谢地!
非常感谢您所有人的帮助!

看起来goldenmean离答案最近。 - Constantin
11个回答

10

记录日志显示崩溃发生在一个本地整数变量的声明上!这怎么可能?内存损坏?

可执行文件/汇编中的基础代码是什么?声明整数根本就不是代码,因此不可能导致崩溃。您是否以某种方式初始化整数?

要查看崩溃发生的代码,应进行所谓的事后分析。

Windows错误报告

如果要分析崩溃情况,应获取崩溃转储。其中一种选择是注册Windows错误报告 - 需要一些费用(需要数字代码签名ID)和一些表格填写。有关更多信息,请访问https://winqual.microsoft.com/

直接从客户处获取为WER准备的崩溃转储

另一种选择是联系一些遇到崩溃情况的用户,并直接从他那里获取为WER准备的崩溃转储。当用户在将崩溃发送到Microsoft之前单击“技术详细信息”时,可以在那里检查崩溃转储文件位置。

自己的小型转储

另一种选择是注册自己的异常处理程序,处理异常并在任何地方编写小型转储。详细说明可以在Code Project Post-Mortem Debugging Your Application with Minidumps and Visual Studio .NET article中找到。


确保在RELEASE(以及debug)模式下为应用程序构建调试信息(PDB文件)。确保保留每个发布版本的PDB集,以便您可以将它们与转储一起使用。可以使用本地符号服务器。投票支持Suma的答案 - 它是正确的! - Aardvark
1
谢谢,我会尝试使用minidump的最后一个想法。不幸的是,我不太习惯低级调试,所以我需要更多地阅读相关资料... - Alex Jenter

5

当配置为DEBUG配置时,希望它不会崩溃?与RELEASE配置不同的许多事情: 1)全局初始化 2)生成的实际机器代码等等。

因此,第一步是找出在RELEASE模式下每个参数的确切设置与DEBUG模式相比有何不同。

-AD


4

1) 崩溃只发生在发布版本中。

这通常意味着您依赖于一些不保证的行为,但在调试构建中恰好是正确的。例如,如果您忘记初始化变量或访问超出边界的数组,请确保已启用所有编译器检查(/RTCsuc)。还要检查是否依赖于函数参数的评估顺序(这并不保证)。

2) 只要我删除所有与GDI相关的内容,崩溃就消失了。

也许这表明您在处理GDI相关内容时做错了什么?例如,您在释放后仍在使用HANDLE吗?


我确实在HFONT句柄方面遇到了一个问题,但是当BoundsChecker指出这个问题时,我很快就解决了它。但不幸的是,这个错误对这个更改没有影响。 - Alex Jenter

2

下载Windows调试工具包。正确设置符号路径,然后在WinDbg下运行应用程序。 在某个时刻,它将因访问冲突而停止。 然后,您应该运行命令“!analyze -v”,它非常智能,并应该为您提供有关出现问题的提示。


我需要在问题机器上本地执行这个吗? - Alex Jenter

1

大多数Heisenbugs / 仅发布的错误是由于控制流依赖于从未初始化的内存/陈旧指针/缓冲区末尾之后的读取,或者竞争条件,或两者都有。

尝试覆盖您的分配器,使它们在分配时将内存清零。问题是否消失(或变得更可重现?)

编写日志显示崩溃发生在本地int变量的声明上!怎么可能?内存损坏?

堆栈溢出! ;)


谢谢您的建议,我一定会在这个方向上进行调查。 - Alex Jenter

1

听起来像是堆栈损坏。我用于追踪这些问题的最喜爱工具是IDA Pro。当然,你无法访问用户的机器。

有些内存检查器很难捕捉到堆栈损坏(如果确实是这样)。我认为最可靠的方法是运行时分析。

这也可能是由于异常路径中的损坏引起的,即使异常已被处理。你是否打开了“捕获一次性异常”进行调试?只要可以,你应该这样做。在许多情况下,它会变得很烦人。

你能否向这些用户发送一个经过检查的应用程序版本?请查看Minidump,处理该异常并写出转储文件。然后使用WinDbg在你的端上进行调试。

另一种方法是编写非常详细的日志。创建一个“记录每个操作”的选项,并要求用户将其打开并发送给您。将内存转储到日志中。在MSDN上查看“_CrtDbgReport()”。
祝你好运!
编辑:
回应您的评论:我对本地变量声明出现错误并不感到惊讶。我经常见到这种情况。这通常是由于堆栈损坏而导致的。
例如,堆栈上的某些变量可能超出了它们的边界。之后就会出现各种问题。然后,堆栈变量声明会引发随机的内存错误,虚拟表被破坏等等。
每当我长时间看到这些问题时,我就必须使用IDA Pro。详细的运行时反汇编调试是我知道可以可靠解决问题的唯一方法。
许多开发人员使用WinDbg进行此类分析。这就是为什么我还建议使用Minidump。

感谢所有的想法。我已经写了一个日志,它指向了一个整型变量的声明。我不是在开玩笑,代码就像这样: log << " before"; log.flush(); int i; log << " after" ; log.flush();
  • 然后只有 "before" 被记录在日志文件中。
- Alex Jenter

1
4) 写日志显示崩溃发生在本地int变量的声明上!怎么会这样?内存损坏。
我发现许多“奇怪的崩溃”原因是在该对象的成员函数中解引用了一个损坏的this指针。

你能详细说明一下,“broken this”是什么意思吗? - Alex Jenter
Alex,破碎的this就像这样:string ps = new string; delete ps; ps->clear()。当你进入clear()函数时,你会看到破碎的this - Constantin

1
尝试使用Rational(IBM)PurifyPlus。它可以捕获BoundsChecker无法捕获的许多错误。

谢谢您的建议。我该如何在演示模式下运行它?它要求许可证服务器。 - Alex Jenter
试用版本下载链接在这里:http://www.ibm.com/developerworks/downloads/r/rpp/ - T.Rob

1

崩溃信息是什么?访问冲突?异常?这将是进一步解决问题的线索

确保您没有使用PageHeap.exe进行前置内存破坏

确保您没有堆栈溢出(CBig array[1000000])

确保您没有未初始化的内存。

此外,您还可以在生成进程的调试符号(与创建调试版本不同)后,在调试器中运行发布版本。逐步执行并查看调试器跟踪窗口中是否有任何警告。


1

"4) 写日志显示崩溃发生在本地int变量的声明上!这怎么可能?内存损坏了吗?"

这可能是硬件实际上存在故障或被过度使用的迹象。找出他们是否超频了电脑。


我认为情况并非如此。这种情况发生在许多没有超频的个人电脑上。 - Alex Jenter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接