前言

heap源码笔记为一个系列,以glibc2.27为主干,详细分析这个版本的源码,再以此分析与其他版本与其的不同之处。其中分析2.27版本的源码分析预计拆成基础知识,函数流程和设计,重要函数方法安全检查这四篇文章。

其中基础知识篇是零散的记录一些细节的知识比如:原子操作锁和多线程断言与异常终止一些特殊的系统调用等等不成体系但在源码中占很大作用的知识
函数流程与设计就是串联起整个源码的核心知识,以其他文章为辅助取出细节而得以更好的理解记忆核心的知识
重要函数方法就是类似unlinkmalloc_consolidate之类的很长很占篇幅的函数或宏
安全检查就是刷题的部分了。带着对源码的基本认知去了解各个攻击手法的过程中对安全检查加深理解。这样去思考的好处是在防御与进攻的对抗的过程中学习二级制安全工程的思维

暂时不分析多线程的部分

_libc_malloc

该函数是malloc之间调用的函数,对分配内存做一些总体的操作

__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

/* 如果存在__malloc_hook,则调用 hook 函数 */
   
  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));


#if USE_TCACHE
  
  size_t tbytes;
  checked_request2size (bytes, tbytes);
  size_t tc_idx = csize2tidx (tbytes);

  /* 初始化tcache */
  MAYBE_INIT_TCACHE ();

  DIAG_PUSH_NEEDS_COMMENT;
  if (tc_idx < mp_.tcache_bins && tcache && tcache->entries[tc_idx] != NULL)
    {
      return tcache_get (tc_idx);
    }
  DIAG_POP_NEEDS_COMMENT;
#endif
    /* 没有启用多线程的情况 */
  if (SINGLE_THREAD_P)
    {
      victim = _int_malloc (&main_arena, bytes);
      assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
	      &main_arena == arena_for_chunk (mem2chunk (victim)));
      return victim;
    }
    /* 多线程则再分配一个av */
  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);
  
   /* 如果成功获取分配区,但是分配内存失败,可能是 mmap 区域的内存耗尽等多种原因 */
  /* 这里重新进行了获取分配区和分配内存操作,确保内存分配成功 */

  if (!victim && ar_ptr != NULL)
    {
      LIBC_PROBE (memory_malloc_retry, 1, bytes);
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      victim = _int_malloc (ar_ptr, bytes);
    }
  return victim;
}

基本上是一些初始化的操作,并且调用了_int_malloc。配合注释即可理解

_int_malloc

大致流程

内存管理的理念一般是:本身管理内存块所占用的内存空间尽量小, 分配算法必须要尽量快。所以malloc采用的是内存池的管理方式(ptmalloc),Ptmalloc 采用边界标记法将内存划分成很多块,预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。

这里我们把free后的chunk叫做bin,malloc的chunk直接叫chunk

所以:malloc时,如果nb(规范后的申请的size)可以直接在对应fastbin,smallbin中找到就直接返回该bin(找到的同时话还会把同size的bin合并到tcache中),不能的话就会根据有无fastbin(有就进入)进入malloc_consolidate,该函数会把fastbin放进unsortedbin(这里是对内存整理的准备),之后会把unsortedbin的chunk再分配到smallbin或largebin中。该过程中还会将比nb大的smallbin切割返回。整理完后去largebin中寻找有没有等于nb的bin。最后会用binmap遍历所有的bin,找到>nb的bin(因为到此流程中就证明没有=nb的bin了)切割将其返回。如果以上流程都没有返回就切割topchunk。这里附上一张大佬的流程图 分配

看下面的代码最好自己手里有一份源码对照着看,不然可能会被我的修改弄昏头

_int_malloc (mstate av, size_t bytes) 
{
checked_request2size (bytes, nb);   // n:normalized,规范化后的bytes 

if (__glibc_unlikely (av == NULL));//暂时不知道怎么利用,就没有抄完 

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
  /* 如果fastbin不为空就将bin首出链并进行安全检查 */
  #if USE_TCACHE
  { 
    if (tcache && tc_idx < mp_.tcache_bins) 
    {
     /* 将fastbin剩余的chunk整理到tcache中 */
    }
  }
  #endif
	      void *p = chunk2mem (victim);  // 将head转化为data
	      alloc_perturb (p, bytes);
	      return p;
        // 最后再统一的返回p,提高兼容性 
}

if (in_smallbin_range (nb));
{
  /* 如果smallbin不为空就将bin末出链并进行安全检查 */

  #if USE_TCACHE
    /* 将smallbin中剩余的chunk整理到tcache中 */

  #endif
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
}
else
{
    idx = largebin_index (nb);
    if (atomic_load_relaxed (&av->have_fastchunks))  
        malloc_consolidate (av);  // 把fastbin合并到unsortedbin中 
}
  #if USE_TCACHE   // 当fastbin,smallbin都不满足时,才会进入这个if 
  {
  INTERNAL_SIZE_T tcache_nb = 0;
  size_t tc_idx = csize2tidx (nb);
  if (tcache && tc_idx < mp_.tcache_bins)    
    tcache_nb = nb;

  int return_cached = 0;   
  }

for(;;)
{
  while(/*unsortedbin不为空且循环次数不超过10000*/)
  {
    /* 安全检查 */
    if (in_smallbin_range (nb) && 
         bck == unsorted_chunks (av) &&  // unsorted bin中只有一个free chunk(bck=victim->bk)
        victim == av->last_remainder &&   // vimctim是last_remainder 
        (unsigned long) (size) > (unsigned long) (nb + MINSIZE))// 分割后的size仍然可以组成一个chunk 
        {
            /* 将该unsortedbin中bin末的chunk分割,size=nb的返回,剩下的放进unsortedbin */
        }

    /* 把bin尾出链 */

    if (in_smallbin_range (size))   
            {
              /* 调整fwd和bck指针为bins中对应idx的头结点和首结点 */
            }
    else
    {
      /* 调整fwd和bck指针为bins中对应idx的头结点和首结点 */
      if (fwd != bck)            //放入之前的largebin不为空
      {
        if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) //小于最小的
          {
            fwd = bck;  
            bck = bck->bk;       //再调整,使fwd指向bins的头节点,bck指向bins的末结点(最小的那个并且没有fd_nextsize指针的bin)
            victim->fd_nextsize = fwd->fd;
            victim->bk_nextsize = fwd->fd->bk_nextsize;
            fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
          }
          else // 大于或等于最小的
          {
            while ((unsigned long) size < chunksize_nomask (fwd))
            {
              fwd = fwd->fd_nextsize;  //fwd从首结点开始遍历(由大到小)fd_nextsize这条链
            }
            if ((unsigned long) size  == (unsigned long) chunksize_nomask (fwd))  //size == chunksize_nomask (fwd)
            {
              fwd = fwd->fd;    //到同层的另一个避免更改size指针
            }
            else    // size > chunksize_nomask (fwd)
            {
              victim->fd_nextsize = fwd;
              victim->bk_nextsize = fwd->bk_nextsize;
              fwd->bk_nextsize = victim;
              victim->bk_nextsize->fd_nextsize = victim;
            } 
            bck = fwd->bk;     //更新bck指针             
          }
                
    }
    else
    {
      victim->fd_nextsize = victim->bk_nextsize = victim; 
    }
    victim->bk = bck;  // 统一更改fd/bk指针,节省代码
    victim->fd = fwd;  
    fwd->bk = victim;   
    bck->fd = victim;
  }
  #if USE_TCACHE  //不懂目的,可能是限制tcache分配的数量?
      ++tcache_unsorted_count;
      if (return_cached
	  && mp_.tcache_unsorted_limit > 0
	  && tcache_unsorted_count > mp_.tcache_unsorted_limit)  
	{
	  return tcache_get (tc_idx);  
	}


 }
}

// -----------------整理完毕,开始再分配-----------------------

  #if USE_TCACHE    
   {   if (return_cached)
	      return tcache_get (tc_idx);
   }    

if (!in_smallbin_range (nb))
{
  /* 分配largebin(给个链接单独分析) */  
}

//初始化遍历bins所需的变量,此时要找一个最接近nb且大于nb的chunk
bin = bin_at (av, idx); 
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
for (;; )
{
  
  if(/* 找到合法且符合要求的chunk,记做victim */)
  {
    size = chunksize (victim);
    remainder_size = size - nb;
    unlink (av, victim, bck, fwd);
    if (remainder_size < MINSIZE)  //remainder不足以做chunk的话就一起返回给malloc
    {
      set_inuse_bit_at_offset (victim, size);
    /* 多线程操作 */
    }
    else
    {
      remainder = chunk_at_offset (victim, nb);
      bck = unsorted_chunks (av);
      fwd = bck->fd;
      if (__glibc_unlikely (fwd->bk != bck))
		    malloc_printerr ("malloc(): corrupted unsorted chunks 2");
      remainder->bk = bck;
      remainder->fd = fwd;
      bck->fd = remainder;
      fwd->bk = remainder;

      if (in_smallbin_range (nb))
        av->last_remainder = remainder;
      if (!in_smallbin_range (remainder_size))
      {
      remainder->fd_nextsize = NULL;
      remainder->bk_nextsize = NULL;
      }

      /* 设置victim和remainder的chunk header */

      check_malloced_chunk (av, victim, nb);
      void *p = chunk2mem (victim);
      alloc_perturb (p, bytes);
      return p;
    }
  }
else
 goto use_top;
}
   use_top:
   if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
   {
      /* 切割topchunk并返回 */
   }
   else if (atomic_load_relaxed (&av->have_fastchunks))
   {
    malloc_consolidate (av);  // 最后再整理一次 

    if (in_smallbin_range (nb)) //返回为遍历bit map前的idx
      idx = smallbin_index (nb);
    else
      idx = largebin_index (nb);   
   }
   else
   {
    /* 用syscall分配空间 */
   }     
}

我省略了细节和安全检查的部分。其中/* */中我用文字描述代理了代码描述,可能因为该代码很复杂,也可能是因为我省略的部分会导致理解该部分代码需要的信息不足(一定不是因为懒),真正的注释用//标识。

里面很多很绕的设计其实都是为了减小内存碎片化以及提高代码性能,理解了其背后设计理念可以更好的记忆代码

_int_free

理解了malloc后free就很好理解了,基本上重点都在堆块的合并


static void _int_free(mstate av, mchunkptr p, int have_lock) 
{

    const char *errstr = NULL;
   

    size = chunksize(p);

    if (__glibc_unlikely(size < MINSIZE || !aligned_OK (size))) 
    {
        goto errout;
    }

    check_inuse_chunk(av, p);

    if ((unsigned long) (size) <= (unsigned long) (get_max_fast ())) 
    {
        free_perturb(chunk2mem(p), size - 2 * SIZE_SZ);
        set_fastchunks(av);
        unsigned int idx = fastbin_index(size);
        fb = &fastbin(av, idx);
        mchunkptr old = *fb, old2;
        unsigned int old_idx = ~0u;
        do 
        {
            if (have_lock && old != NULL)
                old_idx = fastbin_index(chunksize(old));
            p->fd = old2 = old;
        } 
        while ((old = catomic_compare_and_exchange_val_rel(fb, p, old2)) != old2);
    }
    else if (!chunk_is_mmapped(p)) 
    {
        nextchunk = chunk_at_offset(p, size);
        nextsize = chunksize(nextchunk);
        free_perturb(chunk2mem(p), size - 2 * SIZE_SZ);
        if (!prev_inuse(p)) 
        {
            prevsize = p->prev_size;
            size += prevsize;
            p = chunk_at_offset(p, -((long ) prevsize));
            unlink(av, p, bck, fwd);
        }
        if (nextchunk != av->top) 
        {
                nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

                if (!nextinuse) 
                {
                    unlink(av, nextchunk, bck, fwd);
                    size += nextsize;
                } 
                else
                    clear_inuse_bit_at_offset(nextchunk, 0);
                bck = unsorted_chunks(av);
                fwd = bck->fd;
                if (__glibc_unlikely(fwd->bk != bck)) 
                {
                errstr = "free(): corrupted unsorted chunks";
                goto errout;
                }
                p->fd = fwd;
                p->bk = bck;
                if (!in_smallbin_range(size)) 
                {
                    p->fd_nextsize = NULL;
                    p->bk_nextsize = NULL;
                }
                bck->fd = p;
                fwd->bk = p;
                set_head(p, size | PREV_INUSE);
                set_foot(p, size);
                check_free_chunk(av, p);
        }
        else 
        {
                size += nextsize;
                set_head(p, size | PREV_INUSE);
                av->top = p;
                check_chunk(av, p);
        }
      if ((unsigned long) (size) >= FASTBIN_CONSOLIDATION_THRESHOLD)
      {
        if (have_fastchunks(av))
                malloc_consolidate(av);
        if (av == &main_arena) 
        {
    #ifndef MORECORE_CANNOT_TRIM
                if ((unsigned long) (chunksize(av->top))
                        >= (unsigned long) (mp_.trim_threshold))
                    systrim(mp_.top_pad, av);
    #endif
        }
        else 
        {
                heap_info *heap = heap_for_ptr(top(av));  //此线程为非主分配区就获得top chunk对应的非主分配区的heap_info指针,调用heap_trim尝试缩小该heap
                heap_trim(heap, mp_.top_pad);
        }
        
        if (!have_lock) 
        {
            assert(locked);
            (void) mutex_unlock(&av->mutex);
        }
    
      }
    }
    else 
    {
        munmap_chunk(p);
    }

}