关于如何理解Glibc堆管理器(Ⅰ——堆结构)

2024-04-15 05:58
文章标签 理解 结构 管理器 glibc

本文主要是介绍关于如何理解Glibc堆管理器(Ⅰ——堆结构),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本篇实为个人笔记,可能存在些许错误;若各位师傅发现哪里存在错误,还望指正。感激不尽。

若有图片及文稿引用,将在本篇结尾处著名来源。

目录

什么是堆

实际操作


        首先从 什么是堆 开始讲起吧。

        在操作系统加载一个应用程序的时候,会为程序创建一个独立的进程,这个进程拥有着一套独立的内存结构。大致结构如下图:

         进程在运行之处会创建一块固定大小的堆空间,但当用户需要申请一块超出已有堆空间大小的内存时,操作系统就会调用sbrk函数(也有其他类似功能的函数)来延申这块空间

        但正如我们所见,这样的拓展大小的方式似乎还是有极限的。当即便用sbrk去拓展Heap,也不能够满足用户的需求的时候(至少堆不能覆盖到栈上去,对吧?),操作系统就会使用mmap来为进程开辟额外的空间,这些空间可以被视为“虚拟内存”,它们不需要时刻都加载在内存中,因此能够大大提升堆的空间

        当然,如果即便如此也不能够满足用户所需要的空间,那这个申请空间的操作就会失败,例如malloc,它会返回一个NULL

        并且,上图还显示了堆在内存中的结构——一段连续的内存块,记住这个特点将对接下来的理解很有帮助

如下为一个堆的结构体申明:

struct malloc_chunk {INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).*/INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead.*/struct malloc_chunk* fd;   /* double links -- used only if free. */struct malloc_chunk* bk;/* Only used for large blocks: pointer to next larger size.  */struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */struct malloc_chunk* bk_nextsize;};

        这可能会给人一种反直觉的印象,因为这个堆结构体似乎太小了,根本不能够像我们印象里的那样去存放数据

        因此这里需要介绍一下Glibc中堆的寻址方式——隐式链表

         尽管上图已经很详尽的介绍了堆的存放,但我仍然有必要多做些说明

        操作系统会将堆划分成多个chunk以分配给程序,也就是malloc请求到的实则是一个chunk

        而malloc返回的指针实则是指向chunk+16(在x86中则是+8)处的地址,究其原因就是因为结构体中的如下两项

  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).*/INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead.*/

        只有这两个数据是常驻于结构体中的(这句话有些晦涩,现在看不懂也没关系)

        它们分别表示上一个chunk的大小当前chunk的大小,那既然我们能够知道上一个chunk的大小,通过简单的加法就能够找到上一个chunk的位置了,这种方法就被称为隐式链表

        而在mchunk_size的下面就是用来储存用户需要的数据

        显然,如果从这个地方开始储存数据,上面给出的结构体就会被破坏了,因为另外四个成员无处安放了,但对于一个正被使用的chunk来说,这是无关紧要的,因此才说它们并不常驻(其中原因牵涉了其他,也将在下文叙述)

        (但请注意,chunk块的申请是要符合16字节(或8字节)对齐的,尽管用户申请的时候看起来相当随意,但操作系统仍然会返回对齐后的堆结构)

        同时,为了节省资源,mchunk_size的最后三位将用来储存额外的标志位,其意义这里不再赘述,但这里需要再一次强调的是,最后一位 P标记位 指示了上一个chunk是否处于被使用状态

        尽管它们被用作标记,但在计算chunk大小的时候,我们会默认它们为0以计算合理大小

        例如(二进制)1000101:Size=1000000,A=1,M=0,P=1

实际操作:

示范程序:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>int main()
{unsigned long long *chunk1, *chunk2;chunk1=(unsigned long long)malloc(0x80);chunk2=(unsigned long long)malloc(0x80);printf("Chunk1:%p",chunk1);printf("Chunk2:%p",chunk2);return 0;
}

         通过如下命令去编译这个文件

gcc -g heap.c -o heap

        然后用gdb调试heap文件,我们将断点定在第11行,查看此时的堆

gdb-peda$ heap
0x602000 PREV_INUSE {prev_size = 0x0, size = 0x91, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}
0x602090 PREV_INUSE {prev_size = 0x0, size = 0x91, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}
0x602120 PREV_INUSE {prev_size = 0x0, size = 0x20ee1, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}

        可以看出,我们申请的chunk大小为0x80,但实际返回的chunk却有0x90(最后的1为标志位)

        同时,它们是严格的按照堆的顺序往下开辟的,从0x602000到0x602090,没有其他空挡

        而0x602120是则是被称为“Top chunk”的堆结构,在当前的堆仍然充足的时候,操作系统通过分割Top Chunk来提供malloc的服务

gdb-peda$ p chunk1
$1 = (unsigned long long *) 0x602010
gdb-peda$ p chunk2
$2 = (unsigned long long *) 0x6020a0

        而查看chunk1的内容,发现它指向0x602010而不是0x602000

        这也作证了前面所说的内容,在这空挡的16字节中储存了常驻的那两个成员,而其他成员则被舍弃了

图片来源:https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/

这篇关于关于如何理解Glibc堆管理器(Ⅰ——堆结构)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/905013

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

结构体和联合体的区别及说明

《结构体和联合体的区别及说明》文章主要介绍了C语言中的结构体和联合体,结构体是一种自定义的复合数据类型,可以包含多个成员,每个成员可以是不同的数据类型,联合体是一种特殊的数据结构,可以在内存中共享同一... 目录结构体和联合体的区别1. 结构体(Struct)2. 联合体(Union)3. 联合体与结构体的

PostgreSQL如何查询表结构和索引信息

《PostgreSQL如何查询表结构和索引信息》文章介绍了在PostgreSQL中查询表结构和索引信息的几种方法,包括使用`d`元命令、系统数据字典查询以及使用可视化工具DBeaver... 目录前言使用\d元命令查看表字段信息和索引信息通过系统数据字典查询表结构通过系统数据字典查询索引信息查询所有的表名可

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念