16

最近、アプリケーションで奇妙な問題が発生しました。

アプリケーションの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

また、私はいくつかの関連リンクを見つけました:

relatedA

relatedB

  1. この問題を回避または処理する方法はありますか?

  2. 本当の問題を見つける方法は?

  3. コールスタックから、問題の原因が.NET Frameworkであると判断できますか?

回答やコメントありがとうございます!

4

4 に答える 4

25

問題の原因は、管理されたメモリリークではありません。明らかに、アンマネージコードのどこかでバグをくすぐっています。

SyncFlush()メソッドは、いくつかのMILCore呼び出しの後に呼び出され、送信された変更が後で処理するためにキューに残されるのではなく、すぐに処理されるように見えます。呼び出しは以前に送信されたすべてのものを処理するため、ビジュアルツリー内の何も送信した呼び出しスタックから除外することはできません。

管理されていない呼び出しを含む呼び出しスタックは、より有用な情報をもたらす可能性があります。ネイティブデバッグ、またはwindbgまたは別のネイティブコードデバッガーを使用して、VS.NETでアプリケーションを実行します。例外でブレークするようにデバッガーを設定し、相対ブレークポイントで呼び出しスタックを取得します。

もちろん、呼び出しスタックはMILCoreに降り、そこからDirectXレイヤーとDirectXドライバーに入る可能性があります。コードのどの部分が問題を引き起こしたかについての手がかりは、このネイティブの呼び出しスタックのどこかにあるかもしれません。

MILCoreは、あなたが言っていることに基づいて、あるパラメーターの巨大な値をDirectXに渡している可能性があります。DirectXに大量のメモリを割り当てるバグを引き起こす可能性のあるものがないか、アプリケーションを確認してください。探すべきものの例は次のとおりです。

  • 非常に高い解像度でロードするように設定されているBitmapSources。
  • 大きなWritableBitmaps
  • 非常に大きい(または負の)変換値またはサイズ値

この問題を攻撃する別の方法は、問題がなくなるまでアプリケーションを段階的に単純化し、最後に削除したものを非常に注意深く調べることです。便利な場合は、これを二分探索として実行するとよいでしょう。最初は、視覚的な複雑さの半分を切り取ります。動作する場合は、削除したものの半分を元に戻します。それ以外の場合は、残りの半分を削除します。完了するまで繰り返します。

また、通常、UIコンポーネントを実際に削除して、MILCoreに表示されないようにする必要はないことにも注意してください。Visibility.Hiddenのあるビジュアルは完全にスキップできます。

この問題を回避する一般的な方法はありませんが、検索手法を使用すると、特定の場合に修正するために具体的に何を変更する必要があるかを特定できます。

コールスタックから、特定のビデオカードのNETFrameworkまたはDirectXドライバーのいずれかにバグが見つかったと言っても差し支えありません。

投稿した2番目のスタックトレースについて

John Knoellerは、RtlFreeHeapからConvertToUnicodeへの移行はナンセンスであると正しいですが、そこから間違った結論を導き出します。私たちが見ているのは、スタックをトレースバックするときにデバッガーが失われたことです。Assembly.ExecuteMainMethod例外から正しく開始されましたが、例外が処理されてデバッガーが呼び出されたときにスタックのその部分が上書きされたため、フレームの下で失われました。

残念ながら、このスタックトレースの分析は、キャプチャが遅すぎるため、目的には役立ちません。私たちが見ているのは、WM_SYSCOMMANDに変換されたWM_LBUTTONDOWNの処理中に発生した例外であり、これが例外をキャッチします。つまり、システムコマンド(サイズ変更など)の原因となったものをクリックしたため、例外が発生しました。このスタックトレースがキャプチャされた時点で、例外はすでに処理されていました。User32とUxThemeの呼び出しが表示される理由は、これらがボタンクリックの処理に関係しているためです。彼らは本当の問題とは何の関係もありません。

あなたは正しい方向に進んでいますが、割り当てが失敗した瞬間にスタックトレースをキャプチャする必要があります(または、上記で提案した他のアプローチの1つを使用できます)。

最初のスタックトレースのすべての管理対象フレームがそこに表示され、スタックの最上位が失敗したメモリ割り当てである場合、正しいスタックトレースがあることがわかります。呼び出しの上に表示されるアンマネージフレームにのみ関心があることに注意してください。そのDUCE+Channel.SyncFlush下にあるものはすべて、NETFrameworkとアプリケーションコードになります。

適切なタイミングでネイティブスタックトレースを取得する方法

DUCE+Channel.SyncFlush示されている呼び出し内で最初のメモリ割り当てが失敗したときにスタックトレースを取得する必要があります。これは注意が必要かもしれません。私が使用するアプローチは3つあります:(いずれの場合も、SyncFlush呼び出し内のブレークポイントから開始することに注意してください-詳細については、以下の注を参照してください)

  1. すべての例外(マネージドおよびアンマネージド)でブレークするようにデバッガーを設定し、目的のメモリ割り当て例外でブレークするまでgo(F5、または「g」)を押し続けます。これは高速であるため、最初に試すことです。 、ただし、ネイティブコードは例外をスローするのではなく、呼び出し元のネイティブコードにエラーコードを返すことが多いため、ネイティブコードを操作するときに失敗することがよくあります。

  2. すべての例外でブレークするようにデバッガーを設定し、共通のメモリ割り当てルーチンにブレークポイントを設定してから、例外が発生するまでF5(go)を繰り返し押して、ヒットしたF5の数を数えます。次回実行するときは、F5を1つ少なく使用すると、例外を生成した割り当て呼び出しを行っている可能性があります。コールスタックをメモ帳にキャプチャし、そこからF10(ステップオーバー)を繰り返して、本当に失敗した割り当てであるかどうかを確認します。

  3. SyncFlush(これはwpfgfx_v0300!MilComposition_SyncFlush)によって呼び出される最初のネイティブフレームにブレークポイントを設定して、マネージドからネイティブへの遷移をスキップし、次にF5を設定して実行します。EAXにエラーコードE_OUTOFMEMORY(0x8007000E)、ERROR_OUTOFMEMORY(0x0000000E)、またはERROR_NOT_ENOUGH_MEMORY(0x0000008)のいずれかが含まれるまで、関数itをF10(ステップオーバー)します。最新の「呼び出し」命令に注意してください。次にプログラムを実行するときは、そこまで実行してステップインします。問題の原因となったメモリ割り当て呼び出しが発生するまでこれを繰り返し、スタックトレースをダンプします。多くの場合、大きなデータ構造をループしていることに気付くでしょう。そのため、ループをスキップする適切なブレークポイントを設定して、必要な場所にすばやく移動できるようにするために、ある程度のインテリジェンスが必要です。

注:いずれの場合も、アプリケーションが失敗したDUCE+Channel.SyncFlush呼び出しの中に入るまで、ブレークポイントを設定したり、シングルステップを開始したりする必要はありません。これを確実にするには、すべてのブレークポイントを無効にしてアプリケーションを起動します。実行中は、でブレークポイントを有効にしてSystem.Windows.Media.Composition.DUCE+Channel.SyncFlush、ウィンドウのサイズを変更します。初めてF5を押すだけで、最初のSyncFlush呼び出しで例外が失敗することを確認します(そうでない場合は、例外が発生する前にF5を押す必要がある回数を数えます)。次に、ブレークポイントを無効にして、プログラムを再起動します。手順を繰り返しますが、今回はSyncFlush呼び出しを適切なタイミングで実行した後、ブレークポイントを設定するか、上記のようにシングルステップを実行します。

推奨事項

上で説明したデバッグ手法は労働集約的です。少なくとも数時間を費やすように計画してください。このため、私は通常、このような何かのためにデバッガーにジャンプする前に、アプリケーションを繰り返し単純化して、バグをくすぐるものを正確に見つけようとします。これには2つの利点があります。グラフィックカードベンダーに送信するための優れた再現性が得られます。また、表示が少なくなり、シングルステップスルーするコードが少なくなり、割り当てが少なくなるため、デバッグが高速になります。

問題は特定のグラフィックカードでのみ発生するため、問題がグラフィックカードドライバまたはそれを呼び出すMilCoreコードのバグであることに疑いの余地はありません。ほとんどの場合、グラフィックカードドライバにありますが、MilCoreが無効な値を渡している可能性があります。この値は、ほとんどのグラフィックカードでは正しく処理されますが、このカードでは処理されません。上記で説明したデバッグ手法により、これが当てはまることがわかります。たとえば、MilCoreがグラフィックカードに1000000x1000000ピクセル領域を割り当てるように指示し、グラフィックカードが正しい解像度情報を提供している場合、バグはMilCoreにあります。しかし、MilCoreの要求が妥当である場合、バグはグラフィックカードドライバにあります。

于 2009-12-27T06:53:44.690 に答える
2

これは、WPFでのメモリリークに関する有用な記事です。このような問題の診断に役立つ、RedGateのANTSPerformanceやMemoryProfilerなどを検討することもできます。

于 2009-12-22T06:54:17.110 に答える
1

スタック部分(または少なくともUXThemeのもの)が信頼できるかどうかはわかりません。スタックの一番下は正常のようです。そして、クリーンアップを行おうとしている例外ハンドラーのように見えるものがわかります。次に、ヒープ管理コードのさまざまなレイヤーへのネストされた呼び出しがたくさんあります。

しかし、スタックがからRtlFreeHeapに移行するこの部分はConvertToUnicode意味がありません。それ以上のものはすべて、以前のスタックの使用から残っていると思います。

0048f40c 6b88f208 mscorwks!_EH_epilog3_GS+0xa, calling mscorwks!__security_check_cookie 
0048f410 6b8a756e mscorwks!SString::ConvertToUnicode+0x81, calling mscorwks!_EH_epilog3_GS 
0048f424 77b4371e ntdll_77b10000!RtlpFreeHeap+0xbb1, calling ntdll_77b10000!RtlLeaveCriticalSection 
0048f42c 77b436fa ntdll_77b10000!RtlpFreeHeap+0xb7a, calling ntdll_77b10000!_SEH_epilog4 

RtlFreeHeapのクラッシュはヒープの破損を示しています。これは、問題がアンマネージコードにあることを示していますが、管理対象オブジェクトのメモリは最終的にアンマネージメモリから割り当てる必要があるため、どちらかである可能性があります。

管理されていないウィンドウがヒープを破壊する可能性がある場所を探すことをお勧めします。同じ割り当ての複数の空き、または割り当ての境界を上書きします。

于 2009-12-29T00:46:29.237 に答える
0

SyncFlushの問題が発生した場合に備えて、Microsoftの優れたサポート(MSDNサブスクリプションによる)のおかげで問題を解決しました。timeBeginPeriodおよびtimeEndPeriod呼び出しを使用して解放したよりも多くのマルチメディアタイマーを作成していることが判明しました。これらのタイマーは限られたリソースであり、使い果たされると、WPFレンダリングスレッドはタイマーが不足し、動作を停止しました。

于 2017-03-27T18:44:03.643 に答える