Win32堆的调试支持

为了帮助程序员及时发现堆中的问题,堆管理器提供了以下功能来辅助调试。

1:堆尾检查(Heap Tail Check) HTC,在堆尾添加额外的标记信息,用于检测堆块是否溢出。

2:释放检查(Heap Free Check)在释放堆块时进行检查,防止释放同一个堆块。

3:参数检查,对传递给堆的各种参数进行更多的检查。

4:调用时验证(Heap Validate On Call)HVC,每次调用堆函数时都对整个堆进行验证和检查。

5:堆块标记(Heap Tagging)为堆块增加附加标记,以记录堆块的使用情况。

6:用户态栈回溯(User Mode Stack Trace)UST,将每次调用堆函数的函数调用信息记录到一个数据库中。

7:专门用于调试的页堆(Debug  Page Heap)DHP堆,页堆比较常用,且需要专门开启,我们会专门对其进行介绍。

创建堆时,堆会根据当前进程的全局标志来决定是否 启用堆的调试功能。操作系统在加载一个进程时会在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Image File Execution Option表键下寻找以该程序命名的子键。如果存在该子键则读取下面的GlobalFlag键值。

可以使用gflags.exe来编辑系统的全局标志或某个文件的全局标志。

如果在调试器运行一个程序,但注册表中并没有设置GlobalFlag键值 ,那么操作系统的加载器会默认将全局标志置为0x70,也就是启用htc、hfc和hpc三项调试功能。

如果注册表中设置了GlobalFlag键值,则使用注册表中的设置,不再默认提供其他调试选项。

如果是附加到一个已经运行的程序上,则它的全局标志就是注册表中的值。如果注册表中不存在全局标志,则为0。

gflags.exe

gflags.exe被称为全局标志编辑器是,windows调试工具集的一部分。该程序是用于对各个全局标志选项的集中式配置工具。它有gui和控制台两种模式。

  1. GUI模式开启调试选项

运行gflag.exe,打开gui模式:

可以看到程序分为三个标签页:system Registry、Kernel flags 、Image File。

标签页System Registry用于设置针对整个系统的选项。设置之后需要重启系统才能生效。

Image File用于设置针对单个进程的配置。设置之后重启进程后才会生效。

Kernel Flags用于设置只对内核产生影响的选项。

在gflags中包含了针对操作系统的各个方面的配置信息,这些信息是被保存在注册表相应的位置上。

单击Image File标签页,在Image:(Tab to Refresh)编辑框内输入要设置的进程名称。如calc.exe。设置完成后点击Tab进行刷新,刷新后下面的各个控件变为可用状态。在需要设置的调试选项后点勾,确认后即可。

  1. 控制台方式

在cmd窗口输入一下命令来开启相应的调试功能。

开启堆尾检查:gflags  /i calc.exe +htc

开启释放检查:gflags /i calc.exe +hfc

开启调用时验证:gflags /I calc.exe +hvc

开启参数检查: gflags /I calc.exe +hpc

开启用户态栈回溯:gflags /I calc.exe +ust

开启页堆:gflags /p /enable  程序名 /full

或者gflags /I 程序名 +hpa

需要关闭时只需要将+变为-即可。

在windbg中输入!gflag开查看开启的调试选项。

注意,一旦调试之前设置页堆注册表相应位置便不再为0,默认便不会开启hfc、hpc和htc。这一点要特别注意,调试以前要通过!gflag命令查看到底开启了何种调试选项。

因为在调试器运行一个程序且注册表中没有设置GlobalFlag键值 时,操作系统会启用htc、hfc和hpc三项调试功能,且它们原理非常简单,因此我们此处将主要精力放在经常使用,且需要手动开启的页堆上。

页堆DPH

利用堆尾检查可以在释放堆块时或在下次分配时检查到堆结构的破坏。但是这些检查都是滞后的,我们很难知道堆是何时发生的破坏。·   今天我们介绍的页堆(Debug Page Heap DPH)可以解决这个问题。启用DPH后堆管理器会在堆块后增加用于检测溢出的栈栏页,一旦用户数据溢出触及栈栏页将立即引发异常,从而让我们在第一个时间知道堆破坏。

前面介绍的win32堆使用用户数据区前面的_HEAP_ENTRY结构来描述堆块,一旦用户数据超出分配的空间将会覆盖堆块后面的数据。有可能覆盖下一个堆块的_HEAP_ENTRY结构或是空闲堆块的_HEAP_FREE_ENTRY结构导致堆被破坏。为了防止堆块的管理信息被覆盖后使堆发生不可恢复的破坏。页堆管理器除了在堆块用户区前面存储堆块管理信息外,还会将这些管理器信息存储在节点池内。

启用页堆

开启页堆可以使用gflags.exe来实现。

  1. Gui方式开启DPH。
  1. 控制台方式

gflag.exe /I calc.exe +hpa

设置完成后,使用windbg打开进程进行调试。为了验证是否开始DHP,可以执行!gflag /p命令:

也可以查看全局变量ntdll!RtlpDebugPageHeap的值来验证是否开启。当该值为1时表示开启dhp。

与普通堆相比页堆有很大的不同,每个堆块至少占用两个内存页。在存放用户数据的第一个内存页后面,堆管理器会额外多分配一个内存页。这个内存页是用来检测溢出的,被称为栅栏页。栅栏页的属性为PAEG_NOACCESS,因此一旦用户数据发生溢出触及到栅栏页便会引发异常,使调试人员第一时间发现问题,从而可以迅速定位到导致溢出的代码。

有人也许会有疑问,当堆块非常小时,难道也是占用两个内存页么?答案是肯定的。为了及时检测溢出,堆块被放到第一个内存页的末尾紧邻栅栏页,因此第一个内存页前面的大半部分有可能都是没有被使用的。由于分配粒度为8byte,堆块和栅栏页之间可能会有填充字段。对于很小的堆块也需要占用两个内存页,这是很耗费空间的。

测试页堆在调试中的效果

使用下面的代码生成HeapTest.exe,并使用windbg调试。

  1. HANDLE hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0, 1024*1024);
  2. char * p = (char*)HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 1012);
  3. char *q = p;
  4. for(int i = 0 ;i < 1048; i++)
  5. {
  6. *q++ = 0;
  7. }
  8. bool bRetVal = HeapFree(hHeap, HEAP_NO_SERIALIZE, p);

这段代码首先创建一个私有堆然后从私有堆分配1012个字节。但却访问分配地址后的1048byte的地址,很明显发生了堆溢出。

第一次我们不开启任何选项,观察全局标志:

发现默认开启了htc、hfc、hpc。

按F5继续运行,可以看到程序抛出异常而中断。

第一行的调试信息显示在访问堆块00420648时超过了它的大小3f4 = 1012Byte。而导致了访问违规。

查看堆栈调用:

可以发现是在对堆块释放时检测到堆块被破坏。显然这种方法不能在第一时间中断到出现为问题的地方。

第二次我们开启开启dph来观察:

首先开启dhp。gflags /i HeapTest.exe +hpa

在windbg中重新开始HeapTest.exe。可以看到程序发生内存访问异常。

最后一条指令由于访问eax所代表的地址而导致。

查看局部变量的值:

可以看到i等于1016时发生了异常。细心的同学可能会发现,我们申请的空间只有1012byte,为什么访问到1016时才会导致异常。这是因为在堆中的分配粒度是8byte,即分配的空间大小必须为8的倍数。因此此处填充了4个字节。

启用页堆后我们在第一时间发现了堆溢出的问题,结合源码分析便可很容易的找到导致堆溢出的地方。

准页堆

使用页堆确实是非常方便的,但是美中不足的是页堆要为每个堆块都分配两个内存页且只利用第一个内存页得后半部分,利用率是非常低的。在调试需要使用大量内存的应用程序时有可能会导致一些问题。为此引入了准页堆。

准页堆弥补了页堆的不足,同时还具有页堆的一些功能。准页堆也被称为常规页堆,页堆也被称为完全页堆。

准页堆不再为每个堆块分配栅栏页,只是在堆块的前后添加一些类似于安全Cookie的附加标记。当释放堆块时,堆管理器会检测这些标记的完好性。一旦检测到这些标记被破坏,便会产生异常。这种机制与释放时检查hfc类似。

由于是在释放时检测,因此准页堆并不具备页堆第一时间便能检测到堆破坏的优点。因此本文并不准备详细介绍。

启用准页堆

准页堆也需要手动开启,开启命令与页堆很像:gflags /p /enable calc.exe

本文介绍了win32堆的调试支持,重点介绍了页堆,其他调试功能调试器默认是开启的,仅以非常小的篇幅介绍。对于一些非常复杂的堆破坏问题,使用其他方式很难发现问题。即使有错误报告,错误报告处往往距离问题发生地十万八千里。而使用页堆却能很好的解决这个问题。仅仅使用一个命令打开页堆的调试功能便可以使困扰很久的问题迎刃而解,这也是本文之所以如此推崇页堆的原因。

下一篇文章将会介绍CRT堆。

from:http://blog.csdn.net/ithzhang/article/details/12786393

windows程序员进阶系列:《软件调试》之Win32堆的调试支持的更多相关文章

  1. windows程序员进阶系列:《软件调试》之堆 (一)

    windows程序员进阶系列:<软件调试>之堆 (一) 堆是软件在运行时动态申请内存空间的主要途径.从堆上申请来的空间需要程序员自己申请和释放,且申请和释放操作必须绝对匹配.忘记释放或者多 ...

  2. windows程序员进阶系列:《软件调试》之Win32堆

     win32堆及内部结构 Windows在创建一个新的进程时会为该进程创建第一个堆,被称为进程的默认堆.默认堆的句柄会被保存在进程环境块_PEB的ProcessHeap字段中. 要获得_PEB的地址, ...

  3. Boostnote:适合程序员的笔记软件【转】

    本文转载自:https://blog.csdn.net/u013553529/article/details/70306899 Boostnote:适合程序员的笔记软件 注意: Boostnote正在 ...

  4. PHP程序员进阶学习书籍参考指南

    PHP程序员进阶学习书籍参考指南 @heiyeluren lastmodify: 2016/2/18     [初阶](基础知识及入门)   01. <PHP与MySQL程序设计(第4版)> ...

  5. 谈谈Java程序员进阶的那些知识和方向

    谈谈Java程序员进阶的那些知识和方向 记得前段时间看过一篇文章谈到一种程序员叫野生程序员,战斗力极强,可以搞定一切问题,但是通常看问题抓不到本质,或者说是google/baidu/stackover ...

  6. 程序员面试系列之Java单例模式的攻击与防御

    我写的程序员面试系列 Java面试系列-webapp文件夹和WebContent文件夹的区别? 程序员面试系列:Spring MVC能响应HTTP请求的原因? Java程序员面试系列-什么是Java ...

  7. windows程序员开发linux程序的头一个月

    开发环境选择 vim,vscode,qt,visual studio都可以做linux c++开发,但是作为windows程序员,最熟悉的还是visual stuio,加上visual studio ...

  8. 做10年Windows程序员与做10年Linux程序员的区别(附无数评论)(开源软件相当于熟读唐诗三百首,不会作诗也会吟)

    如果一个程序员从来没有在linux,unix下开发过程序,一直在windows下面开发程序, 同样是工作10年, 大部分情况下与在linux,unix下面开发10年的程序员水平会差别很大.我写这篇文章 ...

  9. 做10年Windows程序员与做10年Linux程序员的区别

    如果一个程序员从来没有在linux,unix下开发过程序,一直在windows下面开发程序, 同样是工作10年, 大部分情况下与在linux,unix下面开发10年的程序员水平会差别很大.我写这篇文章 ...

随机推荐

  1. 关于 Swift

    摘自:http://numbbbbb.gitbooks.io/-the-swift-programming-language-/chapter1/01_swift.html Swift 是一种新的编程 ...

  2. android之View的启动过程

    转自:http://www.cdtarena.com/gpx/201308/9607.html 程序里调用了onSizeChanged方法进行了一些设置,不知道onSizeChanged是在什么时候启 ...

  3. android 小结

    1.layout中的布局文件xml中不能有大写字母. 2.时刻要想着空指针,尤其是安卓5.0后,不报异常,直接ANR.

  4. OpenCV 例子代码的讲解、简介及库的安装 .

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎大家提出意见,一起讨论! 一.OpenCV介绍: OpenCV是由Intel性能基元(IPP)团队主持, ...

  5. window批处理-3.go

    go: 控制批处理中的命令运行流程 命令格式: go label lable--行号 demo bat @echo off echo 跳过中间.运行最后 goto last type a.txt :l ...

  6. GCC 编译使用动态链接库和静态链接库的方法

    1 库的分类 依据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的.所以生成的可执行文件就不受库的影响了.即使库被删除了,程序依旧能够成功执行. 有别于静态库,动态库的链接是在程 ...

  7. NYOJ10,skiing

    skiing 时间限制:3000 ms  |  内存限制:65535 KB 难度:5 描写叙述 Michael喜欢滑雪百这并不奇怪, 由于滑雪的确非常刺激.但是为了获得速度,滑的区域必须向下倾斜,并且 ...

  8. jquery 中获取所有选中的checkbox的用法

    以往还错误的把$("input[type='checkbox'][checked]") 是正确的用法,奇怪的是:这样用之前确实是好用的,单当我页面中的html内容超过1000行时, ...

  9. check————身份证

    -- Access 不支持 Substring 查询,可以替换为 mid 查询. select 序号,姓名,身份证号,性别from 身份表where (len(身份证号)<>15 and ...

  10. Eclipse用法和技巧六:自动生成get和set方法1

    java的类中,除了常量声明为静态且公有的,一般的对象数据作用域,都是声明为私有的.这样做能保护对象的属性不会被随意改变,调试的时候也会方便很多:在类的公有方法中大一个调用栈就能看到哪里改了属性值.声 ...