什么是NT堆
在 Windows 平台上有一个非常底层的内存分配函数 VirtualAlloc,它会一次性分配 64k 整数倍的内存段,内部对象按4k的内存页对齐,如果让程序直接接触 VirtualAlloc,很容易造成内存的浪费以及分配效能的降低,为了提高性能和内存使用效率, Windows 给进程提供了一个堆管理器对对象分配进行细粒度化管理,32位程序的颗粒度位8Byete,64位程序则为16Byte。
Windows NT 堆有两种类型 进程堆、私有堆。进程堆是进程启动时,Windows 加载器都会给当前进程创建一个默认的进程堆 (ProcessHeap),生命周期等同于进程,且64位程序额外多一个64k的nt堆。
在Windbg中,NT堆相关的指令如下:
1)!heap -s 显示当前进程相关的堆概要信息
2)!heap xxxx -m 显示包含指定堆的所有段条目
3)!heap xxxxx -h 显示包含指定堆的所有非 LFH 条目
4)!heap xxxxx -a 显示包含指定堆的所有信息
NT堆布局
注:
dt ntdll!_HEAP xxx 显示特定堆的结构
HEAP 开头有一块大小为 0x4a8 的 HEAP_ENTRY 堆块,用来存放 _HEAP 结构的元数据信息。
NT堆数据结构表述
注:
1)Nt堆由Segment组成、而Segment则由若干Block组成
2) 分配对象如果小于512k 则放置于SegmentList中,大于512k中则存在于VirtualAllocdBlocks
3)dt ntdll!_HEAP_VIRTUAL_ALLOC_ENTRY xxx 显示特定虚拟分配块结构,继而查看到该虚拟分配块中实际分配块的地址,如下:
排查NT泄漏思路
由于c、c++没有GC对内存进行管理,所有不能直观查看对象的引用根。但是可以在操作系统层记录内存的分配栈记录,通过gflags /i xxxx.exe +ust 针对某一程序开启记录功能(实际上就是修改针对某程序的注册表配置)。gflags 是调试工具包附带的一个工具
1)首先执行!address -summary 确定内存泄漏处于nt堆中,如下
2) 其次执行!heap -s 查看NT堆的内存分配情况,如下 commit为SegmentList的占用情况(小于512的对象),
VirtBlocks为VirtualAllocdBlocks的占用情况
如果是处于SegmentList上,则通过 !heap -stat -h xxxx -grp S 进行分组分析
执行 !heap -flt s 424 根据大小查找对应的block,最后通过!heap -p -a xxxx 查找指定block的分配栈信息
3)如果是大于512k,则需要执行!heap -m xxxx 查看堆上的Virtual Alloc List,最后执行 !heap -p -a xxxx+0x18 即可查出分配该virtual block的代码
因为ntdll!_HEAP_VIRTUAL_ALLOC_ENTRY的结构体偏移位置+0x18 为BusBlock,因而要加+0x18(32位)