zl程序教程

您现在的位置是:首页 >  其他

当前栏目

PageHeap,调试Heap问题的工具

调试工具 Heap 问题
2023-09-27 14:29:32 时间
2.4.2  PageHeap,调试Heap问题的工具 幸运的是,Heap Manager的确提供了主动检查错误的功能。

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

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


2.4.2  PageHeap,调试Heap问题的工具

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



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

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

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

 


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

 

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

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

 


 

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

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

再来看下面一段代码:

 


 

如果没有激活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

GDB技巧分享,让你的调试变得更方便 GDB是我们在嵌入式程序调试时最常使用到的调试工具,有关GDB的文章,网上早已经层出不穷。这边会分享一些GDB调试上的小技巧,也许会让大家再重新认识一下这个很熟悉的工具,也希望能让大家的调试能够更加方便。
【调试】Windows夯机Memory Dump案例分析 我们已经看了不少Linux的core dump分析案例了,这次我们来看一个案例,其中利用到了Windows memory dump的分析技巧。Windows的memory dump基本原理几乎和Linux并无太大区别,如果是Crash - 内核崩溃类型的dump,分析思路几乎是完全一致的,当然难度主要在于Windows系统封闭性,即无法提供私有符号和源码,所以多需要一些汇编层面的理解。
centos7 lldb 调试netcore应用的内存泄漏和死循环示例(dump文件调试) 写个demo来玩一玩linux平台下使用lldb加载sos来调试netcore应用。 当然,在真实的产线环境中需要分析的数据和难度远远高于demo所示,所以demo的作用也仅仅只能起到介绍工具的作用。