如何在没有有用的调用堆栈的情况下调试难以重现的崩溃?

时间:2011-01-17 23:48:38

标签: delphi crash c++builder callstack

我在软件中遇到了一个奇怪的崩溃,我在调试时遇到了很多麻烦,所以我寻求SO的建议来解决它。

崩溃是读取NULL指针的访问冲突:

  

$ 00CF0041的第一次机会异常。   带消息的异常类$ C0000005   '0x00cf0041处的访问冲突:读取   地址0x00000000'。

它只发生'有时' - 我还没有设法找出任何押韵或理由,但是,当时 - 并且只在主线程中。当它发生时,调用堆栈包含一个不正确的条目:

Call stack with one line, Classes::TList::Get, address 0x00cf0041

对于主线程,它应该显示一大堆其他项目。

此时,所有其他线程都处于非活动状态(大多数都位于WaitForSingleObject或类似的函数中。)我只看到此崩溃发生在主线程中。它始终具有一个条目的相同调用堆栈,在同一地址的相同方法中。此方法可能相关也可能不相关 - 我们在应用程序中使用VCL。不过,我的赌注是(可能很久以前)某些东西正在破坏堆栈,而崩溃的地址实际上是随机的。请注意,它在几个版本中的地址相同 - 但它可能不是真正随机的。

这是我尝试过的:

  • 尝试在某一点可靠地重现它。我没有发现任何东西每次都会重现它,以及偶尔做或不做的一些事情,没有明显的理由。这些并不是“狭隘”的足以将其缩小到特定代码段的行为。它可能与时间有关,但在IDE中断的时候,其他线程通常什么都不做。我不能排除线程问题,但认为这不太可能。
  • 使用额外的调试语句构建(额外的调试信息,额外的断言等)。这样做之后,崩溃永远不会发生。
  • 在启用Codeguard的情况下构建。执行此操作后,崩溃永远不会发生,Codeguard没有显示任何错误。

我的问题:

1。如何找到导致崩溃的代码?我该如何做到相当于向后退步?

2。您对如何追踪此次崩溃的原因有何一般建议?

我正在使用Embarcadero RAD Studio 2010(该项目主要包含C ++ Builder代码和少量Delphi。)

编辑:我认为我应该添加实际导致此问题的内容。有一个调用ReadDirectoryChangesW的线程,然后使用GetOverlappedResult等待事件继续并对更改执行某些操作。事件也发出信号,以便在设置状态标志后终止线程。问题是,当线程退出时,它从未调用CancelIO。因此,Windows仍在跟踪更改,并且可能仍然在目录更改时写入缓冲区,即使缓冲区,重叠的结构和事件不再存在(创建它们的线程上下文也没有。){{1被叫,没有更多的崩溃。

4 个答案:

答案 0 :(得分:15)

即使IDE提供的堆栈跟踪不是很完整,也不意味着堆栈上仍然没有有用的信息。打开CPU视图并查看堆栈窗格;对于每个CALL操作码,返回地址都被压入堆栈。由于堆栈向下增长,您将在当前堆栈位置上方找到这些返回地址,即通过在堆栈窗格中向上滚动。

主线程的堆栈将大约为$ 00120000或$ 00180000(Vista中的地址空间随机化以及更高版本使其更随机)。主可执行文件的代码大约为$ 00400000。您可以通过右键单击堆栈条目并选择关注 - >来推测性地调查堆栈上看起来不像整数数据(低值)或堆栈地址($ 00120000 +范围)的元素。近代码,这将导致反汇编窗口跳转到该代码地址。如果它看起来像无效代码,它可能不是堆栈跟踪中的有效条目。如果它是有效代码,它可能是操作系统代码(通常大约7700万美元及以上),在这种情况下你将没有有意义的符号,但每隔一段时间你就会遇到一个实际的正确堆栈条目。

这种技术虽然有些费力,但在调试器无法跟踪事物时可以为您提供有意义的堆栈跟踪信息。但是,如果ESP(堆栈指针)被拧紧,它对你没有帮助。幸运的是,这种情况非常罕见。

答案 1 :(得分:4)

这就是我制作Process Stack查看器的原因:-) http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer

它可以显示具有原始堆栈跟踪的堆栈,因此当无法进行正常堆栈跟踪时,它将显示完整堆栈。
但要注意:原始堆栈跟踪将显示“误报”!将列出堆栈中可以找到函数名的任何地址。

当我遇到与你相同的问题时,它帮助了我很多次(由于堆栈状态无效,Delphi没有正常的堆栈行走)

编辑:上传的新版本,在网站上是旧版本(我自己使用新版本) http://asmprofiler.googlecode.com/files/AsmProfiler_Sampling%20v1.0.7.13.zip

答案 2 :(得分:2)

线程可能就是这里的原因。通常的怀疑是在堆栈上使用OVERLAPPED结构的线程,以及将指向堆栈上的对象的指针发送到其他线程的线程。

如果使用Deubgging Tools For Windows并使用“dps”命令,则可以恢复部分堆栈信息。

答案 3 :(得分:2)

我不是百分百肯定,但是从您提供的图像中我相信在执行的某个地方您尝试访问TList中的对象是NULL。即:

AList[Index].SomeProperty/SomeMethod/etc. <-- error if (AList[Index] == NULL)

关于调试和查找引发异常的实际位置绝不是一件容易的事,特别是当信息不多或难以重现时,在这种情况下我通常会:

  • 从主表单执行一步一步(如果没有例外,直到那里)

  • 一步一步走,如果我发现任何不安全的代码,我把它放在try ... except和索引条件之间(如果我有数组,列表,要传递的期望值等)

  • 如果以上内容未能找到问题,请检查某些库是否出现故障

  • 使用Eureka日志,它有时会失败(很少次),但它通常会指向正确的方向

我遇到了很多与你类似的问题,我可以告诉你这个问题几乎非常容易解决,但是当错误弹出时,我没有得到一个“接近”错误。