Heap入门

heap

chunk结构

chunk是堆管理中内存分配的基本单元

linux的malloc具体实现在glibc中,chunk头部信息结构体如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct malloc_chunk {
  // 前一个chunk的大小,当前一个chunk释放时堆管理器可以通过这个字段访问并合并前一个空闲块,是内存相邻的
  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).  */
  // 当前chunk总大小
  INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */

  // 下一个空闲chunk,是链表相邻的
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  // 上一个空闲chunk
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  // 指向大小略大于当前chunk的下一个空闲chunk
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  // 指向大小略小于当前chunk的上一个空闲chunk
  struct malloc_chunk* bk_nextsize;
};
  • INTERNAL_SIZE_T本质上就是unsigned int
  • fdbk通常用于管理空闲chunk链表,这使得内存管理器可以快速找到和操作空闲内存块
  • fd_nextsizebk_nextsize字段用于优化大块内存的管理,通过按大小排序的链表提高查找效率

另外在glibc的堆实现中mchunk_size不仅包含chunk的大小,还包含三个标志位

三个标志位用来表示存储标志信息,为什么会有这三个标志位?

以16字节对齐为例,在内存对齐的背景下chunk的最低四位为0000从右往左数第一、二、三位就是三个标志位即AMP

  • PREV_INUSE(P)

    标志当前chunk的前一个chunk是否在使用中,1为分配,0为空闲

  • IS_MMAPPED(M)

    标志当前chunk是否通过mmap分配

  • NON_MAIN_ARENA(A)

    标志当前chunk是否位于主堆区域之外,即是否在main_arena

获取实际chunk大小只需要屏蔽最低三位

1
2
3
4
5
6
7
#define PREV_INUSE 0x1     // P
#define IS_MMAPPED 0x2     // M
#define NON_MAIN_ARENA 0x4 // A
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)

// 获取实际大小
size_t actual_size = mchunk_size & ~SIZE_BITS;

没有被free的chunk

free&bins

bins是一种用于管理和组织chunk的数据结构,当chunk被free后会放入合适的bins中

 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
struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* 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;

  /* Fastbins */    // typedef struct malloc_chunk* mchunkptr;
  mfastbinptr fastbinsY[NFASTBINS];  // 指针数组,指向不同大小的fastbins链表

  /* 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];  // 指向普通bins

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;  // 指向下一个malloc_state

  /* 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;
};

bins的分类有四种,分别是fastbinsunstored binsmall binslarge bins

  • fastbinsY[NFASTBINS] 单向链表

    这个数组每一个元素都是指向链表头的指针,每个指针指向的都是fastbins,fastbins用于管理小且固定大小的chunk

    1
    2
    3
    
    fastbinsY[0]: -> [16字节块1] -> [16字节块2] -> ...
    fastbinsY[1]: -> [32字节块1] -> [32字节块2] -> ...
    ...
    
  • bins[NBINS * 2 - 2] 双向链表

    存储1个unstored bin头指针、62个small bins头指针、63个large bins头指针

只有large bins被free后会有fd_nextsizebk_nextsize,其余的bin只有fdbk,这是因为large bins有排序的需求

malloc和free的实现原理解析 - JackTang’s Blog (jacktang816.github.io)

内存分配

现在的linux系统的堆管理器通常是ptmalloc,这是基于早期的dlmalloc改进而来,在此基础上增加了多线程支持

堆分配内存会涉及到多个系统调用

  • brk/sbrk

    主线程的arenamain_arena

    堆初始化的时候会划分一片堆空间,该空间称为main_arena,在申请较小的内存空间时会优先在main_arena上分配空间。当main_arena空间不足时会使用系统调用brksbrk来扩展空间

    • brk可控制最高地址的指针向高地址/低地址移动,实现堆内存空间变化

    • sbrk直接改变堆空间大小

  • mmap/mumap

    在申请较大的内存空间时通过mmap重新分配一个大的内存空间,通过mumap直接归还给操作系统而不是堆管理器(bins)

【CTF】GLibc堆利用入门-机制介绍_哔哩哔哩_ bilibili

使用 Hugo 构建
主题 StackJimmy 设计