我在超级用户网站上搜索了这个问题,但是没有人发布,所以这是我的问题:蓝屏死机给出了 100% 准确的误差?
答案1
BSOD 代码如下确切地传递给的参数错误检查工具第一个这样的参数称为“错误检查代码”。它会被翻译成您在 BSOD 上看到的消息。例如,错误检查代码 0x50 是 PAGE_FAULT_IN_NONPAGED_AREA,0x44 是 MULTIPLE_IRP_COMPLETE_REQUESTS。
其他四个参数的含义特定于特定的错误检测代码。例如,在 PAGE_FAULT_IN_NONPAGED_AREA 的情况下,其他参数之一将指示发生故障的虚拟地址。对于 MULTIPLE_IRP_COMPLETE_REQUESTS,其中一个参数指示 IRP(I/O 请求包)的地址。
但是:我们在内核模式调试中经常使用的一句话是“受害者并不总是罪魁祸首”。也就是说,导致崩溃的代码并不总是罪魁祸首(创建导致崩溃的环境的代码)。BSOD 仅识别受害者。即使是小型转储通常也没有足够的信息来超越这一点。
BSOD 代码大致分为两类:表示“断言失败”的代码和由于内核模式中引发未处理或无法处理的异常而导致的代码。(调试器文档并未明确区分这两者,但您通常可以从每个错误检测代码的描述中找出答案。)
“断言失败”类似于 C 语言编程中常用的“断言”宏(尽管 Windows 在内核模式下不使用“断言”宏)。它是针对“不应该发生”情况的“内联”测试。例如,NO_MORE_IRP_STACK_LOCATIONS 表示某人创建的 IRP 的“堆栈位置”太少(注:这与用于返回地址、局部变量等的“堆栈”不同),而给定设备(或“DevNode”)的分层驱动程序数量不足。
“异常”是执行指令时发生的副作用。某些异常可以“处理”。例如,页面错误就是一种异常。在大多数情况下(当您处于用户模式或 IRQL < 2 的内核模式时,和(如果引用的虚拟地址被正确定义并且可在当前访问模式下访问),操作系统的分页器可以处理页面错误。
但是,如果上述任何条件不满足,页面错误就无法解决。在用户模式下,这通常会导致进程崩溃。在内核模式下,它会导致 BSOD,并根据具体情况显示多种错误检查代码。常见的错误检查代码有:
- IRQL_NOT_LESS_OR_EQUAL(页面错误发生在 IRQL 2 或以上)
- DRIVER_IRQL_NOT_LESS_OR_EQUAL(类似,但 KeBugCheckEx 发现页面错误是在驱动程序内部引发的,并更改了错误检查代码以表明这一事实)
- KMODE_EXCEPTION_NOT_HANDLED(它处于 IRQL 0 或 1,但由于其他原因无法解决)
- SYSTEM_SERVICE_EXCEPTION(同样位于 IRQL 0 或 1,但在从用户模式调用的内核模式例程中(与“服务”进程无关))
- SYSTEM_THREAD_EXCEPTION_NOT_HANDLED(也是在 IRQL 0 或 1,但问题发生在“系统”进程中的线程中)...等等。
现在,问题来了:
错误检测代码和其他信息总是准确地表明问题被发现时的情形。但它并不一定——事实上它通常不会——表明真正的原因的问题。
例如,内核模式下无法处理的页面错误最常见的原因就是引用的地址不正确。例如,假设我调用 ExAllocatePool(大致相当于 malloc 的 k 模式),但它无法分配我想要的内存。在这种情况下,它返回的不是已分配块的地址,而是零 - “空指针”。现在假设我将零存储在指针应该在的位置。稍后,操作系统中的其他代码(而不是我的代码)尝试使用该指针。BSOD!BSOD 和 minidump 上的明显信息将指向试图使用指针。但真正的罪魁祸首是我的代码,它未能检查 ExAllocatePool 的零返回值并将其存储为“指针”。但到那时我的代码可能早已消失,即不再执行。
另一个例子:假设我成功分配了所需的池(堆),但当我分配 120 个字节时,我的代码错误地写入了返回给我的地址之外的 140 个字节。我刚刚破坏了池元数据下一个池块,如果该块正在使用,那么我也破坏了属于该块所有者的数据。这在一段时间内可能不会造成问题。它不会立即给我带来问题!但最终,当拥有该块的人尝试使用他们的数据时,他们会遇到问题(可能是页面错误,也可能是其他原因)。或者,如果释放或分配池的请求恰好碰到损坏的元数据,则可能会引发某种异常,从而导致 BSOD。而且,再次强调,我这个罪魁祸首可能不会被发现。
在调试这些问题时,您必须找出坏数据(通常是指针)的来源,而不仅仅是谁试图使用它。
类似地,NO_MORE_IRP_STACK_LOCATIONS 错误检查是绝不IoCompleteRequest 中检测到该错误代码的错误。这可能是某些驱动程序设置驱动程序分层不当的错误。简单看一下 minidump(当然还有人们不断发布的“whocrashed”输出)就会很快得出“问题出在 ntoskrnl 中”,因为是 IoCompleteRequest 调用了此路径中的 KeBugCheckEx,而 IoCompleteRequest 位于 ntoskrnl 中。但在这种情况下,真正的问题是总是某些驱动程序错误地设置了设备对象的分层(或者设置正确,但后来损坏了)。哪个驱动程序代码做了这种错误操作可能不会在 minidump 文件中显而易见。
有时可以从内核转储中找出哪个驱动程序是真正的罪魁祸首,但 BSOD 几乎不会告诉您,而小型转储通常没有足够的信息来告诉您。
在少数情况下,错误检测代码和 BSOD 上的其他信息似乎与实际问题相差甚远。例如,UNEXPECTED_KERNEL_MODE_TRAP 曾经是我们经常看到的东西(就像 BSOD 一样……特别是如果您使用的是特定的声卡,我不会在这里指明;该产品甚至它使用的芯片组早已过时,所以现在无关紧要),其参数指示“双重错误”。其中许多实际上是由使用过多内核堆栈空间的代码引起的。没有关于“UNEXPECTED_KERNEL_MODE_TRAP”或甚至“双重错误”的任何信息告诉您这一点。(一段时间后,调试器文档进行了更新,包括一条建议,即“双重错误”可能是由内核堆栈溢出引起的。)
有关所有可能的错误检查代码的更多信息,请参阅 WinDbg 附带的帮助,部分“错误检查代码参考”。如果你还不熟悉Windows 内部原理Solomon、Russinovich 和 Ionescu 撰写,您可能需要参考该文章来理解其中的许多描述。有关“为什么操作系统不能修复问题并继续运行”而不是崩溃的更多解释,请参阅我的回答这里。
答案2
不。错误代码可能不是根本原因,但很多时候与根本原因有关。例如,断电可能会导致硬盘故障,因为它因磁盘 I/O 故障而崩溃(通常在 CPU 断电之前)。或者您的 PSU 可能有故障并意外切断硬盘的电源。
BSOD 代码只是指向 Windows 在当前/上一个会话中崩溃的原因。