代码之家  ›  专栏  ›  技术社区  ›  LeopardSkinPillBoxHat

反向Heisenbug-只有在附加调试器时单元测试才会失败

  •  4
  • LeopardSkinPillBoxHat  · 技术社区  · 14 年前

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

    为了获得良好的实践,我添加了一个单元测试以确保错误不会再次出现。在编写单元测试时,我总是会放弃缺陷修复,并确保单元测试失败,否则我知道它没有正确地完成工作。

    在撤销缺陷修复之后,我发现我的单元测试仍然通过(不好)。当我将调试器附加到单元测试以查看其通过的原因时,测试失败(即引发异常),我可以中断并观察调用堆栈是否与我修复的原始缺陷中的堆栈匹配。

    我没有修改Visual Studio 2005中的“异常中断”设置,这确实是一个导致测试线束终止的关键Win32异常(即没有正常的异常处理程序)。

    例外情况如下:

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

    注: 地点并不总是 0xcdcdcdcd ( allocated but unwritten Win32 heap memory ). 有时是 0x00000000 ,有时它是另一个地址。

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

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

    有什么可能导致这种情况的建议吗?


    更新: 我对这个问题的起因的了解越来越少了。见 this question 更多细节。如果我找到答案的话,会更新这个问题。

    4 回复  |  直到 5 年前
        1
  •  3
  •   Adam Milligan    14 年前

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

    您可以修改生成设置,以启用在释放模式下用已知值填充已释放内存的选项(完成后不要忘记再次将其关闭)。我猜如果你这么做的话,你的应用程序会在释放模式下崩溃。

    我很感激这个值并不总是0xcdcdcdcd,这可能意味着我错了,也可能意味着您有多个指向悬挂指针的路径。

        2
  •  2
  •   Adrian Pronk    14 年前

    我几年前遇到的情况正好相反:问题只在调试器 附属的。

    结果发现代码破坏了先前方法激活的堆栈框架,并且使用调试器引入了一个中间堆栈框架。

    你可能也有类似的情况。

        3
  •  0
  •   Hasturkun    14 年前

    我不知道这是否有帮助,但我曾经遇到一个错误,如果程序是在Visual Studio调试器下运行的,或者程序是在外部运行的,然后附加了调试器,则会出现不同的情况。

        4
  •  0
  •   Community CDub    8 年前

    我已经查出了这个问题的原因 this question 详细情况。

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

    从命令行运行我的测试线束时,同一对象的后续分配/释放始终使用 相同的 内存块。这种共同的行为意味着当我访问测试用例中的一个悬挂指针时,悬挂指针仍然指向一个有效的对象。所以我没看到撞车。

    推荐文章