在Windows中分析崩溃:错误消息告诉我们什么?

33

我写的一个小工具(用C++编写)昨天突然崩溃了(之前使用了100多小时都没有问题),虽然我通常不这样做,但当时我感到有些冒险,想尝试了解一下这个问题。于是我决定进入事件查看器,看看Windows记录了关于崩溃的什么信息:

Faulting application StraightToM.exe, version 0.0.0.0, time stamp 0x4a873d19 
Faulting module name : StraightToM.exe, version 0.0.0.0, time stamp 0x4a873d19
Exception code : 0xc0000005
Fault offset : 0x0002d160,
Faulting process id: 0x17b4
Faulting application start time: time 0x01ca238d9e6b48b9.
我的问题是,这些东西各自代表什么意思,我该如何使用它们来调试我的程序?目前我已经了解到异常代码描述了错误,0xc0000005表示内存访问冲突(尝试访问未拥有的内存)。我特别想知道以下内容的更多信息:
  1. 故障偏移量是什么意思?它代表文件中出现错误的位置,还是代表汇编代码中出现错误的'行'?知道故障偏移量后,我该如何使用像OllyDbg这样的程序找到导致错误的相应汇编代码?或者 - 更好的办法 - 是否可以(轻松地)确定C++源代码中哪一行代码导致了此错误?
  2. 时间戳显然对应于崩溃时的32位UNIX时间,但64位应用程序启动时间是什么意思?如果时间戳为32位,为什么是64位?
请注意,我主要是一位C++程序员,虽然我知道一些关于汇编语言的知识,但我的了解很有限。另外,这不是一个需要修复的严重问题(由于程序的性质,也不容易重现),我只是利用这个问题来学习这些错误消息的含义。我在网上找到的大多数关于这些崩溃日志的信息通常是针对最终用户的,所以它们没有为我(作为程序员)提供很多帮助。
谢谢您提前。

0xc0000005 - 访问被拒绝... - Mandrake
8
针对未来读者,0xc0000005并不是“访问被拒绝”,而是“发生了访问冲突”。代码正确,但设备错误。在Windows中,“访问被拒绝”的代码是5,对应的16进制码是0x80070005,通常被缩写为0x00000005 - niemiro
5个回答

21

64位时间戳是指应用程序的主线程自1601年1月1日(UTC)以来每隔100纳秒创建一次的时间间隔,这被称为FILETIME。而32位时间戳则以time_t格式存储(它告诉我们模块的创建时间,并存储在模块的头部)。

我认为0x0002d160是相对于模块加载地址的偏移量(似乎太低了不太可能是绝对地址)。启动Visual Studio,启动调试器,查看“模块”调试窗口。您的可执行文件应该在其中列出。找到模块加载的地址,将0x0002d160添加到该地址并查看结果地址的反汇编。Visual Studio显示源代码和汇编代码混合,您应该没有问题找出导致问题的源代码行。


1
我明白了。偏移量不对应。我不知道MinGW生成什么样的调试信息,但我肯定会从查看objdump实用程序开始,它可能能够识别有问题的例程。 - avakar
1
仅因为崩溃发生在库中并不意味着它不是你的代码的问题。如果我将一个无效指针传递给strlen,可能会导致崩溃;这不是库作者的错,而是我的错。 - Stephen Nutt
1
你说得完全正确——如果没有类似堆栈跟踪或崩溃时变量值等信息,我们无法确定。尽管如此,考虑到代码的性质以及错误显然是由于内部库指针的取消引用(即我的代码从未访问过也从未在任何时候设置的变量)导致的,我有信心相信我的代码没有问题(尽管我们永远无法确定)。 - GRB
@avakar 有没有一种方法可以在不进行调试的情况下查找模块地址? - patrick
@ÞÄTRÏÇK,Process Explorer可以列出进程中的模块及其基地址。然而,这个地址对于原始问题并没有帮助。那么,你为什么要问呢? - avakar
显示剩余2条评论

7

针对这些信息,你在事后并没有太多可以做的。

有用的信息是异常代码0xc0000005,在这种情况下只表示访问冲突。因此,您可能已经解引用了空指针或其他未分配内存的位。

我怀疑故障偏移量是从加载DLL文件到内存中的偏移量,因此您可以理论上将其添加到基地址中并查找有问题的代码,但我不确定。

调试此问题的最佳方法是在下次发生此问题时在调试器中捕获它。您可以使用图像文件执行选项自动在调试器中运行应用程序。确保准备好符号(如果您当前正在使用RELEASE,则考虑构建DEBUG)。


6
Debugging专家John Robbins开发了一个名为CrashFinder的小工具,帮助解决类似于这种情况: https://www.wintellect.com/crashfinder-2-8-yes-native-code-still-lives/ 保存每个发布到公共环境的构建PDBs是个好主意(这听起来像是一种只在私人使用的工具,但保留最新构建的PDB符号可能是个好主意)。

CrashFinder 看起来非常有前途,但不幸的是似乎并没有起作用。原始故障地址和故障地址 + 模块起始地址都提供了很少的信息。即使构建了一个明显存在错误的测试应用程序(*(int*)0 = 1;),在该应用程序上使用 CrashFinder 给出的故障地址也没有起作用。可能与 MinGW 程序与 MSVC 构建的程序有关。 - GRB
啊,我明白了。我不知道MinGW是否可以生成PDB符号——我怀疑CrashFinder需要这些符号来定位问题所在。 - Kim Gräsman
1
显然不是这样。如果想要在二进制文件中找回源代码行,那么您肯定需要额外的信息。PDB文件提供了这些信息,至少它们提供了尽可能多的信息。但是现代编译器有时会使这变得不可能:如果两个函数映射到相同的指令,并且您没有比较它们的函数指针,则它们可以共享相同的地址。如果崩溃发生在那里,您需要知道调用者才能确定哪一个被调用。此外,在内联和优化之后,调用者和被调用者甚至可能混合超出了C++语句的级别。 - MSalters
mingw-w64曾经在.map文件中提供可用的偏移地址,你可以通过-xLinker -Map=somefile.map生成该文件,但我不知道是Windows更新还是我升级了新的编译器版本导致这个功能失效了。 - Jim Michaels
叹气...托管代码显然导致了.map文件和minidump偏移P8之间的不匹配? - Jim Michaels
显示剩余2条评论

1

看起来这里还没有一个好的答案,如果崩溃发生在开发环境之外怎么办。 我认为偏移量是汇编代码崩溃的地址。 但是你需要知道那个dll的汇编代码的起始位置。或者也许你不需要知道起始地址,因为你可以使用汇编工具打开dll,并通过将偏移量添加到起始地址来查找汇编代码。


0
我的程序CrashExplorer将帮助使用故障偏移量分析此类崩溃:
它将与使用Visual Studio生成的映射和列表文件一起工作: 映射文件列出了程序的所有函数及其地址。列表文件将源代码映射到每个翻译单元的汇编代码。

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