PageHeap,调试Heap问题的工具
《Windows用户态程序高效排错》第二章主要介绍用户态调试相关的知识和工具。本文主要讲了PageHeap,调试Heap问题的工具.
2.4.2 PageHeap,调试Heap问题的工具
幸运的是,Heap Manager的确提供了主动检查错误的功能。只需要在注册表里面做对应的修改,操作系统就会根据设置来改变Heap Manager的行为。Pageheap是用来配置该注册表的工具。关于heap的详细信息和原理请参考:
How to use Pageheap.exe in Windows XP and Windows 2000 |
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 |
简单例子的多种情况
看几个简单的但是却很有意义的例子:
用release模式编译运行下面的代码:
char *p=(char*)malloc(1024); |
这里往分配的空间多写一个字节。但是在release模式下运行,程序不会崩溃。
假设上面的代码编译成mytest.exe,用下面的方法可以对mytest.exe激活pageheap:
C:\Debuggers\pageheap>pageheap /enable mytest.exe /full |
直接运行pageheap可以查看当前pageheap的激活状态:
C:\Debuggers\pageheap>pageheap |
当激活pageheap后,重新运行一次上面的代码,程序就崩溃了。
(直接双击运行程序和在Windbg中用调试模式运行程序,观察到的崩溃有差别。在Windbg中运行,pageheap会首先触发break point异常,同时pageheap还会在调试器中输出额外的调试信息方便调试。)
上面的例子说明了pageheap能够让错误尽快暴露出来。接下来我们稍微修改一下代码:
char *p=(char*)malloc(1023); |
试试看,修改后的代码还会导致程序崩溃吗?
根据我的测试,分配1023字节的情况下,哪怕激活pageheap,也不会崩溃。你能说明原因吗?如果看不出来,可以检查一下每次malloc返回的地址的数值,注意对这个数值在二进制上敏感一点,然后结合Heap Manager和pageheap的原理思考一下,看看有没有发现。
对于上面两种代码,如果用debug模式编译,激活pageheap,程序会崩溃吗?根据我的测试,无论是否激活pageheap,debug模式都不会崩溃的。你能想到原因吗?
再来看下面一段代码:
char *p=(char*)malloc(1023); |
这里显然有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 |
为了更好地理解上面的解释,考虑这样的情况。假设开发人员设计了一个数据结构来描述一首歌曲,数据结构分成两部分,第一部分是歌曲的名字、作者和其他相关的描述性信息,第二部分是歌曲的二进制内容。显然第一部分比第二部分小得多。假设第一部分长度1KB,第二部分399KB。每处理一首歌需要调用两次内存分配函数,分别分配数据结构第一部分和第二部分需要的空间。
假设每次处理完成后,只释放了数据结构的第二部分,忘记释放第一部分,这样每处理一次,就会留下1个1KB的数据块没有释放。程序长时间运行后,留下的1KB数据块就会很多,虽然HeapManager的薄计信息中可能记录了有很多399KB的数据块可以分配,但是如果要申请500KB的内存,就会因为找不到连续的内存块而失败。对于内存碎片的调试,可以参考最后的案例讨论。在Windows 2000上,可以用下面的方法来缓解问题:
The Windows XP Low Fragmentation Heap Algorithm |
关于 CLR上内存碎片的讨论和图文详解,请参考:
.NET Memory usage - A restaurant analogy |
PageHeap,调试Heap问题的工具的更多相关文章
- Debug Hacks中文版——深入调试的技术和工具
关键词:gdb.strace.kprobe.uprobe.objdump.meminfo.valgrind.backtrace等. <Debugs Hacks中文版——深入调试的技术和工具> ...
- 介绍一个axios调试好用的工具:axios-mock-adapter
上一篇文章中写到用promise时应注意的问题,这一篇文章继续介绍一个可以和axios库配合的好工具: axios-mock-adapter.axios-mock-adapter可以用来拦截http请 ...
- C++雾中风景番外篇3:GDB与Valgrind ,调试代码内存的工具
写 C++的同学想必有太多和内存打交道的血泪经验了,常常被 C++的内存问题搅的焦头烂额.(写 core 的经验了)有很多同学一见到 core 就两眼一抹黑,不知所措了.笔者 入"坑&quo ...
- Linux内核调试的方式以及工具集锦【转】
转自:https://blog.csdn.net/gatieme/article/details/68948080 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...
- Linux内核调试的方式以及工具集锦
原文:https://blog.csdn.net/gatieme/article/details/68948080 CSDN GitHubLinux内核调试的方式以及工具集锦 LDD-LinuxDev ...
- VS混淆/反编译/远程调试/Spy++的Tools工具
VS的Tools工具(混淆/反编译/远程调试/Spy++等) https://blog.csdn.net/chunyexiyu/article/details/14445605 参考:http://b ...
- android--------Eclipse中ddms heap内存分析工具
无 论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方. Android tools中的DDMS就带有一个很不错的内存监测工具Heap ...
- 使用 Dalvik 调试监控服务 (DDMS) 工具
Android 附带一个叫Dalvik 调试监控服务 (DDMS) 的调试工具,它提供端口转发服务.在设备上的屏幕捕获,设备上的线程和堆栈信息, logcat,进程, 和无线状态信息,接收呼叫和SMS ...
- 调试Python代码的工具
pdb: 首先来说Python里内建的调试器,pdb.它利用一个简单的命令行界面,还有很多你在用调试器时用得上的功能.帮助系统能为你指出你能运行的命令,比如单步调试代码,操纵调用栈和设置断点. 一些它 ...
随机推荐
- 18-UIKit(Core Animation、广播设计模式)
目录: 一.Core Animation 二.广播设计模式 回到顶部 一.Core Animation 1. 是什么? 底层的动画框架 2. 框架对比 UIKit UI ...
- django目录下的各文件
本文部分转载. 使用Python setup.py install命令从源代码安装完Django后,这些都会被拷贝到Python安装目录下的Lib/site-packages/django子目录中.之 ...
- vc根据域名获取IP地址 gethostbyname()函数
以下是VC Socket初始化时用到的两个函数 一.WSAStartup函数 int WSAStartup ( ...
- svn回滚版本2
svn 版本回滚 取消对代码的修改分为两种情况: 第一种情况:改动没有被提交(commit). 这种情况下,使用svn revert就能取消之前的修改. svn revert用法如下: # svn ...
- java环境变量配置问题
你要配置三个环境变量JAVA_HOMECLASSPATHPath你都配置了吗? 系统变量→新建 JAVA_HOME 变量 .变量值填写jdk的安装目录(本人是 E:\Java\jdk1.7.0) 系统 ...
- Delphi的RTTI还分为对类和对象的判断,以及对普通属性的判断——相比之下,C++的RTTI实在太弱!
堂堂C++沦落到这个地步,也实在是够可怜的.
- 无限层级且乱序的树形结构数据的整理,利用HashMap降低遍历次数
我们在展示一个机构树的时候,经常会遇到这种一个问题,查询数据的时候,是从下往上查的,但展示数据的时候,又要从下往上展示. 这时候就要把查询到的数据进行整理从而得到我们想要的结构. 举个样例. ID P ...
- MSSQL - 存储过程Return返回值
1.存储过程中不使用外部参数. 存储过程: SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ========================== ...
- 基于visual Studio2013解决C语言竞赛题之1092链表转换
题目 解决代码及点评 /************************************************************************/ /* ...
- 做自己的Android ROM,屏蔽对framework中的系统APK的签名检查
最近两天一直在尝试更新Android中的关键库以达到定制ROM的效果,中间比较曲折,记录下来供自己和大家参考. 因为我需要基于Android的原生代码做一定的修改,所以如果无法将我自己编译出的APK或 ...