堆(heap)系列_0x01:堆-Introduction(框架+类型)

提示:下面几个问题都是关于堆的,如果你非常清楚,可以直接跳过不用看下面的内容了

  • 1.堆和栈有什么区别?(网上八股文很多,百度"内存四分区"就有介绍,但是最好不要停在表面知识)
  • 2.写程序时用new/malloc是在堆还是栈上分配的内存?free只传一个指针,没有传大小,它是怎么释放内存的?(侯捷的C++内存管理课程有详细的讲解)
  • 3.NT全局标志、gflags都是用来做什么的?(当堆被破坏时,想要启动页堆的调试支持,就一定绕不开全局标志)
  • 4.页堆、NT堆、CRT堆、默认堆、私有堆,这些术语都是什么意思?(使用应用程序验证器,mallocHeap开头的API、进程创建堆、堆破坏想立刻捕捉到都要涉及到这些堆的使用)

下面将堆的相关内容整理成几篇笔记,先对堆有一个简单的认识吧

1.堆和栈区别

堆(heap)是在运行期间动态申请内存空间的一种方法,平时使用的mallocnew都是在堆上分配内存

提示1:提到堆,就一定要会想到另一个术语:栈(stack),简单区别如下:

说明栈(stack)堆(heap)
分配和释放内存编译器在编译阶段自动产生相关指令,在程序的默认栈上预留空间程序员自己处理,且分配和释放要严格匹配
生存期1.线程的创建就会为其创建栈空间,一般大小为1MB,因此不适合分配运行时才决定大小的缓冲区
2.栈帧会随着函数调用和返回而创建和销毁,因此栈上变量只在函数内有效
进程存在期间一直有效

提示2:堆和栈是2个不相关的内容,一定不要混在一起认为是一回事,也不知道是谁是第一个将堆栈翻译在一起的人…

下面是内存和变量使用的分区图(网上的图片,找不到出处了,里面有堆和栈的区别)

在这里插入图片描述

  • heap

向上(高地址)增长的;newmalloc都是在heap上申请的内存,这部分内存的生命周期是与程序相同的

深入学习:要学习堆块结构、gfalgs工具、页堆相关知识

  • stack

向下(低地址)增长的,主要用途是暂时保存函数的局部变量、传递参数和保存函数的返回地址

深入学习:要学习栈帧的相关知识

提示:IDA中通过变量名就可以区分出是参数还是局部变量,在一个函数内部,ebp-xx的形式基本就是局部变量,ebp+xx的形式基本就是参数

2.堆的框架

下图是windows内存架构示意图,先来了解一些术语
在这里插入图片描述

  • 内存管理器(Memory Manager

将一块大的内存委托给堆管理器,系统提供堆内存的源头

  • 堆管理器(Heap Manager

问题1:堆管理器存在意义?

堆管理器会将大块内存分割成不同的小块内存给应用程序使用,这样内存管理器只要负责处理大规模的内存分配就可以;其实学习堆,很大一部分时间是在学习堆管理器

问题2:堆管理器的实现?

有很多种实现堆管理器的方式,windows操作系统的实现主要在2个地方:ntdll.dll(子系统的API调用ntdll.dll中函数)和ntoskrnl.exe(驱动程序调用的函数在这个exe中)

ntdll.dll中实现了一个通用的堆管理器,目的是给用户态进程提供内存服务,通常称为Win32管理器;其中的SDK提供了一些API(HeapAllocHeapFree等)可以访问堆管理器,这些前缀是Heap的API接口实际上是ntdll.dll中函数的包装而已

  • ntdll.dll中部分堆管理函数
//WinDbg查看部分堆管理函数(节选)
0:000> dt ntdll!*Heap*
7708b890  ntdll!RtlSizeHeap						//获取堆块大小
77070fa0  ntdll!RtlCreateHeap					//创建堆
7705f8c0  ntdll!RtlDestroyHeap
77073bd0  ntdll!RtlFreeHeap						//释放堆
77075da0  ntdll!RtlAllocateHeap					//在堆上分配内存
  • windows常见的堆函数
HeapCreate/HeapDestory							//创建/删除堆
HeapAlloc/HeapFree/HeapReAlloc					//分配/释放堆块、更改堆块大小
HeapLock/HeapUnLock								//控制堆操作互斥
HeapWalk										//枚举堆中的项和区域

下面是一些常用堆介绍,最好知道每种堆的用途和原理;由于页堆比较复杂,这篇笔记是对堆的介绍,因此暂时不介绍复杂的堆类型了

3.堆的类型

type 1:CRT堆

为了支持C的内存分配函数(malloc/free)和c++的运算符(new/delete)要求,编译器的C运行时库创建了一个专门的堆用来给这些函数使用;堆句柄会存储在msvcrt的全局变量_crtheap

根据分配堆块的方式不同,会有3种工作模式;当创建堆时,会选择一种模式

模式说明
SBH(Small Block Heap)、旧SBHCRT堆会使用虚拟内存分配API从内存管理器中申请一块大内存,再分割成小的堆给应用程序使用
系统模式只是将堆块分配请求转发给它基于的Win32堆

type 2:NT堆

简单理解是ntdll.dll实现的堆管理器提供的堆的一种类型,下面是用户模式下的NT堆分层示意图

在这里插入图片描述

其中:核心层提供了堆的基本功能,只有用户模式的堆可以在核心层上存在前端层

Windows10之前,只有一种堆类型(NT堆),Windows10引入了段堆(segment heap)的新类型;默认情况下,所有通用windows平台(UWP)和某些系统进程使用段堆,其他所进程使用NT堆,可通过注册表修改

扩展:UWP至少包含3个堆:1.默认堆、2.共享堆、3.CRT堆

type 3:段堆

下面是段堆类型的架构,根据申请的内存大小,使用不同的分配方式

在这里插入图片描述

NT堆和段堆对比:

  • 段堆的元数据内存占用更小,更适合手机等小内存设备
  • 段堆的元数据和实际数据是分隔的,NT是放在一起的
  • 段堆只能用于可增长堆,且无法用于内存映射文件

段堆启动/禁止:

  • 特定可执行文件:映像文件中的FrontEndHeapDebugOptions(4:禁用;8:启动)
  • 针对全局:这个暂时不用关心

示例:查看UWP进程的堆信息

windows10打开一个UWP应用(Calculator.exe),使用WinDbg附加Calculator进程

#1.显示堆的简要信息
0:025> !heap
        Heap Address      NT/Segment Heap

         178ab040000         Segment Heap	#默认堆
         178aafd0000              NT Heap	#与用户定义的堆块配合使用,因此创建的是NT堆
         178aafe0000         Segment Heap
         178ab1a0000         Segment Heap
         178adff0000         Segment Heap
         178af560000              NT Heap
         178af580000              NT Heap
         178af9e0000              NT Heap

#2.查看NT堆结构
0:025> dt ntdll!_HEAP 178aafd0000
   +0x000 Segment          : _HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x010 SegmentSignature : 0xffeeffee	#签名
   +0x014 SegmentFlags     : 1
   +0x198 FrontEndHeap     : (null) 	#是否存在前端层?非null代表LFH前端层(前端层只有这一种)
   +0x1a0 FrontHeapLockCount : 0
   +0x1a2 FrontEndHeapType : 0 ''

#3.查看段堆结构
0:025> dt ntdll!_segment_heap 178ab040000
   +0x000 EnvHandle        : RTL_HP_ENV_HANDLE
   +0x010 Signature        : 0xddeeddee			#签名
   +0x014 GlobalFlags      : 0
   ...
   +0x280 VsContext        : _HEAP_VS_CONTEXT	#包含VS和LFH相关信息
   +0x340 LfhContext       : _HEAP_LFH_CONTEXT

#说明:NT堆和段堆的签名都是固定的,且相对于堆句柄的偏移量都是0x10,这使如RtlAllocateHeap
#     等函数通过堆句柄 + 偏移可以选择实现哪种堆

WinDbg命令:使用!heap -s可以了解每个堆的详细信息

在这里插入图片描述

type 4:默认堆和私有堆

相对一个进程来说,一般有2种堆:默认堆和私有堆;默认堆是进程创建时创建的,私有堆是程序员调用HeapCreate创建的

问题:程序员怎么使用默认堆和私有堆?

通过句柄(堆的起始地址);默认堆的句柄保存在_PEB->ProcessHeap字段中,程序里其他的堆句柄都是被依次放在一个数组中(在PEBProcessHeaps中存储)

#1.peb中heap相关信息
0:000> .process
Implicit process is now 0032f000							#默认堆的句柄
0:000> dt ntdll!_PEB 0032f000
   +0x018 ProcessHeap      : 0x008f0000 Void				#进程(默认)堆句柄
   +0x090 ProcessHeaps     : 0x7756c760  -> 0x008f0000 Void	#存堆句柄数组,首地址是0x7756c760

#2.查看堆句柄数组
#进程中堆的总数是7个,008f0000是默认堆的的句柄,每个句柄都指向一个类型为_HEAP的数据结构
0:000> dd 0x7765c760 l8
7765c760  008f0000 02480000 00710000 006c0000				#进程默认堆总是位于数组的第一项
7765c770  02930000 05510000 05660000 00000000

堆的Introduction就先介绍这么多吧,写太多看着就枯燥了

4.参考

  • 1.《软件调试》第二版,卷2的第23章
  • 2.《Windows高级调试》,第6章
  • 3.《深入解析Windows操作系统》第七版,第五章
  • 4.《Windows编程调试技术内幕》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值