Inverse Heisenbug - 仅在附加调试器时单元测试失败

时间:2010-11-25 01:10:54

标签: c++ unit-testing debugging heisenbug

我最近在我们的产品中修复了一个缺陷,其症状是访问悬挂指针导致的访问冲突。

为了良好的实践,我添加了一个单元测试,以确保错误不会回来。在编写单元测试时,我将始终退出我的缺陷修复程序并确保单元测试失败,否则我知道它没有正常工作。

在退出缺陷修复程序后,我发现我的单元测试仍然通过(不好)。当我将调试器连接到单元测试以查看它为什么通过时,测试失败(即抛出异常)并且我可以断开并观察到调用堆栈与我修复的原始缺陷中的调用堆栈匹配。

我没有修改Visual Studio 2005中的“Break on exception”设置,这确实是一个关键的Win32异常导致测试工具终止(即没有优雅的异常处理程序)。

例外文本是:

Unhandled exception at 0x0040fc59 in _testcase.exe: 0xC0000005:
Access violation reading location 0xcdcdcdcd.

注意:该位置并非总是0xcdcdcdcdallocated but unwritten Win32 heap memory)。有时它是0x00000000,有时它是另一个地址。

这似乎与传统的Heisenbug相反,当通过调试器观察它时,问题就会消失。在我的情况下,通过调试器观察它会出现问题!

我最初的想法是,这是由调试器中的时序差异暴露的竞争条件。但是,当我向代码添加跟踪并将其与调试器分开运行时,我打印出来的数据向我表明应用程序应该以类似于在调试器下运行时的方式中止。但事实并非如此!

有关可能导致此问题的任何建议吗?


更新:我正在缩小此问题的原因。有关详细信息,请参阅this question。如果我找到答案,我会用答案更新这个问题。

4 个答案:

答案 0 :(得分:3)

通常,当您删除指向该内存的指针时,VC ++调试器将使用某些已知值填充堆分配的内存。自从我使用Visual Studio以来已经有一段时间了,但我认为0xcdcdcdcd可能是这样的值似乎是合理的。在调试器中运行时,应用程序似乎最有可能正常崩溃。在发布模式下运行时,运行时不会浪费时间覆盖解除分配的内存,因此有时您会“幸运”并且存储在该内存中的数据仍然有效。

您可以修改构建设置以打开在释放模式下使用已知值填充已释放内存的选项(完成后不要忘记再将其关闭)。我想如果你这样做,你的应用程序会在发布模式下崩溃。

我很欣赏这个值并不总是0xcdcdcdcd,这可能意味着我错了,或者可能意味着你有多条通往悬空指针的路径。

答案 1 :(得分:1)

我反过来遇到了这个问题:问题只发生在调试器时。

事实证明,代码破坏了之前方法激活的堆栈帧,并使用调试器引入了一个中间堆栈帧。

你可能也有类似的情况。

答案 2 :(得分:0)

我不知道这对你是否会有所帮助,但是我曾经遇到过一个错误,如果程序在Visual Studio调试器下运行,或者程序是在外部运行,那么就会有不同的错误,然后附加调试器。

答案 3 :(得分:0)

我已找出此问题的原因 - 有关详细信息,请参阅this question

在调试器下运行我的测试工具时,调试环境消耗的内存意味着同一对象的后续分配/解除分配总是在不同的内存部分中分配。这意味着当我的测试工具试图访问悬空指针时,它会使测试崩溃(从技术上讲,这是未定义的行为,但这是测试代码,它似乎做了我需要它做的事情)。

从命令行运行测试工具时,同一对象的后续分配/解除分配始终重新使用相同内存块。这种coincedental行为意味着当我在我的测试用例中访问实际上是一个悬空指针时,碰巧悬空指针仍然指向一个有效的对象。这就是我没有看到崩溃的原因。