提示:前面几篇笔记基本都是侧重理论,下面用2个示例进行讲解:一个是堆块的分配和释放,另一个是FreeLists的作用(可以直接看下面的图,很容易理解,画了一个多小时…)

文章目录

  • 示例1:堆块分配和释放
    • 1.HeapAlloc
    • 2.HeapFree
    • 3.`!heap`命令
    • 4.问题:申请大小是0x123个字节,在哪里可以看到?
  • 示例2:内存分配中FreeLists作用
  • 参考

示例1:堆块分配和释放

用一个示例来说明堆块的分配和释放,侧重点主要是对堆块分配后,返回的地址是哪里?以及堆块释放后,申请的内存变化,被谁回收了?

//说明:为了说明3个函数的作用,省略了函数是否调用成功的返回值判断逻辑
#include "stdafx.h"
#pragma optimize("", off)             //关闭优化,为了更好的观察局部变量和栈帧
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{HANDLE h_heap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x2001, 0xfe001);        //创建私有堆LPVOID p_heap_block = HeapAlloc(h_heap, HEAP_GENERATE_EXCEPTIONS, 0x123);   //私有堆上分配一块内存bool free_ok = HeapFree(h_heap, HEAP_NO_SERIALIZE, p_heap_block);          //释放bool destory_ok = HeapDestroy(h_heap);                                     //销毁私有堆 return 0;
}

下面对源码中处理堆块的2个函数HeapAllocHeapFree重点进行讲解

1.HeapAlloc

执行完 HeapAlloc(h_heap, HEAP_GENERATE_EXCEPTIONS, 0x123),在新创建的堆上想要获取一个大小是0x123的堆块

#查看局部变量(返回地址)
0:000> dv /i
prv local           h_heap = 0x01200000            #私有堆的地址(句柄)
prv local     p_heap_block = 0x012004b0            #返回地址是0x012004b0

说明:分配给应用程序内存区(用户数据区)的起始地址0x012004b0 = 第一个用户堆块地址0x012004a8FirstEntry指定的值,后面有介绍) + _HEAP_ENTRY结构大小(8bytes)

查看堆段和堆块的相关信息,主要关注012004b0012004a8是怎么得到的?

###[1].查询 堆段 相关信息
#私有堆的地址(句柄)是0x01200000,直接查看_HEAP_SEGMENT结构
0:000> dt _HEAP_SEGMENT 0x01200000
ntdll!_HEAP_SEGMENT+0x000 Entry            : _HEAP_ENTRY+0x008 SegmentSignature : 0xffeeffee+0x018 Heap             : 0x01200000 _HEAP+0x01c BaseAddress      : 0x01200000 Void+0x024 FirstEntry       : 0x012004a8 _HEAP_ENTRY        #指向第一个用户堆块+0x028 LastValidEntry   : 0x012ff000 _HEAP_ENTRY
#第一个用户堆 + 8bytes(_HEAP_ENTRY长度)就是HeapAlloc函数返回的用户数据区的起始地址
? 0x012004a8+8
Evaluate expression: 18875568 = 012004b0 = p_heap_block,HeapAlloc函数返回的用户数据区的起始地址###[2].查询 堆块 相关信息
#前8个字节是HEAP_ENTRY结构
0:000> dt _HEAP_ENTRY 0x012004b0-8
ntdll!_HEAP_ENTRY+0x000 Size             : 0x4018          #单位是分配粒度8bytes+0x002 Flags            : 0x32 '2'+0x003 SmallTagIndex    : 0xca ''     #堆块的标记序号+0x000 SubSegmentCode   : 0xca324018+0x004 PreviousSize     : 0x9ac5          #前一个堆块的大小+0x006 SegmentOffset    : 0 ''+0x006 LFHFlags         : 0 ''+0x007 UnusedBytes      : 0x1d ''     #用户数据区后未使用的字节数  0:000> dd p_heap_block l1                    #p_heap_block是一个指针
00d8fd68  012004b0
#查看堆块的内容
0:000> dd poi(p_heap_block) l8
012004b0  baadf00d baadf00d baadf00d baadf00d   #填充为bad food(调试器中运行,系统自动启动对堆的调试支持)
012004c0  baadf00d baadf00d baadf00d baadf00d

2.HeapFree

执行完 HeapFree(h_heap, HEAP_NO_SERIALIZE, p_heap_block);,释放刚才申请的堆内存

#执行前
0:000> dd poi(p_heap_block) l8
012004b0  baadf00d baadf00d baadf00d baadf00d   #默认都是baadf00d
012004c0  baadf00d baadf00d baadf00d baadf00d#执行后
0:000> dd poi(p_heap_block) l8
012004b0  012000c0 012000c0 feeefeee feeefeee   #除了前8个字节,其他都被填充为feeefeee(相当于free)
012004c0  feeefeee feeefeee feeefeee feeefeee
#说明:
#1.填充为feeefeee,这正是堆管理器对已经释放堆块所填充的内容
#2.前8个字节实际上构造成_LIST_ENTRY结构体Flink和Blink的指向都是012000c0,下面是详细解释:
#扩展:已经释放的堆块,堆管理器用一个专门的数据结构来描述,这个结构的前8个字节与HEAP_ENTRY结构一样,多增加了
#     8个字节的空闲链表节点
0:000> dt _HEAP_FREE_ENTRY 0x012004b0-8
ntdll!_HEAP_FREE_ENTRY+0x000 HeapEntry        : _HEAP_ENTRY+0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY+0x000 Size             : 0x4557+0x002 Flags            : 0x31 '1'+0x003 SmallTagIndex    : 0x83 ''...+0x008 FreeList         : _LIST_ENTRY [ 0x12000c0 - 0x12000c0 ]    #空闲链表节点

3.!heap命令

使用!heap命令观察堆块信息,下面是释放内存后的堆信息

#!heap简单介绍
0:000> !heap 0x01200000 -hf
Index   Address  Name      Debugging options enabled3:   01200000                               #堆句柄 Segment at 01200000 to 012ff000 (00003000 bytes committed) #0号段Flags:                40001064Granularity:          8 bytes         #分配粒度Segment Reserve:      00100000Segment Commit:       00002000DeCommit Block Thres: 00000200DeCommit Total Thres: 00002000Total Free Size:      00000567         #空闲链表中堆块的总大小(以分配粒度为单位)FreeList[ 00 ] at 012000c0: 012004b0 . 012004b0             #00号空闲链表  012004a8: 004a8 . 02b38 [104] - free                  #空闲堆块Heap entries for Segment00 in Heap 01200000                    #00号段中的堆块#堆块起始地址 前一个堆块的字节数 本堆块字节数 状态(用户数据区字节数)(堆块的标记序号)address: psize . size  flags   state (requested size)01200000: 00000 . 004a8 [101] - busy (4a7)              #包含段结构,尺寸004a8正好是第一个用户堆块地址012004a8: 004a8 . 02b38 [104] free fill                    #等于空闲堆块总和01202fe0: 02b38 . 00020 [111] - busy (1d)              #段中最后一个块,空闲块01203000:      000fc000      - uncommitted bytes.        #未提交区域
#说明:堆块起始地址就是HEAP_ENTRY的起始地址,用户数据区字节数不包含未使用的字节
0:000> dd 01200000+004a8
012004a8  83314557 00009ac5 012000c0 012000c0
012004b8  feeefeee feeefeee feeefeee feeefeee
012004c8  feeefeee feeefeee feeefeee feeefeee

如果感兴趣可以观察一下堆块释放前后的堆结构对比,会发现结构是一样的,唯一的区别:

  • 释放前:堆块用户数据区是用baadfood和必要的对齐信息填充
  • 释放后:堆块开头用16 bytes构造一个_HEAP_FREE_ENTRY结构,然后剩下的内容用feeefeee填充

4.问题:申请大小是0x123个字节,在哪里可以看到?

WinDbg中用!heap加参数-hf可以查看申请和释放内存前后heap的变化

#注意:示例中堆句柄与上面不同,因为是另一次调试的结果,不影响说明内存申请和释放影响#1.栈上申请内存
#LPVOID p_heap_block = HeapAlloc(h_heap, HEAP_GENERATE_EXCEPTIONS, 0x123);
0:000> !heap 01320000 -hf3:   01320000 Heap entries for Segment00 in Heap 01320000address: psize . size  flags   state (requested size)01320000: 00000 . 004a8 [101] - busy (4a7)                #包含段结构,尺寸004a8正好是第一个用户堆块地址013204a8: 004a8 . 00140 [107] - busy (123), tail fill  #申请的内存0x123在这里,有填充,总大小0x140013205e8: 00140 . 029f8 [104] free fill                   #空闲堆块的总和:0x029f801322fe0: 029f8 . 00020 [111] - busy (1d)01323000:      000fc000      - uncommitted bytes.#2.栈上释放内存
#bool free_ok = HeapFree(h_heap, HEAP_NO_SERIALIZE, p_heap_block);
0:000> !heap 01320000 -hf
Index   Address  Name      Debugging options enabled3:   01320000 Heap entries for Segment00 in Heap 01320000address: psize . size  flags   state (requested size)01320000: 00000 . 004a8 [101] - busy (4a7)            013204a8: 004a8 . 02b38 [104] free fill             #申请的内存大小0x123不见了,归还给空闲堆块了01322fe0: 02b38 . 00020 [111] - busy (1d)01323000:      000fc000      - uncommitted bytes.#3.对上释放后的空闲堆块大小 - 释放前的空闲堆块大小 = HeapAlloc申请的0x123内存所在的堆块的大小0x00140
0:000> ? 02b38 - 029f8
Evaluate expression: 320 = 00000140

下面针对内存回收中使用的FreeLists重点进行介绍

示例2:内存分配中FreeLists作用

FreeLists链表里面保存了空闲内存,当程序员申请一个堆块时,会先在FreeLists里面找看看是否能提供要求大小的内存?

  • 能提供:就直接用空闲堆块构造一个符合要求的堆块
  • 不能提供:commit一部分内存,在新内存中构造一个新堆块

堆块构造完,会将这个堆块的用户数据区首地址返回,同时也会更新FreeLists链表

#1.FreeLists记录指定堆中的所有空闲堆块,起始地址 = 堆句柄 + FreeLists偏移(0x0c0)
#2.双向链表
typedef struct _LIST_ENTRY
{struct _LIST_ENTRY *Flink; //指向前一个堆块(的用户数据区)struct _LIST_ENTRY *Blink;   //执行后一个堆块(的用户数据区)
} LIST_ENTRY, *PLIST_ENTRY;
#注意:一定要注意Flink和Blink并不是指向一个完整堆块的起始地址,而是指向用户数据区

源码:

#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{                   HANDLE hProcessHeap = GetProcessHeap();                         //获取默认堆句柄BYTE* pBlock1       = (BYTE*) HeapAlloc(hProcessHeap, 0, 16);//分配HeapFree(hProcessHeap, 0, pBlock1);                          //释放return 0;
}

WinDbg调试

  • 1.运行完GetProcessHeap(),查看默认堆的相关信息
#1.默认堆的句柄
0:000> dvhProcessHeap = 0x012a0000#2.查看默认堆的详细信息
0:000> !heap 012a0000 -hf
Index   Address  Name      Debugging options enabled1:   012a0000 Segment at 012a0000 to 0139f000 (00008000 bytes committed)FreeList[ 00 ] at 012a00c0: 012a5e30 . 012a4c08             #空闲链表 012a4c00: 00040 . 00010 [104] - free012a48e8: 00040 . 00038 [104] - free012a5e28: 00458 . 021b8 [104] - freeHeap entries for Segment00 in Heap 012a0000                   #段中每个堆块的详细信息address: psize . size  flags   state (requested size)012a0000: 00000 . 004a8 [101] - busy (4a7)012a04a8: 004a8 . 00118 [107] - busy (117), tail fill Internal ...012a48a8: 00028 . 00040 [107] - busy (28), tail fill012a48e8: 00040 . 00038 [104] free fill                    #第一个空闲块012a4920: 00038 . 000c0 [107] - busy (a8), tail fill...012a4bc0: 00138 . 00040 [107] - busy (24), tail fill012a4c00: 00040 . 00010 [104] free fill                   #第二个空闲块012a4c10: 00010 . 00028 [107] - busy (10), tail fill...012a59d0: 00060 . 00458 [107] - busy (440), tail fill012a5e28: 00458 . 021b8 [104] free fill                  #第三个空闲块012a7fe0: 021b8 . 00020 [111] - busy (1d)012a8000:      000f7000      - uncommitted bytes.#3.空闲列表信息
0:000> dt _HEAP 012a0000
ntdll!_HEAP                                #Flink      #Blink +0x0c0 FreeLists        : _LIST_ENTRY [ 0x12a4c08 - 0x12a5e30 ]#4.使用dl查看链表,dt和!list命令使用相对难一点,dl会很简单
#dl语法:dl 起始地址 要显示的节点数 节点结构的长度(单位是指针长度)
#说明:起始地址必须是LIST_ENTRY(双向链表) 或 SINGLE_LIST_ENTRY(单向链表)结构
0:000> dl 0x12a00c0 5 2
012a00c0  012a4c08 012a5e30
012a4c08  012a48f0 012a00c0
012a48f0  012a5e30 012a4c08
012a5e30  012a00c0 012a48f0
  • 2.下面是运行HeapAlloc(hProcessHeap, 0, 16);使用WinDbg解析出的FreeLists相关内容重新整理的示意图

关注点:

  • FreeLists链表中Flink和Blink是指向用户数据区的首地址,而使用!heap命令显示的FreeList中地址是堆块的首地址

    #堆块首地址 + HEAP_ENTRY结构体大小(8bytes)= 用户数据区首地址
    
  • 申请的16 bytes内存是在3个空闲链表中的一个构造的;空闲堆块构造一个堆块相当于将HEAP_FREE_ENTRY结构向高地址平移

  • HeapAlloc返回的是堆块中用户数据区的首地址

  • 申请16 bytes内存空间,内存空间所属的堆块消耗了40个bytes

参考

  • 1.《软件调试》第二版,卷2的第23章
  • 2.《Windows高级调试》,第6章
  • 3.《深入解析Windows操作系统》第七版,第五章
  • 4.《Windows编程调试技术内幕》

堆(heap)系列_0x05:一张图剖析堆块分配和FreeLists的联系相关推荐

  1. 一张图剖析企业大数据平台的核心架构

    我们先来看看这张图,这是某公司使用的大数据平台架构图,大部分公司应该都差不多: 从这张大数据的整体架构图上看来,大数据的核心层应该是:数据采集层.数据存储与分析层.数据共享层.数据应用层,可能叫法有所 ...

  2. 33张图剖析ReentrantReadWriteLock源码

    本文大纲如下 纵观全局 我的英文名叫ReentrantReadWriteLock(后面简称RRW),大家喜欢叫我读写锁,因为我常年混迹在读多写少的场景. 读写锁规范 作为合格的读写锁,先要有读锁与写锁 ...

  3. 10 张图剖析同步与异步

    点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 本文来讨论一下到底什么是同步,什么是异步,以及在编程中这两个极为重要的概念到底意味着什么. 相信很多同学遇到同步异步这两个词的时候大脑瞬间就 ...

  4. 『图解Java并发编程系列』10张图告诉你Java并发多线程那些破事

    目录 线程安全问题 活跃性问题 性能问题 有态度的总结 头发很多的程序员:『师父,这个批量处理接口太慢了,有什么办法可以优化?』架构师:『试试使用多线程优化』第二天头发很多的程序员:『师父,我已经使用 ...

  5. 漫谈程序员系列:一张图道尽程序员的出路

    <推背图>相传由唐太宗时期的司天监李淳风和袁天罡合著(此两人其实是超级武学高手,参见小椴的<开唐>),推算大唐以后中国两千多年的国运盛衰,在中国七大预言书中居首,是当之无愧的中 ...

  6. 「核心面试题系列」30张图理解HTTP在面试中所有会出现的题

    前言 随着市场开始回暖,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是比较大的,通过问答 + ...

  7. 「春招系列」30张图理解HTTP在面试中所有会出现的题

    前言 又是一年金三银四,春招与跳槽热闹的开展着,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是 ...

  8. Linux pwn入门教程,pwn堆入门系列教程1

    pwn堆入门系列教程1 因为自己学堆的时候,找不到一个系统的教程,我将会按照ctf-wiki的目录一步步学下去,尽量做到每周有更新,方便跟我一样刚入门堆的人学习,第一篇教程研究了4天吧,途中没人指导. ...

  9. 六、自己动手实现------------“ 堆 Heap 和 优先队列 PriorityQueue ”

    参考文章: https://mp.weixin.qq.com/s/80xSoCfeoK_4uIugAU-8nw       java实现二叉堆(微信文章  强推看这一篇 ) https://githu ...

最新文章

  1. AllegroPCB PDN电源分配系统分析
  2. R语言as.name函数(转化为命名的类别对象)和is.name函数(检验是否是命名的类别对象)实战
  3. setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto
  4. xml python gb2312_使用Python处理XML格式数据的方法介绍
  5. webstorm 两个文件对比不同_DOS 入门到精通 使用 fc 命令比较两个文件,并逐一显示不同之处...
  6. 百度之星2019 初赛一 题解
  7. 为什么实际频率只有1.8G的AMD 2500+处理器运行速度比实际频率2.4G的P4-2.4B还快
  8. 苹果AirPower总是跳票的原因找到了?或因商标被抢注
  9. 1e9个兵临城下(容斥原理)
  10. 安装版本swf文件转换其他视频格式工具(例:swf to mp4) ,转换后的视频无水印...
  11. layer mvc json 中文乱码处理
  12. 【多目标优化求解】基于matlab遗传优化萤火虫算法求解多目标优化问题【含Matlab源码 1484期】
  13. c语言bubblesort函数,C++实现冒泡排序(BubbleSort)
  14. WPF中监听剪贴板存在的Bug:OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))错误
  15. linux/android中aplay/arecord用法以及命令
  16. 2022广东省安全员A证第三批(主要负责人)考试题库及在线模拟考试
  17. java支付宝提现_关于Java调用微信、支付宝的支付、提现
  18. linux jq下载文件,linux 之 jq
  19. Java Web图书管理系统(MVC框架)-包含源码
  20. 从0开始Go语言,用Golang搭建网站

热门文章

  1. 电脑灯不亮,电脑显示灯不亮原因有哪些 电脑显示灯不亮解决方法
  2. 江苏省计算机中级职称题库,计算机中级职称考试复习题「附答案」
  3. ibm服务器 显示器不亮,IBM液晶显示器黑屏故障原因、检测分析及维修方法
  4. Go报错Finished running tool: 路径,current directory outside main module .... dependencies 的解决方法
  5. 安卓pdf阅读器_用户分享Note2阅读器体验:全格式手写、双开翻译功能很给力!...
  6. 新媒体运营:如何做好软文写作?
  7. 制作启动的iso文件
  8. 【日常问题】API文档的.chm后缀自动生成.chw后缀文件
  9. Oracle查询工资前三的员工信息
  10. java计算机毕业设计家庭饮食营养管理源码+mysql数据库+系统+lw文档+部署