堆结构简介


堆结构简介

首先我们需要认识两个重要的空闲堆块管理结构:空表(freelist)和快表(LookAside),在下图中可以看到,空表项位堆结构固定偏移0x178处,而快表指针则位于固定偏移0x580处

堆结构

有一点需要注意,空表只会出现在可扩展的堆中,比如

hp = HeapCreate(0,0x1000,0x10000); //固定了初始大小0x1000和最大堆大小0x10000,堆无法扩展,空表始终为null
hp = HeapCreate(0,0x1000,0x10000); //未设置初始大小和最大大小,堆可动态扩展,存在空表项

空表

空表也就是Freelists,在上图中我们可以看到,空表的表项共有128,又 1024=128*8。由此可以引出空表存储空闲堆块的方式:特殊的第0项用于存储>=1024byte的空闲堆块,而后续表项从free[1]开始依次按8字节递增所存储的堆块大小。

List_entry是一个双向链表结构,因此我们的Freelist的实际存储方式如图(空闲时Flink与Blink均指向自身)

空表存储方式

此处我们可以通过动态调试的数据进行验证(PS:此时堆段基址为0x360000)

快速单向链表

快表即Lookaside,同样通过堆结构体分析,我们可以得知定位快表地址的方式:FrontEndHeap指针位于0x580偏移处,指向快表

快表项远比空表项大,下图是快表项具体结构,每个表项占0x30字节,但我们目前只需要关注首部的堆块地址即可

快表结构

进入这个Union中,看一下地址的具体存储方式:位于0x0处,并且以单链表形式存储地址

同理,快表也有128项,其存储的堆块大小排序方式与空表一致,但第0项未使用,如下图所示

快表存储堆块

接下来跳转到动态调试中的快表处进行验证,可以看到实际使用中即使是1字节的小堆块,也不会用到快表的0、1项,具体原因后文叙述

堆块结构

先前介绍了两个堆块的存储结构,接下来我们对堆块进行介绍

关于堆块分配,释放,合并的知识我们不详述,只需要知道,分配是从初始化的尾块分割,释放是将堆块放入上述两结构中,合并是将相邻的空闲堆块合成更大的堆块就可以

PS:在进行堆块操作中,始终以8byte为操作单位,并且若用户未请求,unused字节不会被初始化。

堆块有着空闲态和占用态两种情况,分别对应着不同的数据结构:

占用态

占用态堆块是不在快表和空表上的,其指针直接由用户进程保留;

堆块均有堆首,占8字节,用户申请的堆块则只对应堆块的数据区,即使只申请1byte堆空间,堆首的selfsize也是0x2(8byte堆首+8byte对齐单位)

关于堆首数据的详解,如下图所示

占用态堆首

空闲态

空闲态堆块则是在占用态堆首的前提下,借用数据区前8byte,存储了一个_LIST_ENTRY结构,这也是空闲堆块索引的关键所在

空闲态堆块数据结构

注意,快表是不会被合并的,就是因为快表中的堆块结构,其堆首中的Flag均是0x1(busy)状态


文章作者: yssx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yssx !
评论
  目录