在GDK8下观察glibc堆

GDK8产品主页:https://www.nanocode.cn/#/gdk8/index

glibc堆布局介绍

在程序运行过程中,堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。我们一般称管理堆的那部分程序为堆管理器。
堆在系统中 的布局可以分成两大类,分别是main_arena主场地和non_main_aren辅场地。

main_arena主场地的布局如下图所示:

在main_arena中,当程序第一次动态分配内存的时候,堆会被分成两块,一块是分配区,另一块是Top Chunk,Top Chunk的起始地址亦是top指针所指向的位置;在之后动态分配内存的过程中,只要Top Chunk的位置还够用,分配区就会从Top Chunk中划分空间供自己使用,但是假如Top Chunk中的空间不够用了,那么就会通过brk机制扩展空间。
在main_arena中的top指针记录了分配区的结束位置的地址,top指针的上方为top chunk;而分配区的开始位置的地址,如下图所示。

non_main_aren辅场地布局如下图所示:

non_main_arena中如果分配内存数量不够大的话,那么分配区的开始位置的地址和结束位置的地址同main_arena中的情况是一样的;但是如果分配内存数量足够的大,那么在non_main_arena中的分配区会划分成1个又1个的子堆;不管non_main_arena是否划分了子堆,在主子堆中,分配区的开始位置永远是[bins指针+mchunk_size(去标志位;bins指针对应malloc_chunk中的mchunk_size)],而其他子堆分配区的起始位置的地址为[heap info的地址+0x20]。
假如想要获取全部的子堆,就要遍历一下链表,不过这个链表并不是循环的,而且需要依靠perv指针找上一个),直到找到第1个为止。
注意:主子堆的heap info内的prev指针永远是空(主子堆一般排在链表的第一个),此链表并不是循环的

chunk的布局如下图所示:

无论是空闲chunk还是被分配的chunk,它们都是由malloc_chunk结构体进行描述,关于空闲的chunk并不会介绍太多,主要介绍一下被分配的chunk;在被分配的chunk中,假如上一个chunk尚未被释放,那么一般情况下prev size中不会记录任何东西(当然也有记录的时候,见下方),但是如果上一个chunk已经被释放了,那么perv size的大小就是上一个块的大小;而size中则会记录当前块的大小及当前块的属性,标志位的具体介绍见下方。

  • 需要注意的是当前块的大小必是 2SIZE_SZ 的整数倍;如果没有清除标志位,那么size的大小可能就不是2SIZE_SZ的整数倍了。
  • 比特0:记录当前块是否已经被分配;0表示不属于,1表示属于。
  • 比特1:记录当前块是否是由mmap分配的;0表示不属于,1表示属于。
  • 比特2:记录当前块是否不属于主线程;1表示不属于,0表示属于。

在malloc_chunk中的fd及bk字段,假如当前块处于被分配的状态,那么从fd开始就是数据;但是假如当前块是空闲的,那么fd及bk会分别指向下/上1个的空闲块。
fd_nextsize, bk_nextsize这两个指针指在块空闲的时候使用,指向下/上第1个大小与当前块不同的空闲块

arena命令的基本格式

!heap [-arena地址] [-显示选项]
显示选项说明见下面。

arena命令的演示

  1. 首先先要和GDK8建立连接;关于建立连接的具体步骤可以参考下面的链接。
    https://www.nanocode.cn/wiki/docs/gdk8_primer/primer_gdk8_guide
  2. 输入.sympath+ c:\temp载入libc符号文件;如下图所示。

    注意:
    如果还没有下载libc符号,可以通过sudo apt install libc6-dbg命令进行下载;完成下载以后,可以通过sudo find /usr/lib/debug -name “libc-x.xx.so”命令查找libc符号所在位置 [ 一般在/usr/lib/debug/lib目录下 ];找到libc符号后,将其复制到主机的c:\temp目录下即可。
  3. 设置断点,输入g后,等待断点命中;如下图所示。
  4. 输入x libc_so!main_arena手动载入libc符号;如下图所示。
  5. 输入.load ndx载入扩展命令。
  6. !arena => 查看所有arena的基本信息,大致列出来了malloc_state的中的重要信息;如下图所示。

    第1列: arena地址信息。
    x为0,代表main_arena;其余数字代表non_main_arena。
    第2列: 标识了是否含有fast chunks。
    第3列: flag标志位信息;bit0记录了分配区是否有fast bin chunk, bit1标识分配区是否能返回连续的虚拟地址空间。
    bit0: F代表有,-代表没有; bit1: C代表连续,N代表不连续。
    第4列: 线程信息;是否含有子线程。
    第5、6列: 分配的内存大小信息。
  7. !arena -地址 -d => 于列出对应地址arena的详细信息;如下图所示。

    从图中我们可以看到,该arena是属于主线程的,并且可以看到他的地址,next指针指向的是下一个arena的地址;top指针指向top chunk的地址,我们仍可以看到top指针对应的malloc_chunk结构体内的详细信息;在往下是bins数组的信息;最后列出的是分配区的情况,从分配区中可以看到第一列是块对应的地址,第二列是块的属性,因为这是主线程,所以N全是0,假如是子线程的话N就是1,M为0代表不是内存映射,P代表这个块是被分配的,第三列prev size是上一个块的大小,由于上一个块未释放且未“越界”,因此看到的prev size就会是0,第四列size是当前块的大小。
  8. !arena -地址 -h => 列出所有子堆的详细信息;如下图所示。

    第2列: 子堆对应的地址(很明显可以看到低位全是0,对应的是_ heap_ info 的典型地址类型)。
    第3、4列: 子堆分配区的大小。
  9. !arena -地址 -s => 统计相同大小块的信息;如下图所示。

    第2列: 按块的大小进行分类。
    第3列:拥有相同大小块的数量。
    第4列:拥有相同大小块的总大小。
    第5列:当前块在所有块当中的占比。
作者:admin  创建时间:2021-11-16 15:31
最后编辑:admin  更新时间:2024-03-12 10:45