Windows 10 段堆结构(一)

前置知识

每个进程至少有一个堆:默认进程堆。默认堆是在进程启动时创建的,并且在进程的生存期内不会被删除。它的默认大小为1 MB。应用程序可以通过调用GetProcessHeap函数来查询默认进程堆。进程还可以使用HeapCreate函数创建其他私有堆。当进程不再需要私有堆时,它可以通过调用HeapDestroy来恢复虚拟地址空间。个进程中都维护一个包含所有堆的数组,线程可以使用GetProcessHeaps函数查询它们。

UWP

UWP即Windows通用应用平台,Windows 10中的Universal Windows Platform简称。UWP不同于传统pc上的exe应用,可以在所有Windows10设备上运行。
UWP应用程序进程至少包括三个堆:
(1) 默认堆
(2) 用于向进程的会话Csrss.exe实例传递大参数的共享堆。这是由CsrClientConnectToServer函数创建的,该函数在Ntdll.dll完成的进程初始化早期执行。
(3) 由Microsoft C运行库创建的堆。该堆是由C/C++内存分配函数(如Maloc、Free、等)内部使用的堆。

堆类型

在Windows10和服务器2016之前,只有一种堆类型,我们称之为NT堆。**Windows 10引入了一种称为段堆的新堆类型。**这两种堆类型包括公共元素,但结构和实现方式不同。默认情况下,所有UWP应用程序和某些系统进程都使用段堆,而所有其他进程都使用NT堆。这可以在注册表中更改。

段堆机制

在这里插入图片描述
(1) 小于或等于16368字节,使用LFH分配器。这与NT堆的逻辑类似。如果LFH还没有启动,那么将使用可变大小(VS)分配器。
(2) 对于小于或等于128 KB的大小(不由LFH提供服务),使用VS分配器。VS和LFH分配器都使用后端根据需要创建所需的堆子段。
(3) 大于128 KB且小于或等于508 KB的分配由堆后端直接提供服务。
(4) 大于508kb的分配直接调用内存管理器(VirtualAlloc),因为这些分配非常大,因此使用默认的64kb分配粒度(并舍入到最接近的页面大小)就足够了。

段堆与NT堆比较

(1) 在某些情况下,段堆可能比NT堆慢一些。不过,未来的Windows版本很可能会使其与NT堆不相上下。
(2) 段堆的元数据内存占用率较低,因此更适合手机等低内存设备。
(3) 段堆的元数据与实际数据分离,而NT堆的元数据与数据本身散布在一起。这使得段堆更加安全,因为仅给定一个块地址就很难获得分配的元数据。
(4) 段堆只能用于可增长堆。它不能与用户提供的内存映射文件一起使用。如果试图创建这样的段堆,则会创建一个NT堆。
(5) 两种堆都支持LFH类型分配,但它们的内部实现完全不同。在内存消耗和性能方面,段堆的实现效率更高。

段堆适用范围

**UWP应用默认使用段堆。**这主要是因为它们的内存占用较低,适用于低内存设备。**段堆还用于某些系统进程:csrss.exe、lsass.exe、runtimebroker.exe、services.exe、smss.exe和svchost.exe。**段堆不是桌面应用程序的默认堆,因为存在一些可能影响现有应用程序的兼容性问题。不过,在未来的版本中,它很可能会成为默认值。要启用或禁用特定可执行文件的段堆,可以设置名为FrontEndHeapDebugOptions(DWORD)的映像文件执行选项值:位2(4)禁用段堆;位3(8)启用段堆。还可以通过向HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\segment heap注册表项添加名为Enabled(DWORD)的值来全局启用或禁用段堆。零值禁用段堆,非零值启用段堆。

NT堆_HEAP结构

0:033> dt ntdll!_heap 2531e980000 
   +0x000 Segment : _HEAP_SEGMENT 
   +0x000 Entry: _HEAP_ENTRY 
   **+0x010 SegmentSignature : 0xffeeffee** 
   +0x014 SegmentFlags: 1 
   +0x018SegmentListEntry: _LIST_ENTRY 
   +0x028 Heap: 0x00000253'1e980000 _HEAP 
   +0x030 BaseAddress: 0x00000253'1e980000 Void 
   +0x038 NumberOfPages: 0x10 
   +0x040 FirstEntry: 0x00000253'1e980720 _HEAP_ENTRY 
   +0x048 LastValidEntry: 0x00000253'1e990000 _HEAP_ENTRY 
   +0x050 NumberOfUnCommittedPages : 0xf 
   +0x054 NumberOfUnCommittedRanges : 1 
   +0x058 SegmentAllocatorBackTraceIndex : 0 
   +0x05a Reserved: 0 
   +0x060 UCRSegmentList: _LIST_ENTRY 
   +0x070 Flags            : 0x8000 
   +0x074 ForceFlags       : 0 
   +0x078 CompatibilityFlags : 0 
   +0x07c EncodeFlagMask   : 0x100000 
   +0x080 Encoding         : _HEAP_ENTRY 
   +0x090 Interceptor      : 0 
   +0x094 VirtualMemoryThreshold : 0xff00 
   +0x098 Signature        : 0xeeffeeff 
   +0x0a0 SegmentReserve   : 0x100000 
   +0x0a8 SegmentCommit    : 0x2000 
   +0x0b0 DeCommitFreeBlockThreshold : 0x100 
   +0x0b8 DeCommitTotalFreeThreshold : 0x1000 
   +0x0c0 TotalFreeSize    : 0x8a 
   +0x0c8 MaximumAllocationSize : 0x00007fff'fffdefff 
   +0x0d0 ProcessHeapsListIndex : 2 
   …
   +0x178 FrontEndHeap : (null) 
   +0x180 FrontHeapLockCount : 0 
   +0x182 FrontEndHeapType : 0 '' 
   +0x183 RequestedFrontEndHeapType : 0 '' 
   +0x188 FrontEndHeapUsageData : (null) 
   +0x190 FrontEndHeapMaximumIndex : 0 
   +0x192 FrontEndHeapStatusBitmap : [129]  "" 
   +0x218 Counters         : _HEAP_COUNTERS 
   +0x290 TuningParameters : _HEAP_TUNING_PARAMETERS

段堆_SEGMENT_HEAP结构

windbg> dt ntdll!_SEGMENT_HEAP 
+0x000 TotalReservedPages : Uint8B 
+0x008 TotalCommittedPages : Uint8B 
**+0x010 Signature : Uint4B  0xddeeddee**
+0x014 GlobalFlags : Uint4B 
+0x018 FreeCommittedPages : Uint8B 
+0x020 Interceptor : Uint4B 
+0x024 ProcessHeapListIndex : Uint2B 
+0x026 GlobalLockCount : Uint2B 
+0x028 GlobalLockOwner : Uint4B 
+0x030 LargeMetadataLock : _RTL_SRWLOCK 
+0x038 LargeAllocMetadata : _RTL_RB_TREE 
+0x048 LargeReservedPages : Uint8B 
+0x050 LargeCommittedPages : Uint8B 
+0x058 SegmentAllocatorLock : _RTL_SRWLOCK 
+0x060 SegmentListHead : _LIST_ENTRY 
+0x070 SegmentCount : Uint8B 
+0x078 FreePageRanges : _RTL_RB_TREE 
+0x088 StackTraceInitVar : _RTL_RUN_ONCE 
+0x090 ContextExtendLock : _RTL_SRWLOCK 
+0x098 AllocatedBase : Ptr64 UChar 
+0x0a0 UncommittedBase : Ptr64 UChar 
+0x0a8 ReservedLimit : Ptr64 UChar 
+0x0b0 VsContext : _HEAP_VS_CONTEXT 
+0x120 LfhContext : _HEAP_LFH_CONTEXT

Signature部分用于区分两种堆,且两个都具有相同的偏移量。

后端分配

后端用于为131,073(0x20001)~520,192(0x7F000)字节的请求分配内存。后端块以页尺寸作为粒度,且每个块的起始位置都没有块头。

段结构

在这里插入图片描述
后端在段结构上操作,这是一个1MB(0x100000)大小的通过 NtAllocateVirtualMemory() 分配的虚拟内存块。由SegmentListHead(+0x060 SegmentListHead : _LIST_ENTRY)指向段头。
段头由256个页描述符构成的数组的组成,它们描述了段中每个页的状态。因为段的数据部分起始位置在偏移0x2000处,所以第一个页范围描述符另作它用——用于存储 _HEAP_PAGE_SEGMENT结构

windbg> dt ntdll!_HEAP_PAGE_SEGMENT 
+0x000 ListEntry : _LIST_ENTRY 
+0x010 Signature : Uint8B

_HEAP_PAGE_RANGE_DESCRIPTOR结构

_HEAP_PAGE_RANGE_DESCRIPTOR结构即页范围描述符结构,用于描述段中每个页的状态。
在这里插入图片描述

windbg> dt ntdll!_HEAP_PAGE_RANGE_DESCRIPTOR ‐r 
+0x000 TreeNode : _RTL_BALANCED_NODE 
+0x000 TreeSignature : Uint4B 
+0x004 ExtraPresent : Pos 0, 1 Bit 
+0x004 Spare0 : Pos 1, 15 Bits 
+0x006 UnusedBytes : Uint2B 
+0x018 RangeFlags : UChar 
+0x019 Spare1 : UChar 
+0x01a Key : _HEAP_DESCRIPTOR_KEY 
   +0x000 Key : Uint2B 
   +0x000 EncodedCommitCount : UChar 
   +0x001 PageCount : UChar 
+0x01a Align : UChar 
+0x01b Offset : UChar 
+0x01b Size : UChar

(1) TreeNode为后端空闲块的’first’页范围描述符是后端空闲树的节点
(2) UnusedBytes为"first"页范围描述符准备。
(3) RangeFlags表示后端块类型的位字段和页范围描述符所表示的页状态。
0x01:为"first"页范围描述符准备。后端块是一个LFH子段。
0x02:页已提交。
0x04:页已分配/繁忙。
0x08:页范围描述符被标记为’first’。
0x20:为"first"页范围描述符准备。后端块是一个VS子段。
(4) Key为空闲后端块的"first"页范围描述符准备。当空闲后端块被插入到后端空闲树时会使用到。
(5) Offset为非’first’页范围描述符而准备。页范围描述符自"first"页范围描述符的偏移。
(6) Size 为"first"页范围描述符而准备,表示后端块的页数量。与 Key.PageCount 值相同(重叠字段)。

后端空闲树

后端分配和释放会使用后端空闲树来查找并存储空闲后端块的信息。后端空闲树的根存储于 HeapBase.FreePageRanges ,树的节点是后端块的’first’页范围描述符。插入节点的键是’first’页范围描述符的 Key.Key 字段。
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值