《Windows用户态程序高效排错》第二章主要介绍用户态调试相关的知识和工具。本文主要讲了PageHeap,调试Heap问题的工具.

AD:51CTO学院:IT精品课程在线看!

2.4.2  PageHeap,调试Heap问题的工具

幸运的是,Heap Manager的确提供了主动检查错误的功能。只需要在注册表里面做对应的修改,操作系统就会根据设置来改变Heap Manager的行为。Pageheap是用来配置该注册表的工具。关于heap的详细信息和原理请参考:

How to use Pageheap.exe in Windows XP and Windows 2000
http://support.microsoft.com/kb/286470/en-us

Pageheap,Gflag和后面介绍的Application Verifier工具一样,都是方便修改对应注册表的工具。如果不使用这两个工具,直接修改注册表也可以达到一样的效果。3个工具里面Application Verifier是目前的主流,Gflag是老牌。除了heap问题外,这两个工具还可以修改其他的调试选项,后面都有说明。Pageheap.exe工具主要针对heap问题,使用起来简单方便。目前gflag.exe包含在调试器的安装包中,Application Verifier可以单独下载安装。如果调试安装包中没有包含pageheap.exe,可以从这里下载:

http://www.heijoy.com/debugdoc/pageheap.zip
http://blogs.msdn.com/lixiong/attachment/2792912.ashx

简单例子的多种情况

看几个简单的但是却很有意义的例子:

用release模式编译运行下面的代码:

 char *p=(char*)malloc(1024);
p[1024]=1;

这里往分配的空间多写一个字节。但是在release模式下运行,程序不会崩溃。

假设上面的代码编译成mytest.exe,用下面的方法可以对mytest.exe激活pageheap:

C:\Debuggers\pageheap>pageheap /enable mytest.exe /full    

直接运行pageheap可以查看当前pageheap的激活状态:

C:\Debuggers\pageheap>pageheap
mytest.exe: page heap enabled with flags (full traces )

当激活pageheap后,重新运行一次上面的代码,程序就崩溃了。

(直接双击运行程序和在Windbg中用调试模式运行程序,观察到的崩溃有差别。在Windbg中运行,pageheap会首先触发break point异常,同时pageheap还会在调试器中输出额外的调试信息方便调试。)

上面的例子说明了pageheap能够让错误尽快暴露出来。接下来我们稍微修改一下代码:

 char *p=(char*)malloc(1023);
p[1023]=1;

试试看,修改后的代码还会导致程序崩溃吗?

根据我的测试,分配1023字节的情况下,哪怕激活pageheap,也不会崩溃。你能说明原因吗?如果看不出来,可以检查一下每次malloc返回的地址的数值,注意对这个数值在二进制上敏感一点,然后结合Heap Manager和pageheap的原理思考一下,看看有没有发现。

对于上面两种代码,如果用debug模式编译,激活pageheap,程序会崩溃吗?根据我的测试,无论是否激活pageheap,debug模式都不会崩溃的。你能想到原因吗?

再来看下面一段代码:

 char *p=(char*)malloc(1023);
free(p);
free(p);

这里显然有double free的问题。

如果没有激活pageheap,分别在debug和release模式下运行,根据我的测试,debug模式下会崩溃,release模式下运行正常。

如果激活pageheap,同样在debug/release模式下运行。根据我的测试,在两种模式下都会崩溃。如果细心观察,会发现两种模式下,崩溃后弹出的提示各自不同。你能想到原因吗?

如果有兴趣,你还可以测试一下heap误用的其他几种情况,看看pageheap是不是都有帮助。

Heap上的内存泄漏和内存碎片

从上面的例子,可以很清楚地看到pageheap对于检查这类问题的帮助。同时也可以看到,pageheap无法保证检查出所有潜在问题,比如分配1023个字节,但是写1024个字节这种情况。只有理解pageheap的工作原理,同时对问题作认真的思考和测试后,才会理解其中的差别。

除了Heap使用不当导致崩溃外,还有一类问题是内存泄漏。内存泄漏是指随着程序的运行,内存消耗越来越多,最后发生内存不足,或者整体性能下降。从代码上看,这类问题是由于内存使用后没有及时释放导致的。这里的内存,可以是VirtualAlloc分配的,也有可能是HeapAllocate分配的。

这里只讨论Heap相关的内存泄漏。检查内存泄漏是一个比较大的题目,第4章会作详细讨论。

举个例子,客户开发一个cd刻录程序。每次把盘片中所有内容写入内存,然后开始刻录。如果每次刻录完成后都忘记去释放分配的空间,那么最多能够刻3张CD。因为3张CD,每一张600MB,加在一起就是1.8GB,濒临2GB的上限。

另外还有一种跟内存泄漏相关的问题,是内存碎片(Fragmentation)。内存碎片是指内存被分割成很多的小块,以至于很难找到连续的内存来满足比较大的内存申请。导致内存碎片常见原因有两种,一种是加载了过多DLL,还有一种是小块Heap的频繁使用。

DLL分割内存空间最常见的情况是ASP.NET中的batch compilation没有打开,导致每一个ASP.NET页面都会被编译成一个单独的DLL文件。运行一段时间后,就可以看到几千个DLL文件加载到进程中。一个极端的例子是5000个DLL把2GB内存平均分成5000份,导致每一份的大小在400KB左右(假设DLL本身只占用1个字节),于是无法申请大于400KB的内存,哪怕总的内存还是接近2GB。对于这种情况的检查很简单,列一下当前进程中所有加载起来的DLL就可以看出问题来。

对于小块Heap的频繁使用导致的内存分片,可以参考下面的解释:

Heap fragmentation is often caused by one of the following two reasons
1. Small heap memory blocks that are leaked (allocated but never freed) over time
2. Mixing long lived small allocations with short lived long allocations
Both of these reasons can prevent the NT heap manager from using free
memory efficiently since they are spread as small fragments that cannot
be used as a single large allocation

为了更好地理解上面的解释,考虑这样的情况。假设开发人员设计了一个数据结构来描述一首歌曲,数据结构分成两部分,第一部分是歌曲的名字、作者和其他相关的描述性信息,第二部分是歌曲的二进制内容。显然第一部分比第二部分小得多。假设第一部分长度1KB,第二部分399KB。每处理一首歌需要调用两次内存分配函数,分别分配数据结构第一部分和第二部分需要的空间。

假设每次处理完成后,只释放了数据结构的第二部分,忘记释放第一部分,这样每处理一次,就会留下1个1KB的数据块没有释放。程序长时间运行后,留下的1KB数据块就会很多,虽然HeapManager的薄计信息中可能记录了有很多399KB的数据块可以分配,但是如果要申请500KB的内存,就会因为找不到连续的内存块而失败。对于内存碎片的调试,可以参考最后的案例讨论。在Windows 2000上,可以用下面的方法来缓解问题:

The Windows XP Low Fragmentation Heap Algorithm 
Feature Is Available for Windows 2000
http://support.microsoft.com/?id=816542

关于 CLR上内存碎片的讨论和图文详解,请参考:

 .NET Memory usage - A restaurant analogy
http://blogs.msdn.com/tess/archive/2006/09/06/742568.aspx

PageHeap,调试Heap问题的工具的更多相关文章

  1. Debug Hacks中文版——深入调试的技术和工具

    关键词:gdb.strace.kprobe.uprobe.objdump.meminfo.valgrind.backtrace等. <Debugs Hacks中文版——深入调试的技术和工具> ...

  2. 介绍一个axios调试好用的工具:axios-mock-adapter

    上一篇文章中写到用promise时应注意的问题,这一篇文章继续介绍一个可以和axios库配合的好工具: axios-mock-adapter.axios-mock-adapter可以用来拦截http请 ...

  3. C++雾中风景番外篇3:GDB与Valgrind ,调试代码内存的工具

    写 C++的同学想必有太多和内存打交道的血泪经验了,常常被 C++的内存问题搅的焦头烂额.(写 core 的经验了)有很多同学一见到 core 就两眼一抹黑,不知所措了.笔者 入"坑&quo ...

  4. Linux内核调试的方式以及工具集锦【转】

    转自:https://blog.csdn.net/gatieme/article/details/68948080 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  5. Linux内核调试的方式以及工具集锦

    原文:https://blog.csdn.net/gatieme/article/details/68948080 CSDN GitHubLinux内核调试的方式以及工具集锦 LDD-LinuxDev ...

  6. VS混淆/反编译/远程调试/Spy++的Tools工具

    VS的Tools工具(混淆/反编译/远程调试/Spy++等) https://blog.csdn.net/chunyexiyu/article/details/14445605 参考:http://b ...

  7. android--------Eclipse中ddms heap内存分析工具

    无 论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方. Android tools中的DDMS就带有一个很不错的内存监测工具Heap ...

  8. 使用 Dalvik 调试监控服务 (DDMS) 工具

    Android 附带一个叫Dalvik 调试监控服务 (DDMS) 的调试工具,它提供端口转发服务.在设备上的屏幕捕获,设备上的线程和堆栈信息, logcat,进程, 和无线状态信息,接收呼叫和SMS ...

  9. 调试Python代码的工具

    pdb: 首先来说Python里内建的调试器,pdb.它利用一个简单的命令行界面,还有很多你在用调试器时用得上的功能.帮助系统能为你指出你能运行的命令,比如单步调试代码,操纵调用栈和设置断点. 一些它 ...

随机推荐

  1. java--多线程之Runnable

    引读: 上一篇博文中讲了Thread的继承,存在一个问题就是,如果类本身已经继承了某个父类,又要继承Thread,导致多重继承. [但是我们知道接口是实现多重继承的重要方式].java提供了Runna ...

  2. 高级UIKit-07(AVAudioPlayer)

    [day09-1-AVAudioPlayer]:播放音乐案例 实现多媒体需要准备以下两点: 需要引入一个框架AVFoundation.framework 然后引入#import <AVFound ...

  3. 【译】在Asp.Net中操作PDF - iTextSharp - 绘制矢量图

    原文 [译]在Asp.Net中操作PDF - iTextSharp - 绘制矢量图 在上一篇iTextSharp文章中讲述了如何将现有的图片插入PDF中并对其进行操作.但有时,你需要在PDF中绘制不依 ...

  4. Boost::filesystem 使用小笔记

    今天拿起手要用C++写个小工具,从指定的目录递归遍历文件,然后做一下处理.又翻了一下boost的filesystem库.小结一下,希望能加深印象,免得下次又要查看文档. 1. path对象就是一个跨平 ...

  5. Jquery progressbar通过Ajax请求获取后台进度演示

    项目源代码下载:http://download.csdn.net/detail/nuptboyzhb/6262253 1.简介 本文主要演示Jquery progressbar的进度条功能.js通过a ...

  6. JS乘法口诀表(一行代码)

    (function(c){for(i=1;i<=9;i++){var s='';for(j=1;j<=i;j++){s+=i+'X'+j+'='+i*j+'\t';}c.debug(s); ...

  7. Java中Timer和TimerTask的使用

    转自:http://www.cdtarena.com/javapx/201306/8893.html java.util.Timer定时器,实际上是个线程,定时调度所拥有的TimerTasks. 一个 ...

  8. Swift - 委托(delegate)的介绍,及使用样例

    1,委托的说明 委托(delegate)是Cocoa的一个术语,表示将一个对象的部分功能转交给另一个对象. 比如对象A希望对象B知道将要发生或已经发生某件事情,对象A可以把对象B的引用存为一个实例变量 ...

  9. 基于visual Studio2013解决面试题之0504单链表逆序

     题目

  10. eclipse for mac 快捷键

    Eclipse for Mac 常用快捷键 为了提高开发效率,Eclipse 为我们提供了许多快捷键,它们能够帮助我们快速和方便的完成一些繁琐的操作. 关于 Eclipse for Windows 的 ...