BSOD的可靠性如何?

沙玛吉节

我在超级用户站点中搜索了此问题,但没有人发布它,所以这是我的问题:BSOD是否会给我们100%的准确错误?

杰米·汉拉汉(Jamie Hanrahan)

BSOD代码正是传递给KeBugCheckEx的参数第一个这样的参数称为“错误检查代码”。它被翻译成您在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的“堆栈位置”(nb:与用于返回地址,局部变量等的“堆栈”不同)太少了给定设备(或“ 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和minidump的明显信息将指向尝试使用的代码指针。但是真正的罪魁祸首是我的代码,该代码无法检查ExAllocatePool的零返回值并将其存储为“指针”。但是到那时,我的代码可能早已一去不复返了,即不再执行。

另一个示例:假设我成功分配了所需的池(堆),但是当我分配120个字节时,我的代码错误地在返回给我的地址之外写入了140个字节。我刚刚破坏了下一个池块的池元数据,并且如果该块正在使用中,我还破坏了属于该块所有者的数据。这可能在一段时间内不会引起问题。它不会立即对我造成问题!但是最终,当拥有该块的任何人尝试使用其数据时,他们都会遇到问题(可能是页面错误,可能有很多事情)。或者,如果释放或分配池的请求恰巧碰到了损坏的元数据,则可能会引发某种异常,从而导致BSOD。而且,罪魁祸首我可能不会很明显。

在调试这些文件时,您必须找出错误数据(通常是指针)的来源,而不仅仅是谁试图使用它。

同样,NO_MORE_IRP_STACK_LOCATIONS错误检查绝不是检测到它的IoCompleteRequest中的代码错误某些驱动程序可能会错误地设置驱动程序分层。简单查看一下小型转储(当然还有人们不断发布的“破烂的”东西),很快就会得出“问题出在ntoskrnl中”,因为在此路径中,是IoCompleteRequest调用KeBugCheckEx,而IoCompleteRequest在ntoskrnl中。但是在这种情况下,真正的问题始终是某些驱动程序不正确地设置了设备对象的分层(或正确设置了设备,但后来损坏了)。在minidump文件中不太可能知道是哪个驱动程序构成恶意行为的。

有时可以从内核转储中找出哪个驱动程序是真正的罪魁祸首,但是BSOD几乎永远不会告诉您,而小型转储通常没有足够的信息来告诉您。

在少数情况下,有关BSOD的错误检查代码和其他信息似乎与实际问题相去甚远。例如,UNEXPECTED_KERNEL_MODE_TRAP曾经是我们经常看到的东西(随着BSOD的发展……尤其是如果您使用的是我在这里无法识别的特定声卡;产品以及所使用的芯片组早已过时,因此(现在无关紧要),其参数表示“双重错误”。其中许多实际上是由使用过多内核堆栈空间的代码引起的。根本没有关于“ UNEXPECTED_KERNEL_MODE_TRAP”甚至“双重错误”的信息。(一段时间后,调试器文档进行了更新,以包含有关内核堆栈溢出可能导致“双重错误”的建议。)

有关所有可能的错误检查代码的更多信息,请参见WinDbg随附的帮助,“错误检查代码参考”部分。如果您还不熟悉Solomon,Russinovich和Ionescu的Windows Internals中的材料,则可能需要参考该材料以了解许多描述。有关“为什么操作系统不能仅仅解决问题并继续进行下去”而不是崩溃的更多解释,请在此处查看我的答案

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章