在有堆的基础认识之后,可以开始动手模拟与堆相关的调试技术。
1.堆简介
对的管理结构如下:
windows堆分为前端堆和后端堆,分配内存的过程如下:
释放内存的过程如下:
由于win7内核变化堆也产生了变化,使用如下代码验证分配和释放过程:
#include <windows.h>
#include <cstdio>
void __cdecl wmain (int argc, WCHAR* args[])
{
BYTE* pAlloc1=NULL;
BYTE* pAlloc2=NULL;
HANDLE hProcessHeap=GetProcessHeap();
printf("Heap addr:0x%08p\r\n",hProcessHeap);
pAlloc1=(BYTE*) HeapAlloc(hProcessHeap, 0, 16);
pAlloc2=(BYTE*) HeapAlloc(hProcessHeap, 0, 1500);
printf("Heap addr1:0x%08p\r\n",pAlloc1);
printf("Heap addr2:0x%08p\r\n",pAlloc2);
//
// Use allocated memory
//
HeapFree(hProcessHeap, 0, pAlloc1);
HeapFree(hProcessHeap, 0, pAlloc2);
}
在wmain入口处下断点,检查堆的情况:
分别对比查看两个对的内容:
SegmentList->heap地址加上firstentry的偏移可见heap_segment被合并到heap中了,而且segmentlist也使用链表而非数组来实现了。由于heap_segment结构被编码了,暂时还不知道如何解析,故而不能遍历这个list。只能通过freelist和heap中freetotal的变化来查看下内存的分配过程:
2.堆破坏
内存问题都是由于对不正确的地址执行访问和写入造成了预期之外的结果。对于堆破坏的调试的困难之处在于在破坏造成后果的现场通常不是堆被破坏的现场。还好操作系统为我们提供了足够的支持,我们可以使用页堆来尽可能早的发现堆破坏。我们准备如下代码用于调试堆的溢出。
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#define SZ_MAX_LEN 10
WCHAR* pszCopy = NULL ;
BOOL DupString(WCHAR* psz);
void __cdecl wmain (int argc, WCHAR* args[])
{
if(argc==2)
{
wprintf(L"Press any key to start\n");
getchar();
DupString(args[1]);
}
else
{
wprintf(L"Please enter a string");
}
}
BOOL DupString(WCHAR* psz)
{
BOOL bRet=FALSE;
if(psz!=NULL)
{
pszCopy=(WCHAR*) HeapAlloc(GetProcessHeap(), 0, SZ_MAX_LEN*sizeof(WCHAR));
if(pszCopy)
{
wcscpy(pszCopy, psz);
wprintf(L"Copy of string: %s", pszCopy);
HeapFree(GetProcessHeap(), 0, pszCopy);
bRet=TRUE;
}
}
return bRet;
}
开启不完全页堆
通过windbg执行程序:
收到异常消息,并查看出可能出问题的代码的范围:
不完全页堆不能在第一现场中断到调试器,但可以为缩小问题代码的问题提供有利的参考,而完全页堆大部分情况下能够在发生堆破坏的第一现场发出中断:
在程序的开发的整个周期中有必要使用AppVerifier来检验软件的正确性。