使用内核调试程序查找内核模式内存泄漏

内核调试器确定内核模式内存泄漏的精确位置。

启用池标记

必须先使用 GFlags 启用池标记。 GFlags 包含在 Windows 调试工具中。 启动 GFlags,选择“系统注册表”选项卡,检查“启用池标记”框,然后选择“应用”。 必须重启 Windows 才能使此设置生效。

在 Windows Server 2003 及更高版本的 Windows 上,始终启用池标记。

确定泄漏的池标记

若要确定哪个池标记与泄漏相关联,通常最简单的方法是使用 PoolMon 工具执行此步骤。 有关详细信息,请参阅 使用 PoolMon 查找 Kernel-Mode 内存泄漏

或者,可以使用内核调试器查找与大型池分配关联的标记。 为此,请遵循以下过程:

  1. 使用 .reload (重新加载模块) 命令重新加载所有模块

  2. 使用 !poolused 扩展。 包括标志“4”以按分页内存使用对输出进行排序:

    kd> !poolused 4 
    Sorting by Paged Pool Consumed
    
    Pool Used:
                NonPaged            Paged     
    Tag    Allocs     Used    Allocs     Used 
    Abc         0        0     36405 33930272 
    Tron        0        0       552  7863232 
    IoN7        0        0     10939   998432 
    Gla5        1      128      2222   924352 
    Ggb         0        0        22   828384 
    
  3. 确定哪个池标记与最大内存使用率相关联。 在此示例中,使用标记“Abc”的驱动程序使用的内存最多,几乎为 34 MB。 因此,内存泄漏最有可能在此驱动程序中。

查找泄漏

确定与泄漏关联的池标记后,请按照以下步骤查找泄漏本身:

  1. 使用 ed (Enter Values) 命令修改全局系统变量 PoolHitTag 的值。 每当使用与值匹配的池标记时,此全局变量会导致调试器中断。

  2. PoolHitTag 设置为你怀疑为内存泄漏源的标记。 应指定模块名称“nt”以加快符号解析速度。 标记值必须以 little-endian 格式输入, (即向后) 。 由于池标记始终为四个字符,因此此标记实际上是 A-b-c 空间,而不仅仅是 A-b-c。 因此,请使用以下命令:

    kd> ed nt!poolhittag ' cbA' 
    
  3. 若要验证 PoolHitTag 的当前值,请使用 db (Display Memory) 命令:

    kd> db nt!poolhittag L4 
    820f2ba4  41 62 63 20           Abc  
    
  4. 每次使用 标记 Abc 分配或释放池时,调试器都会中断。 每次调试器中断其中一个分配或免费操作时,请使用 kb (显示堆栈回溯) 调试器命令查看堆栈跟踪。

使用此过程,可以确定驻留在内存中的哪些代码使用标记 Abc 过度分配池。

若要清除断点,请将 PoolHitTag 设置为零:

kd> ed nt!poolhittag 0 

如果有多个不同位置分配了此标记的内存,并且这些内存位于已编写的应用程序或驱动程序中,则可以更改源代码,以便对每个分配使用唯一标记。

如果无法重新编译程序,但想要确定代码中导致泄漏的多个可能位置之一,则可以在每个位置取消组合代码,并使用调试器编辑驻留在内存中的此代码,以便每个实例使用不同的 (和以前未使用的) 池标记。 然后,允许系统运行几分钟或更长的时间。 经过一段时间后,使用调试器再次中断,并使用 !poolfind 扩展查找与每个新标记关联的所有池分配。