堆的概述

什么是堆

堆用来在程序运行时动态的分配内存,对其实就是虚拟空间里从地址向高地址增长的连续的线性区域。

堆的基本操作

  • void *malloc(unsigned int size):作用是在内存的动态存储区中分配一个长度为size的连续空间。此函数的返回值是分配区域的起始地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
  • void free(void *ptr):释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

堆操作背后的系统调用

  • brk():将数据段(.data)的最高地址指针_edata往高地址推。(从堆头开始,参数为地址)
  • mmap():在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。(分配大于128k)
  • sbrk():将地址指针往高地址推。(从当前指针位置开始,参数为指针增量)
  • mummap():删除地址空间。

堆的相关数据结构

微观结构

malloc_chuck

申请的内存chunk在ptmalloc内部用malloc_chunk结构体表示。

malloc_chunk的一些字段

  • prev_size:如果该 chunk 的物理相邻的前一地址chunk(两个指针的地址差值为前一chunk大小)是空闲的话,那该字段记录的是前一个 chunk 的大小(包括 chunk 头)。否则,该字段可以用来存储物理相邻的前一个chunk 的数据。这里的前一 chunk 指的是较低地址的 chunk。
  • size:该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。 该字段的低三个比特位对 chunk 的大小没有影响,它们从高到低分别表示:
    • NON_MAIN_ARENA:记录当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
    • IS_MAPPED:记录当前 chunk 是否是由 mmap 分配的。
    • PREV_INUSE:记录前一个 chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的P位都会被设置为1,以便于防止访问前面的非法内存。当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲chunk之间的合并。
  • fd,bk:chunk 处于分配状态时,从 fd 字段开始是用户的数据。chunk 空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下:
    • fd:指向下一个(非物理相邻)空闲的 chunk。
    • bk:指向上一个(非物理相邻)空闲的 chunk。
    • 通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理。
  • fd_nextsize, bk_nextsize:也是只有 chunk 空闲的时候才使用,不过其用于较大的 chunk(large chunk)。
    • fd_nextsize:指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。(指向比它大的空闲块)
    • bk_nextsize:指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。(指向比它小的空闲块)
    • 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。

chunk相关宏

  • chunk与mem指针头部的转换

    • define chunk2mem(p) ((void *) ((char *) (p) + 2 * SIZE_SZ))
    • define mem2chunk(mem) ((mchunkptr)((char *) (mem) -2 * SIZE_SZ))
  • 最小的chunk大小
    • define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))(offsetof 函数计算出 fd_nextsize 在 malloc_chunk 中的偏移,说明最小的 chunk 至少要包含 bk 指针。)
  • define MINSIZE (unsigned long) (((MIN_CHUNK_SIZE + MALLOC_ALIGN_MASK) &~MALLOC_ALIGN_MASK))(满足SIZE_SZ的最小上界)
  • 检查分配给用户的内存是否对齐
    • define aligned_OK(m) (((unsigned long) (m) & MALLOC_ALIGN_MASK) == 0)
    • define misaligned_chunk(p) ((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem(p)) & MALLOC_ALIGN_MASK)
  • 请求字节数判断
    • define REQUEST_OUT_OF_RANGE(req) ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE))
  • 将用户请求内存大小转为实际分配内存大小
    • define request2size(req) (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? MINSIZE : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
    • define checked_request2size(req, sz) if (REQUEST_OUT_OF_RANGE(req)) { __set_errno(ENOMEM); return 0;} (sz) = request2size(req);
  • 标记位相关
    • define PREV_INUSE 0x1
    • define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)
    • size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap()
    • define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
    • define NON_MAIN_ARENA 0x4
    • define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)+
  • 获取chunk size
    • define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS))
    • define chunksize_nomask(p) ((p)->mchunk_size)
  • 获取下一个物理相邻的chunk
    • define next_chunk(p) ((mchunkptr)(((char *) (p)) + chunksize(p)))
  • 获取前一个chunk的信息
    • define prev_size(p) ((p)->mchunk_prev_size)
    • define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))
  • 当前chunk使用状态相关操作
    • define inuse(p)((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE)
    • define set_inuse(p)((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size |= PREV_INUSE
    • define clear_inuse(p)((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size &= ~(PREV_INUSE)
  • 设置chunk的size字段
    • define set_head_size(p, s)((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))
    • define set_head(p, s) ((p)->mchunk_size = (s))
    • define set_foot(p, s) (((mchunkptr)((char *) (p) + (s)))->mchunk_prev_size = (s))
  • 获取指定偏移的chunk
    • define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))
  • 指定偏移处chunk使用状态相关操作
    • define inuse_bit_at_offset(p, s)(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)
    • define set_inuse_bit_at_offset(p, s)(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)
    • define clear_inuse_bit_at_offset(p, s)(((mchunkptr)(((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))

bin

根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为4类:fast bins,small bins,large bins,unsorted bin

数组中的bin

  • unsorted bin:这里面的chunk没有进行排序,存储的chunk比较杂。
  • small bin:索引从 2 到 63 的 bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为2个机器字长,即32位相差8字节,64位相差16字节。
  • large bins:small bins后面的bin,large bins中的每一个 bin 都包含一定范围内的chunk,其中的chunk按fd指针的顺序从大到小排列。相同大小的chunk同样按照最近使用顺序排列。
  • 上述这些bin的排布都会遵循一个原则:任意两个物理相邻的空闲chunk不能在一起。

fastbin

unsorted bin的来源

  • 当一个较大的chunk被分割成两半后,如果剩下的部分大于MINSIZE,就会被放到unsorted bin中。
  • 释放一个不属于fast bin的chunk,并且该chunk不和top chunk紧邻时,该chunk会被首先放到unsorted bin中。

last remainder

在用户使用malloc请求分配内存时,ptmalloc2找到的chunk可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为last remainder chunk,unsort bin也会存这一块。top chunk分割剩下的部分不会作为last remainer。

宏观结构

arena

我们知道一个线程申请的1个/多个堆包含很多的信息:二进制位信息,多个malloc_chunk信息等这些堆需要东西来进行管理,那么Arena就是来管理线程中的这些堆的。

heap_info

程序刚开始执行时,每个线程是没有heap区域的。当其申请内存时,就需要一个结构来记录对应的信息,而heap_info的作用就是这个。而且当该heap的资源被使用完后,就必须得再次申请内存了。此外,一般申请的heap是不连续的,因此需要记录不同heap之间的链接结构。

该数据结构是专门为从Memory Mapping Segment处申请的内存准备的,即为非主线程准备的。

主线程可以通过sbrk()函数扩展program break location获得(直到触及Memory Mapping Segment),只有一个heap,没有heap_info数据结构。

typedef struct _heap_info
{
mstate ar_ptr; /* 堆对应的 arena 的地址 */
struct _heap_info *prev; /* 由于一个线程申请一个堆之后,可能会使用完,之后就必须得再次申请。因此,一个可能会有多个堆。prev即记录了上一个 heap_info 的地址。这里可以看到每个堆的 heap_info 是通过单向链表进行链接的 */
size_t size; /* size 表示当前堆的大小 */
size_t mprotect_size; /* 最后一部分确保对齐 */
/* 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当前申请的内存的具体状态,比如说是否有空闲chunk,有什么大小的空闲chunk等等。无论是thread arena还是main arena,它们都只有一个malloc state结构。由于thread的arena可能有多个,malloc state结构会在最新申请的arena中。注意,main arena的malloc_state并不是 heap segment的一部分,而是一个全局变量,存储在libc.so的数据段。
struct malloc_state {
/* 该变量用于控制程序串行访问同一个分配区,当一个线程获取了分配区之后,其它线程要想访问该分配区,就必须等待该线程分配完成候才能够使用。 */
__libc_lock_define(, mutex); /* flags记录了分配区的一些标志,比如 bit0 记录了分配区是否有 fast bin chunk ,bit1 标识分配区是否能返回连续的虚拟地址空间。 */
int flags; /* 存放每个 fast chunk 链表头部的指针 */
mfastbinptr fastbinsY[ NFASTBINS ]; /* 指向分配区的 top chunk */
mchunkptr top; /* 最新的 chunk 分割之后剩下的那部分 */
mchunkptr last_remainder; /* 用于存储 unstored bin,small bins 和 large bins 的 chunk 链表。 */
mchunkptr bins[ NBINS * 2 - 2 ]; /* ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chun..*/
unsigned int binmap[ BINMAPSIZE ]; /* Linked list, points to the next arena */
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_consolidate()

函数实现步骤

1、若 get_max_fast() 返回 0,则进行堆的初始化工作,然后进入第 7 步。

2、从 fastbin 中获取一个空闲 chunk。

3、尝试向后合并。

4、若向前相邻 top_chunk,则直接合并到 top_chunk,然后进入第 6 步。

5、否则尝试向前合并后,插入到 unsorted_bin 中。

6、获取下一个空闲 chunk,回到第 2 步,直到所有 fastbin 清空后进入第 7 步。

7、退出函数。

创建堆

unlink

unlink 用来将一个双向链表(只存储空闲的 chunk)中的一个元素取出来,可能在以下地方使用
  • malloc

    • 从恰好大小合适的 large bin 中获取 chunk
    • 从比请求的 chunk 所在的 bin 大的 bin 中取 chunk
  • Free
    • 后向合并,合并物理相邻低地址空闲 chunk
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)
  • malloc_consolidate
    • 后向合并,合并物理相邻低地址空闲 chunk
    • 前向合并,合并物理相邻高地址空闲 chunk(除了 top chunk)
  • realloc
    • 前向扩展,合并物理相邻高地址空闲 chunk(除了top chunk)

      在unlink后,拖链的p的fd跟bk的指针都没有变化,我们可以利用这个泄露地址。
  • libc 地址
    • P 位于双向链表头部,bk 泄漏
    • P 位于双向链表尾部,fd 泄漏
    • 双向链表只包含一个空闲 chunk 时,P 位于双向链表中,fd 和 bk 均可以泄漏
  • 泄漏堆地址,双向链表包含多个空闲 chunk
    • P 位于双向链表头部,fd 泄漏
    • P 位于双向链表中,fd 和 bk 均可以泄漏
    • P 位于双向链表尾部,bk 泄漏

申请内存块

_libc_malloc

函数实现步骤

1、该函数会首先检查是否有内存分配函数的钩子函数(__malloc_hook)

2、接着会寻找一个 arena 来试图分配内存

3、然后调用 _int_malloc 函数去申请对应的内存

4、如果分配失败的话,ptmalloc 会尝试再去寻找一个可用的 arena,并分配内存

5、如果申请到了 arena,那么在退出之前还得解锁。

6、判断目前的状态是否满足以下条件

+ 要么没有申请到内存

+ 要么是 mmap 的内存

+ 要么申请到的内存必须在其所分配的arena中

7、最后返回内存

_int_malloc

1、它根据用户申请的内存块大小以及相应大小 chunk 通常使用的频度(fastbin chunk, small chunk, large chunk),依次实现了不同的分配方法

2、它由小到大依次检查不同的 bin 中是否有相应的空闲块可以满足用户请求的内存

3、当所有的空闲 chunk 都无法满足时,它会考虑 top chunk

4、当 top chunk 也无法满足时,堆分配器才会进行内存块申请

chunk不同范围申请实现

  • fastbin

    • 得到对应的fastbin的下标
    • 得到对应的fastbin的头指针
    • 利用fd遍历对应的bin内是否有空闲的chunk块
    • 检查取到的 chunk 大小是否与相应的 fastbin 索引一致
    • 根据取得的 victim ,利用 chunksize 计算其大小
    • 利用fastbin_index 计算 chunk 的索引
    • 将获取的到chunk转换为mem模式
    • 如果设置了perturb_type, 则将获取到的chunk初始化为 perturb_type ^ 0xff
  • small bin

    • 获取 small bin 的索引
    • 获取对应 small bin 中的 chunk 指针
    • 先执行 victim = last(bin),获取 small bin 的最后一个 chunk
    • 如果 victim = bin ,那说明该 bin 为空
    • 如果不相等,那么会有两种情况
      • 第一种情况,small bin 还没有初始化

        • 执行初始化,将 fast bins 中的 chunk 进行合并
      • 第二种情况,small bin 中存在空闲的 chunk
        • 获取 small bin 中倒数第二个 chunk
        • 检查 bck->fd 是不是 victim,防止伪造
        • 设置 victim 对应的 inuse 位
        • 修改 small bin 链表,将 small bin 的最后一个 chunk 取出
        • 如果不是 main_arena,设置对应的标志
        • 将申请到的 chunk 转化为对应的 mem 状态
        • 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
  • large bin

    • 获取large bin的下标
    • 如果存在fastbin的话,会处理 fastbin
  • 大循环

    如果程序执行到了这里,那么说明 与 chunk 大小正好一致的 bin (fast bin, small bin) 中没有 chunk可以直接满足需求 ,但是large chunk 则是在这个大循环中处理

    • 尝试从 unsorted bin 中分配用户所需的内存

      • unsort bin 遍历
      • small request
      • 初始取出
      • exact fit
      • place chunk in small bin
      • place chunk in large bin
      • 最终取出
      • while 最多迭代10000次
    • 尝试从 large bin 中分配用户所需的内存
    • 寻找较大 chunk
      • 找到一个合适的 map
      • 找到合适的 bin
      • 简单检查 chunk
      • 真正取出chunk
    • 尝试从 top chunk 中分配用户所需内存

释放内存块

_libc_free

  • 判断是否有钩子函数 __free_hook
  • free NULL没有作用
  • 将mem转换为chunk状态
  • 如果该块内存是mmap得到的
  • 根据chunk获得分配区的指针
  • 执行释放

内容来源

Linux进程分配内存的两种方式brk和mmap

sbrk与brk的使用

ctfwiki概述

堆漏洞挖掘:02---堆的glibc实现与Arena

ctfwiki堆相关数据结构

浅谈 malloc_consolidate() 函数具体实现

ctfwiki深入理解堆实现

Linux堆的一些基础知识的更多相关文章

  1. linux 文件权限的基础知识

    由于自己总是记不住linux里权限的一些知识,因此简单总结如下: 查看文件权限 // 列出所有文件 ls -al // 最前面的一串10个字母的字符串 // 可能像 drwxrwxr-x // 第一位 ...

  2. Linux下Apache(HTTP)基础知识梳理-运维笔记

    HTTP介绍: HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传 ...

  3. linux 学习随笔-shell基础知识

    1:用户的shell历史命令保存在home/username/.bash_history中 #!!  执行用户的上一条命令 #!pw  执行命令历史中最近一次以pw开头的命令 2:'*'来匹配零或多个 ...

  4. 服务器Linux系统安全维护基础知识介绍

    事先规划好Linux操作系统的分区 Linux操作系统的分区规划跟微软操作系统的分区规划不同.后者分区规划对于其性能的影响很小.但是Linux操作系统的分区规划则不同,其对服务器的性能影响很大.其实我 ...

  5. Linux目录结构和基础知识

    目录结构: /bin:存放系统常用的命令程序 /boot:系统启动或引导所需要的一些文件 /dev:可用的设备文件 /etc:系统配置相关的东西 /home:所有用户的主目录 /lib,lib64:存 ...

  6. 第0章Linux环境到内核基础知识

    #include<stdio.h> int main(void) { printf("hello world\n"); return 0; } gcc -g -wall ...

  7. 【Linux 应用编程】基础知识

    错误提示 Linux 提供的系统调用API,通常会在失败的时候返回 -1.如果想获取更多更详细的报错信息,需要借助全局变量 errno 和 perror 函数: #include <stdio. ...

  8. Linux驱动开发1——基础知识

    1.三类驱动 字符设备驱动:字节流,/dev下有设备节点,file_operations,inode, file 块设备驱动:数据块,/dev下有设备节点,通常有文件系统 网络设备驱动:网络报文的收发 ...

  9. Heap(堆)的基础知识入门

    堆 逻辑结构: 1   /        \ 1          3 /     \     /    \ 4    5   6      null 物理结构; 1.首先堆是一个完全二叉查找书(Co ...

随机推荐

  1. 用D3js的区域生成器实现简单波浪图

    最近做控件遇到含有波浪图的图表,一开始用Echarts虽然很快完成了,但Echarts的波浪图与其他图表的响应式不同步,于是学习了D3js,D3js写起来确实复杂一些,但能够实现的效果也更丰富,做的时 ...

  2. 通透理解viewport

    摘自:https://blog.csdn.net/u014787301/article/details/44466697 在移动设备上进行网页的重构或开发,首先得搞明白的就是移动设备上的viewpor ...

  3. 在Eclipse配置Tomcat服务器+JSP实例创建

    欢迎任何形式的转载,但请务必注明出处. 1.jdk安装及环境配置 点击进入教程 2.Eclipse安装 点击进入官网下载 注意下载完成打开.exe后,出现的界面,有很多版本供选择.选择下图版本 3.T ...

  4. javascript中bind()、call()、apply()的使用

    一直以来对bind().apply().call()这三个方法都模模糊糊的,现在有时间详细的看看这三个方法,并记录下来. bind() 参考文档:https://developer.mozilla.o ...

  5. 易优cms后台RCE以及任意文件上传漏洞

    前言 EyouCms是基于TP5.0框架为核心开发的免费+开源的企业内容管理系统,专注企业建站用户需求提供海量各行业模板,降低中小企业网站建设.网络营销成本,致力于打造用户舒适的建站体验.易优cms ...

  6. Win10开启蓝屏信息记录及文件查看位置的方法

    蓝屏,是电脑最常见的故障,一般出现蓝屏时都会显示详细的蓝屏错误信息,方便用户排查故障.但是如果系统未开启蓝屏记录,下文介绍蓝屏日志开启及蓝屏日志文件存放位置.我用的是win10系统 蓝屏日志开启方法步 ...

  7. 他爬取了B站所有番剧信息,发现了这些……

    本文来自「楼+ 之数据分析与挖掘实战 」第 4 期学员 -- Yueyec 的作业.他爬取了B站上所有的番剧信息,发现了很多有趣的数据- 关键信息:最高播放量 / 最强up主 / 用户追番数据 / 云 ...

  8. Sql Server 2017 安装问题记录

    记录了我在虚拟机中安装Sql server 2017遇到的一些问题. 安装环境: Sql server 2017 + Windows Server 2012 R2 提供两个网上的下载链接: https ...

  9. 前端学习笔记--CSS样式--文本

    1.文本与文字样式主要的属性: 子标签可以继承父标签的样式: 关于颜色: 文本属性: letter-spacing: line-height: text-align: 字体:

  10. 阿里云上遇到: virtual memory exhausted: Cannot allocate memory

    # dd if=/dev/zero of=/swap bs=1024 count=1M Format the swap file: # mkswap /swap Enable the swap fil ...