各类不同的堆分配器
不同也应用堆内存需求各异,促使了多重堆分配器的诞生
- dlmalloc:通用分配器,满足多种场景
- ptmalloc2:用于glibc,在GNU系统中广泛使用
- jemalloc:用于FreeBSD和Fierfox,针对特定系统和软件优化
- tcamalloc:是Google开发,适用于Google的软件生态
- libumem:用于Solaris系统,为其提供内存管理支持
堆管理器
堆管理器处于用户程序与内核中间
- 响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户
程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大
的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有
当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。 - 管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给
操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用
户新申请的内存的请求。
堆的基本操作
malloc函数
- malloc(size_t n):malloc函数返回8对应大小字节的内存块的指针
- 当 n=0 时,返回当前系统允许的堆的最小内存块。
- 当 n 为负数时,由于在大多数系统上,size_t 是无符号数(这一点非常重要),所以程序就会申请很大的内存空间,但通常来说都会失败,因为系统没有那么多的内存可以分配。
free函数
- free(void*p):free函数会释放p所指向的堆块
- 当 p 为空指针时,函数不执行任何操作。
- 当 p 已经被释放之后,再次释放会出现乱七八糟的效果,这其实就是double free。
- 除了被禁用 (mallopt) 的情况下,当释放很大的内存空间时,程序会将这些内存空间还给系统,以便于减小程序所使用的内存空间。
堆的微观结构
称有malloc申请的内存为chunk,在ptmalloc内部用malloc_chunk结构体来表示,当程序申请的chunk被free后,会被加入到相应的空闲管理列表中
无论一个chunk的大小如何,处于分配状态还是释放状态,他们都使用一个统一的结构
chunk
struct malloc_chunk{
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk*fd;
struct malloc_chunk*bk;
/* Only used for large blocks:point*/
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};
prev_size
如果该 chunk 的物理相邻的前一地址 chunk(两
个指针的地址差值为前一chunk 大小)是空闲的话,那该字段记录的是前一个 chunk 的大小 (包括 chunk 头)。否则,该字段可以用来存储物理相邻的前一个 chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk 。
size
该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 *SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。 该字段的低三个比特位对chunk 的大小没有影响,它们从高到低分别表示
NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1 表示不属于,0 表示属于。
- IS_MAPPED,记录当前 chunk 是否是由
mmap 分配的。 - PREV_INUSE,记录前一个 chunk 块是否被分
配。一般来说,堆中第一个被分配的内存块
的 size 字段的 P 位都会被设置为 1,以便于
防止访问前面的非法内存。当一个 chunk 的
size 的 P 位为 0 时,我们能通过 prev_size 字
段来获取上一个 chunk 的大小以及地址。这
也方便进行空闲 chunk 之间的合并。
fd,bk
chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下:
- fd 指向下一个(非物理相邻)空闲的chunk
- bk 指向上一个(非物理相邻)空闲的 chunk
- 通过 fd 和 bk 可以将空闲的chunk 块加入到空闲的 chunk 块链表进行统一管理
fd_nextsize bk_nextsize
也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
- fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
- bk_nextsize 指向后一个与当前chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
- 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。
整体结构

被释放的chunk被记录在链表中

size
-
如果一个chunk处于free状态,那么会有两个位置记录其相应的大小
- 本身的size字段会记录
- 他后面的chunk会记录
-
一般情况下,物理相邻的两个空闲chunk‘会在适合的时间被合并为一个chunk,堆管理器会通过prev_size字段以及size字段合并为两个物理相邻的空闲chunk块,以提高内存利用率
bin-空闲chunk管理结构
- 用户释放掉的 chunk 不会马上归还给系统,ptmalloc 会统一管理heap 和 mmap 映射区域中的空闲的 chunk
- 当用户再一次请求分配内存时,ptmalloc 分配器会试图在空闲的chunk 中挑选一块合适的给用户。这样可以避免频繁的系统调用,降低内存分配的开销
- 这结构就是bins – 一种分箱式的chunk管理方法
- 根据空闲的 chunk 的大小以及使用状态将chunk 初步分为 4 类
- fast bins
- small bins
- large bins
- unsorted bin
bin的实现
对于 small bins,large bins,unsorted bin 来说,ptmalloc 将它们维护在同一个数组中

- 第一个为 unsorted bin,字如其面,这里面的 chunk 没有进行排序,存储的chunk 比较杂。
- 索引从 2 到 63 的 bin 称为 small bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为 2 个机器字长,即 32 位相差 8 字节,64 位相差 16 字节。
- small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 都包含一定范围内的 chunk,其中的 chunk 按 fd 指针的顺序从大到小排列。相同大小的 chunk 同样按照最近使用顺序排列。
fastbin
- fastbin 中默认支持最大的 chunk 的数据空间大小为 64 字节。但是其可以支持的 chunk 的数据空间最大为 80 字节。除此之外,fastbin 最多可以支持的 bin 的个数为 10 个,从数据空间为 8 字节开始一直到 80 字节
- fastbin 范围的 chunk 的 inuse 始终被置为 1。因此它们不会和其它被释放的 chunk 合并。
堆溢出
- 堆溢出漏洞发生的基本前提是
- 程序向堆上写入数据。
- 写入的数据大小没有被良好地控制。
- 堆溢出漏洞没有办法直接去覆盖返回地址控制程序eip
- 堆溢出漏洞的利用方法通常分为两种:
- 去想办法覆盖堆结构里的虚函数指针
- 通过bins结构的一些特点去实现任意地址读写,进而控制程序流
简单例子

对课件的简单整理,如有不足,还请指教