调内存泄漏 - DRIVER_VERIFIER_DETECTED_VIOLATION (C4):0x62

驱动程序验证程序 生成 Bug 检查0xC4:当 驱动程序卸载时,DRIVER_VERIFIER_DETECTED_VIOLATION参数 1 值为 0x62,而无需首先释放其所有池分配。 未释放的内存分配(也称为内存泄漏)是降低操作系统性能的常见原因。 这些可能会碎片系统池,并最终导致系统崩溃。

如果内核调试器连接到运行 驱动程序验证程序的测试计算机,如果驱动程序验证程序检测到冲突,则 Windows 会中断调试器并显示错误的简要说明。

>在驱动程序卸载时调试内存泄漏

使用 !analyze 显示有关 bug 检查的信息

与发生的任何 bug 检查一样,一旦控制调试器,最好的第一步是运行 !analyze -v 命令。

kd> !analyze -v
Connected to Windows 8 9600 x86 compatible target
Loading Kernel Symbols
.................................................................................
Loading User Symbols
.......................
Loading unloaded module list
........
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught.  This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arguments:
Arg1: 00000062, A driver has forgotten to free its pool allocations prior to unloading.
Arg2: 9707712c, name of the driver having the issue.
Arg3: 9c1faf70, verifier internal structure with driver information.
Arg4: 00000003, total # of (paged+nonpaged) allocations that weren't freed.
    Type !verifier 3 drivername.sys for info on the allocations
    that were leaked that caused the bugcheck.

bug 检查0xC4:DRIVER_VERIFIER_DETECTED_VIOLATION参数 1(Arg1)值为 0x62,如下所示:

DRIVER_VERIFIER_DETECTED_VIOLATION (C4) Arg1 Arg2 Arg2 Arg3 Arg4 导致驱动程序验证程序标志0x62驱动程序的名称。 未释放的预留分配总数,包括分页池和非分页池。 驱动程序在没有首先释放池分配的情况下正在卸载。 在 Windows 8.1 中,如果驱动程序卸载而不首先释放已使用 IoAllocateWorkItem 分配的任何工作项(IO_WORKITEM),也会进行此 bug 检查。 仅当池跟踪选项处于活动状态时,才会对此参数进行 bug 检查。 指定 池跟踪验证程序/标志0x8)。 “池跟踪”选项是使用标准标志(验证程序 /standard )启用的。

使用 !verifier 3 扩展命令了解池分配

对于此特定 bug 检查,参数 4(Arg4)中提供的信息最为重要。 Arg4 显示未释放的分配数。 !analyze 命令的输出还显示 !verifier 调试器扩展命令,可用于显示这些分配的内容。 以下示例显示了 !verifier 3 MyDriver.sys 命令的完整输出

kd> !verifier 3 Mydriver.sys

Verify Flags Level 0x000209bb

  STANDARD FLAGS:
    [X] (0x00000000) Automatic Checks
    [X] (0x00000001) Special pool
    [X] (0x00000002) Force IRQL checking
    [X] (0x00000008) Pool tracking
    [X] (0x00000010) I/O verification
    [X] (0x00000020) Deadlock detection
    [X] (0x00000080) DMA checking
    [X] (0x00000100) Security checks
    [X] (0x00000800) Miscellaneous checks
    [X] (0x00020000) DDI compliance checking

  ADDITIONAL FLAGS:
    [ ] (0x00000004) Randomized low resources simulation
    [ ] (0x00000200) Force pending I/O requests
    [ ] (0x00000400) IRP logging
    [ ] (0x00002000) Invariant MDL checking for stack
    [ ] (0x00004000) Invariant MDL checking for driver
    [ ] (0x00008000) Power framework delay fuzzing
    [ ] (0x00040000) Systematic low resources simulation
    [ ] (0x00080000) DDI compliance checking (additional)
    [ ] (0x00200000) NDIS/WIFI verification
    [ ] (0x00800000) Kernel synchronization delay fuzzing
    [ ] (0x01000000) VM switch verification

    [X] Indicates flag is enabled


Summary of All Verifier Statistics

  RaiseIrqls           0x0
  AcquireSpinLocks     0x0
  Synch Executions     0x0
  Trims                0x0

  Pool Allocations Attempted             0x2db1a
  Pool Allocations Succeeded             0x2db1a
  Pool Allocations Succeeded SpecialPool 0x2db1a
  Pool Allocations With NO TAG           0x0
  Pool Allocations Failed                0x0

  Current paged pool allocations         0x0 for 00000000 bytes
  Peak paged pool allocations            0x0 for 00000000 bytes
  Current nonpaged pool allocations      0x3 for 00001058 bytes
  Peak nonpaged pool allocations         0x13 for 0004A4A0 bytes

## Driver Verification List


  MODULE: 0x84226b28 MyDriver.sys (Loaded)

    Pool Allocation Statistics: ( NonPagedPool / PagedPool )

      Current Pool Allocations: ( 0x00000003 / 0x00000000 )
      Current Pool Bytes:       ( 0x00001058 / 0x00000000 )
      Peak Pool Allocations:    ( 0x00000013 / 0x00000000 )
      Peak Pool Bytes:          ( 0x0004A4A0 / 0x00000000 )
      Contiguous Memory Bytes:       0x00000000
      Peak Contiguous Memory Bytes:  0x00000000

    Pool Allocations:

      Address     Length      Tag   Caller    
      ----------  ----------  ----  ----------
      0x982a8fe0  0x00000020  VMdl  0x9a3bf6ac  MyDriver!DeviceControlDispatch
      0x8645a000  0x00001008  mdrv  0x9a3bf687  MyDriver!DeviceControlDispatch
      0x9a836fd0  0x00000030  Vfwi  0x9a3bf6ed  MyDriver!GetNecessaryObjects

例如,驱动程序MyDriver.sys具有两个内存分配和一个未正确释放的 I/O 工作项。 每个列表显示当前分配的地址、大小、使用的池标记以及驱动程序代码中发出分配请求的地址。 如果为有问题的驱动程序加载符号,它还会显示调用方地址旁的函数的名称。

在显示的标记中,驱动程序本身(mdrv)只提供一个(地址0x8645a000分配)。 每当驱动程序验证程序验证驱动程序调用 IoAllocateMdl 时,都使用标记 VMdl 同样,每当驱动程序验证程序验证驱动程序发出使用 IoAllocateWorkItem 分配工作项的请求时,将使用标记 Vfwi

如果有符号,可以在源文件中找到内存分配发生的位置

为驱动程序加载符号时,如果这些符号包含行号信息,则可以使用 ln CallerAddress 命令显示调用所在的行。 此输出还会在进行分配的函数中显示偏移量。

kd> ln 0x9a3bf6ac  
d:\coding\wdmdrivers\mydriver\handleioctl.c(50)+0x15
(9a3bf660)   MyDriver!DeviceControlDispatch+0x4c   |  (9a3bf6d0)   MyDriver!DeviceControlDispatch

kd> ln 0x9a3bf687  
d:\coding\wdmdrivers\mydriver\handleioctl.c(38)+0x12
(9a3bf660)   MyDriver!DeviceControlDispatch+0x27   |  (9a3bf6d0)   MyDriver!DeviceControlDispatch

kd> ln 0x9a3bf6ed  
d:\coding\wdmdrivers\mydriver\handleioctl.c(72)+0xa
(9a3bf6d0)   MyDriver!GetNecessaryObjects+0x1d   |  (9a3bf71c)   MyDriver!GetNecessaryObjects

检查日志中的内存分配

启用池跟踪时,驱动程序验证程序还会保留内核空间中所有内存分配的循环日志。 默认情况下,将保留最新的 65,536 (0x10000) 分配。 进行新的分配时,会覆盖日志中最早的分配。 如果在崩溃之前最近进行了分配,则可能可以获取有关上述分配的其他信息,特别是分配时内核堆栈的线程地址和帧。

可以使用命令 !verifier 0x80 AddressOfPoolAllocation 访问此日志。 请注意,这将列出此特定地址的日志中的所有分配和释放。 若要取消或停止显示日志历史记录,请使用键盘快捷方式: Ctrl + Break 和 WinDbg 和 带 KD 的 Ctrl + C

kd> !verifier 0x80 0x982a8fe0

Log of recent kernel pool Allocate and Free operations:

There are up to 0x10000 entries in the log.

Parsing 0x00010000 log entries, searching for address 0x982a8fe0.

# 

Pool block 982a8fe0, Size 00000020, Thread 9c158bc0
81b250cd nt!IovAllocateMdl+0x3d
8060e41d VerifierExt!IoAllocateMdl_internal_wrapper+0x35
81b29388 nt!VerifierIoAllocateMdl+0x22
9a3bf6ac MyDriver!DeviceControlDispatch+0x4c
9a3bf611 MyDriver!NonPNPIRPDispatch0x51
9a3bf05a MyDriver!AllIRPDispatch+0x1a
80611710 VerifierExt!xdv_IRP_MJ_DEVICE_CONTROL_wrapper+0xd0
81b3b635 nt!ViGenericDispatchHandler+0x2d
81b3b784 nt!ViGenericDeviceControl+0x18
81b24b4d nt!IovCallDriver+0x2cc
81703772 nt!IofCallDriver+0x62
8191165e nt!IopSynchronousServiceTail+0x16e
81915518 nt!IopXxxControlFile+0x3e8

kd> !verifier 0x80 0x8645a000

Log of recent kernel pool Allocate and Free operations:

There are up to 0x10000 entries in the log.

Parsing 0x00010000 log entries, searching for address 0x8645a000.

# 

Pool block 8645a000, Size 00001000, Thread 9c158bc0
8060ee4f VerifierExt!ExAllocatePoolWithTagPriority_internal_wrapper+0x5b
81b2619e nt!VerifierExAllocatePoolWithTag+0x24
9a3bf687 MyDriver!DeviceControlDispatch+0x27
9a3bf611 MyDriver!NonPNPIRPDispatch0x51
9a3bf05a MyDriver!AllIRPDispatch+0x1a
80611710 VerifierExt!xdv_IRP_MJ_DEVICE_CONTROL_wrapper+0xd0
81b3b635 nt!ViGenericDispatchHandler+0x2d
81b3b784 nt!ViGenericDeviceControl+0x18
81b24b4d nt!IovCallDriver+0x2cc
81703772 nt!IofCallDriver+0x62
8191165e nt!IopSynchronousServiceTail+0x16e
81915518 nt!IopXxxControlFile+0x3e8
81914516 nt!NtDeviceIoControlFile+0x2a

kd> !verifier 0x80 0x9a836fd0  

Log of recent kernel pool Allocate and Free operations:

There are up to 0x10000 entries in the log.

Parsing 0x00010000 log entries, searching for address 0x9a836fd0.

# 

Pool block 9a836fd0, Size 00000030, Thread 88758740
8151713d nt!IovAllocateWorkItem+0x1b
84a133d9 VerifierExt!IoAllocateWorkItem_internal_wrapper+0x29
8151b3a7 nt!VerifierIoAllocateWorkItem+0x16
9a3bf6ed MyDriver!GetNecessaryObjects+0x1d
9a3bf620 MyDriver!NonPNPIRPDispatch0x51
9a3bf05a MyDriver!AllIRPDispatch+0x1a
84a16710 VerifierExt!xdv_IRP_MJ_DEVICE_CONTROL_wrapper+0xd0
8152d635 nt!ViGenericDispatchHandler+0x2d
8152d784 nt!ViGenericDeviceControl+0x18
81516b4d nt!IovCallDriver+0x2cc
810f5772 nt!IofCallDriver+0x62
8130365e nt!IopSynchronousServiceTail+0x16e
81307518 nt!IopXxxControlFile+0x3e8

修复内存泄漏

此驱动程序验证程序 bug 检查旨在防止驱动程序泄漏内核内存。 在每个情况下,正确的修补程序是识别分配的对象未释放的任何现有代码路径,并确保它们得到正确释放。

静态驱动程序验证程序 是一种工具,它通过模拟各种代码路径来扫描 Windows 驱动程序源代码并报告可能的问题。 静态驱动程序验证程序是一个出色的开发时实用工具,可帮助识别此类问题。

有关可以使用的其他技术,包括驱动程序验证程序不涉及的方案,请参阅 查找内核模式内存泄漏

查找内核模式内存泄漏

静态驱动程序验证程序

Windows 调试

在启用驱动程序验证程序的情况下处理错误检查