其他检查
驱动程序验证程序的“杂项检查”选项监视驱动程序是否存在导致驱动程序或系统崩溃的常见错误,例如释放仍包含活动内核对象的内存。
具体而言,“杂项检查”选项查找以下不正确的驱动程序行为:
已释放内存中的活动工作项。 驱动程序调用 ExFreePool 以释放包含使用 IoQueueWorkItem 排队的工作项的池块。
已释放内存中的活动资源。 驱动程序调用 ExFreePool 以释放包含活动 ERESOURCE 结构的池块。 在调用 ExFreePool 之前,驱动程序应调用 ExDeleteResource 以删除 ERESOURCE 对象。
已释放内存中的活动查看列表。 驱动程序调用 ExFreePool 以释放仍包含活动观察列表的池块, (NPAGED_LOOKASIDE_LIST 或 PAGED_LOOKASIDE_LIST 结构。 在调用 ExFreePool 之前,驱动程序应调用 ExDeleteNPagedLookasideList 或 ExDeletePagedLookasideList 以删除 lookaside 列表。
Windows Management Instrumentation (Windows (ETW 的 WMI) 和事件跟踪) 注册问题。 驱动程序验证程序检测到的此类问题包括:
尝试卸载但不注销其 WMI 回调的驱动程序。
尝试删除尚未从 WMI 注销的设备对象的驱动程序。
尝试卸载但不注销其 ETW 内核模式提供程序的驱动程序。
尝试注销已取消注册的提供程序的驱动程序。
内核处理错误。 (Windows Vista 及更高版本) 启用“杂项检查”选项还将启用系统进程的句柄跟踪,以帮助调查内核句柄泄漏和 bug 检查0x93:INVALID_KERNEL_HANDLE。 启用句柄跟踪后,内核将收集最近句柄打开和关闭操作的堆栈跟踪。 可以使用 !htrace 调试器扩展在内核调试器中显示堆栈跟踪。 有关 !htrace 的详细信息,请参阅 Windows 调试工具文档。
具有内核模式访问权限的用户模式句柄 从 Windows 7 开始,当你选择“杂项检查”选项时,驱动程序验证程序还会检查对 ObReferenceObjectByHandle 的调用。 不能传递具有内核模式访问权限的用户模式句柄。 如果发生此类操作,驱动程序验证程序0xC4发出 Bug 检查,参数 1 值为 0xF6。
UserMode 等待内核堆栈上分配的同步对象
从 Windows 7 开始,驱动程序验证程序可以检测驱动程序可能错误地使用操作系统提供的多线程同步机制的其他方式。
将同步对象(如 KEVENT 结构)分配为内核堆栈上的局部变量是一种常见做法。 当进程加载到内存中时,其线程的内核堆栈永远不会从工作集剪裁或分页到磁盘。 在此类不可分页内存中分配同步对象是正确的。
但是,当驱动程序调用 KEWaitForSingleObject 或 KeWaitForMultipleObjects 等 API 来等待堆栈上分配的对象时,它们必须为 API 的 WaitMode 参数指定 KernelMode 值。 当进程的所有线程都在 UserMode 模式下等待时,该进程将有资格交换到磁盘。 因此,如果驱动程序将 UserMode 指定为 WaitMode 参数,则操作系统可以交换当前进程,前提是同一进程中的其他线程也等待 UserMode。 将整个进程交换到磁盘包括对其内核堆栈进行分页。 等待操作系统已交换的同步对象不正确。 在某些时候,线程必须出现并发出同步对象的信号。 向同步对象发出信号涉及 Windows 内核在 IRQL = DISPATCH_LEVEL 或更高版本处操作对象。 在DISPATCH_LEVEL或更高位置触摸分页或交换内存会导致系统崩溃。
从 Windows 7 开始,当你选择“杂项检查”选项时,驱动程序验证程序会检查已验证的驱动程序用于在 UserMode 中等待的同步对象是否未在当前线程的内核堆栈上分配。 当驱动程序验证程序检测到此类不正确的等待时,它会发出 bug 检查0xC4:DRIVER_VERIFIER_DETECTED_VIOLATION,参数 1 值为 0x123。
内核句柄引用不正确
每个 Windows 进程都有一个句柄表。 可以将句柄表作为句柄条目的数组进行查看。 每个有效的句柄值都引用此数组中的有效条目。
内核句柄,作为对系统进程的句柄表有效的句柄。 用户句柄作为句柄,对除系统进程之外的任何进程都有效。
在 Windows 7 中,驱动程序验证程序检测到引用内核句柄值的尝试不正确。 这些驱动程序缺陷报告为 Bug 检查0x93: 如果启用了驱动程序验证杂项检查选项,INVALID_KERNEL_HANDLE。 通常,这种不正确的句柄引用意味着驱动程序已经关闭了该句柄,但正在尝试继续使用它。 这种缺陷可能会导致系统出现不可预知的问题,因为引用的句柄值可能已被另一个不相关的驱动程序重复使用。
如果内核驱动程序最近关闭了内核句柄,并且后来引用了关闭的句柄,驱动程序验证程序将强制检查 bug,如前所述。 在这种情况下, !htrace 调试器扩展的输出为关闭此句柄的代码路径提供堆栈跟踪。 使用系统进程的地址作为 !htrace 的参数。 若要查找系统进程的地址,请使用 !process 4 0 命令。
从 Windows 7 开始,驱动程序验证程序将检查添加到 ObReferenceObjectByHandle。 现在禁止通过 KernelMode 访问传递用户空间句柄。 如果检测到此类组合,驱动程序验证程序 会发出 bug 检查0xC4:DRIVER_VERIFIER_DETECTED_VIOLATION,参数 1 值为 0xF6。
激活此选项
可以使用驱动程序验证程序管理器或 Verifier.exe 命令行激活一个或多个驱动程序的“杂项检查”选项。 有关详细信息,请参阅 选择驱动程序验证程序选项。
在命令行
在命令行中,“杂项检查”选项由 位 11 (0x800) 表示。 若要激活“杂项检查”,请使用标志值0x800或向标志值添加0x800。 例如:
verifier /flags 0x800 /driver MyDriver.sys
选项将在下一次启动后处于活动状态。
在 Windows Vista 及更高版本的 Windows 上,还可以通过将 /volatile 参数添加到 命令来激活和停用杂项检查,而无需重新启动计算机。 例如:
verifier /volatile /flags 0x800 /adddriver MyDriver.sys
此设置立即生效,但在关闭或重新启动计算机时会丢失。 有关详细信息,请参阅 使用易失性设置。
标准设置中还包含“杂项检查”选项。 例如:
verifier /standard /driver MyDriver.sys
使用驱动程序验证程序管理器
启动驱动程序验证程序管理器。 在命令提示符窗口中键入 验证程序 。
选择“ 为代码开发人员创建自定义设置 () ”,然后单击“ 下一步”。
从完整列表中选择“选择单个设置”。
选择“ 杂项检查”。
标准设置中还包括“杂项检查”功能。 若要使用此功能,请在驱动程序验证程序管理器中,单击“ 创建标准设置”。
查看结果
若要查看“杂项检查”选项的结果,请在内核调试器中使用 !verifier 扩展。 (有关 !verifier 的信息,请参阅 Windows 调试工具 文档。)
在以下示例中,“杂项检查”选项在内存中检测到驱动程序尝试释放的活动 ERESOURCE 结构,导致 bug 检查0xC4:DRIVER_VERIFIER_DETECTED_VIOLATION。 Bug 检查0xC4显示包括 ERESOURCE 的地址和受影响的内存。
1: kd> !verifier 1
Verify Level 800 ... enabled options are:
Miscellaneous checks enabled
Summary of All Verifier Statistics
RaiseIrqls 0x0
AcquireSpinLocks 0x0
Synch Executions 0x0
Trims 0x0
Pool Allocations Attempted 0x1
Pool Allocations Succeeded 0x1
Pool Allocations Succeeded SpecialPool 0x0
Pool Allocations With NO TAG 0x0
Pool Allocations Failed 0x0
Resource Allocations Failed Deliberately 0x0
Current paged pool allocations 0x0 for 00000000 bytes
Peak paged pool allocations 0x0 for 00000000 bytes
Current nonpaged pool allocations 0x0 for 00000000 bytes
Peak nonpaged pool allocations 0x0 for 00000000 bytes
Driver Verification List
Entry State NonPagedPool PagedPool Module
8459ca50 Loaded 00000000 00000000 buggy.sys
*** Fatal System Error: 0x000000c4
(0x000000D2,0x9655D4A8,0x9655D468,0x000000B0)
0xD2 : Freeing pool allocation that contains active ERESOURCE.
2 - ERESOURCE address.
3 - Pool allocation start address.
4 - Pool allocation size.
若要调查池分配,请将 !pool 调试器扩展与池分配的起始地址 9655D468 一起使用。 (2 标志仅显示包含指定地址的池的标头信息。禁止显示其他池的标头信息。)
1: kd> !pool 9655d468 2
Pool page 9655d468 region is Paged pool
*9655d468 size: b0 previous size: 8 (Allocated) *Bug_
若要查找有关 ERESOURCE 的信息,请将 !locks (!kdext*.locks) 调试器扩展与 结构的地址一起使用。
1: kd> !locks 0x9655D4A8 <<<<<- ERESOURCE @0x9655D4A8 lives inside the pool block being freed
Resource @ 0x9655d4a8 Available
1 total locks
还可以使用 kb 调试器命令显示导致失败的调用的堆栈跟踪。 以下示例演示堆栈,包括驱动程序验证程序截获的 对 ExFreePoolWithTag 的调用。
1: kd> kb
ChildEBP RetAddr Args to Child
92f6374c 82c2c95a 00000003 92f68cdc 00000000 nt!RtlpBreakWithStatusInstruction
92f6379c 82c2d345 00000003 9655d468 000000c4 nt!KiBugCheckDebugBreak+0x1c
92f63b48 82c2c804 000000c4 000000d2 9655d4a8 nt!KeBugCheck2+0x5a9
92f63b6c 82e73bae 000000c4 000000d2 9655d4a8 nt!KeBugCheckEx+0x1e
92f63b88 82e78c32 9655d4a8 9655d468 000000b0 nt!VerifierBugCheckIfAppropriate+0x3c
92f63ba4 82ca7dcb 9655d468 000000b0 00000000 nt!VfCheckForResource+0x52
92f63bc8 82e7fb2d 000000b0 00000190 9655d470 nt!ExpCheckForResource+0x21
92f63be4 82e6dc6c 9655d470 92f63c18 89b6c58c nt!ExFreePoolSanityChecks+0x1fb
92f63bf0 89b6c58c 9655d470 00000000 89b74194 nt!VerifierExFreePoolWithTag+0x28
92f63c00 89b6c0f6 846550c8 846550c8 846e2200 buggy!MmTestProbeLockForEverStress+0x2e
92f63c18 82e6c5f1 846e2200 846550c8 85362e30 buggy!TdDeviceControl+0xc4
92f63c38 82c1fd81 82d4d148 846550c8 846e2200 nt!IovCallDriver+0x251
92f63c4c 82d4d148 85362e30 846550c8 84655138 nt!IofCallDriver+0x1b
92f63c6c 82d4df9e 846e2200 85362e30 00000000 nt!IopSynchronousServiceTail+0x1e6
92f63d00 82d527be 00000001 846550c8 00000000 nt!IopXxxControlFile+0x684
92f63d34 82cb9efc 0000004c 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
92f63d34 6a22b204 0000004c 00000000 00000000 nt!KiFastCallEntry+0x12c