堆(heap)系列_0x05:一张图剖析堆块分配和FreeLists的联系
提示:前面几篇笔记基本都是侧重理论,下面用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个函数HeapAlloc
和HeapFree
重点进行讲解
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
= 第一个用户堆块地址0x012004a8
(FirstEntry
指定的值,后面有介绍) +_HEAP_ENTRY
结构大小(8bytes)
查看堆段和堆块的相关信息,主要关注012004b0
和012004a8
是怎么得到的?
###[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的联系相关推荐
- 一张图剖析企业大数据平台的核心架构
我们先来看看这张图,这是某公司使用的大数据平台架构图,大部分公司应该都差不多: 从这张大数据的整体架构图上看来,大数据的核心层应该是:数据采集层.数据存储与分析层.数据共享层.数据应用层,可能叫法有所 ...
- 33张图剖析ReentrantReadWriteLock源码
本文大纲如下 纵观全局 我的英文名叫ReentrantReadWriteLock(后面简称RRW),大家喜欢叫我读写锁,因为我常年混迹在读多写少的场景. 读写锁规范 作为合格的读写锁,先要有读锁与写锁 ...
- 10 张图剖析同步与异步
点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 本文来讨论一下到底什么是同步,什么是异步,以及在编程中这两个极为重要的概念到底意味着什么. 相信很多同学遇到同步异步这两个词的时候大脑瞬间就 ...
- 『图解Java并发编程系列』10张图告诉你Java并发多线程那些破事
目录 线程安全问题 活跃性问题 性能问题 有态度的总结 头发很多的程序员:『师父,这个批量处理接口太慢了,有什么办法可以优化?』架构师:『试试使用多线程优化』第二天头发很多的程序员:『师父,我已经使用 ...
- 漫谈程序员系列:一张图道尽程序员的出路
<推背图>相传由唐太宗时期的司天监李淳风和袁天罡合著(此两人其实是超级武学高手,参见小椴的<开唐>),推算大唐以后中国两千多年的国运盛衰,在中国七大预言书中居首,是当之无愧的中 ...
- 「核心面试题系列」30张图理解HTTP在面试中所有会出现的题
前言 随着市场开始回暖,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是比较大的,通过问答 + ...
- 「春招系列」30张图理解HTTP在面试中所有会出现的题
前言 又是一年金三银四,春招与跳槽热闹的开展着,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是 ...
- Linux pwn入门教程,pwn堆入门系列教程1
pwn堆入门系列教程1 因为自己学堆的时候,找不到一个系统的教程,我将会按照ctf-wiki的目录一步步学下去,尽量做到每周有更新,方便跟我一样刚入门堆的人学习,第一篇教程研究了4天吧,途中没人指导. ...
- 六、自己动手实现------------“ 堆 Heap 和 优先队列 PriorityQueue ”
参考文章: https://mp.weixin.qq.com/s/80xSoCfeoK_4uIugAU-8nw java实现二叉堆(微信文章 强推看这一篇 ) https://githu ...
最新文章
- AllegroPCB PDN电源分配系统分析
- R语言as.name函数(转化为命名的类别对象)和is.name函数(检验是否是命名的类别对象)实战
- setjmp()、longjmp() Linux Exception Handling/Error Handling、no-local goto
- xml python gb2312_使用Python处理XML格式数据的方法介绍
- webstorm 两个文件对比不同_DOS 入门到精通 使用 fc 命令比较两个文件,并逐一显示不同之处...
- 百度之星2019 初赛一 题解
- 为什么实际频率只有1.8G的AMD 2500+处理器运行速度比实际频率2.4G的P4-2.4B还快
- 苹果AirPower总是跳票的原因找到了?或因商标被抢注
- 1e9个兵临城下(容斥原理)
- 安装版本swf文件转换其他视频格式工具(例:swf to mp4) ,转换后的视频无水印...
- layer mvc json 中文乱码处理
- 【多目标优化求解】基于matlab遗传优化萤火虫算法求解多目标优化问题【含Matlab源码 1484期】
- c语言bubblesort函数,C++实现冒泡排序(BubbleSort)
- WPF中监听剪贴板存在的Bug:OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))错误
- linux/android中aplay/arecord用法以及命令
- 2022广东省安全员A证第三批(主要负责人)考试题库及在线模拟考试
- java支付宝提现_关于Java调用微信、支付宝的支付、提现
- linux jq下载文件,linux 之 jq
- Java Web图书管理系统(MVC框架)-包含源码
- 从0开始Go语言,用Golang搭建网站
热门文章
- 电脑灯不亮,电脑显示灯不亮原因有哪些 电脑显示灯不亮解决方法
- 江苏省计算机中级职称题库,计算机中级职称考试复习题「附答案」
- ibm服务器 显示器不亮,IBM液晶显示器黑屏故障原因、检测分析及维修方法
- Go报错Finished running tool: 路径,current directory outside main module .... dependencies 的解决方法
- 安卓pdf阅读器_用户分享Note2阅读器体验:全格式手写、双开翻译功能很给力!...
- 新媒体运营:如何做好软文写作?
- 制作启动的iso文件
- 【日常问题】API文档的.chm后缀自动生成.chw后缀文件
- Oracle查询工资前三的员工信息
- java计算机毕业设计家庭饮食营养管理源码+mysql数据库+系统+lw文档+部署