你目前正处于脱机状态,正在等待 Internet 重新连接

如何在 Windows XP 和 Windows 2000 中使用 Pageheap.exe

针对 Windows XP 的支持已终止

Microsoft 已于 2014 年 4 月 8 日终止了针对 Windows XP 的支持。该更改已影响到您的软件更新和安全选项。 了解这一措施对于您的含义以及如何继续保持受保护状态。

针对 Windows Server 2003 的支持已于 2015 年 7 月 14 日终止。

Microsoft 已于 2015 年 7 月 14 日终止了对于 Windows Server 2003 的支持。该更改已影响到您的软件更新和安全选项。 了解这一措施对于您的含义以及如何继续保持受保护状态。

概要
本文介绍如何在 Microsoft Windows XP 和 Microsoft Windows 2000 中使用“页堆”工具 (Pageheap.exe)。
更多信息
Pageheap.exe 设置页堆标志有助于查找与堆相关的损坏。它还有助于检测在 Windows 2000 Professional Service Pack 2 (SP2) 和 Windows XP Professional 系统上运行的程序中的泄漏情况。

Pageheap.exe 在应用程序与系统之间引入了软件验证层(“页堆”管理器),该层验证所有动态内存操作(分配、释放及其他堆操作)。启用“页堆”管理器时,被测试的应用程序在调试器下启动。如果遇到问题,调试器将会中断。

重要说明:Pageheap.exe 不指明到底是什么错误,但在遇到问题时,它将导致系统崩溃。它启用 Windows 2000 Professional SP2 和 Windows XP Professional 的 Ntdll.dll 系统库中现有的验证层。Pageheap.exe 不适用于 Microsoft Windows 的早期版本。

如果被测试的应用程序未在调试器下启动并且遇到错误,它只会崩溃而无任何反馈。

概念

堆破坏是应用程序开发中的一个常见问题。当应用程序分配一个指定大小的堆内存块,然后又写入所需大小的堆块之外的内存地址时,通常会发生堆破坏。当应用程序对已释放的内存块进行写入操作时,也会发生堆破坏。

以下两个概念对于理解 Pageheap.exe 的相关命令及其使用方式极为重要:
  • 通过在分配的结尾放置不可访问的页,或在释放块时检查填充模式,即可发现堆块中的损坏。
  • 对于在已启用页堆的进程内创建的每个堆,均存在两种堆(整页堆和正常页堆)。
    • “整页堆”通过在分配的结尾放置不可访问的页来揭示堆块中的破坏。此方法的优点是可以实现“突然死亡”,这意味着进程将恰好在故障点上出现访问冲突 (AV)。此行为便于调试故障。它的缺点是每个分配至少使用一页已提交的内存。对于占用大量内存的进程来说,系统资源很快就会被耗尽。
    • 当内存限制造成整页堆不可用时,则可以使用“正常页堆”。它在释放堆块时检查填充模式。此方法的优点是大大减少了内存耗用量。缺点是只能在释放块时检测损坏。这样难以调试故障。

“页堆”工具的下载地址

要下载最新的调试工具包,请单击以下链接:

选择最新发布的调试工具。当安装这些工具时,选择“自定义”安装,然后将其安装到采用适当名称的目录下。例如,将工具安装到 C:\Debug 或 C:\Debugtools 文件夹下。

选择调查堆块损坏的方法

您可以采用以下两种方法之一发现堆块中的大多数破坏:
  • 整页堆:在分配的结尾放置不可访问的页。
  • 正常页堆:在释放块时检查填充模式

整页堆

整页堆应该针对单个进程启用,或者,对于大进程,则在使用一定参数的情况下启用,因为大进程对内存要求较高。不能在整个系统范围内启用整页堆,因为难以估计所需的页文件大小。如果在整个系统范围内启用整页堆,而使用的页文件太小,则会造成系统不可启动。

整页堆的优点是它导致进程恰好在故障点上出现访问冲突 (AV)。这样便于调试故障。为了能够查明故障,首先使用正常页堆确定进程失败范围,然后对各个大规模进程中有限的一类分配(即,特定大小的范围或特定库)使用整页堆。

正常页堆

正常页堆可以用于测试大规模进程,而不耗用整页堆所需的大量内存。不过,正常页堆在释放块之后才能进行检测,因此更难以调试故障。

通常,使用正常页堆进行初始大规模进程测试。然后,如果检测到问题,再对这些进程中有限的一类分配启用整页堆。

您可以为整个系统中的所有进程安全地启用正常页堆。正常页堆非常适用于执行常规系统验证(而非以组件为中心的测试)的测试工作台。您也可以为单个进程启用正常页堆。

使用 GFlags 在整个系统内启用页堆

“GFlags”工具用来在整个系统内启用页堆。为了使 GFlags 命令生效,必须在发出该命令后重新启动计算机。

在整个系统内启用正常页堆:
  1. 在命令行键入以下命令:gflags -r +hpa

  2. 重新启动计算机。
在整个系统内禁用正常页堆:
  1. 在命令行键入以下命令:gflags -r -hpa

  2. 重新启动计算机。
注意:启用页堆时不得使用任何其他 GFlags 设置。如果启用了看似与堆相关的其他设置,则可能会因页堆管理器与这些“无害”堆标志之间的冲突而引起页堆错误。

使用 GFlags 在单一进程内启用页堆

您可以启用页堆来监视某一个特定进程。为此,请按照下列步骤操作:
  1. 在命令提示符下,更改安装调试工具的目录。
  2. 在命令提示符下键入以下内容,然后按 Enter:
    Gflags.exe –p /enable lsass.exe
    注意lsass.exe 表示要使用“页堆”工具监视的进程的名称。
  3. 当您不再需要页堆监视时,请禁用监视。为此,请在命令提示符下键入以下内容,然后按 Enter:
    Gflags.exe -p /disable lsass.exe
    注意lsass.exe 表示要使用“页堆”工具监视的进程的名称。
  4. 要列出所有当前已启用了“页堆”验证的程序,请在命令提示符下键入以下内容,然后按“Enter”:
    Gflags.exe -p

未调整的分配

Windows 堆管理器(所有版本)始终保证堆分配具有 8 字节调整(该调整在 64 位平台上为 16 字节)的起始地址。页堆管理器也可以保证这一点。不过,如果要让分配的结尾刚好位于页结尾,则无法保证这一点。精确的页结尾分配的目的是让“偏离一个字节”的错误在不可访问的页中触发读或写操作,从而导致即时错误。

如果用户所需的块大小不是 8 字节调整的,则页堆无法满足“8 字节调整的起始地址”和“调整的结束地址页”限制要求。该解决方法是为了满足第一条限制要求,使块的起始部分成为 8 字节调整的。然后,在块的结尾与不可访问页的起始部分之间使用小填充模式。在 32 位体系结构上,此填充模式的长度范围是从 0 字节到 7 字节。系统在释放块时检查填充模式。

如果需要对这些分配进行即时错误检测,否则结尾将出现填充模式,则可让页堆管理器忽略 8 字节调整规则,始终使用 /unaligned/full 参数在页边界调整分配的结尾。有关更多信息,请参阅 /unaligned 参数。

注意:某些程序会对 8 字节调整作假设,而停止与 /unaligned 参数正常协作。Microsoft Internet Explorer 就是这样的一个程序。

整页堆分配的未提交页

核心的整页堆实现为小于一页的所有分配提交两页。一页用于用户分配,另一页作为不可访问页置于缓冲区结尾。

使用一块保留的虚拟空间即可检测缓冲区结尾溢出,而不必使用不可访问的提交页进行检测。当进程触及该保留的虚拟空间时,将出现访问冲突异常。此方法最多可减少 50% 的内存耗用量。有关更多信息,请参阅 /decommit 参数。

错误注入

您可以控制页堆管理器,以便故意让某些分配失败。这样可以模拟内存不足的情况,而不必实际使用所有系统资源。

指定 1 到 10,000 之间的某个数字来表示分配失败的可能性。使用 10,000 的可能性确保 100% 的分配将失败。2,000 的可能性指定约 20% 的分配将失败。

页堆管理器特别注意避免在进程一开始的前 5 秒和 Windows NT 加载器代码路径(如 LoadLibrary、FreeLibrary)中进行错误注入。如果 5 秒不足以让进程完成启动,则可在进程开始处指定更长的超时值。有关更多信息,请参阅 /fault 参数。

当使用 /fault 参数并且被测试的进程有错误时,将引发异常。通常,引发异常的原因是分配操作返回了 NULL,而应用程序稍后尝试访问分配的内存。但是,由于分配失败而无法访问内存,因此会出现访问冲突。

引发异常的另一个原因是应用程序尝试处理分配问题,但不释放某些资源。它表现为内存泄漏,并且更难以调试。

为了帮助诊断这些问题,页堆管理器保留从错误注入开始的堆跟踪历史记录。您可以使用下面的调试器命令显示这些跟踪信息:

!heap -p -f [NUMBER-OF-TRACES]

该扩展默认只显示最后四个跟踪。

在应用程序启动时自动附加调试器

某些应用程序难以从命令提示符下启动,或者它们是从其他进程生成的。对于这些应用程序,请指定在它们启动时自动附加调试器。如果为该进程启用了页堆并且发生堆失败,那么这种做法是非常有用的。有关更多信息,请参阅 /debug 参数。

只要自定义分配/释放功能最终调入 NT 堆管理接口(即 RtlAllocateHeap、RtlFreeHeap),Pageheap.exe 在用来验证任何内存分配进程(包括 C++ 样式分配新增和删除)时就是有效的。以下函数可以保证调入 NT 堆管理接口:
  • 诸如 HeapAllocHeapFreeHeapReAlloc 的函数:这些函数由 kernel32.dll 导出,然后直接调入 NT 堆接口。诸如 GlobalAllocGlobalFreeGlobalReAlloc 的函数:这些函数由 kernel32.dll 导出,然后直接或间接调入 NT 堆接口。
  • 诸如 LocalAllocLocalFreeLocalReAlloc 的函数:这些函数由 kernel32.dll 导出,然后直接或间接调入 NT 堆接口。
  • 函数 mallocfreereallocmsizeexpand:这些函数由 msvcrt.dll 导出,然后直接或间接调入 NT 堆。情况并非总是如此。C 运行时过去采用不同的堆实现,但最新的 C 运行时直接调入 NT 堆。
  • 运算符 newdeletenew[ ]delete[ ]:这些函数由 msvcrt.dll 导出,然后直接或间接调入 NT 堆。
任何其他分配/释放函数集都可能是自定义方案,不保证可以直接或间接调入 NT 堆。只有检查源代码或在调试器下运行才能揭示实际的实现。

避免使用静态链接。某些应用程序已静态链接到旧版本的 C 运行时。这些旧版本不调用 Windows NT 堆 API,Pageheap.exe 无法用来验证这些分配。动态链接确保获得最新的 C 运行时库 (msvcrt.dll)。

Pageheap.exe 发现的错误类型

Pageheap.exe 会检测到大多数与堆相关的错误;不过,它的检测重点是堆被破坏的问题,而不是泄漏问题。虽然 Pageheap.exe 有检测堆泄漏的功能,但它成功发现堆泄漏的能力有限。

Pageheap.exe 的一个优点是在许多错误发生时检测到它们。例如,在动态分配缓冲区的结尾“偏离一个字节”的错误可能导致即时访问冲突。只有少数几类错误无法在发生时被检测到。在这些情况下,错误报告将被延至释放块之后。
  • 堆指针无效:所有 Win32 和 Windows NT 级别的堆接口都将应该发生操作的堆的指针作为第一个参数。页堆管理器在进行调用时检测无效的堆指针。
  • 堆块指针无效:在分配块后,它可以用作若干堆接口的参数,尤其是接口的 free() 类。页堆管理器立即检测无效的堆块指针。请参阅“调试页堆错误”,了解如何确定无效地址是只错几个字节,还是完全不正确。
  • 多线程不同步的堆访问:某些应用程序从多个线程调入堆。此类方案要求用户设置将要触发获取堆锁的标志。页堆管理器将在两个线程尝试同时调入堆时检测此类冲突。
  • 假设在相同的地址重新分配块:重新分配操作不保证能够返回相同的地址。当重新分配操作减小块的大小时尤其如此。某些应用程序假设重新分配将返回相同的地址。页堆管理器始终在重新分配过程中分配新块并释放旧块。空闲块被保护起来,避免对其进行读/写访问,因此,对空闲块的任何访问都将引发访问冲突。
  • 双重释放:该错误将相同的堆块释放多次,这是某些应用程序中的常见错误。页堆管理器立即检测到该错误,因为在第二次释放时,块不带正确的前缀头信息,因而无法在已分配的块中找到该块。请参阅“调试页堆错误”,了解如何分析第一次释放操作的堆跟踪信息。该错误可能是重新分配问题的另一种表现形式,因为当应用程序释放自己认为的块地址时,该块其实已在重新分配过程中被释放。
  • 访问已释放的块:释放的内存块由页堆管理器短期保留在受保护的内存池中。对这些块的任何访问都将引发访问冲突。根据“位置”原则,如果受保护的空闲池足够大,则大多数问题都能被发现。如果已释放的块仍在受保护的池中,则能立即发现错误。不过,如果重用内存,那么找到错误或确定导致错误的代码的可能性就要小一些。
  • 访问分配块结尾之后的内容:页堆管理器在分配的块之后立即放置一个不可访问的页。对块结尾之后的内容所做的任何访问都将引发访问冲突。某些应用程序需要的分配是 8 字节调整的。自 Windows NT 3.5 堆管理器以来一直支持此功能。所需的大小即使不是 8 字节调整的,也能获得 8 字节调整的地址,但在块的结尾之后会保留几个仍可访问的字节。如果应用程序只破坏这几个字节,那么只能在释放块时通过检查块后缀模式来发现错误。
  • 访问分配块开始之前的内容:您可以通过可设置的标志指示页堆管理器将不可访问的页放在块的开始位置,而不放在结尾。对块开始之前的内容所做的任何访问都将引发访问冲突。
错误正常页堆整页堆
堆指针无效立即发现立即发现
堆块指针无效立即发现立即发现
不同步访问立即发现立即发现
对重新分配地址的假设90% 在实际释放后发现90% 立即发现
双重释放90% 立即发现90% 立即发现
在释放后重用90% 在实际释放后发现90% 立即发现
访问块结尾之后的内容在释放后发现立即发现
访问块开始之前的内容在释放后发现立即发现(特殊标志)

调试页堆错误

有关“调试页堆错误”的更多信息,请参阅“应用程序兼容性工具包”中的“应用程序兼容性工具包参考”。

有关 Pageheap.exe 的“语法”及使用“示例”,请参阅“应用程序兼容性工具包”中的“应用程序兼容性工具包参考”。

有关其他信息,请参阅下面的 Microsoft 知识库文章:
294895 如何获取 Windows 应用程序兼容性工具包
Page Heap
属性

文章 ID:286470 - 上次审阅时间:04/20/2006 11:02:00 - 修订版本: 4.1

Microsoft Windows Server 2003 Standard Edition, Microsoft Windows Server 2003 Enterprise Edition, Microsoft Windows Server 2003 Datacenter Edition, Microsoft Windows XP Professional Edition, Microsoft Windows XP Home Edition, Microsoft Windows 2000 Professional Edition, Microsoft Windows 2000 Server, Microsoft Windows 2000 Advanced Server, Microsoft Windows 2000 Datacenter Server

  • kbinfo kbenv KB286470
反馈