强制挂起 I/O 请求

“强制挂起 I/O 请求”选项随机返回STATUS_PENDING,以响应驱动程序对 IoCallDriver 的调用。 此选项测试驱动程序的逻辑以响应 IoCallDriver 中的STATUS_PENDING返回值。

此选项仅在 Windows Vista 和更高版本的 Windows 操作系统上受支持。

谨慎 请勿在驱动程序上使用此选项,除非你对驱动程序的操作有详细的了解,并且已验证驱动程序设计为处理STATUS_PENDING从其对 IoCallDriver 的所有调用返回值。 在并非旨在处理来自所有调用STATUS_PENDING的驱动程序上运行此选项可能会导致崩溃、内存损坏和异常的系统行为,这些行为可能难以调试或更正。

为何使用强制挂起的 I/O 请求?

驱动程序堆栈中的较高级别驱动程序调用 IoCallDriver ,以将 IRP 向下传递到驱动程序堆栈中的较低级别驱动程序。 接收 IRP 的较低级别驱动程序中的驱动程序调度例程可以立即完成 IRP,也可以返回STATUS_PENDING并在以后完成 IRP。

通常,调用方必须准备好处理任一结果。 但是,由于大多数调度例程会立即处理 IRP,因此不经常执行调用方中的STATUS_PENDING逻辑,并且可能不会检测到严重的逻辑错误。 “强制挂起 I/O 请求”选项截获对 IoCallDriver 的 调用,并返回STATUS_PENDING以测试调用驱动程序的不经常使用的逻辑。

何时使用强制挂起的 I/O 请求?

在运行此测试之前,请查看驱动程序设计和源代码,并确认驱动程序旨在处理来自其所有 IoCallDriver 调用的STATUS_PENDING。

许多驱动程序并非用于处理对 IoCallDriver 的所有调用的STATUS_PENDING。 他们可能会将 IRP 发送到保证立即完成 IRP 的特定知名驱动程序。 将STATUS_PENDING发送到不处理它的驱动程序可能会导致驱动程序和系统崩溃以及内存损坏。

驱动程序应如何处理STATUS_PENDING?

调用 IoCallDriver 的更高级别的驱动程序必须处理STATUS_PENDING返回值,如下所示:

  • 在调用 IoCallDriver 之前,驱动程序必须调用 IoBuildSynchronousFsdRequest 以安排 IRP 的同步处理。

  • 如果 IoCallDriver 返回STATUS_PENDING,则驱动程序必须通过在指定事件上调用 KeWaitForSingleObject 来等待 IRP 完成。

  • 驱动程序必须预期 IRP 可能在 I/O 管理器发出事件信号之前释放。

  • 调用 IoCallDriver 后,调用方无法引用 IRP。

强制挂起的 I/O 请求检测哪些错误?

“强制挂起 I/O 请求”选项检测驱动程序中调用 IoCallDriver 并接收STATUS_PENDING返回值的以下错误:

  • 驱动程序不调用 IoBuildSynchronousFsdRequest 来安排同步处理。

  • 驱动程序不调用 KeWaitForSingleObject

  • 驱动程序在调用 IoCallDriver 后引用 IRP 结构中的值。 调用 IoCallDriver 后,较高级别的驱动程序无法访问 IRP,除非它设置了完成例程,并且仅当所有较低级别的驱动程序都已完成 IRP 时。 如果释放 IRP,驱动程序将崩溃。

  • 驱动程序错误地调用了相关函数。 例如,驱动程序调用 KeWaitForSingleObject ,并将句柄传递给事件 (作为 Object 参数) ,而不是将指针传递到事件对象。

  • 驱动程序等待错误事件。 例如,驱动程序调用 IoSetCompletionRoutine,但会等待由其自己的完成例程发出信号的内部事件,而不是等待 IRP 完成后由 I/O 管理器发出信号的 IRP 事件。

强制 Windows 7 中引入的挂起 I/O 请求更改

从 Windows 7 开始,强制挂起 I/O 请求选项在强制在已验证的驱动程序中执行STATUS_PENDING代码路径方面更有效。 在早期 Windows 版本中,驱动程序验证程序仅在执行该 IRP 的第一个 IoCompleteRequest 时强制延迟 IRP 完成。 这意味着同一设备堆栈中的 Driver2 行为可以降低验证 Driver1 的有效性。 Driver2 可能会在从调度例程返回到 Driver1 之前同步等待完成。 IRP 完成的强制延迟恰好发生在 I/O 请求展开回完成路径上的已验证驱动程序之前。 这意味着已验证驱动程序的STATUS_PENDING代码路径实际上已执行,并且验证驱动程序会感知完成延迟。

激活此选项

若要激活强制挂起的 I/O 请求,还必须激活 I/O 验证。 可以使用驱动程序验证程序管理器或Verifier.exe命令行激活一个或多个驱动程序的强制挂起 I/O 请求选项。 有关详细信息,请参阅 选择驱动程序验证程序选项

“强制挂起 I/O 请求”选项仅在 Windows Vista 和更高版本的 Windows 上受支持。

  • 在命令行

    若要激活“强制挂起的 I/O 请求”,请使用标志值0x210或向标志值添加0x210。 此值激活 I/O 验证 (0x10) ,并强制 (0x200) 挂起的 I/O 请求。

    例如:

    verifier /flags 0x210 /driver MyDriver.sys
    

    选项将在下一次启动后处于活动状态。

    如果尝试仅激活强制挂起的 I/O 请求 (验证程序 /flags 0x200) ,驱动程序验证程序会自动启用强制挂起的 I/O 请求 (0x200) 和 I/O 验证

    还可以通过将 /volatile 参数添加到 命令来激活和停用强制挂起的 I/O 请求,而无需重启计算机。 例如:

    verifier /volatile /flags 0x210 /adddriver MyDriver.sys
    

    此设置会立即生效,但在关闭或重启计算机时会丢失。 有关详细信息,请参阅 使用易失性设置

  • 使用驱动程序验证程序管理器

    1. 启动驱动程序验证程序管理器。 在命令提示符窗口中键入 验证程序
    2. 选择 “为代码开发人员创建自定义设置 () ”,然后单击“ 下一步”。
    3. 从完整列表中选择“选择单个设置”。
    4. 选择“ I/O 验证 并强制挂起的 I/O 请求”。

    如果仅选择 “强制挂起的 I/O 请求”,驱动程序验证程序管理器会提醒你 I /O 验证 是必需的,并为你提供启用它的功能。

查看结果

若要查看强制挂起 I/O 请求测试的结果,请使用标志值为 0x40 的 !验证程序调试器 扩展。

有关 !verifier 的信息,请参阅适用于 Windows 的调试工具文档中的 !verifier 主题。

如果测试计算机因强制挂起 I/O 请求测试而崩溃,则可以使用 !verifier 40 命令查找原因。 在当前堆栈跟踪中,找到驱动程序最近使用的 IRP 的地址。 例如,如果使用 kP 命令显示线程的堆栈帧,则可以在当前堆栈跟踪的函数参数中找到 IRP 地址。 然后,运行 !验证程序 40 并查找 IRP 的地址。 最新的强制挂起堆栈跟踪显示在显示顶部。

例如,Pci.sys的以下堆栈跟踪显示了它对强制挂起的 I/O 请求的响应。 测试不会显示Pci.sys逻辑中的任何错误。

kd> !verifier 40
# Size of the log is 0x40
========================================================
IRP: 8f84ef00 - forced pending from stack trace:

     817b21e4 nt!IovpLocalCompletionRoutine+0xb2
     81422478 nt!IopfCompleteRequest+0x15c
     817b2838 nt!IovCompleteRequest+0x9c
     84d747df acpi!ACPIBusIrpDeviceUsageNotification+0xf5
     84d2d36c acpi!ACPIDispatchIrp+0xe8
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     817c6a9d nt!ViFilterDispatchPnp+0xe9
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     84fed489 pci!PciCallDownIrpStack+0xbf
     84fde1cb pci!PciDispatchPnpPower+0xdf
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     817c6a9d nt!ViFilterDispatchPnp+0xe9
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     84ff2ff5 pci!PciSendPnpIrp+0xbd
 84fec820 pci!PciDevice_DeviceUsageNotification+0x6e
     84fde1f8 pci!PciDispatchPnpPower+0x10c
 817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     84d76ce2 acpi!ACPIFilterIrpDeviceUsageNotification+0x96
     84d2d36c acpi!ACPIDispatchIrp+0xe8
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c
     84f7f16c PCIIDEX!PortWdmForwardIrpSynchronous+0x8e
     84f7b2b3 PCIIDEX!GenPnpFdoUsageNotification+0xcb
     84f7d301 PCIIDEX!PciIdeDispatchPnp+0x45
     817b258f nt!IovCallDriver+0x19d
     8142218e nt!IofCallDriver+0x1c

堆栈跟踪显示 Acpi.sys 正在尝试完成 IRP 8f84ef00。 驱动程序验证程序强制延迟完成,因此 Acpi.sys STATUS_PENDING返回到 pci!PciCallDownIrpStack。 如果此调用导致崩溃,驱动程序所有者将需要查看 pci 的源代码 !PciCallDownIrpStack 并对其进行修改以正确处理STATUS_PENDING。