未知的用户断点 NTDLL.DLL

未知的用户断点 NTDLL.DLL(http://www.debuginfo.com/tips/userbpntdll.html)

We were in the middle of the debugging session, when suddenly the debugger displayed a message similar to the following:

User breakpoint called from code at 0x77fa018c

or the following:

Unhandled exception at 0x77f767cd (ntdll.dll) in myapp.exe: User breakpoint.

What happened? We did not even set any breakpoints, so why there is a user breakpoint? The problem starts looking even stranger when we notice that the application works fine when we start it outside of the debugger! But under debugger it keeps showing the message about user breakpoint, so that we cannot effectively debug our application anymore.

Searching for an answer, we look at Debug Output window. And yes, there is another message! For example:

HEAP[myapp.exe]: Heap block at 00360F78 modified at 00360F90 past requested size of 10

Aha, it looks like the heap is corrupted. The contents of Call Stack window also point to the heap:

NTDLL! DbgBreakPoint@0 address 0x77fa018c
NTDLL! RtlpBreakPointHeap@4 + 38 bytes
NTDLL! RtlpCheckBusyBlockTail@4 + 106 bytes
NTDLL! RtlpValidateHeapEntry@12 + 146975 bytes
NTDLL! RtlDebugFreeHeap@12 + 191 bytes
NTDLL! RtlFreeHeapSlowly@12 + 70765 bytes
NTDLL! RtlFreeHeap@12 + 4078 bytes
MYAPP! free + 102 bytes
MYAPP! operator delete(void *) + 9 bytes
main(int 1, char * * 0x003610b0) line 21 + 15 bytes
MYAPP! mainCRTStartup + 180 bytes
KERNEL32! BaseProcessStart@4 + 130958 bytes

It turns out that when we start the application under debugger, the operating system uses a special kind of heap – Debug Win32 heap – instead of the normal heap. Whenever an operation on the heap is performed (such as allocating or freeing memory), the debug heap manager checks integrity of the heap entries and internal structures. If it founds a corruption, it notifies us via a hard-coded breakpoint, which is reported by the debugger.

How can we find the reason of the heap corruption? We can of course employ a specialized runtime checker application (such as BoundsChecker, Purify or Memory Validator), but I prefer to use PageHeap, because it is part of the operating system (since Windows 2000 SP2) and is easy to use. Detailed technical information about PageHeap can be found in KB286470.

We can enable Full PageHeap with the help of the following Registry entries:

HKLM/Software/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/yourapp.exe
    GlobalFlag = REG_DWORD 0x2000000
    PageHeapFlags = REG_DWORD 0x3

(replace yourapp.exe with the real name of your executable)

If you have Debugging Tools for Windows installed, there is another way to achieve the same – run the following command:

gflags –p /enable yourapp.exe /full

(gflags.exe can be found in the installation directory of Debugging Tools)

After we have enabled Full PageHeap, we should run the application under debugger and wait for access violations or hard-coded breakpoints. After an access violation occurred or a breakpoint has been hit, examination of the current source line and the call stack usually allows us to immediately see who is corrupting the heap.

Note also that it is recommended to link the application with release version of CRT library (that is, use /ML, /MT or /MD compiler option (or the corresponding IDE equivalent) instead of /MLd, /MTd or /MDd option). This is because debug version of CRT library uses its own heap checking infrastructure (by the way, not as powerful as Full PageHeap), which can overlap with PageHeap and hide some errors instead of exposing them.

Another situation that worth talking about is when we are debugging a DLL that is loaded by a large application (often a 3rd party application). Since there is probably no point in checking the whole application with PageHeap, we can ask it to check only the heap entries that are allocated by our DLL:

gflags –p /enable yourapp.exe /full /dlls yourdll.dll

 

(http://support.microsoft.com/?id=286470)

概要

本文介绍如何在 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 的相关命令及其使用方式极为重要:

通过在分配的结尾放置不可访问的页,或在释放块时检查填充模式,即可发现堆块中的损坏。

对于在已启用页堆的进程内创建的每个堆,均存在两种堆(整页堆和正常页堆)。

整页堆通过在分配的结尾放置不可访问的页来揭示堆块中的破坏。此方法的优点是可以实现突然死亡,这意味着进程将恰好在故障点上出现访问冲突 (AV)。此行为便于调试故障。它的缺点是每个分配至少使用一页已提交的内存。对于占用大量内存的进程来说,系统资源很快就会被耗尽。

当内存限制造成整页堆不可用时,则可以使用正常页堆。它在释放堆块时检查填充模式。此方法的优点是大大减少了内存耗用量。缺点是只能在释放块时检测损坏。这样难以调试故障。

 页堆工具的下载地址

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

http://www.microsoft.com/whdc/devtools/debugging/default.mspx (http://www.microsoft.com/whdc/devtools/debugging/default.mspx)

 

选择调查堆块损坏的方法

您可以采用以下两种方法之一发现堆块中的大多数破坏:

整页堆:在分配的结尾放置不可访问的页。

正常页堆:在释放块时检查填充模式

整页堆

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

整页堆的优点是它导致进程恰好在故障点上出现访问冲突 (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 就是这样的一个程序。

整页堆分配的未提交页

<script type="text/javascript"> loadTOCNode(2, 'moreinformation'); </script> 核心的整页堆实现为小于一页的所有分配提交两页。一页用于用户分配,另一页作为不可访问页置于缓冲区结尾。

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

错误注入

<script type="text/javascript"> loadTOCNode(2, 'moreinformation'); </script> 您可以控制页堆管理器,以便故意让某些分配失败。这样可以模拟内存不足的情况,而不必实际使用所有系统资源。

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

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

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

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

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

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

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

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

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

只要自定义分配/释放功能最终调入 NT 堆管理接口(即 RtlAllocateHeapRtlFreeHeap),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 APIPageheap.exe 无法用来验证这些分配。动态链接确保获得最新的 C 运行时库 (msvcrt.dll)

Pageheap.exe 发现的错误类型

<script type="text/javascript"> loadTOCNode(2, 'moreinformation'); </script> 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 语法及使用示例,请参阅应用程序兼容性工具包中的应用程序兼容性工具包参考

 

 

调试信息专业网站:

1、   www.debuginfo.com

2、   http://www.softwareverify.com/cpp/memory/index.html

3、   http://www-306.ibm.com/software/awdtools/purifyplus/

4、    

 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值