Ptmalloc2中,两个线程同时调用malloc时,每一个线程内存都会被立即分配。因为每个线程维护单独的heap segment和freelist数据结构。
Per thread arena:为每个线程维护单独的heap和freelist数据结构的行为。
l arena的数目基于CPU核的数目
For 32 bit systems:
Number of arena = 2 * number of cores.
For 64 bit systems:
Number of arena = 8 * number of cores.
l 多个arena的共享
Main线程和先执行malloc的线程会使用单独的arena,直到到达数目限制;
到达数目限制后,新的线程调用malloc时,会重用已有的arena;
遍历可用的arenas,尝试锁定该arena;
如果超过锁定,则返回该arena;
后续调用malloc时,对于共享的arena,如果可用(free)则被使用,否则线程被阻塞直到共享arena变得可用(freed);
l 多个heap
主要有下面3种数据结构
heap_info – Heap Header –一个线程arena可以有多个heaps。每个heap都有自己的header。开始时线程只有一个heap,如果没有空间则mmap到线程arean,生成新的heap(不连续区域)。
malloc_state – Arena Header – 一个线程arena可以有多个heaps,但所有这些heaps只有一个arena header。包含关于bins, top chunk, last remainder chunk等信息。
malloc_chunk – Chunk Header – 一个heap基于用户请求被划分为多个chunks;没有chunk都有自己的chunk header。
注意:
l Main arean有没有多个heap,因此没有heap_info结构;
当main arean没有空间时,会使用sbrk进行扩展,直到遇到内存映射段;
(PS:后面的测试表明Ubuntu glibc 2.23中,Main arean也有多个heap,启动时就有两个heap。一个是sbrk生成的,一个是mmap生成的)
l 不像thread arean,main arean的arean header不是heap段的一部分,而是静态变量
static struct malloc_state main_arena = {.mutex = _LIBC_LOCK_INITIALIZER,
.next = &main_arena,
.attached_threads = 1};
Pictorial view of main arena and thread arena (single heap segment):
Pictorial view of thread arena (multiple heap segment’s):
Ubuntu 16.04 64上测试
millionsky@ubuntu-16:~/tmp$ ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23
Mt.c
millionsky@ubuntu-16:~/tmp$ gcc -m32 mt.c -lpthread -o mt |
malloc之前,默认的heap为132KB(0x21000):
millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps 08048000-08049000 r-xp 00000000 08:01 11143836 /home/millionsky/tmp/mt 08049000-0804a000 r--p 00000000 08:01 11143836 /home/millionsky/tmp/mt 0804a000-0804b000 rw-p 00001000 08:01 11143836 /home/millionsky/tmp/mt 09a4d000-09a6e000 rw-p 00000000 00:00 0 [heap] f75da000-f75db000 rw-p 00000000 00:00 0 |
分配大于128KB时,直接使用mmap进行分配
这里请求分配133KB(0x21400),已存在4KB,实际分配后合并为140KB(0x23000)
释放后恢复原样
millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps 08048000-08049000 r-xp 00000000 08:01 11143836 /home/millionsky/tmp/mt 08049000-0804a000 r--p 00000000 08:01 11143836 /home/millionsky/tmp/mt 0804a000-0804b000 rw-p 00001000 08:01 11143836 /home/millionsky/tmp/mt 09a4d000-09a6e000 rw-p 00000000 00:00 0 [heap] f75b8000-f75db000 rw-p 00000000 00:00 0 |
进入线程函数后
Main_arena增加到8196KB
Thread_arena初始为4KB,当前不可用
millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps 08048000-08049000 r-xp 00000000 08:01 11143836 /home/millionsky/tmp/mt 08049000-0804a000 r--p 00000000 08:01 11143836 /home/millionsky/tmp/mt 0804a000-0804b000 rw-p 00001000 08:01 11143836 /home/millionsky/tmp/mt 09a4d000-09a6e000 rw-p 00000000 00:00 0 [heap] f6dd9000-f6dda000 ---p 00000000 00:00 0 thread_arena f6dda000-f75db000 rw-p 00000000 00:00 0 main_arena |
线程函数中调用malloc后
[AFTER malloc] Thread 1, addr=0xf6c00470
mmap重新分配了1024KB的空间,但只有前132KB可用
释放后内存布局没有变化
millionsky@ubuntu-16:~/tmp$ cat /proc/9982/maps 08048000-08049000 r-xp 00000000 08:01 11143836 /home/millionsky/tmp/mt 08049000-0804a000 r--p 00000000 08:01 11143836 /home/millionsky/tmp/mt 0804a000-0804b000 rw-p 00001000 08:01 11143836 /home/millionsky/tmp/mt 09a4d000-09a6e000 rw-p 00000000 00:00 0 [heap] f6c00000-f6c21000 rw-p 00000000 00:00 0 thread_arena f6c21000-f6d00000 ---p 00000000 00:00 0 不可用 f6dd9000-f6dda000 ---p 00000000 00:00 0 thread_arena f6dda000-f75db000 rw-p 00000000 00:00 0 main_arena |
1. 虽然程序可能只是向操作系统申请很小的内存,但是为了方便,操作系统会把很大的内存分配给程序。这样的话,就避免了多次内核态与用户态的切换,提高了程序的效率。
2. 连续的内堆存区域称为 arena,主线程申请的内存为 main_arena。
3. 当 arena 空间不足时,它可以通过增加brk的方式来增加堆的空间。类似地,arena 也可以通过减小 brk 来缩小自己的空间。
4. 释放内存时,其对应的 arena 并没有进行回收,而是交由glibc来进行管理。
5. 当用户请求的内存大于128KB时,并且没有任何arena有足够的空间时,那么系统就会执行mmap函数来分配相应的内存空间。这与这个请求来自于主线程还是从线程无关。
1. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_overview/。
2. https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/。