堆入门之glibc分析(一)
0x00
我们都知道glibc是linux的底层api,想要理解堆那么必须对glibc有所理解,本文从glibc的malloc函数来展开分析,帮助理解动态内存是如何被分配的。
0x10 malloc
-
函数执行流程 glibc_2.27
__libc_malloc
void* __libc_malloc(size_t bytes) //bytes:用户申请分配的空间 __malloc_hook //全局变量 //如果需要自定义堆分配函数, 在__libc_malloc中调用以下函数: void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0)); //如果我们没有自定义堆分配函数,默认ptmalloc来完成 void *weak_variable (*__malloc_hook) (size_t __size, const void *) = malloc_hook_ini; //malloc_hook 被赋值为malloc_hook_ini static void *_int_malloc (mstate av, size_t bytes) //最后调用_int_malloc
__malloc_hook_inin
static void * malloc_hook_ini (size_t sz, const void *caller) { __malloc_hook = NULL; //被置null,利用点:修改__malloc_hook ptmalloc_init (); //函数中 全局变量__malloc_initialize // >0 初始化完成 =0 正在初始化 <0 未初始化 return __libc_malloc (sz); }
用户申请空间时提交的单位是bytes ptmalloc分配是以chunk为单位,bytes计算成chunk的空间大小由checked_request2size完成
checked_request2size (bytes, nb); nb为计算的chunk
_int_malloc 结构体
victim = _int_malloc (&main_arena, bytes); static struct malloc_state main_arena = { .mutex = _LIBC_LOCK_INITIALIZER, .next = &main_arena, .attached_threads = 1 }; /* 分析malloc_state 结构体 */ struct malloc_state { /* Serialize access. */ __libc_lock_define (, mutex); //0x4 /* Flags (formerly in max_fast). */ int flags; //0x8 /* Set if the fastbin chunks contain recently inserted free blocks. */ /* Note this is a bool but not all targets support atomics on booleans. */ int have_fastchunks; //0xc /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; //mfastbinptr -->point 8bytes fasbin的bin链管理头 NFASTBINS = 10 //0x10 /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; //0x60 +96 /* 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. Access to this field is serialized by free_list_lock in arena.c. */ struct malloc_state *next_free; /* Number of threads attached to this arena. 0 if the arena is on the free list. Access to this field is serialized by free_list_lock in arena.c. */ INTERNAL_SIZE_T attached_threads; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; }; /* malloc_chunk 结构体 */ struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). 8bytes */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. 8bytes*/ struct malloc_chunk* fd; /* double links -- used only if free. 8bytes*/ //指向前一个物理相邻的chunk struct malloc_chunk* bk; /*8bytes*/ /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ //指向前一个largebin struct malloc_chunk* bk_nextsize; };
分配堆块是基于main_arena来寻址的,首先查找fastbin,其次再找bins
64位chunk的size必须是16字节对齐 2×SIZE_T 32位chunk的size必须是8字节对齐 2×SIZE_T
_int_malloc 实现——bins结构
fastbin
do { victim = pp; if (victim == NULL) break; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) //get_max_fast判断nb是否属于fastbin { idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); //fastbin 的bin头 mchunkptr pp; victim = *fb; if (victim != NULL) { if (SINGLE_THREAD_P) *fb = victim->fd; else REMOVE_FB (fb, pp, victim); if (__glibc_likely (victim != NULL)) { size_t victim_idx = fastbin_index (chunksize (victim)); if (__builtin_expect (victim_idx != idx, 0)) malloc_printerr ("malloc(): memory corruption (fast)"); check_remalloced_chunk (av, victim, nb); #if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks. */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL) { if (SINGLE_THREAD_P) *fb = tc_victim->fd; else { REMOVE_FB (fb, pp, tc_victim); if (__glibc_unlikely (tc_victim == NULL)) break; } tcache_put (tc_victim, tc_idx); } } #endif void *p = chunk2mem (victim); //chunk2mem返回chunk中数据区的地址 alloc_perturb (p, bytes); return p; } } }
- 堆中fastbin的地址范围0x20-0x80
- fastbin的prev_size位为0,prev_inuse为1
- chunk为空说明fastbin中没有刚好匹配nb大小的空闲chunk
- 精准匹配
- 单链表结构
- set_max_fast(DEFAULT_MXFAST);在malloc_init_state会调用set_max_fast 设置fastbin chunk的最大值
- LIFO 从main_arena开始遍历
- 当chunk被释放到unsorted bin ,当前chunk内就有libc上的地址lib.address=libc_on-libc.symbols[“main_arena”]-88
smallbins
if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) //判空 为空则为没有合适的空闲chunk { if (victim == 0) /* initialization check */ malloc_consolidate (av); //整理 else { bck = victim->bk; //获得链表尾的chunk if (__glibc_unlikely (bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } }
1.small bin 一共有62个bin链,每个链表中的大小都一直 size = 2 * SIZE *index (0x20 --0x400)64位 2.FIFO 3.inuse 如果前一个堆块释放置为0 4. 2-63为small bin 每个small bin中的size 都相同,相邻的两个链中长度相差2个机器字节
**malloc_consolidate **
/* If this is a large request, consolidate fastbins before continuing. While it might look excessive to kill all fastbins before even seeing if there is space available, this avoids fragmentation problems normally associated with fastbins. Also, in practice, programs tend to have runs of either small or large requests, but less often mixtures, so consolidation is not invoked all that often in most programs. And the programs that it is called frequently in otherwise tend to fragment. */ else { idx = largebin_index (nb); if (have_fastchunks (av)) malloc_consolidate (av); }
- malloc_consolidate 会整理fastbin, 将能合并的fastbin合并,一起放入到unsorted bin
- 在unsorted bin malloc的最后,会将unsorted bin上的堆块进行整理,并放入到对应的bin中(防止碎片化)
unsorted bin
- unsorted bin 没有排序
整理知识点
1. 当不能够print()的时候 可以使用 0xdeadbeef ,gdb中使用search -4 0xdeadbeef 来查看 2. 当top chunk 大小不能满足当前堆块的大小了,会调用sbrk创建top chunk,之前的不够的块被分配到unsorted bin。 HOUSE OF ORANGE 3.当fastbin中有chunk可以满足需求,再次malloc的时候不会访问unsorted bin 4.有任意一个块,被放入了除fastbin的一个bin钟,其中就含有libc上的地址
- 堆中fastbin的地址范围0x20-0x80