内存使用出错检测--PageHeap.Exe

Windows has triggered a breakpoint in xxx.exe

看到这个错误就可以推测是内存使用错了,但是这种错误很隐蔽。所以昨天花了一整天找到这个错误,最后发现是一个全局变量指针指向了一个非全局类实例的一个成员。当文档关闭时,这个类就销毁了,但是全局的指针还在,这个时候变成了野指针。而下一次打开文档时接着使用了,所以异常。

pageheap.exe工具帮了不少忙,在打开它后错误基本就能定位到了。在没用它之前,每次跳出异常的地方不确定,在各种new的地方都可能出现,让人极其郁闷。下面贴个pageheap.exe的使用说明。

 

推荐使用PageHeap.Exe和Gflags.Exe,主要的原因还是因为当有人问内存越界的错误如何查出来的时候,国外的朋友经常会推荐这两个工具(highly recommend)。我用过之后,也觉得有些时候用用还是有好处的。

PageHeap.Exe将针对某个指定的应用程序启用Page Heap标志,从而自动监视所有的malloc、new和heapAlloc的内存分配,找出内存错误。

PageHeap.Exe的下载地点:

http://download.microsoft.com/download/vc60pro/utility/6.0/win98/en-us/pageheap1.exe

下面我们简单地给出PageHeap使用步骤:

第一步:

在命令行中运行PageHeap.Exe。如果你以前设置过启用Global Page Heap标志,那么你将看到一个列表,给出所有已经启用了的应用程序的名字,不含路径。

如下所示:

C:\>pageheap

pgh.exe                                  enabled

testSplit.exe                            enabled


第二步:

编译一个小程序,其中有如下代码:

void main()
{
  int m_len = 5;
  char *m_p = (char *)HeapAlloc (GetProcessHeap (),    HEAP_ZERO_MEMORY, m_len);
  m_p[m_len] = 0;
  HeapFree (GetProcessHeap (),0, m_p);
}

Build出一个Debug版本。运行之,你看不到有任何异常的报告。

但其实m_p[m_len]=0这句话就是越界写了,因为只分配到了m_p[m_len-1]!这种情况就叫Dynamic memory overrun。用BoundsChecker是可以查到的。

这时,表面上看不出任何问题,但是一颗定时炸弹已经埋下了。


第三步:

在命令行中运行PageHeap /enable YourApplicationName.exe 0x01。

再运行一次不带参数的PageHeap,察看上面的命令是否生效。你的应用程序应该在启用的列表中。

注意:千万不要在YourApplication.Exe前面加上路径!!

0x01的含义在后面说明。


第四步:

再次运行你的程序。

你将会注意到在Output窗口的加载各种DLL之前,多了几句话:

Loaded exports for 'C:\WINNT\System32\ntdll.dll'
Page heap: process 0x57C created heap @ 00130000 (00230000, flags 0x1)
Loaded 'C:\WINNT\system32\MFC42D.DLL', no matching symbolic information found.
..
Loaded 'C:\WINNT\system32\MSVCP60D.DLL', no matching symbolic information found.
Page heap: process 0x57C created heap @ 00470000 (00570000, flags 0x1)
Loaded exports for 'C:\WINNT\system32\imm32.dll'

这就是Page Heap的监视机制在发挥作用!他告诉你你的堆00470000被创建出来了。

然后程序退出后,Output窗口有这么几句话表明一定有什么错误发生了:

Page heap: block @ 0015AFF8 is corrupted (reason 10)
Page heap: reason: corrupted suffix pattern
Page heap: process 0x57C destroyed heap @ 00471000 (00570000)
The thread 0x8A8 has exited with code 0 (0x0).

这说明在销毁堆00470000时遇到了麻烦,就是数据块0015AFF8被误用了,原因是误用了下标语法。看,说得多么清楚!也节省了许多翻来覆去查代码的工作!


PageHeap的使用中有几点值得注意:

1:启用PageHeap不能够影响正在运行中的应用程序。如果你需要启用一些正在运行且不能重启的程序的PageHeap,那请运行PageHeap启用后,重新启动机器。

2:要想查看PageHeap把信息放到哪里了,请打开你的注册表,来到HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

你将会看到你的应用程序也在这个项下面。你的应用程序的GlobalFlag被设置为了0x02000000,PageHeapFlags被设置为了0x01。

3:PageHeap的原理是这样,它在已分配的内存的后面放上几个守护字节(Guard Bytes),再跟上一个标记为PAGE_NOACCESS的内存页。这样,已分配内存的后面如果被重写了,那么守护字节就会被改变,于是当内存被释放时,PageHeap就会引发一个AV(Access Violation)。大体上就是这样。所以只有最后释放这块问题内存时,才会有PageHeap的报告!这就是PageHeap的局限性吧。

参数0x01的含义:

FLAGS hex value (0x...) has the following structure:

    B7-B0   Bit flags    1 - enable page heap

   01 - enable page heap. If zero normal heap is used. In 99% of the cases you will want this to be set.
   02 - collect stack traces (default on checked builds)
   04 - minimize memory impact
   08 - minimize randomly(1)/based on size range(0)
   10 - catch backward overruns

看到了吗?你还可以设置参数为0x10,从而可以检查内存向前的越界写!

Gflags.Exe是微软的Debugging Tools里面的工具。在Windows 2000的Resource Kit中也可以找得到。我们也可以用它来完成和PageHeap相同的任务。当然,Gflags.EXE还能做许许多多其他的事情。这里我们就不介绍了,总之物超所值。

具体的使用办法是:

1)     运行Gflags.Exe;

2)  你将看到一个对话框。在”Image File”的编辑框中写下你的应用程序的名字,如YourApp.Exe。注意不要路径!

3)  选择”Image File Options”的单选钮;

4)  这时,你会看到对话框的内容突然一变。选中“Place heap
allocations at ends of pages”前的复选框。

5)  点击Apply按钮。

这样,就达到了PageHeap的效果。现在运行你的程序,overwrite你的堆,就应该生成一个AV了!


(请结合查看微软KB:SAMPLE: PageHeap1.exe Finds Heap Corruption and Memory Errors (Q264471))

 

其他人也遇到过这个问题,贴一些他们的分享:

过程是这样的,在vc debug的过程中,突然弹出了一个assert窗口:

Windows has triggered a breakpoint in cs.exe.

This may be due to a corruption of the heap, which indicates a bug in cs.exe or any of the DLLs it has loaded.

This may also be due to the user pressing F12 while cs.exe has focus.

查看output输出:

Heap missing last entry in committed range near e6****630

Windows has triggered a breakpoint in cs.exe.

This may be due to a corruption of the heap, which indicates a bug in cs.exe or any of the DLLs it has loaded.

不废话,直接查看该内存是什么:

0×0E6****630 0e 00 0f 00 59 03 10 04 aa aa cd ab 00 10 72 85 38 00

看不出什么,往上再看看:

0×0E6****60C 07 e2 04 00 a8 01 66 0e 01 00 cd cd fd fd fd fd….

看来这就是块堆内存,实在看不出什么,决定暂时忽略. 过了几天,该错误没有再出现过.

但就在交付程序前一天测试中,又出现了这个错误.看来一颗定时炸弹是埋藏在了程序中.必须找出来,否则后果很严重.

首先要做的就是要重现这个问题,如果每次碰巧遇到这个问题的话,实在无从下手.从症状来看,是个典型的Heap Corrupt.所以先使用工具:pageheap,以做到能够百分百重现那个堆破坏.

page heap工具启用的两种方法:Full-page heap和Normal page heap

我先使用了Normal page heap 命令为pageheap /enable xx.exe /normal

然后通过windbg设置符号表:x:\symbols_folder;srv*x:\symbols_folder*http://msdl.microsoft.com/download/symbols

开始各种调试,几乎用遍了<>这本书中指导的各种命令,(调试过程省略2000字)未果.几乎崩溃时,突然看到书上的这么一段话:

普通页堆通过填充模式检测堆块破坏,需要再发生堆破坏之后再调用一次堆管理器.

完全页堆除了特定的填充模式外,它还为每个堆块增加了防护页,防护页是一个页不可访问的内存,它被放置在堆块的起始位置和结束位置上.防止堆块上下溢出.

我靠,直接pageheap /enable xx.exe /full 设置完全页堆模式.重新运行程序.

瞬间程序崩掉,这个激动呀.还是第一次看见程序崩掉那么激动,查看崩溃的代码:

原来是把一个类指针强转为另一个子类的指针,调用了子类的一个函数,但是该类不存在这个函数.同时这个子类函数里面做了一次bool变量赋值,结果这个值就写到了堆地址里面去了.

总结

以后遇到类似问题,直接pageheap full 全开.

选自:http://hi.baidu.com/wayright/item/13dbe1b574763975254b09f2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章