微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

win2000堆的调试

0day安全》读书调试笔记

 

一.堆和栈的区别

 

 

二.堆的数据结构

 

堆块:出于性能的考虑,堆区的内存按不同大小组织成块,以堆块为单位进行标识,而不是传统的按字节标识。一个堆块包括两个部分,块首和块身。块首是一个堆块头部的几个字节,用来标识这个堆块自身的信息,例如,本块的大小、本块空闲还是占用等信息;块身是紧跟在块首后面的部分,也是最终分配给用户使用的数据区。


堆表:堆表一般位于堆区的起始位置,用于索引堆区中所有堆块的重要信息,包括堆块的位置、堆块的大小、空闲还是占用等。堆表的数据结构决定了整个堆区的组织方式,是快速检索空闲块、保证堆分配效率的关键。堆表在设计时可能会考虑采用平衡二叉树等高级数据结构用于优化查找效率。现代操作系统的堆表往往不止一种数据结构。
在Windows中,占用态的堆块被使用它的程序索引,而堆表只索引所有空闲态的堆块。其中,最重要的堆表有两种:空闲双向链表Freelist(简称空表)和快速单向链表Lookaside(简称快表)。


1.空表
空闲堆块的块首中包含一对重要的指针,这对指针用于将空闲堆块组织成双向链表。按照堆块的大小不同,空表总共被分为128条。 空表索引的每一项标识了堆中大小为(索引项x8字节)的空闲堆块。把空闲堆块按照大小的不同链入不同的空表,可以方便堆管理系统高效检索指定大小的空闲堆块。空表索引free[0]所标识的空表很特殊,这条双向链表链入了所有大于等于1024字节的堆块(小于512KB),这些堆块按照各自的大小在零号空表中升序地依次排列下去。

 

2.快表
快表是Windows用来加速堆块分配而采用的一种堆表。快表也有128条,组织结构与空表类似,只是其中的堆块按照单链表组织。快表总是被初始化为空,而且每条快表最多只有4个结点,故很快就会被填满。

 

 

三.堆的管理策略

1.堆块分配
堆块分配可以分为三类:快表分配、普通空表分配和零号空表(free[0])分配。
(1)从快表中分配堆块比较简单,包括寻找到大小匹配的空闲堆块、将其状态设置为占用态、把它从堆表中卸下、返回一个指向堆块块身的指针给程序使用。
(2)普通空表分配时首先寻找最优的空闲块分配,若失败,则寻找次优的空闲快分配,即最小的能够满足要求的空闲快。
(3)零号空表中按照大小升序链着大小不同的空闲块,故在分配时先从free[0]反向查找最后一个块(表中最大块),看能否满足要求,如果能满足要求,再正向搜索最小能够满足要求的空闲块进行分配(这就明白为什么零号空表要按照升序排列了)。
堆块分配中的“找零钱”现象:当空表中无法找到匹配的最优堆块时,一个稍大的块会被分配,这种次优分配发生时,会先从大块中按请求的大小精确的割出一块进行分配,然后给剩下的部分重新标注块首,链入空表。由于快表只有在精确匹配时才会分配,故不存在这种现象。
(这里没有讨论堆缓存,低碎片堆和虚分配) 


2.堆块释放
释放堆块的操作包括将堆块状态改为空闲,链入相应的堆表。所有的释放块都链入堆表的末尾,分配的时候也先从堆表末尾拿。另外需要强调,快表最多只有4项。   


3.堆块合并
经过反复的申请与释放操作,堆区很可能产生很多内存碎片。为了合理有效地利用内存,堆管理系统还要能够进行堆块合并操作。当堆管理系统发现两个空闲块彼此相邻时,就会进行堆块合并,包括将两个块从空闲表中卸下、合并、调整合并后大块的块首信息、将新块重新链入空闲链表。
具体的堆块分配和释放,把内存块按大小分三类:
小块,SIZE<1KB
大块,1KB<=SIZE<=512KB
巨块,SIZE>=512KB
对应的分配和释放算法有三类。

最后,再强调一下Windows堆管理的几个要点。
(1)快表中的空闲块被设置为占用态,故不会发生堆块合并操作。
(2)快表只有精确匹配时才会分配,不存在“搜索次优解”和“找零钱”现象。
(3)快表是单链表,操作比双链表简单,插入删除都少用很多指令。
(4)综上所述,快表很“快”,故在分配和释放时总是优先使用快表,失败时才用空表。
(5)快表只有4项,很容易被填满,因此空表也是频繁使用的。

 


四、堆分配函数间的调用关系

所有的堆分配函数最终都将使用位于ntdll.dll中的RtlAllocateHeap()函数进行分配,这个函数也是在用户态能够看到的最底层的堆分配函数

 

 

五、堆的工作方式之空表

这一节通过调试一段简单的程序验证堆的空表的管理策略。下一节验证堆的快表的管理策略。程序中用到的函数在MSDN的说明如下。
HANDLE WINAPI HeapCreate(
  _In_ DWORD  flOptions,
  _In_ SIZE_T dwInitialSize,
  _In_ SIZE_T dwMaximumSize
);
函数功能
创建可由调用进程使用的私有堆对象。该函数保留进程的虚拟地址空间中的空间,并为该块的指定初始部分分配物理存储。
参数说明:
flOptions:堆分配选项。这些选项通过调用函数影响对新堆的后续访问。在这里设为0就可以了。
dwInitialSize:堆的初始大小(以字节为单位)。此值确定为堆提交的初始内存量。值将舍入为系统页大小的倍数。该值必须小于dwMaximumSize。如果此参数为0,则函数提交一页。
dwMaximumSize:堆的最大大小(以字节为单位)。 HeapCreate函数将dwMaximumSize四舍五入为系统页大小的倍数,然后在进程的堆的虚拟地址空间中保留该大小的块。如果由HeapAlloc或HeapReAlloc函数发出的分配请求超过dwInitialSize指定的大小,系统将为堆提交额外的内存页,直到堆的最大大小。如果dwMaximumSize不为0,则堆大小是固定的,不能超过最大大小。如果dwMaximumSize为0,则堆的大小可能会增大。堆的大小只受可用内存的限制。需要分配大内存块的应用程序应将dwMaximumSize设置为0。
返回值:
如果函数成功,返回值是新创建的堆的句柄。如果函数失败,返回值为NULL。要获取扩展错误信息,请调用GetLastError。


LPVOID WINAPI HeapAlloc(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ SIZE_T dwBytes
);
函数功能
从堆中分配一块内存。分配的内存不可移动。
参数说明:
hHeap:将从其分配内存的堆的句柄。此句柄由HeapCreate或GetProcessHeap函数返回。
dwFlags:堆分配选项。指定任何这些值将覆盖使用HeapCreate创建堆时指定的相应值。在这里设为HEAP_ZERO_MEMORY把分配的内存初始化为零就可以了。
dwBytes:要分配的字节数。
返回值:
如果函数成功,返回值是指向分配的内存块的指针。如果函数失败并且尚未指定HEAP_GENERATE_EXCEPTIONS则返回值为NULL。


BOOL WINAPI HeapFree(
  _In_ HANDLE hHeap,
  _In_ LPVOID lpMem
);
函数功能
释放由HeapAlloc或HeapReAlloc函数从堆分配的内存块。
参数说明:
hHeap:要释放其内存块的堆的句柄。 此句柄由HeapCreate或GetProcessHeap函数返回。
dwFlags:指定几个可控释放的内存块。在这里设为0就可以了。
lpMem:指向要释放的内存块的指针。 此指针由HeapAlloc或HeapReAlloc函数返回。如果此指针为NULL,则行为未定义。
返回值:
如果函数成功,返回值为非零。如果函数失败,返回值为零。 应用程序可以调用GetLastError以获取扩展错误信息。

 

 

六、堆中空表的调试

#include <windows.h>
int main()
{
        HLOCAL h1,h2,h3,h4,h5,h6;
        HANDLE hp;
        hp = HeapCreate(0,0x1000,0x10000);
		__asm int 3
        h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
        h2 = HeapAlloc(hp,5);
        h3 = HeapAlloc(hp,6);
        h4 = HeapAlloc(hp,8);
        h5 = HeapAlloc(hp,19);
        h6 = HeapAlloc(hp,24);
        //free block and prevent coaleses
        HeapFree(hp,h1); //free to freelist[2] 
        HeapFree(hp,h3); //free to freelist[2] 
        HeapFree(hp,h5); //free to freelist[4]         
        HeapFree(hp,h4); //coalese h3,link the large block to freelist[8]
        return 0;
}

调试堆与调试栈不同,不能直接用调试器加载程序,否则堆管理函数会检测到当前进程处于调试状态,而使用调试态堆管理策略。调试态堆管理策略与常态堆管理策略有很大 的不同。为了能够看到真实的堆,在程序中创建堆之后加入一个人工断点,中断程序后再用调试器attach进程。

把OllyDbg设置成认的调试器,点击取消按钮将自动打开OllyDbg并attach进程。

单击OllyDbg中M按钮,可以得到当前的内存映射状态。HeapCreate成功创建堆区后会把整个堆区的起始地址返回给EAX,在这里是00360000。

 

观察堆块之前先介绍一下堆块的块首中数据的含义。占用态堆块的结构如图所示。

空闲态堆块和占用态堆块的块首结构基本一致,只是将块首后数据区的前8个字节用于存放空表指针了。这8个字节在变回占用态时将重新分回块身用于存放数据。

 

以下数据分析  蓝色表示内存地址    红色表示堆块数量    紫色表示初始化清零   绿色表示块首重的Unused bytes标志

堆表中包含的信息依次是段表索引、虚表索引、空表使用标识和空表索引区。
空表索引区:偏移0x178
指向快表的指针:偏移0x584
0x00360178指向0x00360688,而接下来其它的索引都是指向自身。
00360170  00 00 00 00 00 00 00 00 88 06 36 00 88 06 36 00  
00360180  80 01 36 00 80 01 36 00 88 01 36 00 88 01 36 00  
00360190  90 01 36 00 90 01 36 00 98 01 36 00 98 01 36 00  
……
0x00360688指向0x00360178。
00360680  30 01 08 00 00 10 00 00 78 01 36 00 78 01 36 00
根据块首信息表,这个堆块的大小为0x0130,计算单位是8个字节,也就是0x980个字节。

注意堆块的单位字节是8字节 和00360682的0008无关  00360682是PrevIoUs chunk size 算法中用到的数据

对于堆块的分配我们应该了解以下细节。

1.堆块的大小包括了块首在内,即如果请求32字节,实际会分配的堆块为40字节。
2.堆块的单位是8字节,不足8字节的部分按8字节分配。
3.初始状态下,快表和空表都为空,不存在精确分配。
实际分配情况如图所示。


第一次分配后。

现在:00360020  00 02 00 00 00 20 00 00 2E 01 00 00 FF EF FD 7F 
原来:00360020  00 02 00 00 00 20 00 00 30 01 00 00 FF EF FD 7F
内存0x00360028处的0x00000130变成了0x0000012E,减小了2个堆单位,这个数字的意思就是尾块还有多少可以分配的空间。
现在:00360170  00 00 00 00 00 00 00 00 98 06 36 00 98 06 36 00 
原来:00360170  00 00 00 00 00 00 00 00 88 06 36 00 88 06 36 00
0x00360178指向的是尾块,现在0x00360178处的指针变成了0x00360698,也就是说现在尾块的起始位置是0x00360690。
现在:
00360670  88 05 36 00 00 00 00 00 90 06 36 00 00 00 00 00  
00360680  02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690  2E 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00  
原来:
00360670  88 05 36 00 00 00 00 00 80 06 36 00 00 00 00 00  
00360680  30 01 08 00 00 10 00 00 78 01 36 00 78 01 36 00  
00360690  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
分配第一次后0x00360680的值,也就是刚分配的空间,前面8字节是块首,0x00360680的0x0002表示两个堆单位。尾块块首的前两个字节是代表堆块的数量,所以0x00360690的0x012E是现在未分配堆块的数量,0x12E*0x8=0x970 比前面的0x980正好少了0x10个字节


第二次分配后。

现在:00360020  00 02 00 00 00 20 00 00 2C 01 00 00 FF EF FD 7F
原来:00360020  00 02 00 00 00 20 00 00 2E 01 00 00 FF EF FD 7F 
现在:00360170  00 00 00 00 00 00 00 00 A8 06 36 00 A8 06 36 00 
原来:00360170  00 00 00 00 00 00 00 00 98 06 36 00 98 06 36 00 
现在:
00360670  88 05 36 00 00 00 00 00 A0 06 36 00 00 00 00 00  
00360680  02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690  02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0  2C 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00 
原来:
00360670  88 05 36 00 00 00 00 00 90 06 36 00 00 00 00 00  
00360680  02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690  2E 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00  
003606A0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x00360690和0x00360680除了块首有所不同以外,数据区有一位也变成了0,因为我们要求的是5个字节初始化为0,而上一次的3个字节本来就是0。


第三次分配后。

现在:00360020   00 02 00 00 00 20 00 00 2A 01 00 00 FF EF FD 7F
原来:00360020   00 02 00 00 00 20 00 00 2C 01 00 00 FF EF FD 7F
现在:00360170   00 00 00 00 00 00 00 00 B8 06 36 00 B8 06 36 00
原来:00360170   00 00 00 00 00 00 00 00 A8 06 36 00 A8 06 36 00 
现在:
00360670   88 05 36 00 00 00 00 00 B0 06 36 00 00 00 00 00 
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   2A 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00 
原来:
00360670  88 05 36 00 00 00 00 00 A0 06 36 00 00 00 00 00 
00360680  02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690  02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0  2C 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00 
003606B0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


第四次分配后。

现在:00360020   00 02 00 00 00 20 00 00 28 01 00 00 FF EF FD 7F 
原来:00360020   00 02 00 00 00 20 00 00 2A 01 00 00 FF EF FD 7F
现在:00360170   00 00 00 00 00 00 00 00 C8 06 36 00 C8 06 36 00
原来:00360170   00 00 00 00 00 00 00 00 B8 06 36 00 B8 06 36 00 
现在:
00360670   88 05 36 00 00 00 00 00 C0 06 36 00 00 00 00 00  
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00  
003606C0   28 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00 
原来:
00360670   88 05 36 00 00 00 00 00 B0 06 36 00 00 00 00 00  
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   2A 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00  
003606C0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


第五次分配后。

现在:00360020   00 02 00 00 00 20 00 00 24 01 00 00 FF EF FD 7F
原来: 00360020   00 02 00 00 00 20 00 00 28 01 00 00 FF EF FD 7F 
现在:00360170   00 00 00 00 00 00 00 00 E8 06 36 00 E8 06 36 00
原来:00360170   00 00 00 00 00 00 00 00 C8 06 36 00 C8 06 36 00
现在:
00360670   88 05 36 00 00 00 00 00 E0 06 36 00 00 00 00 00 
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00  
003606C0   04 00 02 00 00 01 0D 00 00 00 00 00 00 00 00 00  
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
003606E0   24 01 04 00 00 10 00 00 78 01 36 00 78 01 36 00 
原来:
00360670   88 05 36 00 00 00 00 00 C0 06 36 00 00 00 00 00  
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00 
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00  
003606C0   28 01 02 00 00 10 00 00 78 01 36 00 78 01 36 00  
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
003606E0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


第六次分配后。

现在:00360020   00 02 00 00 00 20 00 00 20 01 00 00 FF EF FD 7F
原来:00360020   00 02 00 00 00 20 00 00 24 01 00 00 FF EF FD 7F
现在:00360170   00 00 00 00 00 00 00 00 08 07 36 00 08 07 36 00
原来:00360170   00 00 00 00 00 00 00 00 E8 06 36 00 E8 06 36 00
现在:
00360670   88 05 36 00 00 00 00 00 00 07 36 00 00 00 00 00  
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00  
003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00  
003606C0   04 00 02 00 00 01 0D 00 00 00 00 00 00 00 00 00 
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
003606E0   04 00 04 00 00 01 08 00 00 00 00 00 00 00 00 00  
003606F0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00360700   20 01 04 00 00 10 00 00 78 01 36 00 78 01 36 00  
原来:
00360670   88 05 36 00 00 00 00 00 E0 06 36 00 00 00 00 00 
00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00 
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00 
003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00
003606C0   04 00 02 00 00 01 0D 00 00 00 00 00 00 00 00 00
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
003606E0   24 01 04 00 00 10 00 00 78 01 36 00 78 01 36 00 
003606F0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00360700   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  


第一次释放后。

现在:00360020   00 02 00 00 00 20 00 00 22 01 00 00 FF EF FD 7F 
原来:00360020   00 02 00 00 00 20 00 00 20 01 00 00 FF EF FD 7F 
现在:00360150   00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 
原来:00360150   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
现在:00360180   80 01 36 00 80 01 36 00 88 06 36 00 88 06 36 00
原来:00360180   80 01 36 00 80 01 36 00 88 01 36 00 88 01 36 00
0x00360188是Freelist[2]的地址,变为了0x00360688,是h1申请的空间。
原来:00360680   02 00 08 00 00 01 0D 00 00 00 00 00 78 01 36 00 
现在:00360680   02 00 08 00 00 00 0D 00 88 01 36 00 88 01 36 00 
0x00360688由占用态变回空闲态。Unused bytes发生变化,原来用于存放数据的地方现在成为了指针。

 

第二次释放后。

现在:00360020   00 02 00 00 00 20 00 00 24 01 00 00 FF EF FD 7F
原来:00360020   00 02 00 00 00 20 00 00 22 01 00 00 FF EF FD 7F 
现在:00360180   80 01 36 00 80 01 36 00 88 06 36 00 A8 06 36 00
原来:00360180   80 01 36 00 80 01 36 00 88 06 36 00 88 06 36 00
现在:
00360680   02 00 08 00 00 00 0D 00 A8 06 36 00 88 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 00 0A 00 88 01 36 00 88 06 36 00
原来:
00360680   02 00 08 00 00 00 0D 00 88 01 36 00 88 01 36 00  
00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
003606A0   02 00 02 00 00 01 0A 00 00 00 00 00 00 00 36 00
观察一下这几组值,0x00360188指向了空闲堆块的头和尾,构成了一个双向链表。
0x00360188:0x003606A8 0x00360688
0x00360688:0x003606A8 0x00360188
0x003606A8:0x00360688 0x00360188 


第三次释放后。

现在:00360020   00 02 00 00 00 20 00 00 28 01 00 00 FF EF FD 7F 
原来:00360020   00 02 00 00 00 20 00 00 24 01 00 00 FF EF FD 7F 
现在:00360150   00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 
原来:00360150   00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 
现在:00360190   90 01 36 00 90 01 36 00 C8 06 36 00 C8 06 36 00
原来:00360190   90 01 36 00 90 01 36 00 98 01 36 00 98 01 36 00 
0x00360198是Freelist[4],变为了0x003606C8,是h5申请的空间。
现在:
003606C0   04 00 02 00 00 00 0D 00 98 01 36 00 98 01 36 00  
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
原来:
003606C0   04 00 02 00 00 01 0D 00 00 00 00 00 00 00 00 00  
003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  

0x003606C8由占用态变回空闲态。Unused bytes发生变化,原来用于存放数据的地方现在成为了指针。


第四次释放后。

现在:00360020   00 02 00 00 00 20 00 00 2A 01 00 00 FF EF FD 7F 
原来:00360020   00 02 00 00 00 20 00 00 28 01 00 00 FF EF FD 7F
现在:00360150   00 00 00 00 00 00 00 00 04 01 00 00 00 00 00 00 
原来:00360150   00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00
现在:
00360180   80 01 36 00 80 01 36 00 88 06 36 00 88 06 36 00 
00360190   90 01 36 00 90 01 36 00 98 01 36 00 98 01 36 00 
003601B0   B0 01 36 00 B0 01 36 00 A8 06 36 00 A8 06 36 00
原来:
00360180   80 01 36 00 80 01 36 00 88 06 36 00 A8 06 36 00 
00360190   90 01 36 00 90 01 36 00 C8 06 36 00 C8 06 36 00 
003601B0   B0 01 36 00 B0 01 36 00 B8 01 36 00 B8 01 36 00 
0x00360188是Freelist[2] 只剩一个0x00360688,0x00360198是Freelist[4] 没了,0x003601B8是FreeList[8] 变为了0x003606A8。

现在:

原来:
h1:00360680   02 00 08 00 00 00 0D 00 A8 06 36 00 88 01 36 00(已释放)  
h2:00360690   02 00 02 00 00 01 0B 00 00 00 00 00 00 01 36 00  
h3:003606A0   02 00 02 00 00 00 0A 00 88 01 36 00 88 06 36 00(已释放)
h4:003606B0   02 00 02 00 00 01 08 00 00 00 00 00 00 00 00 00  
h5:003606C0   04 00 02 00 00 00 0D 00 98 01 36 00 98 01 36 00(已释放)
       003606D0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
h6:003606E0   04 00 04 00 00 01 08 00 00 00 00 00 00 00 00 00  
       003606F0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
这就是堆块合并的过程。堆块合并可以更加有效地利用内存,但往往需要修改多处指针,也是一个费时的工作。因此,堆块合并只发生在空表中。在强调分配效率的快表中,堆块合并一般会被通过设置堆块为占用态来禁止。另外,空表中的第一个块不会向前合并,最后一个块不会向后合并。

 

 

七、堆中快表的调试

为了观察堆中快表的工作方式,在创建堆时使用hp=HeapCreate(0,0)语句,这样创建的堆是可扩展的,而只有可扩展的堆才会启用快表。

#include<stdio.h>
#include<windows.h>
int main()
{
	HLOCAL h1,h4;
	HANDLE hp;
	hp=HeapCreate(0,0);
	_asm int 3
	h1=HeapAlloc(hp,8);
	h2=HeapAlloc(hp,8);
	h3=HeapAlloc(hp,16);
	h4=HeapAlloc(hp,24);
	HeapFree(hp,h1);
	HeapFree(hp,h2);
	HeapFree(hp,h3);
	HeapFree(hp,h4);
	h2=HeapAlloc(hp,16);
	HeapFree(hp,h2);
	return 0;	
} 

先看一下初始化的堆区
00360020  00 02 00 00 00 20 00 00 2F 02 00 00 FF EF FD 7F 
尾块有0x0000022F个堆单位可分配。

00360170  00 00 00 00 00 00 00 00 90 1E 36 00 90 1E 36 00 
0x00360178任然是指向空表空表索引Freelist[0]  它指向0x00361E90,表示尾块数据区的起始位置。

00360680 01 03 08 00 00 01 08 00 00 00 00 00 00 00 00 00 
0x00360688 快表的起始位置,原来未使用快表的时候这里是空表尾块的起始位置。
堆刚初始化后快表是空的 当我们反复申请释放内存后 会加入快表 这样就能看到快表的使用情况了

0x00360688开始的0x30是快表头结构
0x003606B8开始的0x30是Lookaside[0]
0x003606E8开始的0x30是Lookaside[1]
0x00360718开始的0x30是Lookaside[2]
0x00360748开始的0x30是Lookaside[3]

 

第一次分配后

现在:00360020  00 02 00 00 00 20 00 00 2D 02 00 00 FF EF FD 7F
原来:00360020  00 02 00 00 00 20 00 00 2F 02 00 00 FF EF FD 7F
可以看到,0x0000022F变成了0x0000022D,减小了2个堆单位。

现在:00360170  00 00 00 00 00 00 00 00 A0 1E 36 00 A0 1E 36 00
原来:00360170  00 00 00 00 00 00 00 00 90 1E 36 00 90 1E 36 00
0x000360178的Freelist[0]由0x00361E90变成了0x00361EA0,因为尾块划分16个字节分配给了h1使用。

现在:
00361E80  00 00 00 00 00 00 00 00 02 00 01 03 00 01 08 00 
00361E90  00 00 00 00 00 00 00 00 2D 02 02 00 00 10 00 00
00361EA0  78 01 36 00 78 01 36 00 00 00 00 00 00 00 00 00
原来:
00361E80  00 00 00 00 00 00 00 00 2F 02 01 03 00 10 00 00
00361E90  78 01 36 00 78 01 36 00 00 00 00 00 00 00 00 00 
00361EA0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
现在0x00361E88开始被分配2个堆块单位 前8字节是块首 其中0x0002表示2个堆块单位
0x00361E90开始的8个0x00 表示程序申请h1的8字节清零

 

第二次分配后

现在:00360020 00 02 00 00 00 20 00 00 2B 02 00 00 FF EF FD 7F
原来:00360020 00 02 00 00 00 20 00 00 2D 02 00 00 FF EF FD 7F 
可以看到,0x0000022D变成了0x0000022B,减小了2个堆单位。

现在:00360170  00 00 00 00 00 00 00 00 B0 1E 36 00 B0 1E 36 00
原来:00360170  00 00 00 00 00 00 00 00 A0 1E 36 00 A0 1E 36 00 
0x000360178的Freelist[0]由0x00361EA0变成了0x00361EB0,因为尾块划分16个字节分配给了h2使用。

现在:
00361E90  00 00 00 00 00 00 00 00 02 00 02 00 00 01 08 00 
00361EA0  00 00 00 00 00 00 00 00 2B 02 02 00 00 10 00 00 
00361EB0  78 01 36 00 78 01 36 00 00 00 00 00 00 00 00 00 
原来:
00361E90  00 00 00 00 00 00 00 00 2D 02 02 00 00 10 00 00  
00361EA0  78 01 36 00 78 01 36 00 00 00 00 00 00 00 00 00  
00361EB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
现在0x00361E98开始被分配2个堆块单位 前8字节是块首 其中0x0002表示2个堆块单位
0x00361EA0开始的8个0x00 表示程序申请h2的8字节清零


第三次分配后

现在:00360020  00 02 00 00 00 20 00 00 28 02 00 00 FF EF FD 7F 
原来:00360020  00 02 00 00 00 20 00 00 2B 02 00 00 FF EF FD 7F
可以看到,0x0000022B变成了0x00000228,减小了3个堆单位。

现在:00360170  00 00 00 00 00 00 00 00 C8 1E 36 00 C8 1E 36 00 
原来:00360170  00 00 00 00 00 00 00 00 B0 1E 36 00 B0 1E 36 00 
0x000360178的Freelist[0]由0x00361EB0变成了0x00361EC8,因为尾块划分24个字节分配给了h3使用。

现在:
00361EA0  00 00 00 00 00 00 00 00 03 00 02 00 00 01 08 00 
00361EB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00361EC0  28 02 03 00 00 10 00 00 78 01 36 00 78 01 36 00 
原来:
00361EA0  00 00 00 00 00 00 00 00 2B 02 02 00 00 10 00 00  
00361EB0  78 01 36 00 78 01 36 00 00 00 00 00 00 00 00 00  
00361EC0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
现在0x00361EA8开始被分配3个堆块单位 前8字节是块首 其中0x0003表示3个堆块单位
0x00361EB0开始的16个0x00 表示程序申请h3的16字节清零


第四次分配后

现在:00360020  00 02 00 00 00 20 00 00 24 02 00 00 FF EF FD 7F
原来:00360020  00 02 00 00 00 20 00 00 28 02 00 00 FF EF FD 7F 
可以看到,0x00000228变成了0x00000224,减小了4个堆单位。

现在:00360170  00 00 00 00 00 00 00 00 E8 1E 36 00 E8 1E 36 00
原来:00360170  00 00 00 00 00 00 00 00 C8 1E 36 00 C8 1E 36 00
0x000360178的Freelist[0]由0x00361EC8变成了0x00361EE8,因为尾块划分32个字节分配给了h4使用

现在:
00361EC0  04 00 03 00 00 01 08 00 00 00 00 00 00 00 00 00 
00361ED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00361EE0  24 02 04 00 00 10 00 00 78 01 36 00 78 01 36 00
原来:
00361EC0  28 02 03 00 00 10 00 00 78 01 36 00 78 01 36 00  
00361ED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00361EE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
现在0x00361EC0开始被分配4个堆块单位 前8字节是块首 其中0x0004表示4个堆块单位
0x00361EC8开始的24个0x00 表示程序申请h4的24字节清零
至此前面四次分配就全部结束了。


第一次释放后

00360020 00 02 00 00 00 20 00 00 24 02 00 00 FF EF FD 7F
可分配空间为0x00000224个堆单位,没有变化,因为释放刚好16个字节,优先放入快表。

现在:
003606E0  00 00 00 00 00 00 00 00 90 1E 36 00 01 00 01 00 
003606F0  04 00 00 01 02 00 00 00 02 00 00 00 01 00 00 00 
原来:
003606E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
003606F0  04 00 00 01 02 00 00 00 02 00 00 00 00 00 00 00  
0x003606E8是Lookaside[1] 变成了0x00361E90,h1释放后串在了lookaside[1]上。


第二次释放后

现在:
003606E0  00 00 00 00 00 00 00 00 A0 1E 36 00 02 00 02 00 
003606F0  04 00 00 01 02 00 00 00 02 00 00 00 02 00 00 00  
原来:
003606E0  00 00 00 00 00 00 00 00 90 1E 36 00 01 00 01 00
003606F0  04 00 00 01 02 00 00 00 02 00 00 00 01 00 00 00 
0x003606E8是Lookaside[1]  从0x00361E90变成了0x00361EA0。

现在:00361EA0  90 1E 36 00 00 00 00 00 03 00 02 00 00 01 08 00  
原来:00361EA0  00 00 00 00 00 00 00 00 03 00 02 00 00 01 08 00
0x00361EA0指向了0x00361E90,相当于Lookaside[1]串着h2,h2后面串着h1。


第三次释放后

现在:
00360710  00 00 00 00 00 00 00 00 B0 1E 36 00 01 00 01 00 
00360720  04 00 00 01 01 00 00 00 01 00 00 00 01 00 00 00  
原来:
00360710  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
00360720  04 00 00 01 01 00 00 00 01 00 00 00 00 00 00 00  
0x00360718是Lookaside[2] 变成了0x00361EB0,h3释放后串在了lookaside[2]上。


第四次释放后

现在:
00360740  00 00 00 00 00 00 00 00 C8 1E 36 00 01 00 01 00 
00360750  04 00 00 01 01 00 00 00 01 00 00 00 01 00 00 00  
原来: 
00360740  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00360750  04 00 00 01 01 00 00 00 01 00 00 00 00 00 00 00 
0x00360748是Lookaside[3]变成了0x00361EC8,h4释放后串在了lookaside[3]上。

看一下现在堆块的状态。
00361E80  00 00 00 00 00 00 00 00 02 00 01 03 00 01 08 00 
00361E90  00 00 00 00 00 00 00 00 02 00 02 00 00 01 08 00  
00361EA0  90 1E 36 00 00 00 00 00 03 00 02 00 00 01 08 00
00361EB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
00361EC0  04 00 03 00 00 01 08 00 00 00 00 00 00 00 00 00  
00361ED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00361EE0  24 02 04 00 00 10 00 00 78 01 36 00 78 01 36 00  
每个申请的堆块状态都是0x01 Busy状态
h2释放后从清零数据变为指向h1的地址0x00361E90  


再次申请空间

现在:
00360710  00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00
00360720  04 00 00 01 02 00 00 00 01 00 00 00 01 00 00 00 
原来:
00360710  00 00 00 00 00 00 00 00 B0 1E 36 00 01 00 01 00  
00360720  04 00 00 01 01 00 00 00 01 00 00 00 01 00 00 00 
0x00360718是Lookaside[2],它后面串着的堆块已经不见了,因为如果是8的整数倍的空间申请,优先使用快表。


再次释放空间

现在:
00360710  00 00 00 00 00 00 00 00 B0 1E 36 00 01 00 03 00  
00360720  04 00 00 01 02 00 00 00 01 00 00 00 02 00 00 00  
原来:
00360710  00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 
00360720  04 00 00 01 02 00 00 00 01 00 00 00 01 00 00 00 
可以看到,释放的16字节堆块又串在了0x00360718的Lookaside[2]上 

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐