.NET アプリケーションで GC 中に発生する例外コード 0xc0000409 のエラーについて

Last Update: feedback 共有

こんにちは、Japan Developer Support Core チームの松井です。今回は、.NET アプリケーションでガベージ コレクション (GC) の実行中にランタイムが例外コード 0xc0000409 を通知してアプリケーションが異常終了する事象について、一般的な例をもとにエラーの発生経緯や調査方法についてご紹介します。

1. 0xC0000409 のエラー コードについて

0xc0000409 は STATUS_STACK_BUFFER_OVERRUN のエラー コードで、一般的にはシステムやランタイムがスタック領域におけるバッファー オーバーランを検出した状況を示しています。このエラーが発生すると、イベント ログには Application Error のソースで ID 1000 のログが以下のような内容で記録されます。

障害が発生しているアプリケーション名: ConsoleApp1.exe、バージョン: 1.0.0.0、タイム スタンプ: 0xbba3dadf
障害が発生しているモジュール名: clr.dll、バージョン: 4.8.4300.0、タイム スタンプ: 0x5f7e61bb
例外コード: 0xc0000409
障害オフセット: 0x0060cd78
障害が発生しているプロセス ID: 0x130a8
障害が発生しているアプリケーションの開始時刻: 0x01d796b92363709c
障害が発生しているアプリケーション パス: C:\Users\user1\source\repos\GSFailureDuringGC\Debug\ConsoleApp1.exe
障害が発生しているモジュール パス: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll

このログの内容だけ見ると、.NET Framework のランタイム clr.dll で障害が発生しているとのことなので、一見すると .NET Framework のランタイムに不具合があるように見えます。確かにランタイムの不具合により同様のエラーとなる可能性もありますが、結論から述べると、今回はアプリケーションのコードの問題に起因しています。clr.dll で発生する例外コード 0xc0000409 のすべてがこの状況に該当するとは限りませんが、疑われるポイントの一つとして参考にしていただければと幸いです。

2. 例外発生時のコールスタック

以下は例外発生時のマネージ スタックです。こちらは特に異常は見られません。

0:000> !ClrStack
OS Thread Id: 0x7d0c (0)
Child SP       IP Call Site
005aeeb0 7493cd78 [HelperMethodFrame: 005aeeb0] 
005aef3c 028208da ConsoleApp1.Program.Main(System.String[])
005af0c8 7433f036 [GCFrame: 005af0c8] 

以下は例外発生時のアンマネージ スタックです。frame 11 の clr!JIT_NewArr1 で配列のメモリ割り当てが試みられたものの、マネージ ヒープに十分なサイズがなく frame 0c の clr!WKS::gc_heap::trigger_gc_for_alloc から GC が行われていたことが確認できます。その先で最終的に clr!__report_gsfailure が呼びされているため、GC の処理の中でスタックの問題が検出されたことが確認できます。(GS は Guard Stack の略です。__report_gsfailure 関数についてはこちらで詳しく解説されていますのであわせて参考にしてください。)

0:000> k
# ChildEBP RetAddr      
00 005ae648 74350688     clr!__report_gsfailure+0x17
01 005ae650 7435065f     clr!CrawlFrame::SetCurGSCookie+0x24
02 005ae660 74350e75     clr!CrawlFrame::GotoNextFrame+0x47
03 005ae6a0 74350788     clr!StackFrameIterator::NextRaw+0x4a5
04 005ae964 7435085a     clr!Thread::StackWalkFramesEx+0xc2
05 005aec98 74435ed3     clr!Thread::StackWalkFrames+0x9d
06 005aecbc 74435f6e     clr!standalone::ScanStackRoots+0x43
07 005aecd8 74438e6b     clr!GCToEEInterface::GcScanRoots+0xdb
08 005aed30 74438145     clr!WKS::gc_heap::mark_phase+0x170
09 005aed50 7443847b     clr!WKS::gc_heap::gc1+0xae
0a 005aed68 74438585     clr!WKS::gc_heap::garbage_collect+0x367
0b 005aed88 7443868a     clr!WKS::GCHeap::GarbageCollectGeneration+0x1bd
0c 005aedac 74438703     clr!WKS::gc_heap::trigger_gc_for_alloc+0x1f
0d 005aede0 744344ff     clr!WKS::gc_heap::try_allocate_more_space+0x152
0e 005aedfc 743496f3     clr!WKS::GCHeap::Alloc+0x30
0f 005aee3c 7434f50c     clr!Alloc+0xa5
10 005aee90 7434f598     clr!FastAllocatePrimitiveArray+0x10c
11 005aef34 028208da     clr!JIT_NewArr1+0x126
12 005aef58 7433f036     0x28208da
13 005aef64 743422da     clr!CallDescrWorkerInternal+0x34
14 005aefb8 7434859b     clr!CallDescrWorkerWithHandler+0x6b
15 005af024 744eb11b     clr!MethodDescCallSite::CallTargetWorker+0x16a
16 005af148 744eb7fa     clr!RunMain+0x1b3
17 005af3b4 744eb727     clr!Assembly::ExecuteMainMethod+0xf7
18 005af898 744eb8a8     clr!SystemDomain::ExecuteMainMethod+0x5ef
19 005af8f0 744eb9ce     clr!ExecuteEXE+0x4c
1a 005af930 744e7305     clr!_CorExeMainInternal+0xdc
1b 005af96c 74b3fa84     clr!_CorExeMain+0x4d
1c 005af9a4 7540e81e     mscoreei!CorExeMain+0x64
1d 005af9b4 75414338     mscoree!ShellShim__CorExeMain+0x9e
1e 005af9bc 765cfa29     mscoree!_CorExeMain_Exported+0x8
1f 005af9cc 77617a7e     kernel32!BaseThreadInitThunk+0x19
20 005afa28 77617a4e     ntdll!__RtlUserThreadStart+0x2f
21 005afa38 00000000     ntdll!_RtlUserThreadStart+0x1b

GS のエラーが報告されるまでの間の frame 07 から frame 02 は何をしている処理なのでしょうか。次はこのエラーが発生したプログラムのソース コードと、.NET の実装を見ていきます。また、上記は .NET Framework で実行した例ですが、ランタイムの実装にも少し触れるために .NET 5 に変更して確認します。

3. デバッグ

.NET 5 でビルドしなおして事象を再現させたときのコール スタックは以下のようになります。概ね内容は同じですが、.NET 5 はオープン ソースでプライベート シンボルが公開されているので行番号も確認できます。

0:000> k
# ChildEBP RetAddr      
00 0077e61c 78e1c6bc     coreclr!__report_gsfailure+0x18 [d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\gs\gs_report.c @ 220] 
01 0077e624 78e1d405     coreclr!CrawlFrame::SetCurGSCookie+0x24 [D:\workspace\_work\1\s\src\coreclr\src\vm\stackwalk.cpp @ 380] 
02 0077e640 78e1e2aa     coreclr!StackFrameIterator::Init+0xda [D:\workspace\_work\1\s\src\coreclr\src\vm\stackwalk.cpp @ 1184] 
03 0077e90c 78e1d167     coreclr!Thread::StackWalkFramesEx+0x76 [D:\workspace\_work\1\s\src\coreclr\src\vm\stackwalk.cpp @ 909] 
04 0077ec44 78e1cfdf     coreclr!Thread::StackWalkFrames+0x8a [D:\workspace\_work\1\s\src\coreclr\src\vm\stackwalk.cpp @ 997] 
05 0077ec80 78e1ce3b     coreclr!ScanStackRoots+0x4b [D:\workspace\_work\1\s\src\coreclr\src\vm\gcenv.ee.cpp @ 152] 
06 0077eca0 78e2853c     coreclr!GCToEEInterface::GcScanRoots+0x8e [D:\workspace\_work\1\s\src\coreclr\src\vm\gcenv.ee.cpp @ 234] 
07 (Inline) --------     coreclr!GCScan::GcScanRoots+0x12 [D:\workspace\_work\1\s\src\coreclr\src\gc\gcscan.cpp @ 154] 
08 0077ecf0 78e290ed     coreclr!WKS::gc_heap::mark_phase+0x1bf [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 20757] 
09 0077ed40 78e271a4     coreclr!WKS::gc_heap::gc1+0x7f [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 16694] 
0a (Inline) --------     coreclr!GCToOSInterface::GetLowPrecisionTimeStamp+0x5 [D:\workspace\_work\1\s\src\coreclr\src\vm\gcenv.os.cpp @ 1033] 
0b 0077ed60 78e2f94e     coreclr!WKS::gc_heap::garbage_collect+0x3d5 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 18280] 
0c 0077ed78 78e2f84d     coreclr!WKS::GCHeap::GarbageCollectGeneration+0xea [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 37751] 
0d 0077ed9c 78da6243     coreclr!WKS::gc_heap::trigger_gc_for_alloc+0x19 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 13873] 
0e 0077edd0 78e8569f     coreclr!WKS::gc_heap::try_allocate_more_space+0x168 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 13985] 
0f 0077ede8 78da55b0     coreclr!WKS::gc_heap::allocate_more_space+0x18 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 14486] 
10 (Inline) --------     coreclr!WKS::gc_heap::allocate+0x41 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 14517] 
11 0077ee08 78da5221     coreclr!WKS::GCHeap::Alloc+0x50 [D:\workspace\_work\1\s\src\coreclr\src\gc\gc.cpp @ 36745] 
12 (Inline) --------     coreclr!Alloc+0x62 [D:\workspace\_work\1\s\src\coreclr\src\vm\gchelpers.cpp @ 228] 
13 0077ee68 78da53e7     coreclr!AllocateSzArray+0x161 [D:\workspace\_work\1\s\src\coreclr\src\vm\gchelpers.cpp @ 483] 
14 0077eeec 07e9541e     coreclr!JIT_NewArr1+0xb7 [D:\workspace\_work\1\s\src\coreclr\src\vm\jithelpers.cpp @ 2723] 
...

frame 07 の coreclr!GCScan::GcScanRoots は src\coreclr\src\gc\gcscan.cpp の 154 行目であることが分かるので、ソースコードを参照するため coreclr.dll のファイル情報からコミット ID を確認します。

0:000> lmvmcoreclr
Browse full module list
start    end        module name
78d90000 7919f000   coreclr    (private pdb symbols)  C:\ProgramData\Dbg\sym\coreclr.pdb\26F3E7BD15B6422EB86A93E754AAA8CA1\coreclr.pdb
    Loaded symbol image file: coreclr.dll
    Image path: C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\5.0.9\coreclr.dll
    Image name: coreclr.dll
    Browse all global symbols  functions  data
    Timestamp:        Sat Jul 10 02:57:08 2021 (60E88DF4)
    CheckSum:         0040BDCB
    ImageSize:        0040F000
    File version:     5.0.921.35908
    Product version:  5.0.921.35908
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        0.0 Unknown
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® .NET
        InternalName:     CoreCLR.dll
        OriginalFilename: CoreCLR.dll
        ProductVersion:   5,0,921,35908 @Commit: 208e377a5329ad6eb1db5e5fb9d4590fa50beadd
        FileVersion:      5,0,921,35908 @Commit: 208e377a5329ad6eb1db5e5fb9d4590fa50beadd
        FileDescription:  Microsoft .NET Runtime
        LegalCopyright:   © Microsoft Corporation. All rights reserved.
        Comments:         Flavor=Retail

.NET 5 ランタイムのリポジトリは https://github.com/dotnet/runtime ですので、これにコミット ID 208e377a5329ad6eb1db5e5fb9d4590fa50beadd を組み合わせると、frame 07 のソースコード D:\workspace_work\1\s\src\coreclr\src\gc\gcscan.cpp の URL は以下のようになります。

https://github.com/dotnet/runtime/blob/208e377a5329ad6eb1db5e5fb9d4590fa50beadd/src/coreclr/src/gc/gcscan.cpp#L154

src\coreclr\src\gc\gcscan.cpp#L154link
1
2
3
4
5
void GCScan::GcScanRoots(promote_func* fn,  int condemned, int max_gen,
ScanContext* sc)
{
GCToEEInterface::GcScanRoots(fn, condemned, max_gen, sc);
}

ここだけ見てもどのような処理か分からないので、同じ要領で次のスタック フレーム 06 のコードを見てみます。

src\coreclr\src\vm\gcenv.ee.cpp#L234link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc)
{
STRESS_LOG1(LF_GCROOTS, LL_INFO10, "GCScan: Promotion Phase = %d\n", sc->promotion);

Thread* pThread = NULL;
while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL)
{
STRESS_LOG2(LF_GC | LF_GCROOTS, LL_INFO100, "{ Starting scan of Thread %p ID = %x\n", pThread, pThread->GetThreadId());

if (GCHeapUtilities::GetGCHeap()->IsThreadUsingAllocationContextHeap(
pThread->GetAllocContext(), sc->thread_number))
{
sc->thread_under_crawl = pThread;
#ifdef FEATURE_EVENT_TRACE
sc->dwEtwRootKind = kEtwGCRootKindStack;
#endif // FEATURE_EVENT_TRACE
ScanStackRoots(pThread, fn, sc);
ScanTailCallArgBufferRoots(pThread, fn, sc);
#ifdef FEATURE_EVENT_TRACE
sc->dwEtwRootKind = kEtwGCRootKindOther;
#endif // FEATURE_EVENT_TRACE
}
STRESS_LOG2(LF_GC | LF_GCROOTS, LL_INFO100, "Ending scan of Thread %p ID = 0x%x }\n", pThread, pThread->GetThreadId());
}
...

while 文でマネージ スレッドのリストを辿りながら ScanStackRoots 関数を呼び出していることが確認できます。現在処理をしているスレッドの情報を見てみます。(以下で使用している dx コマンド は Windows 10 SDK から追加された比較的新しいコマンドですので、Windows デバッガーを利用したことがある方でもあまり馴染みがないかもしれません。色々便利な使い方ができるので、いずれブログ記事で取り上げたいと思います。)

0:000> dx @$curthread.Stack.Frames[6].LocalVariables.pThread
@$curthread.Stack.Frames[6].LocalVariables.pThread                 : 0x83ed20 [Type: Thread *]
    [+0x000] m_stackLocalAllocator : 0x0 [Type: StackingAllocator *]
    [=0x79156a3c] m_DetachCount    : 0 [Type: long]
    [=0x79156a38] m_ActiveDetachCount : 0 [Type: long]
    [+0x004] m_State          [Type: Volatile<enum Thread::ThreadState>]
    [+0x008] m_fPreemptiveGCDisabled [Type: Volatile<unsigned long>]
    [+0x00c] m_pFrame         : 0x81cf898 [Type: Frame *]
    [+0x010] m_pDomain        : 0x825fd0 [Type: AppDomain *]
    [+0x014] m_ThreadId       : 0x4 [Type: unsigned long]
...
    [+0x11c] m_OSThreadId     : 0x5b30 [Type: unsigned long]
...

ここで表示されている m_ThreadId はマネージ スレッドの ID になるので、~ コマンドでスレッドを切り替える場合は m_OSThreadId を使用します。0x5b30 のスレッドのコールスタックを見てみます。

0:000> ~~[0x5b30]e!ClrStack
OS Thread Id: 0x5b30 (8)
Child SP       IP Call Site
081CF898 77c329dc [InlinedCallFrame: 081cf898] 
081CF894 07e96246 ILStubClass.IL_STUB_PInvoke()
081CF898 07e961d3 [InlinedCallFrame: 081cf898] ConsoleApp1.Program.Foo()
081CF8E4 07e961d3 ConsoleApp1.Program.Worker() [C:\Users\user1\source\repos\GSFailureDuringGC\ConsoleApp1\Program.cs @ 16]
081CF8F4 79611fb0 System.Threading.ThreadHelper.ThreadStart_Context(System.Object) [/_/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 42]
081CF904 79619785 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 186]
081CF938 79612073 System.Threading.ThreadHelper.ThreadStart() [/_/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 91]
081CFB80 78e96e6f [DebuggerU2MCatchHandlerFrame: 081cfb80] 

前述のマネージ スタックのうち、どのスタック フレームを処理しようとしていたか確認するために frame 02 の処理を確認すると、m_crawl.pFrame が示すフレームであることを確認できます。

src\coreclr\src\vm\stackwalk.cpp#L1184link
1
2
3
4
5
6
7
8
9
10
BOOL StackFrameIterator::Init(Thread *    pThread,
PTR_Frame pFrame,
PREGDISPLAY pRegDisp,
ULONG32 flags)
{
...
if (m_crawl.pFrame != FRAME_TOP && !(m_flags & SKIP_GSCOOKIE_CHECK))
{
m_crawl.SetCurGSCookie(Frame::SafeGetGSCookiePtr(m_crawl.pFrame));
}

メンバー変数なので this から辿る必要があることに注意しつつ、デバッガーで表示してみます。

0:000> dx @$curthread.Stack.Frames[2].LocalVariables.this->m_crawl->pFrame
@$curthread.Stack.Frames[2].LocalVariables.this->m_crawl->pFrame  : 0x81cf898 [Type: InlinedCallFrame * (derived from Frame *)]
    [+0x004] m_Next           : 0x81cfb80 [Type: Frame *]
    [+0x008] m_Datum          : 0x0 [Type: NDirectMethodDesc *]
    [+0x00c] m_pCallSiteSP    : 0x81cf894 [Type: void *]
    [+0x010] m_pCallerReturnAddress : 0x7e96246 [Type: unsigned long]
    [+0x014] m_pCalleeSavedFP : 0x81cf8dc [Type: unsigned long]
    [+0x018] m_pThread        : 0x78eb6898 [Type: void *]

m_pCallSiteSP や m_pCallerReturnAddress の値と 0x5b30 の !ClrStack コマンドの結果を比較すると、プラットフォーム相互運用に関連するフレームであったことが確認できます。(ちなみに今回表示したフレームは InlineCallFrame クラスですが、Frame クラスを基底にした様々なスタック フレームの種類があることが https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/frames.h で確認できます。)

プラットフォーム相互運用周辺が怪しいということが分かったところで、今回の確認に使用したプログラムのソース コードを見てみます。

"ConsoleApp1.exe の実装 (source.cs) Debug|x86"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApp1
{
class Program
{
[DllImport("Dll1.dll")]
private extern static int Foo();

static void Worker()
{
while (true)
{
Foo();
}
}

static void Main(string[] args)
{
var t = new Thread(Worker);
t.Start();
while (true)
{
var _garbage = new int[8000];
}
GC.KeepAlive(t);
}
}
}
"dll1.dll の実装 (source.cpp) Debug|x86"
1
2
3
4
5
extern "C"
int __stdcall Foo(int arg)
{
return arg;
}

DLL がエクスポートしている Foo 関数は int 型の引数を取りますが、マネージ コードの DllImport の定義には引数がなく、呼出し元と呼び出し先でスタックの扱いに不整合が生じて破損する可能性があります。結局のところ、__report_gsfailure の報告は GC の処理が各スタック フレームからのオブジェクト参照を操作する中でこの問題を検出したものでした。上記のソースコードは問題を極端にシンプル化した例ですが、実際のアプリケーションはより複雑で規模も大きく、コード レビューで問題を見つけることも容易ではありません。

ちなみに、Foo 関数の呼び出しでスタックの扱いに不整合が生じているなら呼び出したらすぐ問題が起きそうなものですが、このケースの場合、実際には GC が発生するまで問題が顕在化しません。なぜでしょうか?

スタックの不整合で不正な状態が生じる範囲が軽微な場合、プラットフォーム相互運用の呼び出し先から戻る際に影響を生じないため、破損していても問題が顕在化せずそのまま処理が進むことがあります。ところが破損しているスタック フレームがある状態で GC がトリガーされると、GC がスタック フレームを走査する過程で GS チェックが行われるため問題が検出され強制終了します。実際のアプリケーションでは、問題があるコードが実行されるタイミングと GC のトリガー タイミングが一致した場合にのみ顕在化する再現性がない事象となるため、トラブルシュートが難しい不具合につながります。

4. Managed Debugging Assistants による再現性の確保

x86 アプリケーションの場合、Visual Studio でデバッグ実行する際に [デバッグ] > [ウィンドウ] > [例外の設定] で [例外の設定] ダイアログを表示し、[Managed Debugging Assistants] - [PInvokeStackImbalance] にチェックを入れておくと、このようなプラットフォーム相互運用におけるスタックの問題を検出出来る場合があります。

PInvokeStackImbalance MDA を有効にした場合、.NET Framework のランタイムによってプラットフォーム相互運用の前後でスタックの検証が行われ、問題が検出された場合に直ちに設定されている事後デバッガーを起動します。PInvokeStackImbalance MDA によって事後デバッガーから出力されたダンプ ファイルは以下のようなコールスタックとなり、問題があったメソッドが容易に確認できます。また、検証はプラットフォーム相互運用呼び出しの都度行われるため、GC によって不定なタイミングで発生する再現の困難さも解消することができます。

0:006> !ClrStack
OS Thread Id: 0x4b28 (6)
Child SP       IP Call Site
053df490 77c32f6c [InlinedCallFrame: 053df490] 
053df48c 014109ca DomainBoundILStubClass.IL_STUB_PInvoke()
053df490 01410932 [InlinedCallFrame: 053df490] ConsoleApp1.Program.Foo()
053df4d8 01410932 ConsoleApp1.Program.Worker() [C:\Users\user1\source\repos\GSFailureDuringGC\ConsoleApp1\Program.cs @ 16]
053df4e8 73ff2e01 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
053df4f4 74018604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
053df560 74018537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
053df574 740184f4 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
053df58c 73ff2d5b System.Threading.ThreadHelper.ThreadStart()
053df770 7510f066 [GCFrame: 053df770] 
053df8b4 7510f066 [DebuggerU2MCatchHandlerFrame: 053df8b4] 

0:006> k
# ChildEBP RetAddr      
00 053dccec 76b3b143     ntdll!NtWaitForMultipleObjects+0xc
01 053dce80 7566b76f     KERNELBASE!WaitForMultipleObjectsEx+0x103
02 053dd200 7566b931     clr!Debugger::LaunchJitDebuggerAndNativeAttach+0x10d
03 053dd21c 7566b8d6     clr!Debugger::EnsureDebuggerAttached+0x41
04 053dd254 756722ce     clr!Debugger::JitAttach+0x3d
05 053dd2ac 754898a4     clr!Debugger::SendMDANotification+0xdb
06 053dd744 7548944c     clr!MdaXmlMessage::SendDebugEvent+0x1a1
07 053dd76c 75489bef     clr!MdaXmlMessage::SendEvent+0x54
08 053ddbb4 75489aaa     clr!MdaXmlMessage::SendMessage+0x111
09 053dddec 754c3a5a     clr!MdaXmlMessage::SendMessagef+0x65
0a 053df428 754aa4a8     clr!MdaPInvokeStackImbalance::CheckStack+0x14e
0b 053df43c 7510f3b7     clr!PInvokeStackImbalanceWorker+0x1d
0c 053df484 014109ca     clr!PInvokeStackImbalanceHelper+0x3c
0d 053df4d0 01410932     0x14109ca
0e 053df4e0 73ff2e01     0x1410932
0f 053df4ec 74018604     mscorlib_ni!System.Threading.ThreadHelper.ThreadStart_Context(System.Object)$##6003BFF+0xa1
10 053df550 74018537     mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)$##6003AF0+0xc4
11 053df564 740184f4     mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)$##6003AEF+0x17
12 053df580 73ff2d5b     mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)$##6003AEE+0x44
13 053df598 7510f066     mscorlib_ni!System.Threading.ThreadHelper.ThreadStart()$##6003C01+0x47
14 053df5a4 7511230a     clr!CallDescrWorkerInternal+0x34
15 053df5f8 751185eb     clr!CallDescrWorkerWithHandler+0x6b
16 053df66c 752296c7     clr!MethodDescCallSite::CallTargetWorker+0x16a
17 053df7dc 752be356     clr!ThreadNative::KickOffThread_Worker+0x131
18 053df7f4 752be3e1     clr!ManagedThreadBase_DispatchInner+0x71
19 053df898 752be2d2     clr!ManagedThreadBase_DispatchMiddle+0x7e
1a 053df8ec 752be4c1     clr!ManagedThreadBase_DispatchOuter+0x99
1b 053df910 75229578     clr!ManagedThreadBase_FullTransitionWithAD+0x2f
1c 053df994 751d4c37     clr!ThreadNative::KickOffThread+0x260
1d 053dfab8 7747fa29     clr!Thread::intermediateThreadProc+0x58
1e 053dfac8 77c27a7e     kernel32!BaseThreadInitThunk+0x19
1f 053dfb24 77c27a4e     ntdll!__RtlUserThreadStart+0x2f
20 053dfb34 00000000     ntdll!_RtlUserThreadStart+0x1b

MDA の機能や設定方法の詳細については、以下のドキュメントをご確認ください。
https://docs.microsoft.com/ja-jp/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants

なお、MDA は Visual Studio 固有の機能ではなく .NET Framework 自体の機能ですので、Visual Studio をインストールしなくても利用することが可能です。ただし、PInvokeStackImbalance の MDA は問題の検出をデバッガーへ通知するため、Visual Studio と共に使用しない場合は ProcDump など他のツールやデバッガーを事後デバッガーとして指定しておく必要があります。例えば ProcDump ツールを使う場合は procdump.exe -i -ma を実行することで典型的な構成での事後デバッガーの構成が可能です。事後デバッガーの設定の詳細は以下のドキュメントを参考にしてください。
https://docs.microsoft.com/ja-jp/windows-hardware/drivers/debugger/enabling-postmortem-debugging

5. まとめ

clr.dll で例外コード 0xc0000409 が発生する場合、まずダンプ ファイルを採取して例外のスタックを確認してください。スタックに clr!CrawlFrame::* が現れる場合はマネージ スタック フレームのいずれかで破損が検出された状況が推測されます。この場合、GC で処理を行っている対象のスレッドとスタック フレームが特定できれば原因が特定できる可能性があります。スタックの破損に至る要因やその範囲は様々で、この記事で取り上げたケースのように簡単な状況ではないことも多くありますが、アプリケーションのデバッグの参考になれば幸いです。.NET Framework アプリケーションの場合はランタイム内部の構造体フィールドの確認などでプライベート シンボルが必要となりますので、弊社のプレミア サポートやユニファイド サポートのご利用もご検討ください。


本ブログの内容は弊社の公式見解として保証されるものではなく、開発・運用時の参考情報としてご活用いただくことを目的としています。もし公式な見解が必要な場合は、弊社ドキュメント (https://docs.microsoft.comhttps://support.microsoft.com) をご参照いただくか、もしくは私共サポートまでお問い合わせください。