一、堆的调用流程
第一步
- 当应用程序“第一次”使用malloc函数申请动态内存时,glibc库会向内核申请一块非常大的动态内存,这块动态内存会比malloc申请的大小大很多。
- brk/mmap系统调用:glibc库使用的是brk或者mmap系统调用来想内核申请内存空间的。至于两者有什么区别后面介绍。
第二步
- glibc申请到这块大的内存之后,根据malloc需要的大小,然后切割相应的大小给应用程序malloc函数使用。
第三步
- 当应用层free之后,会将刚才使用到的动态内存返回给glibc,但是返回的内存不是返回给top chunk,而是由bins链管理(后面会介绍bins链)。
第四步
- 当程序再次malloc时,会从刚才申请的很大的动态内存去取,不会再去向内核申请内存。
- 只有当第一次申请的动态内存使用完时,glibc才会再次通过brk/mmap系统调用向内核去要内存。
二、细节介绍
struct malloc_chunk结构体
- 堆是由一系列struct malloc_chunk结构体组成的链表,申请和释放的堆都是malloc_chunk结构体。
- 其他文章会详细介绍此结构体。
bin链
- 当应用层malloc申请的内存使用完之后,通过free函数将堆内存(也就是struct malloc_chunk结构体)返回给glibc时,不返回给top chunk,而是由称为bin的链所管理。因此bin链也是由struct malloc_chunk组成的链表。
- bins链分为:fastbin、smallbin、unsorted bin、large bin。
- 其他文章会详细介绍bin链。
arena
- 由于堆中存在许多的信息,例如各种bin链、chunk结构体等,这些东西需要管理,就是通过arena来管理的。
- arena分为main_arena和thread_arena。
- arena由struct malloc_state结构体实现。
三、一些相关概念
- ①malloc是应用层的概念,malloc申请的内存实际上是由glibc通过brk和mmap系统调用向内核申请的。
- ②第一次malloc申请的内存是一块非常大的内存,这块内存供程序后面多次调用malloc使用。只有当这块非常大的动态内存使用完之后,glibc才会再次通过brk/mmap系统调用向内核去要内存;否则,glibc不会再通过brk/mmap系统调用向内核要内存。
- ③虽然第一次malloc,操作系统分配给程序一块很大的内存,但这块内存只是虚拟内存,只有当用户使用到相应的内存时,系统才会真正分配物理页面给用户使用。
- ④第一次申请的大的动态内存称为“top chunk”。
- ⑤free函数返回的堆内存不是返回给“top chunk”,而是由glibc中称为bin的链接收和管理。
四、struct malloc_chunk
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size;
INTERNAL_SIZE_T mchunk_size;
struct malloc_chunk* fd;
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
- 每个成员都是8字节(64位系统中),4字节(32位系统中)。
mchunk_prev_size、mchunk_size
- mchunk_prev_size:只有当该chunk的物理相邻的前一地址chunk是空闲的话,该字段在本chunk中才有用,用来记录前一个chunk 的大小 (包括chunk头)。否则,该字段为0是没有用的;但是当前一个chunk申请的大小大于前一个chunk的大小时,那么该字段可以用来给前一个chunk使用(这就是chunk的空间复用,后面文章介绍)。
- mchunk_size:当前chunk的大小。
fd、bk
- 当前chunk处于分配状态时:从fd字段开始的是用户的数据。
- 当前chunk处于空闲时:
- 因为chunk处于空闲时,会被放到bin链中,所以fd和bk用于指向自己所在bin链中前后的空闲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 时挨个遍历。
五、结构体大小的对齐原则
- 原则:struct malloc_chunk结构体的大小必须是2 * SIZE_SZ的整数倍。如果申请的内存大小不是2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的2 * SIZE_SZ 的倍数(32位系统中,SIZE_SZ是4;64 位系统中,SIZE_SZ是8)。
- 当前chunk结构体的大小用mchunk_size成员的值表示。
- 我是小董,V公众点击"笔记白嫖"解锁更多【堆漏洞挖掘】资料内容。