分析 Windows 中的崩溃:错误消息告诉我们啥?
Posted
技术标签:
【中文标题】分析 Windows 中的崩溃:错误消息告诉我们啥?【英文标题】:Analyzing a crash in Windows: what does the error message tell us?分析 Windows 中的崩溃:错误消息告诉我们什么? 【发布时间】:2010-11-22 02:21:41 【问题描述】:我为个人使用(用 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 是内存访问冲突(试图访问它不拥有的内存)。我特别有兴趣了解以下内容:
-
故障偏移是什么意思?这是否表示文件中发生错误的位置,还是表示发生错误的装配“行”?知道了故障偏移量,我将如何使用像 OllyDbg 这样的程序来找到导致错误的相应汇编代码?或者——甚至更好——是否可以(轻松)确定 C++ 源代码中的哪一行代码导致了这个错误?
很明显时间戳对应崩溃时的32位UNIX时间,但是64位应用程序启动时间是什么意思呢?如果时间戳是 32,为什么会是 64 位?
请注意,我主要是一名 C++ 程序员,所以虽然我对汇编有所了解,但我对它的了解非常有限。此外,这确实不是一个需要修复的严重问题(并且鉴于程序的性质,也不容易重现),我只是更多地以此为借口来了解更多关于这些错误消息的含义。我在网上找到的有关这些崩溃日志的大部分信息通常都是针对最终用户的,因此它们对我(作为程序员)的帮助不大。
提前致谢
【问题讨论】:
0xc0000005 - 访问被拒绝... 对于未来的读者,0xc0000005
不是拒绝访问,而是“发生访问冲突”。正确的代码,错误的设施。 Access denied 是 Windows 工具下的代码 5,0x80070005
也是如此,通常缩写为 0x00000005
。
【参考方案1】:
64 位时间戳是自 1601 年 1 月 1 日 (UTC) 以来以 100 纳秒为间隔创建应用程序主线程的时间(这称为 FILETIME
)。 32 位时间戳确实是time_t
格式(它告诉了模块的创建时间并存储在模块的标头中)。
我会说 0x0002d160 是模块加载地址的偏移量(对于绝对地址来说似乎太低了)。启动 Visual Studio,启动调试器,查看“模块”调试窗口。您的 exe 文件应在此处列出。找到加载模块的地址,将 0x0002d160 添加到该地址并查看结果地址处的反汇编。 Visual Studio 显示与程序集混合的源代码,您应该可以毫无问题地找出导致问题的源代码行。
【讨论】:
啊,好吧,我以前听说过FILETIME
,我不知道为什么我没有在这种情况下将这些部分放在一起。至于在 Visual Studio 中查看模块,这听起来是一种可靠的方法——唯一的问题是,我使用 MinGW 构建了这个程序。我有 MSVC 并且可以使用它重建 .exe,但我的猜测是原始故障偏移量(使用 MinGW 构建的 exe 生成)不会对应于 MSVC 构建的 exe,对吧?
我明白了。不,偏移量不对应。我不知道 MinGW 生成什么样的调试信息,但我肯定会从查看 objdump
实用程序开始,它可能能够识别罪魁祸首例程。
objdump 帮了我很多。经过一个小时左右的不同设置和(MinGW)调试版本的试验,我正在使用的内部库函数似乎发生了故障。虽然无法确定这一点,但这表明这是库中的错误,而不是我的代码。
库中发生崩溃并不意味着不是您的代码有问题。如果我将无效指针传递给 strlen,则可能会导致崩溃;这不是图书馆作者的错,而是我的错。
你是绝对正确的——如果没有堆栈跟踪或崩溃时的变量值等信息,就不可能知道任何一种方式。也就是说,鉴于代码的性质以及错误是如何(显然)由内部库指针的取消引用(即我的代码在任何时候都无法访问或设置的变量)引起的,我有信心相信我的代码是没有错(尽管我们永远无法确定)。【参考方案2】:
您无法使用这些信息进行事后分析。
有用的信息是异常代码,0xc0000005,在这种情况下,它只是意味着访问冲突。所以你取消引用 null 或其他一些你不拥有的内存。
我怀疑,故障偏移量是您的 DLL 加载到内存中的偏移量,所以理论上您可以将它添加到您的基地址并找到有问题的代码,但我不确定。
您最好的调试方法是在下次发生这种情况时在调试器中捕获它。您可以使用Image File Execution Options 在debugger 中自动运行您的应用程序。确保您已准备好符号(如果您当前正在使用 RELEASE,请考虑构建 DEBUG)。
【讨论】:
【参考方案3】:调试之神 John Robbins 构建了一个名为 CrashFinder 的小工具来帮助解决以下情况: https://www.wintellect.com/crashfinder-2-8-yes-native-code-still-lives/
为您向公众发布的每个构建保存 PDB 始终是一个好主意(这听起来像是您只在私人使用的工具,但为最新构建保留 PDB 符号可能是个好主意)。
【讨论】:
CrashFinder 看起来很有前途,但不幸的是似乎没有用。原始故障地址和故障地址+模块起始地址都产生了很少的信息。即使在构建具有明显错误 (*(int*)0 = 1;
) 的测试应用程序之后,在该应用程序上使用 CrashFinder 提供的故障地址也不起作用。可能与 MinGW 程序与 MSVC 构建程序有关。
啊,我明白了。我不知道 MinGW 是否可以生成 PDB 符号——我怀疑 CrashFinder 需要这些符号来定位问题位置。
显然不是。您肯定需要额外的信息才能找到给定二进制文件的源代码行。 PDB 尽可能提供这些信息。但是现代编译器有时会使其成为不可能:如果两个函数映射到相同的指令,并且您不比较它们的函数指针,它们可以共享相同的地址。如果那里发生崩溃,您需要知道调用者以确定调用两者中的哪一个。此外,内联和优化后,调用者和被调用者甚至可能超出 C++ 的语句级别。
一些调试器依靠 PE 导出来模糊地了解问题所在。然而,这实际上只适用于 DLL。关于优化的好信息,我不知道编译器可以消除相等但不相关的代码。我想这是链接时代码生成的好处之一,即有一个更高级别的优化机会视图。谢谢!
MinGW/GCC 有一个我在使用objdump
时发现的类似功能。我在我的 GCC 设置中启用了调试信息重建了我的二进制文件,然后在该 .exe 上使用了 objdump
和 --source 设置。这允许objdump
将源代码行/行号混合到程序集转储中,因此我可以看到错误的汇编行对应的 C++ 源代码。所以,是的,MinGW/GCC 有自己的方法。【参考方案4】:
看来这里还是没有好的答案,万一崩溃发生在开发环境之外怎么办。 我认为off set是汇编代码崩溃的地址。 但是您需要知道该 dll 的汇编代码的开头在哪里。或者你可能不需要知道起始地址,因为你可以使用汇编工具打开dll,通过在起始地址上添加偏移量来找到汇编代码
【讨论】:
【参考方案5】:我的程序CrashExplorer 将帮助使用故障偏移量分析此类崩溃:
它适用于使用 Visual Studio 生成的地图和列表文件: 映射文件列出了程序的所有功能及其地址。列表文件将源代码映射到每个翻译单元的汇编代码。
【讨论】:
以上是关于分析 Windows 中的崩溃:错误消息告诉我们啥?的主要内容,如果未能解决你的问题,请参考以下文章