学习链接
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
http://www.cnblogs.com/alisecurity/p/5486458.html
http://www.cnblogs.com/alisecurity/p/5520847.html
首先还是看看linux内存的布局
实验演示
代码:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> void* threadFunc(void* arg){ printf("Before malloc in thread 1\n"); getchar(); char* addr = (char*) malloc(1000); printf("After malloc and before free in thread 1\n"); getchar(); free(addr); printf("After free in thread 1\n"); getchar(); } int main(int argc, char const *argv[]){ pthread_t t1; void* s; int ret; char* addr; printf("Welcome to per thread arena example::%d\n", getpid()); printf("Before malloc in main thead\n"); getchar(); addr = (char*) malloc(1000); printf("After malloc and before free in main thread\n"); getchar(); ret = pthread_create(&t1, NULL, threadFunc, NULL); if (ret){ printf("Thread creation error\n"); return -1; } ret = pthread_join(t1, &s); if (ret){ printf("Thread join error\n"); return -1; } return 0; }
我用的是kali1.1 32位系统
编译一开始报
pthread1.c:5:22: fatal error: sys/types.h: No such file or directory
安装一下
sudo apt-get install build-essential flex libelf-dev libc6-dev-amd64 binutils-dev libdwarf-dev
继续
/tmp/ccpy3gZb.o: In function `main': pthread1.c:(.text+0xda): undefined reference to `pthread_create' pthread1.c:(.text+0x10d): undefined reference to `pthread_join'
解决方法
pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
最终:
gcc pthread1.c -o pthread1 -l pthread
开始运行
Before malloc in main thead
root@kali:~/learn/learnHeap# ./pthread1 Welcome to per thread arena example::22219 Before malloc in main thead
看看内存,是没有heap段的,栈段是有的,当然线程栈也是没有
root@kali:/# cat /proc/22219/maps 08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 b7551000-b7553000 rw-p 00000000 00:00 0 b7553000-b76f7000 r-xp 00000000 08:01 1311582 /lib/i386-linux-gnu/i686/cmov/libc-2.19.so b76f7000-b76f9000 r--p 001a4000 08:01 1311582 /lib/i386-linux-gnu/i686/cmov/libc-2.19.so b76f9000-b76fa000 rw-p 001a6000 08:01 1311582 /lib/i386-linux-gnu/i686/cmov/libc-2.19.so b76fa000-b76fd000 rw-p 00000000 00:00 0 b76fd000-b7715000 r-xp 00000000 08:01 1311574 /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so b7715000-b7716000 r--p 00017000 08:01 1311574 /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so b7716000-b7717000 rw-p 00018000 08:01 1311574 /lib/i386-linux-gnu/i686/cmov/libpthread-2.19.so b7717000-b7719000 rw-p 00000000 00:00 0 b7733000-b7737000 rw-p 00000000 00:00 0 b7737000-b7739000 r--p 00000000 00:00 0 [vvar] b7739000-b773b000 r-xp 00000000 00:00 0 [vdso] b773b000-b775a000 r-xp 00000000 08:01 1311723 /lib/i386-linux-gnu/ld-2.19.so b775a000-b775b000 r--p 0001f000 08:01 1311723 /lib/i386-linux-gnu/ld-2.19.so b775b000-b775c000 rw-p 00020000 08:01 1311723 /lib/i386-linux-gnu/ld-2.19.so bfba5000-bfbc6000 rw-p 00000000 00:00 0 [stack]
以免占用太多的篇幅,将后面不相关的删掉了
After malloc and before free in main thread
08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 09d74000-09d95000 rw-p 00000000 00:00 0 [heap]
可以看到出现了heap段,而且是132KB,我们程序只申请了1000(B)字节
那下次再malloc,直接从这里搞就行了
如果这132KB都用完了,就增大这个heap段的大小,调整program break(可看上面内存布局图片)的位置就行了
After free in main thread
可以看到内存是没有马上给回操作系统
而是由glibc 的malloc库函数加以管理。它会将释放的chunk添加到main arenas的bin(这是一种用于存储同类型free chunk的双链表数据结构),记录空闲空间的freelist数据结构称之为bins。之后当用户再次调用malloc申请堆空间的时候,glibc malloc会先尝试从bins中找到一个满足要求的chunk,如果没有才会向操作系统申请新的堆空间。
08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 09d74000-09d95000 rw-p 00000000 00:00 0 [heap]
Before malloc in thread 1
thread1的堆还没分配,但栈空间已经分配了
08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 09d74000-09d95000 rw-p 00000000 00:00 0 [heap] b6d11000-b6d12000 ---p 00000000 00:00 0 b6d12000-b7514000 rw-p 00000000 00:00 0 [stack:24056]
After malloc and before free in thread 1
root@kali:/# cat /proc/24050/maps 08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 09d74000-09d95000 rw-p 00000000 00:00 0 [heap] b6c00000-b6c21000 rw-p 00000000 00:00 0 b6c21000-b6d00000 ---p 00000000 00:00 0 b6d11000-b6d12000 ---p 00000000 00:00 0 b6d12000-b7514000 rw-p 00000000 00:00 0 [stack:24056]
可以看到,比没malloc前多了这两行
b6c00000-b6c21000 rw-p 00000000 00:00 0 b6c21000-b6d00000 ---p 00000000 00:00 0
同时从这个区域的起始地址可以看出,它并不是通过brk分配的,而是通过mmap分配
因为地址是b6c21000,地址是比较高的,而brk分配的内存的地址是比较低的,就在程序后面一点的地方
那么main函数的堆区是brk分配,线程的就mmap分配
总共的话是1MB,算一下就知道
但是实际根据内存属性有分成了两端,还是只有132KB可读写的
这里应该也是一次性分配多一点,那不可读不可写的给可能其他线程(如果有的话)malloc的时候用的?或者当前thread1的132KB不够用的时候用的吧
笔记:作者还说,如果一次性申请超过128字节( lets say malloc(132*1024)),而且没有足够的arena能够满足用户的需求时,系统使用的是mmap系统调用来申请内存,而不管是从main arena还是thread arena申请的
After free in thread 1
最后跟main的释放是一样的,释放只是给了thread arenas bin
root@kali:/# cat /proc/24050/maps 08048000-08049000 r-xp 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 08049000-0804a000 rw-p 00000000 08:01 1598728 /root/learn/learnHeap/pthread1 09d74000-09d95000 rw-p 00000000 00:00 0 [heap] b6c00000-b6c21000 rw-p 00000000 00:00 0 b6c21000-b6d00000 ---p 00000000 00:00 0 b6d11000-b6d12000 ---p 00000000 00:00 0 b6d12000-b7514000 rw-p 00000000 00:00 0 [stack:24056]
Arena
arena数量
在上面的例子中main thread和thread1有自己独立的arena,那么是不是无论有多少个线程,每个线程都有自己独立的arena呢?这样太浪费内存了。事实上,arena的个数是跟系统中处理器核心个数相关的
国内的翻译是给作者的公式+1了,不过根据下面的描述也对
For 32 bit systems: Number of arena = 2 * number of cores + 1. For 64 bit systems: Number of arena = 8 * number of cores + 1.
Multiple Arena
那假如我的程序有4个线程(main thread(主线程) 和 3 个 user thread),我运行在32位的单核系统上,那我这系统只能有2+1=3个arena ,那user thread就只能共享arena咯
那怎么共享的呢
首先,主线程首次malloc的话,直接给他arena,不需要其他条件
thread 1 和 thread 2也是第一次申请的话,也是创建一个新的arena给他,这时候各个线程和arena是一一对应的
假如thread 3也是第一次申请,算一下,arena已经达到上限,只能跟前面的共用咯,那前面3个用哪个呢?
首先循环可用的arena,并且再循环的时候尝试锁住它,如果成功就返回那个arena给用户(比如说是main arena),但如果失败就阻塞,知道有可用的arena为止
现在thread3第二次malloc,首先先尝试上一次拿到额共享的arena,有空闲的就直接用,否则就阻塞,直到共享的 arena有空闲的空间为止
Multiple Heaps
作者在‘glibc malloc’源码中看到有3种数据结构
heap_info
就像是heap Header,一个thread arena可以有多个heap,所以为了便于管理就都有一个heap header
那么在什么情况下一个thread arena会包含多个heaps呢?在当前heap不够用的时候,malloc会通过系统调用mmap申请新的堆空间,新的堆空间会被添加到当前thread arena中,便于管理。
typedef struct _heap_info { mstate ar_ptr; /* Arena for this heap. */ struct _heap_info *prev; /* Previous heap. */ size_t size; /* Current size in bytes. */ size_t mprotect_size; /* Size in bytes that has been mprotected PROT_READ|PROT_WRITE. */ /* Make sure the following data is properly aligned, particularly that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of MALLOC_ALIGNMENT. */ char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; } heap_info;
malloc_state
通俗来说就是arena Header
一个线程可以有多个堆,但是这个线程的所有这些堆,只有一个arena Header
这个Header包含了bins,top chunk,last remainder chunk…
struct malloc_state { /* Serialize access. */ mutex_t mutex; /* Flags (formerly in max_fast). */ int flags; /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */ mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins */ unsigned int binmap[BINMAPSIZE]; /* Linked list */ struct malloc_state *next; /* Linked list for free arenas. */ struct malloc_state *next_free; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; };
malloc_chunk
即chunk Header,一个heap被分成很多个chunk,一个chunk的大小是跟用户malloc的大小相关的,每个chunk都有自己的Header
struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };
作者笔记:
main arena没有多个heap,因此没有heap info 这个结构,但它需要更多空间就通过brk申请更多,直到memory mapping段
和thread arena不同, main arena的arena header不是brk的heap段的一部分,他是一个全局变量,在libc的data段中
复制一下main arena和thread arena只有单个堆的示意图
可以看到不同的是main arena的arena Header(malloc state)是在libc的data段
而且main arena没有heap info结构
thread arena则在堆的地址上
那么有多个heap段的Thead arena呢
thread arena只含有一个malloc_state(即arena header),却有两个heap_info(即heap header)。
可以看到malloc state和heap info形成一个循环单向链表
原来那个堆的top chunk变成了free chunk
后面那些其实做了笔记的,但由于意外,没保存,所以就这样吧
其实动态调试去理解更好