如何调试托管堆中的损坏问题

38

我的程序抛出了一个错误,无法通过catch (Exception e)块处理,然后就崩溃了:

访问冲突已损坏状态异常。

奇怪的是,因为据我所知,损坏的状态异常是由非托管代码引发的,而在这里,我在调用StringBuilder方法时遇到了这个异常。

该代码在后台线程中运行,偶尔会崩溃,很难复现。因此,我将WinDbg附加到进程并获得了以下异常堆栈:

000000001dabd8c8 000007feea129a1d [HelperMethodFrame: 000000001dabd8c8]
000000001dabda00 000007fee90cfce8 System.Text.StringBuilder.ExpandByABlock(Int32)
000000001dabda40 000007fee90cfba4 System.Text.StringBuilder.Append(Char*, Int32)
000000001dabdaa0 000007fee9102955 System.Text.StringBuilder.Append(System.String, Int32, Int32)
000000001dabdaf0 000007ff00bf5ce3 MineUtils.Common.Strings.Strings.Replace(System.String, System.String, System.String, Boolean, Boolean)
000000001dabdb90 000007ff00bf5a59 MineUtils.Common.Strings.Strings.RemoveSubstrings(System.String, System.String, System.String, Boolean) [D:\Programs\Visual Studio 2005 Projects\MineUtils.Common\Strings\Strings.Common-Main.cs @ 1481

WinDbg 显示发生了此异常:

EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 000007feea129a1d (clr!WKS::gc_heap::find_first_object+0x0000000000000092)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: 0000000000003d80
Attempt to read from address 0000000000003d80

我读到这种异常可以通过使用一个方法属性[HandleProcessCorruptedStateExceptions]来处理,但是如果我只使用StringBuilder为什么会发生这种异常呢?

这是之前WinDbg分析的结果(StringBuilder.ToString()引起了异常)

*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

FAULTING_IP:
clr!WKS::gc_heap::find_first_object+92
000007fe`ea129a1d f70100000080    test    dword ptr [rcx],80000000h

EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 000007feea129a1d (clr!WKS::gc_heap::find_first_object+0x0000000000000092)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000001
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: 0000000000001c98
Attempt to read from address 0000000000001c98

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

EXCEPTION_PARAMETER1:  0000000000000000

EXCEPTION_PARAMETER2:  0000000000001c98

READ_ADDRESS:  0000000000001c98

FOLLOWUP_IP:
clr!WKS::gc_heap::find_first_object+92
000007fe`ea129a1d f70100000080    test    dword ptr [rcx],80000000h

MOD_LIST: <ANALYSIS/>

NTGLOBALFLAG:  0

APPLICATION_VERIFIER_FLAGS:  0

MANAGED_STACK:
(TransitionMU)
000000001AB7DFC0 000007FEE90CFE07 mscorlib_ni!System.Text.StringBuilder.ToString()+0x27
000000001AB7E010 000007FF00C750A9 SgmlReaderDll!Sgml.Entity.ScanToken(System.Text.StringBuilder, System.String, Boolean)+0x169
000000001AB7E080 000007FF00C760E6 SgmlReaderDll!Sgml.SgmlDtd.ParseParameterEntity(System.String)+0xc6
000000001AB7E0F0 000007FF00C76FD8 SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x298
000000001AB7E160 000007FF00C7701C SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x2dc
000000001AB7E1D0 000007FF00C7701C SgmlReaderDll!Sgml.SgmlDtd.ParseModel(Char, Sgml.ContentModel)+0x2dc
000000001AB7E240 000007FF00C76BA5 SgmlReaderDll!Sgml.SgmlDtd.ParseContentModel(Char)+0x65
000000001AB7E290 000007FF00C763D7 SgmlReaderDll!Sgml.SgmlDtd.ParseElementDecl()+0xe7
000000001AB7E320 000007FF00C747A1 SgmlReaderDll!Sgml.SgmlDtd.Parse()+0xc1
000000001AB7E370 000007FF00C73EF5 SgmlReaderDll!Sgml.SgmlDtd.Parse(System.Uri, System.String, System.IO.TextReader, System.String, System.String, System.Xml.XmlNameTable)+0x175
000000001AB7E410 000007FF00C73B33 SgmlReaderDll!Sgml.SgmlReader.LazyLoadDtd(System.Uri)+0x163
000000001AB7E480 000007FF00C737B9 SgmlReaderDll!Sgml.SgmlReader.OpenInput()+0x19
000000001AB7E4E0 000007FF00C7334C SgmlReaderDll!Sgml.SgmlReader.Read()+0x1c
000000001AB7E530 000007FEE5983C4C System_Xml_ni!System.Xml.XmlLoader.Load(System.Xml.XmlDocument, System.Xml.XmlReader, Boolean)+0xac
000000001AB7E590 000007FEE5983730 System_Xml_ni!System.Xml.XmlDocument.Load(System.Xml.XmlReader)+0x90
...
000000001AB7F0A0 000007FEE97ED792 mscorlib_ni!System.Threading.Tasks.Task.Execute()+0x82
000000001AB7F100 000007FEE90A181C mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0xdc
000000001AB7F160 000007FEE97E7F95 mscorlib_ni!System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)+0x1b5
000000001AB7F1E0 000007FEE97E7D90 mscorlib_ni!System.Threading.Tasks.Task.ExecuteEntry(Boolean)+0xb0
000000001AB7F220 000007FEE90EBA83 mscorlib_ni!System.Threading.ThreadPoolWorkQueue.Dispatch()+0x193
000000001AB7F2C0 000007FEE90EB8D5 mscorlib_ni!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()+0x35
(TransitionUM)

EXCEPTION_OBJECT: !pe 2a61228
Exception object: 0000000002a61228
Exception type:   System.ExecutionEngineException
Message:          <none>
InnerException:   <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131506

MANAGED_OBJECT_NAME:  System.ExecutionEngineException

MANAGED_STACK_COMMAND:  _EFN_StackTrace

LAST_CONTROL_TRANSFER:  from 000007feea12bce4 to 000007feea129a1d

ADDITIONAL_DEBUG_TEXT:  Followup set based on attribute [Is_ChosenCrashFollowupThread] from Frame:[0] on thread:[PSEUDO_THREAD]

FAULTING_THREAD:  ffffffffffffffff

DEFAULT_BUCKET_ID:  INVALID_POINTER_READ_CALL

PRIMARY_PROBLEM_CLASS:  INVALID_POINTER_READ_CALL

BUGCHECK_STR:  APPLICATION_FAULT_INVALID_POINTER_READ_WRONG_SYMBOLS_CALL__SYSTEM.EXECUTIONENGINEEXCEPTION

再次更新

启用分页堆后,以下是异常的WinDbg堆栈:

 (1480.e84): Access violation - code c0000005 (first chance)
ntdll!ZwTerminateProcess+0xa:
00000000`77c415da c3              ret
0:023> !clrstack
OS Thread Id: 0xe84 (23)
Child SP         IP               Call Site
0000000037ded848 0000000077c415da [HelperMethodFrame: 0000000037ded848]
0000000037dedab0 000007fee9effd17 System.Text.StringBuilder.ToString()*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_64\mscorlib\8f7f691aa155c11216387cf3420d9d1b\mscorlib.ni.dll

0000000037dedb00 000007ff00cceae9 Sgml.Entity.ScanToken(System.Text.StringBuilder, System.String, Boolean)

0000000037dedb70 000007ff00cd19b2 Sgml.SgmlDtd.ParseAttDefault(Char, Sgml.AttDef)
0000000037dedbc0 000007ff00cd120b Sgml.SgmlDtd.ParseAttDef(Char)
0000000037dedc00 000007ff00cd1057 Sgml.SgmlDtd.ParseAttList(System.Collections.Generic.Dictionary`2<System.String,Sgml.AttDef>, Char)
0000000037dedc50 000007ff00cd10cd Sgml.SgmlDtd.ParseAttList(System.Collections.Generic.Dictionary`2<System.String,Sgml.AttDef>, Char)
0000000037dedca0 000007ff00cd0e9a Sgml.SgmlDtd.ParseAttList()
0000000037dedd10 000007ff00cce1f1 Sgml.SgmlDtd.Parse()
0000000037dedd60 000007ff00ccd945 Sgml.SgmlDtd.Parse(System.Uri, System.String, System.IO.TextReader, System.String, System.String, System.Xml.XmlNameTable)
0000000037dede00 000007ff00ccd582 Sgml.SgmlReader.LazyLoadDtd(System.Uri)
0000000037dede70 000007ff00ccd1f9 Sgml.SgmlReader.OpenInput()
0000000037deded0 000007ff00cccd8c Sgml.SgmlReader.Read()
0000000037dedf20 000007fee67b3bfc System.Xml.XmlLoader.Load(System.Xml.XmlDocument, System.Xml.XmlReader, Boolean)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Xml\8e4323f5bfb90be4621456033d8b404b\System.Xml.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Xml\8e4323f5bfb90be4621456033d8b404b\System.Xml.ni.dll

0000000037dedf80 000007fee67b36e0 System.Xml.XmlDocument.Load(System.Xml.XmlReader)
[deleted]
0000000037deea90 000007feea61d432 System.Threading.Tasks.Task.Execute()
0000000037deeaf0 000007fee9ed17ec System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0000000037deeb50 000007feea617c35 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)
0000000037deebd0 000007feea617a30 System.Threading.Tasks.Task.ExecuteEntry(Boolean)
0000000037deec10 000007fee9f1b953 System.Threading.ThreadPoolWorkQueue.Dispatch()
0000000037deecb0 000007fee9f1b7a5 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
0000000037def310 000007feeae4dc54 [DebuggerU2MCatchHandlerFrame: 0000000037def310]
0:023> !verifyheap
-verify will only produce output if there are errors in the heap
The garbage collector data structures are not in a valid state for traversal.
It is either in the "plan phase," where objects are being moved around, or
we are at the initialization or shutdown of the gc heap. Commands related to
displaying, finding or traversing objects as well as gc heap segments may not
work properly. !dumpheap and !verifyheap may incorrectly complain of heap
consistency errors.
object 000000000e34caf8: bad member 000000001024b9a0 at 000000000e34cb08
curr_object:      000000000e34caf8
Last good object: 000000000e34cab0
----------------
0:023> !analyze
Last event: 1480.e84: Exit process 0:1480, code 80131506
  debugger time: Sun Sep 18 14:22:42.592 2011 (UTC + 1:00)
0:023> !analyze -v
Last event: 1480.e84: Exit process 0:1480, code 80131506
  debugger time: Sun Sep 18 14:22:42.592 2011 (UTC + 1:00)
0:023> .do e34cab0
          ^ Syntax error in '.do e34cab0'
0:023> !do e34cab0
Name:        System.String
MethodTable: 000007feea026870
EEClass:     000007fee9baed58
Size:        72(0x48) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      appliedFiltersContainer
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feea02c758  4000103        8         System.Int32  1 instance               23 m_stringLength
000007feea02b298  4000104        c          System.Char  1 instance               61 m_firstChar
000007feea026870  4000105       10        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000021343a0:000000000db21420 <<
0:023> !do e34caf8
<Note: this object has an invalid CLASS field>
Name:        System.Reflection.RuntimeAssembly
MethodTable: 000007feea02a128
EEClass:     000007fee9baf968
Size:        48(0x30) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feea9ef7f0  4000e14        8 ...solveEventHandler  0 instance 0000000000000000 _ModuleResolve
000007feea036338  4000e15       10 ...che.InternalCache  0 instance 000000001024b9a0 m_cachedData
000007feea0259c8  4000e16       18        System.Object  0 instance 000000000e3abd18 m_syncRoot
000007feea033450  4000e17       20        System.IntPtr  1 instance         37a95f10 m_assembly

它可以是什么?


你不应该得到那种异常,但是你没有展示你的代码。你需要发布“Replace”函数,也许还有“RemoveSubstring”函数。 - Gabe
我可能错误地解释了WinDbg的结果,但CLR堆栈指向这些方法,我已经发布了它们的源代码。 - net_prog
该程序解析网页并在不同的位置崩溃,因为它使用了众多线程,但始终有一个StringBuilder位于堆栈顶部。上一次是由于StringBuilder.ToString()方法引起了相同的异常,现在是由于StringBuilder.Append()方法引起的。 - net_prog
3
实际上,位于堆栈顶部的是垃圾收集器。StringBuilder 存在是因为它会触发 GC。如下所述,你的问题是堆损坏,而通常不是由实际崩溃的代码引起的。 - Gabe
1
这看起来非常像您的非托管代码没有开发为在多线程环境下运行。我建议您将对非托管代码的所有调用封装在 lock {} 块中,以便一次只有一个线程可以访问它。但这仍然可能不足够,因为该代码可能要求只有一个线程,永远 可以访问它。 - John Saunders
显示剩余3条评论
2个回答

63

最近,我遇到了一种管理堆损坏的问题,这对我来说是新事物。我感到非常沮丧,必须学习许多东西才能进行调试。我要感谢 Seva Titov 给了我正确的方向。他的答案简明扼要,非常有帮助。我想记录我为调试问题所采取的行动,以便自己参考。这可能对其他刚接触这个问题的人有所帮助。

在.NET 4中调试堆损坏:

如何怀疑堆损坏?

简而言之:

  1. The application crashes randomly with no regards to the applied exception catching and even goes through blankets like catch(Exception) which are supposed to catch all exceptions.

  2. Examining the CLR stack in the application crash dumps shows the garbage collector on the top of the stack:

    000000001dabd8c8 000007feea129a1d [**HelperMethodFrame**: 000000001dabd8c8]
    000000001dabda00 000007fee90cfce8 System.Text.StringBuilder.ExpandByABlock(Int32)
    000000001dabda40 000007fee90cfba4 System.Text.StringBuilder.Append(Char*, Int32)
    ...
    
    EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
    ExceptionAddress: 000007feea129a1d (**clr!WKS::gc_heap**::find_first_object+0x0000000000000092)
       ExceptionCode: c0000005 (Access violation)
      ExceptionFlags: 00000000
    NumberParameters: 2
       Parameter[0]: 0000000000000000
       Parameter[1]: 0000000000003d80
    ...
    
  3. The CLR stack always shows different points. Whether the crash occurred or the code which is shown is clearly irrelevant, like StringBuilder's method which is shown to cause the exception.

如需更多详细信息,请参阅.NET Crash:调用非托管代码时发生的托管堆损坏

逐步进行。 如果前一步没有帮助,则使用下一步。

步骤1。 检查代码。

检查代码是否使用了不安全或本机代码:

  1. 审查代码中的unsafeDllImport语句。
  2. 下载.NET Reflector并使用它来分析应用程序集中的PInvoke。同样地,分析应用程序使用的第三方程序集。

如果发现使用了不安全或本机代码,请对其进行额外关注。在这种情况下,堆损坏最常见的原因是缓冲区溢出或参数类型不匹配。确保提供给本机代码的缓冲区足够大,并且传递给本机代码的所有参数都是预期类型。

步骤2。 检查是否可以捕获此损坏状态异常

要处理此类异常,需要使用[HandleProcessCorruptedStateExceptions]属性修饰包含catch(Exception)语句的方法,或在app.config文件中应用以下内容:

<configuration>
    <runtime>
        <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

在成功捕获异常的情况下,您可以记录并检查它。这意味着这不是堆损坏问题。
无法处理堆损坏异常:HandleProcessCorruptedStateExceptions似乎不起作用
有关损坏状态异常的更多信息,请参见.NET4中有关损坏状态异常的所有内容
第3步。实时调试。
在此步骤中,我们在生产环境(或可以重现崩溃的地方)实时调试崩溃应用程序。
Microsoft Windows SDK for Windows 7 and .NET Framework 4下载Windows调试工具(将下载Web安装程序,允许选择要安装的必需组件 - 标记所有组件)。它将安装所需调试工具的32位和64位版本(如果您的系统是x64)。
在此需要知道如何将WinDbg附加到实时进程,如何获取崩溃转储并检查它们,如何在WinDbg中加载SOS扩展(详细信息请搜索谷歌)。

启用调试助手:

  1. 启动应用程序验证器 (C:\Program Files\Application Verifier - 使用所需的版本,x86或x64,取决于您的可执行文件编译模式),在左窗格中添加您的可执行文件,在右窗格中检查一个节点 "Basics / Heaps"。保存更改。

  2. 启动全局标志助手 (C:\Program Files\Debugging Tools for Windows\gflags.exe - 再次选择正确的版本,x86或x64)。一旦启动了 全局标志,转到 "Image File" 选项卡,在顶部文本框中输入您的可执行文件名称,不包括任何路径 (例如,"MyProgram.exe")。然后按下 Tab 键并设置以下框:

    • 启用堆尾检查
    • 启用堆空闲检查
    • 启用堆参数检查
    • 启用调用时堆验证
    • 禁用释放时堆合并
    • 启用页面堆
    • 启用堆标记
    • 启用应用程序验证器
    • 调试器 (在右侧的文本框中键入已安装 WinDbg 的路径,例如,C:\Program Files\Debugging Tools for Windows (x64)\windbg.exe -g)。

    有关更多详细信息,请参见 Heap Corruption, Part 2

  3. 转到 "控制面板/系统和安全/系统" (或右键单击“开始”菜单中的“计算机”,然后选择“属性”。在显示的对话框中,转到 "高级" 选项卡并单击 "环境变量" 按钮。在显示的对话框中,添加一个新的系统变量 (如果您是系统管理员 - 否则是用户变量 - 在这种情况下,您需要注销/登录)。所需的变量是 "COMPLUS_HeapVerify",其值为 "1"。更多细节可以在 Stack Overflow 问题 .NET/C#: How to set debugging environment variable COMPLUS_HeapVerify? 中找到。

现在我们准备好开始调试了。启动应用程序。WinDbg 应该会自动启动。让应用程序运行,直到它崩溃并进入 WinDbg,然后检查转储。

提示:要快速删除 Global FlagsApplication Verifier 和调试器附加设置,请在注册表中删除以下键: x64 - HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*YourAppName*

第四步。启用 MDA。

尝试使用托管调试助手。详情请参见 Stack Overflow 问题 What MDAs are useful to track a heap corruption?

必须与 WinDbg 一起使用 MDA。我甚至与 Global FlagsApplication Verifier 一起使用它们。

第五步。启用 GCStress。

使用 GCStress 是一个极端的选择,因为应用程序几乎无法使用,但它仍然是一种方法。更多细节请参见 GCStress: How to turn on in Windows 7?

第六步。编译成 x86。

如果您的应用程序当前正在编译为“Any CPU”或“x64”平台,请尝试将其编译为“x86”,如果对于您使用哪个平台没有区别。我看到有人报告说这样可以解决问题。
步骤7. 禁用并发GC - 这是对我有效的方法
在.NET 4中有一个已知问题,在线程Access Violation in .NET 4 Runtime in gc_heap::garbage_collect with no unmanaged modules中报告了该问题。可以通过在app.config文件中禁用并发GC来解决问题。
<?xml version="1.0"?>
<configuration>
    <runtime>
        <gcConcurrent enabled="false" />
    </runtime>
</configuration>

10
您遇到了托管堆破坏问题。由于通常在堆被破坏之后很长时间才会显示出问题,因此找到托管堆破坏问题的根本原因并不容易。在您的情况下,StringBuilder是一个误导线索。破坏发生在之前的某个时刻。
我会进行以下操作:
1. 检查是否有任何不安全的C#代码。如果有,请再次检查逻辑。 2. 为您的应用程序启用分页堆。使用分页堆运行它将有助于发现与非托管代码相关的问题 - 如果非托管代码正在破坏托管堆。 3. 在不同的地方运行!VerifyHeap。这样,您可能能够定位代码中发生损坏的地方。 4. 如果您已经为应用程序启用了服务器类型的垃圾收集,请临时更改为工作站垃圾收集--这样您将获得更可预测的行为。 5. 阅读Tesses'博客文章 .NET Crash: Managed Heap Corruption calling unmanaged code。它演示了一些托管堆损坏的例子。
请注意,在使用WinDbg运行代码时,您会遇到偶发的“first chance AV”(AV为异常错误)。可以安全地忽略它,只需在将WinDbg附加到进程后键入sxd av,并仅调查“second chance AV”。

我在启用分页堆之后又遇到了另一个崩溃堆栈,现在指向另一段代码,请您看一下。这次没有任何VerifyHeap的提示信息。 - net_prog
@net_prog,这次是第二次机会的 AV 吗?你只需要查看第二次机会的 AVs 来处理堆损坏问题。如果 !VerifyHeap 没有报告任何损坏,那么这意味着堆仍然良好,并且您遇到了其他问题,而不是堆损坏。 - seva titov
我想这是第一次机会:"(f24.103c):访问冲突 - 代码c0000005(第一次机会)",我没有继续查看程序是否会退出,我只停止了调试。我将不得不等待另一个崩溃以获取详细信息。 - net_prog
1
在我遇到的大多数情况下,pageheap总是会导致二次机会AV。然而,我很容易想象它可能不会引起第二次机会AV。如果发生以下情况:a)所有异常都有显式的catch (...)处理程序;和b)应用程序认为它比操作系统聪明得多,并且吞噬了它不应该吞噬的异常。在编写良好的应用程序中,如果您不知道如何从异常中恢复,应将其传递给操作系统(以及调试器,如果已连接)。 - seva titov
非常有趣,请看看这里的最后一个答案(http://social.msdn.microsoft.com/Forums/en-US/clr/thread/33920b39-690c-42c8-b04a-0f1f7176835a/)。这与我的情况有关吗? - net_prog
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接