[译] ptmalloc介绍
字数 2497 2025-08-19 12:41:16
ptmalloc内存分配器详解
1. ptmalloc基础概念
ptmalloc是glibc中使用的内存分配器,基于dlmalloc实现并进行了优化。它负责管理进程堆内存的分配和释放。
1.1 基本分配单位:malloc_chunk
ptmalloc中最基本的分配单位是malloc_chunk结构体,包含6个元数据域:
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; // 前一块大小(如果前一块空闲)
INTERNAL_SIZE_T mchunk_size; // 当前块大小(包括元数据)
// 以下字段只在块空闲时使用
struct malloc_chunk* fd; // 前向指针(指向链表下一个块)
struct malloc_chunk* bk; // 后向指针(指向链表前一个块)
// 以下字段只在large块空闲时使用
struct malloc_chunk* fd_nextsize; // 指向下一个不同大小的块
struct malloc_chunk* bk_nextsize; // 指向上一个不同大小的块
};
1.2 块类型
ptmalloc中有三种基本类型的块:
-
已分配块(allocated chunk):
- 前一块大小字段可能被应用程序数据覆盖
- 下一个邻接块的P位会被设置
-
释放块(freed chunk):
- 包含完整的元数据
- 下一个邻接块的P位会被清除
- 根据块大小放入不同的bin中
-
top块(top chunk):
- 表示arena当前剩余的可用空间
- 当空间不足时会通过brk()或mmap()扩展
1.3 块状态标志
mchunk_size的最后三位用于表示块的状态:
- A (NON_MAIN_ARENA, 0x4): 当块来自非主arena时设置
- M (IS_MMAPPED, 0x2): 当块通过mmap获取时设置
- P (PREV_INUSE, 0x1): 当前一个邻接块在使用中时设置
2. 关键数据结构
2.1 malloc_state
malloc_state结构体用于管理堆内存:
struct malloc_state {
__libc_lock_define(, mutex); // 访问锁
int flags; // 标志位
// Fast bins
mfastbinptr fastbinsY[NFASTBINS]; // 快速bin数组
// Base of the topmost chunk
mchunkptr top; // top chunk指针
// The remainder from the most recent split
mchunkptr last_remainder; // 最近分割的剩余部分
// Normal bins
mchunkptr bins[NBINS * 2 - 2]; // 普通bin数组
// Bitmap of bins
unsigned int binmap[BINMAPSIZE]; // bin位图
// Linked list
struct malloc_state *next; // 下一个arena
struct malloc_state *next_free; // 空闲arena链表
// Thread count
INTERNAL_SIZE_T attached_threads; // 附加线程数
// Memory statistics
INTERNAL_SIZE_T system_mem; // 系统分配的内存
INTERNAL_SIZE_T max_system_mem; // 最大系统内存
};
2.2 重要宏定义
| 宏定义 | x86值 | x86-64值 | 描述 |
|---|---|---|---|
| SIZE_SZ | 4 | 8 | size_t的大小 |
| MIN_CHUNK_SIZE | 16 | 32 | 最小块大小 |
| MALLOC_ALIGNMENT | 8 | 16 | 内存对齐大小 |
| NBINS | 128 | 128 | bin总数 |
| NFASTBINS | 10 | 10 | fast bin数量 |
| NSMALLBINS | 64 | 64 | small bin数量 |
| SMALLBIN_WIDTH | 8 | 16 | small bin宽度 |
| DEFAULT_MXFAST | 64 | 128 | 默认fast bin最大大小 |
| MAX_FAST_SIZE | 80 | 160 | fast bin最大大小 |
| MIN_LARGE_SIZE | 512 | 1024 | large bin最小大小 |
3. bin类型与管理
ptmalloc使用四种类型的bin来管理释放的块:
3.1 Fast bin
-
特点:
- 单链表管理
- 块大小 < 0x40 (x86) / < 0x80 (x86-64)
- 不合并相邻空闲块(P位不清除)
- 先进后出(FILO)分配策略
-
示例:
char *p1 = malloc(0x20);
char *p2 = malloc(0x20);
char *p3 = malloc(0x20);
free(p1); free(p2); free(p3);
// 释放顺序: p1 → p2 → p3
// 分配顺序: p3 → p2 → p1
3.2 Unsorted bin
-
特点:
- 双链表管理
- 块大小 ≥ 0x40 (x86) / ≥ 0x80 (x86-64)
- 是释放大块的第一站
- 分配时会遍历查找合适块
-
分配策略:
- 如果找到精确匹配的块,直接返回
- 否则将块放入small bin或large bin
3.3 Small bin
-
特点:
- 双链表管理
- 块大小 < 0x200 (x86) / < 0x400 (x86-64)
- 每个bin管理固定大小的块
- 先进先出(FIFO)分配策略
-
示例:
char *p1 = malloc(0xa0);
char *p2 = malloc(0x30);
char *p3 = malloc(0xa0);
free(p1); free(p3);
// 释放顺序: p1 → p3
// 分配顺序: p1 → p3
3.4 Large bin
-
特点:
- 双链表管理
- 块大小 ≥ 0x200 (x86) / ≥ 0x400 (x86-64)
- 每个bin管理一个大小范围的块
- 使用fd_nextsize和bk_nextsize维护大小排序
- 最佳适配分配策略
-
分配策略:
查找比请求大小大的最小块进行分配
4. 分配流程
ptmalloc的分配流程如下:
- 大小转换:将请求大小转换为实际分配大小
- fast bin查找:如果大小≤global_max_fast,尝试fast bin
- small bin查找:如果大小在small bin范围内,尝试small bin
- malloc_consolidate:如果small bin未找到,合并fast bin到unsorted bin
- unsorted bin遍历:
- 查找精确匹配的块
- 否则将块放入small/large bin
- large bin查找:使用最佳适配算法查找合适块
- top chunk分割:如果以上都失败,分割top chunk
- 系统调用:如果top chunk不足,调用brk/mmap扩展堆
4.1 malloc_consolidate
malloc_consolidate函数用于合并fast bin中的块:
- 检查前一个邻接块是否空闲:
- 如果空闲,合并到前一个块
- 检查下一个邻接块是否是top chunk:
- 如果是,合并到top chunk
- 否则检查下一个邻接块是否空闲:
- 如果空闲,合并到当前块
- 将合并后的块放入unsorted bin
4.2 关键分配函数
void* __libc_malloc(size_t bytes) {
// 1. 大小转换
checked_request2size(bytes, nb);
// 2. fast bin尝试
if (nb <= get_max_fast()) {
idx = fastbin_index(nb);
victim = fastbin(av, idx);
if (victim != NULL) {
// 检查fast bin是否损坏
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
malloc_printerr("malloc(): memory corruption (fast)");
// 返回分配的内存
return chunk2mem(victim);
}
}
// 3. small bin尝试
if (in_smallbin_range(nb)) {
idx = smallbin_index(nb);
bin = bin_at(av, idx);
if ((victim = last(bin)) != bin) {
// 检查small bin链表是否损坏
if (victim == 0) malloc_consolidate(av);
else if (__glibc_unlikely(bck->fd != victim))
malloc_printerr("malloc(): smallbin double linked list corrupted");
// 返回分配的内存
return chunk2mem(victim);
}
}
// 4. unsorted bin遍历
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
// 检查大小是否合法
if (__builtin_expect(chunksize_nomask(victim) <= 2 * SIZE_SZ, 0) ||
__builtin_expect(chunksize_nomask(victim) > av->system_mem, 0))
malloc_printerr("malloc(): memory corruption");
size = chunksize(victim);
// 精确匹配检查
if (size == nb) {
// 返回精确匹配的块
return chunk2mem(victim);
}
// 放入small/large bin
if (in_smallbin_range(size)) {
// 放入small bin
} else {
// 放入large bin
}
}
// 5. large bin查找
if (!in_smallbin_range(nb)) {
// 最佳适配算法查找
}
// 6. top chunk分割
victim = av->top;
size = chunksize(victim);
if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {
// 分割top chunk
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
// 返回分配的内存
return chunk2mem(victim);
}
// 7. 系统调用扩展堆
void *p = sysmalloc(nb, av);
return p;
}
5. 安全机制
ptmalloc包含多种安全检查:
- fast bin检查:验证fast bin中的块大小是否正确
- small bin检查:验证small bin的双向链表是否损坏
- unsorted bin检查:验证块大小是否合法
- large bin检查:验证large bin的排序是否正确
- top chunk检查:验证top chunk大小是否合法
当检测到错误时,会调用malloc_printerr输出错误信息并终止程序。
6. 总结
ptmalloc是一个复杂的内存分配器,其核心特点包括:
- 使用chunk作为基本管理单位,包含丰富的元数据
- 通过四种bin管理空闲内存,针对不同大小优化分配效率
- 采用多阶段分配策略,平衡速度和内存利用率
- 包含严格的安全检查,防止内存破坏
- 支持多线程环境,每个线程有自己的arena
理解ptmalloc的内部机制对于分析堆相关漏洞和进行堆利用至关重要。