堆异常调试

原创 2015年07月08日 10:02:38

因为VS对于堆调试的支持不够,与堆相关的问题一般较为棘手,近日,我在使用VS调试平台SDK时,发现一个问题。程序正常运行没有问题,当程序退出时,会报告一个堆错误异常,如下图:

 

类似的错误比较常见了,相信很多人都见过,提示也显而易见,是操作程序的堆时发生了异常。

因为VS识别程序堆比较困难,遇到这种问题就比较捉襟见肘了,此时,强大的windbg就派上了用场。

Windbg有两种调试模式,侵入式和非侵入式。在侵入式调试模式下,可以操作程序的线程,控制程序流程,修改内存数据,这也是我们平时使用VS的调试模式,在这种模式下,进程的debugport会被独占,因此不能使用两个调试器侵入同一个进程。而在非侵入模式下,基本不能做任何控制程序流程,修改内存的操作,只能是做一些检查进程状态的操作,但这种模式有个好处,它并不占用进程的debugport,也就是说,可以附加到一个正在被调试的进程上。

打开windbg,按F6附加到进程,此时程序已经被VS调试,因此当附加的时候,要选择非侵入式调试,即勾上最下面那个noninvasive选项。这样我们既能利用VS的易操作,界面友好,又能使用windbg查看到一些vs看不到的信息。如下图所示:

 

 

附加成功后,windbg显示了当前进程正在执行的命令,敲入kb,查看当前线程的调用栈:

 

从上图可以看出,该线程在调用free()这个C运行时库函数,用来释放一块内存。Kb命令显示了函数调用的前三个参数,由于free只有一个参数,即图中的0975bfa0,所以我们可以断定,在使用free释放0975bfa0处的内存时出错了。

具体什么错误呢,接着往下看,free调用了RtlFreeHeap这个API,通过查询MSDN,得到这个函数的声明如下:

BOOLEAN RtlFreeHeap(

 _In_      PVOID HeapHandle,

 _In_opt_  ULONG Flags,

 _In_      PVOID HeapBase

);

第一个参数是堆的句柄,第二个是一个标识,第三个参数是要释放的内存地址。从函数的调用栈中,可以看出,第一个参数为030a0000,第二个参数为00000000,第三个参数为0975bfa0,即我们要释放的内存地址。

这样可以得出了结论,当释放030a0000这个堆上的地址为0975bfa0内存块时发生了异常。

使用windbg查看030a0000这个堆,可以敲入命令!heap 030a0000,查看该堆分配的segment。

通过观察这个堆发现,0975bfa0这个地址,并不在这个堆上,释放了一块不属于这个堆的内存,难怪会发生问题。那这块地址到底属于哪个堆呢?敲入!heap 0 显示出所有堆的堆块。

输出比较长,截取了一部分。

可以看到,0975bfa0恰好位于第19个堆的第二个堆块范围内。我们可以进一步验证,显示出第19个堆块的详细分配情况,输入命令!heap  -a 03e10000

输出很长,图中被标记的堆块即为要释放内存所在地址,它们之间相差了8个字节,这8个字节是管理结构。

到此为止,一切都清楚了,程序在19号堆申请了一块内存,却在5号堆里面进行释放,因此出现了问题。

那么为什么会出现这个情况?windows C运行时库会维护一个CRT堆,以支持new,delete等调用。Delete调用了FREE()这个函数,默认操作的是CRT堆,即本案例中的5号堆,而调用new的时候,默认操作的CRT堆是19号堆,也就是说这个进程中居然有两个CRT堆。

这种情况一般是因为两个模块引用了不同的CRT堆导致,该demo有两个模块,一个是平台sdk,一个是exe执行文件,检查下工程配置选项中,可以看到DLL引用的编译选项是/MT

/MT和/MTd表示采用多线程CRT库的静态lib版本。该选项会在编译时将运行时库以静态lib的形式完全嵌入。该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specified to RtlValidateHeap”问题。另外托管C++和CLI中不再支持/MT和/MTd选项。

/MD和/MDd表示采用多线程CRT库的动态dll版本,会使应用程序使用运行时库特定版本的多线程DLL。链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。

因此,通过将各个模块的C运行库链接选项改为/MD,可以保证进程内只有同一个CRT堆,这样就避免了以上问题的产生。

 

3ds Max SDK 导入插件的一些技术障碍记录

一个3ds max导入插件的任务,大意就是把模型导入到场景中,前期走了弯路最后真正开始做两周就做完了。以上,不算墨迹,还算可以吧。 不过一个问题一直没解决,就是模型本身除了自身纹理还有一层光照阴影的...
  • tzmyf234
  • tzmyf234
  • 2016年01月21日 11:48
  • 322

xp下调试堆溢出(上)--HeapAlloc分配堆块

题记:win10都开始推广了,我还在折腾xp,真low,不过总得有这个过程吧。     DWORD SHOOT的原理是利用Release版发布的程序在正常启动(非调试启动)情况下,使用Lookasid...
  • lixiangminghate
  • lixiangminghate
  • 2016年07月06日 22:04
  • 1142

windbg检测句柄泄露(定位到具体代码)

1.构造一个测试用例
  • yockie
  • yockie
  • 2014年10月30日 01:12
  • 6272

windbg检测句柄泄露(可定位到具体代码)

1、用c++写一个句柄泄露的样例程序: #include "stdafx.h" #include voidfun1(void); voidfun2(void); voidf...
  • xiliang_pan
  • xiliang_pan
  • 2012年10月19日 19:42
  • 1447

堆溢出(DwordShoot)利用SEH异常处理

异常处理的身影处处可见,最常见的处理方式就是当异常发生时,在异常处理模块中记录日志,便于程序员事后定位。但是,被异常处理包含的代码真的会在异常发生时让程序优雅的退出吗?在程序的世界里什么都可能发生,所...
  • lixiangminghate
  • lixiangminghate
  • 2016年11月25日 00:20
  • 540

Windbg的gflags.exe调试堆栈溢出,访问越界等问题。

gflags.exe是Windbg下的一个小工具,非常好用,对于调试程序隐藏的bug很有帮助。  如:我在vs2015中遇到访问越界的问题,但程序不会在越界的地方发生崩溃中断,而是在一个不可能存在访问...
  • bao_bei
  • bao_bei
  • 2017年06月28日 14:22
  • 543

windbg调试句柄泄漏

转自http://blog.csdn.net/hexieshangwang/article/details/47187877 句柄泄露调试(Handles Leak Debug)  ...
  • rankun1
  • rankun1
  • 2017年04月06日 18:36
  • 242

使用HandleSpy定位托管代码句柄泄漏

引子  我们知道句柄泄漏原因多种多样,一般泄漏的对象主要是内核句柄 、 文件句柄、互操作句柄等。 由于Framework的GC帮我们干了很多事情,所以很多C#程序员养成了吃饭后不洗碗的习惯,new出来...
  • pc0de
  • pc0de
  • 2017年05月21日 18:10
  • 900

win7下堆管理结构分析

win7下堆管理结构主要包括HEAP HEAP_SEGMENT HEAP_ENTRY 这几个结构。
  • shuizhilan
  • shuizhilan
  • 2015年10月10日 13:43
  • 1812

软件调试23章 堆管理器向内存管理器释放的疑惑

张银奎老师的第23章提到可以用windbg !heap -v HeapHandle来查看堆解除提交的粒度。我在win7 32bit机器上测试系统堆解除提交的情况,得到了不同的结果。     首先我的系...
  • lixiangminghate
  • lixiangminghate
  • 2016年06月06日 20:51
  • 609
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:堆异常调试
举报原因:
原因补充:

(最多只允许输入30个字)