0%

libc-2.23-malloc,free,realloc

_int_malloc(mstate av, size_t bytes)

  • 根据bytes参数计算要申请的chunk大小nb
  • 判断av是否为空,不为空跳过这一步,进入之后的流程
    • 直接通过nb和av用sysmalloc调用分配
      • 分配成功,返回分配区对应指针
      • 分配失败,返回0

fastbin↓

  • 调用get_max_fast()获得global_max_fast变量的值判断nb是否在fastchunk范围内,不是则略过这一步
    • 通过位移忽略nb低位来计算fastbin中的index,然后获取要分配的bin链头
    • 判断链头是否为null,为null则跳出fastbin操作
    • 否则从单链表取下该victim chunk
      • 判断fastbin_index (chunksize (victim)) != idx,如果该victim的index与本链不对应则报错结束
      • 指针转换chunk2mem,然后返回该指针

smallbin↓

  • 判断nb是否<MIN_LARGE_SIZE(Macro,非变量)
    • 是:
    • 位移忽略nb低位来计算smallbin中的index,然后获取要分配的bin链头
    • 如果bin链头的bk指向的不是自身则赋值bk给victim并进行以下操作,否则跳过(这里可以看出smallbin是从链尾开始取chunk的)
      • 如果判断victim为0则说明该arena需要初始化,调用malloc_consolidate,之后跳出该smallbin操作
      • 不为0说明该bin链有chunk,获取victim->bk指针bck
        • bck->fd != victim,则报错结束
        • 这里说明成功分配,根据victim和nb设置相邻前向chunk的prev_inuse位为1,然后从双向链表取下victim chunk,设置size字段然后chunk2mem,返回
    • 不是:
    • 位移忽略nb低位来计算对应largebin中的index
    • 如果fastbin中有chunk(通过av的标识位判断),调用malloc_consolidate

recently freed or remaindered chunks↓

  • 大循环###########################################################################
    • 嵌套循环(由unsortedbin中是否有chunk和最大遍历chunk数决定次数)↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
      • 如果unsortedbin头bk指针不为自身则赋值victim(同样是从链尾取chunk),否则退出第二层循环
      • 获取victim chunk的bk指针bck
      • 判断victim的size字段是否满足2*size_t < victim->size <= system_mem,不满足则报错结束
      • 如果满足(in_smallbin_range)(bck == unsorted_chunks ,即unsortedbin中只有一个chunk)(vicitm==last_remainder,即上次被切割的chunk)(size>nb+MINSIZE,MINSIZE是能够分配的最小chunk)这四个条件则进行以下操作(这里只是为了从unsortedbin唯一chunk,而且还是last_remainder中切割出smallbin)
        • 设置新的remainder,并链入unsortedbin
        • 如果remainder不是largebin需要将fd_nextsize和bk_nextsize清零
        • 设置victim的size字段(prev_inuse默认为1)
        • 设置remainder的size字段和更新前向chunk的prev_size字段
        • chunk2mem,返回切割下来的vicitm
      • 从unsorted chunk中取下该victim
      • 如果该victim的size==nb进行以下操作否则略过(刚好碰到unsortedbin中相等大小的chunk)
        • 设置victim前向chunk的prev_inuse为1
        • 设置vicitm的size字段
        • chunk2mem,返回vicitm
      • 通过in_smallbin_range判断victim的size(这个操作是为了将不满足的chunk链入bin链,判断只是在small chunk和large chunk上做不同的工作而已)
        • 满足:
          • 计算对应smallbin中的index
        • 不满足:
          • 说明是largechunk
          • 找到largebin中对应的index和bin链中的位置
        • 在binmap设置对应的index为1,说明该bin链有chunk
        • 将该chunk通过获得的fwd和bck链入bin链
      • 嵌套循环结尾↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    • 通过in_smallbin_range判断nb,不是则进行以下操作,是则跳过(这里是去largebin中对应的index切割出chunk来,small chunk和largebin中对应bin链没有chunk的还得等到下面binmap去找)
      • 获取对应largebin中的bin链头作为victim
      • bin链中有chunk,并且victim的size大于等于nb则进行下一系列操作
        • 反向遍历chunk size链表,直到找到第一个大于等于所需chunk大小的chunk
        • 如果从 large bin 链表中选取的 chunk victim 不是链表中的最后一个 chunk,并且与 victim大小相同的chunk不止一个,那么意味着victim为chunk size链表中的节点,为了不调整chunksize 链表,需要避免将 chunk size 链表中的节点取出,所以取 victim->fd 节点对应的 chunk作为候选 chunk。由于 large bin 链表中的 chunk 也是按大小排序,同一大小的 chunk 有多个时,这些 chunk 必定排在一起,所以 victim->fd 节点对应的 chunk 的大小必定与 victim 的大小一样(这段我直接复制的华庭,因为涉及largebin的概念太多了)
        • 计算victim切割后的大小,并调用 unlink()宏函数将 victim 从 bin 链中取出 ※※
        • 判断切割后的大小是否小于MINSIZE(判断返回chunk之前要不要切一下)
          • 是,切割失败,不用切了,设置victim前向chunk的prev_inuse为1并设置victimsize字段
          • 否,切割成功,设置remainder
            • 检查unsortedbin链表头是否正常,不正常则报错结束
            • 重复将remainder链入unsortedbin中的操作(前面嵌套循环中打处)
        • chunk2mem,返回victim
    • 到这里便开始使用binmap分配(largebin和smallbin中对应index都没有chunk的)
    • 获取我们需要大小chunk对应bin的下一个bin的空闲chunk链表,并获取该bin对于binmap中的bit位的值 (开始从稍大的chunk中寻找)
    • 嵌套循环↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
      • 首先找到对应的binmap中对应的block(找不到则直接去use_top),然后找到block中的对应bit位和对应的bin链
      • 判断此时 victim 与 bin 链表头是否相同
        • 是:表示该 bin 中没有空闲 chunk, binmap 中的相应位设置不准确(接着找),将 binmap 的相应 bit 位清零, 获取当前 bin 下一个 bin,将 bit 移到下一个 bit位,回到前面循环↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
        • 否:当前 bin 中的最后一个 chunk 满足要求
          • 重复之前打※※处操作
      • 嵌套循环结尾↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    • use_top:
      • 前面的全部都没找到则直接使用topchunk,获取topchunk的size
      • 如果size+MINSIZE>nb,则切出victim(此时不更改last_remainder),chunk2mem,返回victim
      • 如果top_chunk都不满足,判断此时是否有fastchunk
        • :调用malloc_consolidate,并计算nb对应bin的index
        • 没有:重复之前调用sysmalloc的流程
    • ###########################################################################

Free就从封装函数开始吧

__libc_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */

void (*hook) (void *, const void *) = atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0)); //有hook就先调用hook,和其他函数一样
return;
}

if (mem == 0) /* free(0) has no effect ,0直接return */
return;

p = mem2chunk (mem);

if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);//如果是mmap分配的chunk就使用munmap,不调用_int_free,暂时不深究
return;
}
//一般的free流程
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}

_int_free (mstate av, mchunkptr p, int have_lock)

这里have_lock默认传入的是0

  • 先获取p的chunksize size,然后开始做检查
  • 检查1:p不能大于-size,p需要对齐,否则free(): invalid pointer报错结束
  • 检查2:size要大于MINSIZE,而且size要对齐,否则free(): invalid size报错结束

#下面的三个大块是if..else if..else…结构

  • 通过get_max_fast ()获取global_max_fast变量判断是否为fastchunk,是则进行以下操作(如果存在宏TRIM_FASTBINS,靠近topchunk的fastbin不会进入该流程)
    • 检查该chunk相邻的前向chunk的大小是否合法(是否满足2*size_t < size < system_men)
      • 不合法则free(): invalid next size (fast)报错结束
    • set_fastchunks(av),设置av中对应标识,代表此时有fastchunk了
    • 将bin链上已存在的chunk赋值到old,检查old是否和本chunk相等
      • 相等则double free or corruption (fasttop)报错结束
    • p->fd = old2 = old;加入bin链,并存下old2为后续表头操作
    • 接下来在有锁(have_lock=1)的条件下,保证表头指向的chunk所属的bin链与当前chunk所属的bin链相同
      • 不相同则invalid fastbin entry (free)报错结束
  • 这里,如果不是mmap分配的chunk则进行以下操作

    • 先加锁(free里面的锁操作好像很多)

    • 根据p和size计算nextchunk

    • 如果p是top_chunk则double free or corruption (top)报错结束

    • 如果nextchunk 的地址已经超过了 top chunk 的结束地址,超过了当前分配区的结束地址,double free or corruption (out)报错结束

    • 如果nextchunk的prev_inuse为0,又因为此时不是fastchunk。double free or corruption (!prev)报错结束

    • 获取nextchunk的nextsize,如果nextsize不满足2*size_t < nextsize < system_menfree(): invalid next size (normal)报错结束(这一步在前面fastbin中也做了)

    • 接着向后合并:

      • if (!prev_inuse(p)) {//通过prev_inuse判断如果相邻的后向chunk不在使用态
              prevsize = p->prev_size;//通过本chunk的prev_size段获取prevsize
              size += prevsize;
              p = chunk_at_offset(p, -((long) prevsize));//更新p到prevchunk
              unlink(av, p, bck, fwd);//对prevchunk进行unlink
            }//此处prev_size字段为0会怎么样?prev_size为负数会怎么样?修改偏移来实现任意unlink?
        <!--1-->
      • 检查unsortedbin中表头指针是否正常,不正常则free(): corrupted unsorted chunks报错结束

      • 如果size属于largebin,则将fd_nextsize,bk_nextsize置零

      • 将p加入unsortedbin头并设置size字段prev_inuse为1,并设置相邻前向chunk的prev_size为size

      • 直接链入topchunk,设置size字段prev_inuse为1

    • 如果当前分配区为主分配区,并且 top chunk 的大小大于 heap 的收缩阈值,调用 systrim()函数收缩 heap,不是主分配区的话,调用 heap_trim()函数收缩非主分配区的 sub_heap

  • 这里说明是mmap分配的区域,调用munmap

_int_realloc之前先做检查,如果传入的指针为NULL,就直接调用_int_malloc

如果传入的chunk不满足(uintptr_t) oldp > (uintptr_t) -oldsize,且也不是16bit对齐,报错结束

_int_realloc(mstate av, mchunkptr oldp, INTERNAL_SIZE_T oldsize, INTERNAL_SIZE_T nb)

检查oldp的size字段是否满足2*size_t < oldp.size < system_mem,不满足则报错结束

oldchunk不能是mmapped,否则报错结束

通过oldp和oldsize计算next和next size

检查next的size字段是否满足2*size_t < oldp.size < system_mem,不满足则报错结束

(下面开始if..else..流程)

  • oldsize>=nb?
    • 则准备从原chunk切割
    • newp=oldp,newsize=oldsize
  • :(newsize=oldsize+nextsize)
    • 如果前向chunk是topchunk且oldsize+topsize>nb+MINSIZE
      • 直接从topchunk切割出一部分补上去,设置新tophead,chunk2mem(oldp),完成退出
    • 如果前向chunk不是topchunk且未使用,并满足oldsize+nextsize>nb+MINSIZE
      • newp=oldp
      • unlink前向chunk
    • 前面两种情况都不是,则进行以下操作
      • 调用_int_malloc(av, nb - MALLOC_ALIGN_MASK),分配内存,这里nb - MALLOC_ALIGN_MASK是因为在_int_malloc里面还会再计算一遍nb。然后计算newp和newsize
      • newp == next?
        • 直接设置newp=oldp,不用复制内容了,扩充就好
        • 拷贝内容到新chunk
        • _int_free(av,oldp,1)

检查newsize是否>=nb,不满足则报错结束

计算remainder_size = newsize - nb

  • remainder_size<MINSIZE?
    • 直接设置头部,不用分割了
    • 分割出remainder,并设置其prev_inuse为1,接着调用_int_free(av,remainder,1)

返回chunk2mem,完成退出

unlink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
unlink(AV, P, BK, FD)//P是指向本chunk的指针
{
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");
//检查本chunk的size和next chunk的prev_size段是否相等,排除了fast chunk

FD = P->fd;//P+0x10
BK = P->bk;//P+0x18 FD和BK分别指向forward chunk和back chunk

if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) //pass check
malloc_printerr ("corrupted double-linked list");
//检查前chunk的bk和后chunk的fd是否与P相等
else
{
FD->bk = BK;//0x6020b8+0x18=0x6020d0(small_ptr)=0x6020c0(big_ptr)
BK->fd = FD;//链表的卸下操作0x6020c0+0x10=0x6020d0(small_ptr)=0x6020b8(...)

if (!in_smallbin_range (chunksize_nomask (P))&&
__builtin_expect (P->fd_nextsize != NULL, 0))//当链表为large bin且fd_nextsize不为空
{
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr ("corrupted double-linked list (not small)");
//检查前chunk的bk_nextsize和后chunk的fd_nextsize是否与P相等

if (FD->fd_nextsize == NULL)
{
if (P->fd_nextsize == P) FD->fd_nextsize = FD->bk_nextsize = FD;
else
{
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
}
else
{
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}