前阵子一个偶然的机会碰到了一个wow64的crash dump,是在Windows Server 2008 R2上产生的,硬件平台是x64。在分析过程中发现,原来2008的核心堆结构有了很大的变化,所以这里总结一下。
在Windows Server 2003上,核心堆结构如下:
lkd> dt nt!_heap
+0x000 Entry : _HEAP_ENTRY
+0x008 Signature : Uint4B
+0x00c Flags : Uint4B
+0x010 ForceFlags : Uint4B
+0x014 VirtualMemoryThreshold : Uint4B
+0x018 SegmentReserve : Uint4B
+0x01c SegmentCommit : Uint4B
+0x020 DeCommitFreeBlockThreshold : Uint4B
+0x024 DeCommitTotalFreeThreshold : Uint4B
+0x028 TotalFreeSize : Uint4B
+0x02c MaximumAllocationSize : Uint4B
+0x030 ProcessHeapsListIndex : Uint2B
+0x032 HeaderValidateLength : Uint2B
+0x034 HeaderValidateCopy : Ptr32 Void
+0x038 NextAvailableTagIndex : Uint2B
+0x03a MaximumTagIndex : Uint2B
+0x03c TagEntries : Ptr32 _HEAP_TAG_ENTRY
+0x040 UCRSegments : Ptr32 _HEAP_UCR_SEGMENT
+0x044 UnusedUnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE
+0x048 AlignRound : Uint4B
+0x04c AlignMask : Uint4B
+0x050 VirtualAllocdBlocks : _LIST_ENTRY
+0x058 Segments : [64] Ptr32 _HEAP_SEGMENT
+0x158 u : __unnamed
+0x168 u2 : __unnamed
+0x16a AllocatorBackTraceIndex : Uint2B
+0x16c NonDedicatedListLength : Uint4B
+0x170 LargeBlocksIndex : Ptr32 Void
+0x174 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY
+0x178 FreeLists : [128] _LIST_ENTRY
+0x578 LockVariable : Ptr32 _HEAP_LOCK
+0x57c CommitRoutine : Ptr32 long
+0x580 FrontEndHeap : Ptr32 Void
+0x584 FrontHeapLockCount : Uint2B
+0x586 FrontEndHeapType : UChar
+0x587 LastSegmentIndex : UChar
而在Windows Server 2008 R2上,核心堆结构如下:
lkd> dt nt!_heap
+0x000 Entry : _HEAP_ENTRY
+0x010 SegmentSignature : Uint4B
+0x014 SegmentFlags : Uint4B
+0x018 SegmentListEntry : _LIST_ENTRY
+0x028 Heap : Ptr64 _HEAP
+0x030 BaseAddress : Ptr64 Void
+0x038 NumberOfPages : Uint4B
+0x040 FirstEntry : Ptr64 _HEAP_ENTRY
+0x048 LastValidEntry : Ptr64 _HEAP_ENTRY
+0x050 NumberOfUnCommittedPages : Uint4B
+0x054 NumberOfUnCommittedRanges : Uint4B
+0x058 SegmentAllocatorBackTraceIndex : Uint2B
+0x05a Reserved : Uint2B
+0x060 UCRSegmentList : _LIST_ENTRY
+0x070 Flags : Uint4B
+0x074 ForceFlags : Uint4B
+0x078 CompatibilityFlags : Uint4B
+0x07c EncodeFlagMask : Uint4B
+0x080 Encoding : _HEAP_ENTRY
+0x090 PointerKey : Uint8B
+0x098 Interceptor : Uint4B
+0x09c VirtualMemoryThreshold : Uint4B
+0x0a0 Signature : Uint4B
+0x0a8 SegmentReserve : Uint8B
+0x0b0 SegmentCommit : Uint8B
+0x0b8 DeCommitFreeBlockThreshold : Uint8B
+0x0c0 DeCommitTotalFreeThreshold : Uint8B
+0x0c8 TotalFreeSize : Uint8B
+0x0d0 MaximumAllocationSize : Uint8B
+0x0d8 ProcessHeapsListIndex : Uint2B
+0x0da HeaderValidateLength : Uint2B
+0x0e0 HeaderValidateCopy : Ptr64 Void
+0x0e8 NextAvailableTagIndex : Uint2B
+0x0ea MaximumTagIndex : Uint2B
+0x0f0 TagEntries : Ptr64 _HEAP_TAG_ENTRY
+0x0f8 UCRList : _LIST_ENTRY
+0x108 AlignRound : Uint8B
+0x110 AlignMask : Uint8B
+0x118 VirtualAllocdBlocks : _LIST_ENTRY
+0x128 SegmentList : _LIST_ENTRY
+0x138 AllocatorBackTraceIndex : Uint2B
+0x13c NonDedicatedListLength : Uint4B
+0x140 BlocksIndex : Ptr64 Void
+0x148 UCRIndex : Ptr64 Void
+0x150 PseudoTagEntries : Ptr64 _HEAP_PSEUDO_TAG_ENTRY
+0x158 FreeLists : _LIST_ENTRY
+0x168 LockVariable : Ptr64 _HEAP_LOCK
+0x170 CommitRoutine : Ptr64 long
+0x178 FrontEndHeap : Ptr64 Void
+0x180 FrontHeapLockCount : Uint2B
+0x182 FrontEndHeapType : UChar
+0x188 Counters : _HEAP_COUNTERS
+0x1f8 TuningParameters : _HEAP_TUNING_PARAMETERS
lkd> dt nt!_heap_segment
+0x000 Entry : _HEAP_ENTRY
+0x010 SegmentSignature : Uint4B
+0x014 SegmentFlags : Uint4B
+0x018 SegmentListEntry : _LIST_ENTRY
+0x028 Heap : Ptr64 _HEAP
+0x030 BaseAddress : Ptr64 Void
+0x038 NumberOfPages : Uint4B
+0x040 FirstEntry : Ptr64 _HEAP_ENTRY
+0x048 LastValidEntry : Ptr64 _HEAP_ENTRY
+0x050 NumberOfUnCommittedPages : Uint4B
+0x054 NumberOfUnCommittedRanges : Uint4B
+0x058 SegmentAllocatorBackTraceIndex : Uint2B
+0x05a Reserved : Uint2B
+0x060 UCRSegmentList : _LIST_ENTRY
从上面的列表结构可以看出:
1. Windows Server 2003上:
a) _heap数据结构完全占据一个_heap_entry,这点可以通过比较_heap_entry中的size和_heap结构的尺寸得出。
b) _heap_segment最多有64个,以数组方式存在_heap数据结构中。见列表中标红的部分。进一步研究表明,堆块的初始尺寸是1M,然后每次增加到原来的2倍尺寸,如果没有这么大的连续空间就回退一半,直到找到满足条件的连续空间或者失败。这样的算法使得在虚存空间分段严重的情况下,一个堆只能管理很小的内存空间。
c) FreeList是一个拥有128元素的数组,每个数组元素包含一个链表的头结点,用于链接相同大小的堆块。
2. Windows Server 2008 R2上:
a) _heap数据结构包含第一个_heap_segment,这点可以通过比较列表中标成蓝色的部分得到。
b) _heap_segment从数组变成链表了,进一步研究表明,初始对快大小是1M,然后对堆块每次增长到原来的2倍,但是堆块最大尺寸是0xfd00,即15MB。这样的好处有两个:
- 对虚存的连续性要求大大降低,内存的使用率就会大大提高。这样可以让系统只用一个堆就能管理所有的内存空间。
- 支持堆的安全特性。
c) FreeList变成了只有一个链表,感觉已经不再按照堆块大小区分不同链表了。
Windows Server 2008 R2的_heap结构中容易迷惑的是SegmentList成员和SegmentListEntry成员。实际上SegmentList才是链接所有_heap_segment的头结点。SegmentListEntry是第一个堆段链接到堆的头结点的成员。
另外两个结构中的VirtualAllocdBlocks结构都没有改变。该结构用于管理大于512kB的内存。
备注:
在用windbg amd64版调试wow64程序时,由于wow64.dll没有导出_TEB32和_PEB32数据结构,所以扩展命令!heap,!peb, !teb都不能用,可以用!info得到32位和64为的_peb和_teb,然后用dt ntdll32!_peb,dt ntdll32!_teb的到相应信息,包括32位堆的首地址。
另外一个解决方案就是直接使用windbg x86版调试wow64程序,这样就和32位完全一样,非常方便,但是缺点就是不能访问wow64所在的原生64位进程的信息。