首先编写以下代码用于检测进程默认堆是如何工作的
因为便于调试,所以后续内容是通过对”调试友好“的堆的分析。(直接用调试器加载调试程序)
******************************************************************************
用于了解堆的代码
******************************************************************************
int _tmain(int argc, _TCHAR* argv[])
{
BYTE * pAlloc1=NULL;
BYTE * pAlloc2=NULL;
HANDLE hProcessHeap=GetProcessHeap();
pAlloc1=(BYTE *)HeapAlloc(hProcessHeap,0,16);
pAlloc2=(BYTE *)HeapAlloc(hProcessHeap,0,1500);
HeapFree(hProcessHeap,0,pAlloc1);
HeapFree(hProcessHeap,0,pAlloc2);
CloseHandle(hProcessHeap);
return 0;
}
******************************************************************************
申请前堆堆表
******************************************************************************
[000] 0x00150178:|F-00152dd0,B-00152dd0| <=0号链表中有数据块,但是只有一个因为F和B指针指向了相同块
[001] 0x00150180:|F-00150180,B-00150180| <=前后指针都指向自己所以1号链表为空
[002] 0x00150188:|F-00150188,B-00150188|
[003] 0x00150189:|F-00150190,B-00150190|
... |
[126] 0x00150568:|F-00150568,B-00150568|
[127] 0x00150570:|F-00150570,B-00150570|
0号空闲堆块头数据
00152dc8 47 02 46 00 fc 14 18 00 <=堆块头部信息
00152dd0 78 01 15 00 78 01 15 00 <=堆块用户可用部分
00152dd8 ee fe ee fe ee fe ee fe
00152de0 ee fe ee fe ee fe ee fe
...
0号空闲堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0x247
+0x002 PreviousSize : 0x46
+0x000 SubSegmentCode : 0x00460247 Void
+0x004 SmallTagIndex : 0xee ''
+0x005 Flags : 0x14 ''
+0x006 UnusedBytes : 0xee ''
+0x007 SegmentIndex : 0 ''
******************************************************************************
分配一个16字节的堆
******************************************************************************
分配后堆表
[000] 0x00150178:|F-00152df8,B-00152df8| <=0号链表中连接的数据块地址变为0x00152df8,原始为0x00152dd0,大小减小了0x28个字节,分配给用户使用0x10字节,存储信息0x18字节
[001] 0x00150180:|F-00150180,B-00150180| <=前后指针都指向自己所以1号链表为空
[002] 0x00150188:|F-00150188,B-00150188|
[003] 0x00150189:|F-00150190,B-00150190|
... |
[126] 0x00150568:|F-00150568,B-00150568|
[127] 0x00150570:|F-00150570,B-00150570|
0号空闲堆块头数据
00152df0 42 02 05 00 ee 14 ee 00 <=堆块头部信息
00152df8 78 01 15 00 78 01 15 00 <=堆块用户可用部分
00152e00 ee fe ee fe ee fe ee fe
...
0号空闲堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0x242
+0x002 PreviousSize : 0x5
+0x000 SubSegmentCode : 0x00050242 Void
+0x004 SmallTagIndex : 0xee ''
+0x005 Flags : 0x14 ''
+0x006 UnusedBytes : 0xee ''
+0x007 SegmentIndex : 0 '' 分配的堆块数据
00152dc8 05 00 46 00 95 07 18 00 <=堆块头部信息
00152dd0 0d f0 ad ba 0d f0 ad ba <=堆块用户可用部分,16字节
00152dd8 0d f0 ad ba 0d f0 ad ba
00152de0 ab ab ab ab ab ab ab ab <=堆结构部分16字节
00152de8 00 00 00 00 00 00 00 00
00152df0 42 02 05 00 ee 14 ee 00 <=后面是下一个堆的头信息,此例中为分割后的0号空闲堆块
00152df8 78 01 15 00 78 01 15 00 <=堆块数据,空闲时指向0号链表F-00150178,B-00150178
00152de0 ee fe ee fe ee fe ee fe
00152e08 ee fe ee fe ee fe ee fe
...
分配的堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0x5 <=分配块大小0x5*8=40=0x28字节
+0x002 PreviousSize : 0x46
+0x000 SubSegmentCode : 0x00460005 Void
+0x004 SmallTagIndex : 0x95 ''
+0x005 Flags : 0x7 ''
+0x006 UnusedBytes : 0x18 ''
+0x007 SegmentIndex : 0 ''
******************************************************************************
分配一个1500字节的堆块
******************************************************************************
申请后空闲链表
[000] 0x00150178:|F-001533f0,B-001533f0| <=0号链表中连接的数据块地址变为0x001533f0,原始为0x00152dd0,大小减小了0x620=1568个字节
[001] 0x00150180:|F-00150180,B-00150180| <=前后指针都指向自己所以1号链表为空
[002] 0x00150188:|F-00150188,B-00150188|
[003] 0x00150189:|F-00150190,B-00150190|
... |
[126] 0x00150568:|F-00150568,B-00150568|
[127] 0x00150570:|F-00150570,B-00150570|
0号空闲堆块数据
001533e8 83 01 bf 00 ee 14 ee 00 <=堆块头部信息
001533f0 78 01 15 00 78 01 15 00 <=堆块用户可用部分,16字节,指向空闲链表,0x00150178
001533f8 ee fe ee fe ee fe ee fe
0号空闲堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0x183
+0x002 PreviousSize : 0xbf
+0x000 SubSegmentCode : 0x00bf0183 Void
+0x004 SmallTagIndex : 0xee ''
+0x005 Flags : 0x14 ''
+0x006 UnusedBytes : 0xee ''
+0x007 SegmentIndex : 0 ''
分配的堆块数据
00152df0 bf 00 05 00 92 07 1c 00 <=堆块头部信息
00152df8 0d f0 ad ba 0d f0 ad ba <=堆块用户可用部分
00152e00 0d f0 ad ba 0d f0 ad ba
...
001533d0 .. .. .. .. ab ab ab ab <=0x001533d4开始为块信息数据,系统保留末尾20个字节加上块头的8字节正好28字节
001533d8 ab ab ab ab ee fe ee fe
001533e0 00 00 00 00 00 00 00 00
001533e8 83 01 bf 00 ee 14 ee 00 <=后面是下一个堆块的头信息,可以看出着就是0号空闲对块
001533f0 78 01 15 00 78 01 15 00 <=指向空闲链表,当被申请时可被用户使用
001533f8 ee fe ee fe ee fe ee fe
00153400 ee fe ee fe ee fe ee fe
...
分配的堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0xbf <=分配了1528个字节,多了28字节
+0x002 PreviousSize : 5
+0x000 SubSegmentCode : 0x000500bf Void
+0x004 SmallTagIndex : 0x92 ''
+0x005 Flags : 0x7 ''
+0x006 UnusedBytes : 0x1c ''
+0x007 SegmentIndex : 0 ''
******************************************************************************
释放16字节的堆块
******************************************************************************
释放后空闲链表
[000] 0x00150178:|F-001533f0,B-001533f0| <=0号链表中连接的数据块地址变为0x001533f0,原始为0x00152dd0,大小减小了0x620=1568个字节,因为位于两个堆块所以比分配的堆大40个字节
[001] 0x00150180:|F-00150180,B-00150180| <=前后指针都指向自己所以1号链表为空
[002] 0x00150188:|F-00150188,B-00150188|
[003] 0x00150189:|F-00150190,B-00150190|
[004] 0x00150198:|F-00150198,B-00150198|
[005] 0x001501a0:|F-00152dd0,B-00152dd0| <=因为分配16字节的堆块加上头及其他信息后为40字节,所以连接到了4号链表中,4号链表连接(4+1)*8大小的堆块,地址为0x00152dd0.
... |
[126] 0x00150568:|F-00150568,B-00150568|
[127] 0x00150570:|F-00150570,B-00150570|
0号空闲块数据与信息不变
释放堆块数据
00152dc8 05 00 46 00 fc 04 18 00 <=堆块头部信息
00152dd0 a0 01 15 00 a0 01 15 00 <=堆块用户可用部分
00152dd8 ee fe ee fe ee fe ee fe
00152de0 ee fe ee fe ee fe ee fe
00152de8 ee fe ee fe ee fe ee fe
00152df0 bf 00 05 00 92 07 1c 00 <=后面是另一个堆块头部信息,即为申请的1500字节的堆块
00152df8 0d f0 ad ba 0d f0 ad ba <=堆块用户可用部分
00152e00 0d f0 ad ba 0d f0 ad ba
...
释放堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 5
+0x002 PreviousSize : 0x46
+0x000 SubSegmentCode : 0x00460005 Void
+0x004 SmallTagIndex : 0xfc ''
+0x005 Flags : 0x4 ''
+0x006 UnusedBytes : 0x18 ''
+0x007 SegmentIndex : 0 ''
******************************************************************************
释放1500字节的堆块
******************************************************************************
释放后空闲链表
[000] 0x00150178:|F-00152dd0,B-00152dd0| <=0号链表中连接的数据块地址变为0x00152dd0,原始为0x001533f0,大小增大了0x620=1568个字节
[001] 0x00150180:|F-00150180,B-00150180| <=前后指针都指向自己所以1号链表为空
[002] 0x00150188:|F-00150188,B-00150188|
[003] 0x00150189:|F-00150190,B-00150190|
[004] 0x00150198:|F-00150198,B-00150198|
[005] 0x001501a0:|F-001501a0,B-001501a0| <=因为与16字节堆块相临所以发生了合并,此链表上的16字节的堆块被摘除
... |
[126] 0x00150568:|F-00150568,B-00150568|
[127] 0x00150570:|F-00150570,B-00150570|
0号空闲堆块头数据
00152dc8 47 02 46 00 fc 14 18 00 <=堆块头部信息
00152dd0 78 01 15 00 78 01 15 00 <=堆块用户可用部分
00152dd8 ee fe ee fe ee fe ee fe
00152de0 ee fe ee fe ee fe ee fe
...
0号空闲堆块头信息
ntdll!_HEAP_ENTRY
+0x000 Size : 0x247 <=堆块大小为0x1238=4664,恢复到了原始状态
+0x002 PreviousSize : 0x46
+0x000 SubSegmentCode : 0x00460247 Void
+0x004 SmallTagIndex : 0xfc ''
+0x005 Flags : 0x14 ''
+0x006 UnusedBytes : 0x18 ''
+0x007 SegmentIndex : 0 ''
******************************************************************************
一些总结
******************************************************************************
到这我们了解到了”调试友好“的堆在分配及回收时的一些信息,希望对以后想继续深入研究堆结构及内部实现的同学有所帮助。 因为边做实验边做记录,难免有疏漏,如果发现请不吝指出。谢谢!
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!