whu*_*nmr 16 .net wpf windbg out-of-memory
最近我们的应用遇到了一个奇怪的问
应用程序在WPF窗口中有一个win32窗口,当调整WPF窗口大小时,问题就出现了.
堆栈跟踪:
Exception object: 0000000002ab2c78
Exception type: System.OutOfMemoryException
InnerException: <none>
StackTrace (generated):
SP IP Function
0048D94C 689FB82F PresentationCore_ni!System.Windows.Media.Composition.DUCE+Channel.SyncFlush()+0x80323f
0048D98C 681FEE37 PresentationCore_ni!System.Windows.Media.Composition.DUCE+CompositionTarget.UpdateWindowSettings(ResourceHandle, RECT, System.Windows.Media.Color, Single, System.Windows.Media.Composition.MILWindowLayerType, System.Windows.Media.Composition.MILTransparencyFlags, Boolean, Boolean, Boolean, Int32, Channel)+0x127
0048DA38 681FEAD1 PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean, System.Nullable`1<ChannelSet>)+0x301
0048DBC8 6820718F PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean)+0x2f
0048DBDC 68207085 PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowPos(IntPtr)+0x185
0048DC34 681FFE9F PresentationCore_ni!System.Windows.Interop.HwndTarget.HandleMessage(Int32, IntPtr, IntPtr)+0xff
0048DC64 681FD0BA PresentationCore_ni!System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0x3a
0048DC88 68C6668E WindowsBase_ni!MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0xbe
0048DCD4 68C665BA WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)+0x7a
0048DCE4 68C664AA WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Boolean)+0x8a
0048DD08 68C6639A WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Boolean, System.Delegate)+0x4a
0048DD50 68C64504 WindowsBase_ni!System.Windows.Threading.Dispatcher.WrappedInvoke(System.Delegate, System.Object, Boolean, System.Delegate)+0x44
0048DD70 68C63661 WindowsBase_ni!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Boolean)+0x91
0048DDB4 68C635B0 WindowsBase_ni!System.Windows.Threading.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority, System.Delegate, System.Object)+0x40
0048DDD8 68C65CFC WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)+0xdc
StackTraceString: <none>
HResult: 8007000e
Run Code Online (Sandbox Code Playgroud)
另外,我发现了一些相关的链接:
有没有办法避免或处理这个问题?
如何找出真正的问题?
从调用堆栈,我们可以确定问题来自.NET Framework吗?
感谢您的回答或评论!
Ray*_*rns 23
您的问题不是由托管内存泄漏引起的.很明显,你在非托管代码中的某个地方发现了一个错误.
在几次MILCore调用之后调用SyncFlush()方法,它似乎会导致已发送的更改立即处理,而不是留在队列中供以后处理.由于调用会处理先前发送的所有内容,因此可以从您发送的调用堆栈中排除可视树中的任何内容.
包含非托管调用的调用堆栈可能会显示更多有用的信息.使用本机调试或使用windbg或其他本机代码调试器在VS.NET下运行应用程序.设置调试器以中断异常,并在相对断点处获取调用堆栈.
调用堆栈当然会进入MILCore,从那里它可以进入DirectX层和DirectX驱动程序.可以在此本机调用堆栈中的某处找到关于代码的哪个部分导致问题的线索.
有可能是MILCore根据您所说的内容将一些参数的巨大值传递给DirectX.检查您的应用程序是否存在任何可能导致DirectX分配大量内存的错误.要寻找的事情的例子是:
解决此问题的另一种方法是逐步简化应用程序,直到问题消失,然后非常封闭地查看最后删除的内容.方便时,最好将其作为二分查找:最初减少一半的视觉复杂性.如果它有效,则将已移除的一半放回去,否则移除另一半.重复直到完成.
另请注意,实际删除UI组件通常是不必要的,以防止MILCore看到.任何带Visibility.Hidden的Visual都可以完全跳过.
没有通用的方法来避免这个问题,但搜索技术将帮助您确定具体需要更改的内容以在特定情况下修复它.
从调用堆栈中可以肯定地说,您在.NET Framework或特定视频卡的DirectX驱动程序中发现了一个错误.
关于您发布的第二个堆栈跟踪
John Knoeller是正确的,从RtlFreeHeap到ConvertToUnicode的转换是无稽之谈,但从中得出了错误的结论.我们看到的是,在追溯堆栈时,您的调试器丢失了.它从异常中正确启动但在Assembly.ExecuteMainMethod帧下面丢失,因为在处理异常并调用调试器时,堆栈的那部分已被覆盖.
不幸的是,任何对此堆栈跟踪的分析对于您的目的都是无用的,因为它被捕获得太晚了.我们看到的是在处理WM_LBUTTONDOWN期间发生的异常,该异常转换为WM_SYSCOMMAND,然后捕获异常.换句话说,您单击导致系统命令的某些内容(例如调整大小),这会导致异常.在捕获此堆栈跟踪时,异常已在处理中.您看到User32和UxTheme调用的原因是因为这些涉及处理按钮单击.他们与真正的问题无关.
您处于正确的轨道上,但是您需要在分配失败时捕获堆栈跟踪(或者您可以使用我上面建议的其他方法之一).
当第一个堆栈跟踪中的所有托管帧出现在堆栈跟踪中并且堆栈顶部是内存分配失败时,您将知道堆栈跟踪正确.请注意,我们只对出现在DUCE+Channel.SyncFlush调用上方的非托管框架感兴趣- 下面的所有内容都将是.NET Framework和您的应用程序代码.
如何在正确的时间获取本机堆栈跟踪
您希望在显示的DUCE+Channel.SyncFlush调用中第一次内存分配失败时获得堆栈跟踪.这可能很棘手.我使用了三种方法:(请注意,在每种情况下,您都会在SyncFlush调用中使用断点开始 - 有关详细信息,请参阅下面的注释)
设置调试器以中断所有异常(托管和非托管),然后继续按下go(F5或"g"),直到它打破你感兴趣的内存分配异常.这是第一个尝试因为它很快,但是在使用本机代码时经常会失败,因为本机代码通常会向调用本机代码返回错误代码,而不是抛出异常.
设置调试器以中断所有异常并在常用内存分配例程上设置断点,然后重复按F5(go)直到异常发生,计算您点击的F5数量.下次运行时,使用少一个F5,您可能正在进行生成异常的分配调用.将调用堆栈捕获到记事本,然后从那里重复F10(跳过)以查看它是否真的是失败的分配.
在SyncFlush调用的第一个本机帧(这是wpfgfx_v0300!MilComposition_SyncFlush)上设置断点,以跳过托管到本机的转换,然后运行F5以运行它.F10(跳过)直到EAX包含错误代码E_OUTOFMEMORY(0x8007000E),ERROR_OUTOFMEMORY(0x0000000E)或ERROR_NOT_ENOUGH_MEMORY(0x0000008)之一.请注意最近的"调用"指令.下次运行程序时,运行到那里并进入程序.重复此操作,直到找到导致问题的内存分配调用并转储堆栈跟踪.请注意,在许多情况下,您会发现自己循环通过较大的数据结构,因此需要一些智能来设置适当的断点以跳过循环,以便您可以快速到达需要的位置.这种技术非常可靠,但非常耗费人力.
注意:在每种情况下,您都不希望设置断点或开始单步执行,直到您的应用程序位于失败的DUCE+Channel.SyncFlush调用中.要确保这一点,请在禁用所有断点的情况下启动应用程序.在它运行时,启用断点System.Windows.Media.Composition.DUCE+Channel.SyncFlush并调整窗口大小.第一次只是点击F5以确保在第一次SyncFlush调用时异常失败(如果没有,则计算在异常发生之前必须多少次击中F5).然后禁用断点并重新启动程序.重复此过程,但这次是在正确的时间点击SyncFlush呼叫后,设置断点或单步执行如上所述.
建议
我上面描述的调试技术是劳动密集型的:计划至少花费几个小时.因此,我通常会反复简化我的应用程序,以便在跳转到调试器之前找出确切的问题.这有两个好处:它会给你一个很好的repro来发送显卡供应商,它会使你的调试更快,因为显示更少,因此单步执行的代码更少,分配更少等.
因为问题只发生在特定的图形卡上,所以毫无疑问问题是显卡驱动程序中的错误或调用它的MilCore代码中的错误.很可能是在显卡驱动程序中,但MilCore可能传递的是大多数显卡正确处理的无效值,但不是这个.我上面描述的调试技术会告诉你这种情况:例如,如果MilCore告诉显卡分配1000000x1000000像素区域并且显卡正在提供正确的分辨率信息,那么错误就在MilCore中.但如果MilCore的请求合理,那么这个错误就在显卡驱动程序中.
| 归档时间: |
|
| 查看次数: |
8220 次 |
| 最近记录: |