C# アプリケーションでミニダンプを生成してみた
ずいぶん前に C# で minidump を生成する方法という記事を見つけていたのだけれど、今更引っ張り出して試してみた。
# 理由は自宅に帰れなくなったからなんだけど、そこは気にしない。
元ネタ
ここに記載されている MiniDump クラスを感謝しつつ、まるっともらいました。
http://blogs.msdn.com/b/dondu/archive/2010/10/24/writing-minidumps-in-c.aspx
minidump を生成する
MiniDump クラスはこんな感じ。
コードを見るとわかりますが、dbghelp.dll にある MiniDumpWriteDump 関数を P/Invoke で呼び出してダンプを生成してます。
using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace MiniDumpSample { public static class MiniDump { // Taken almost verbatim from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/ [Flags] public enum Option : uint { // From dbghelp.h: Normal = 0x00000000, WithDataSegs = 0x00000001, WithFullMemory = 0x00000002, WithHandleData = 0x00000004, FilterMemory = 0x00000008, ScanMemory = 0x00000010, WithUnloadedModules = 0x00000020, WithIndirectlyReferencedMemory = 0x00000040, FilterModulePaths = 0x00000080, WithProcessThreadData = 0x00000100, WithPrivateReadWriteMemory = 0x00000200, WithoutOptionalData = 0x00000400, WithFullMemoryInfo = 0x00000800, WithThreadInfo = 0x00001000, WithCodeSegs = 0x00002000, WithoutAuxiliaryState = 0x00004000, WithFullAuxiliaryState = 0x00008000, WithPrivateWriteCopyMemory = 0x00010000, IgnoreInaccessibleMemory = 0x00020000, ValidTypeFlags = 0x0003ffff, }; public enum ExceptionInfo { None, Present } //typedef struct _MINIDUMP_EXCEPTION_INFORMATION { // DWORD ThreadId; // PEXCEPTION_POINTERS ExceptionPointers; // BOOL ClientPointers; //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64! public struct MiniDumpExceptionInformation { public uint ThreadId; public IntPtr ExceptionPointers; [MarshalAs(UnmanagedType.Bool)] public bool ClientPointers; } //BOOL //WINAPI //MiniDumpWriteDump( // __in HANDLE hProcess, // __in DWORD ProcessId, // __in HANDLE hFile, // __in MINIDUMP_TYPE DumpType, // __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, // __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, // __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam // ); // Overload requiring MiniDumpExceptionInformation [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); // Overload supporting MiniDumpExceptionInformation == NULL [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] static extern uint GetCurrentThreadId(); public static bool Write(SafeHandle fileHandle, Option options, ExceptionInfo exceptionInfo) { Process currentProcess = Process.GetCurrentProcess(); IntPtr currentProcessHandle = currentProcess.Handle; uint currentProcessId = (uint)currentProcess.Id; MiniDumpExceptionInformation exp; exp.ThreadId = GetCurrentThreadId(); exp.ClientPointers = false; exp.ExceptionPointers = IntPtr.Zero; if (exceptionInfo == ExceptionInfo.Present) { exp.ExceptionPointers = System.Runtime.InteropServices.Marshal.GetExceptionPointers(); } bool bRet = false; if (exp.ExceptionPointers == IntPtr.Zero) { bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } else { bRet = MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero); } return bRet; } public static bool Write(SafeHandle fileHandle, Option dumpType) { return Write(fileHandle, dumpType, ExceptionInfo.None); } } }
検証用コード
試しに例外が出たらダンプを生成するようにして、 try の中で 0 除算をしています。
というわけで、 DivideByZeroException 例外が出て、ダンプが生成されるはずです。
class Program { static void Main(string[] args) { try { int a = 100; int b = 0; int c = a/b; Console.WriteLine(c); } catch (Exception) { // create minidump string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MiniDumpDemo_Mainline.dmp"); using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) { MiniDump.Write(fs.SafeFileHandle, MiniDump.Option.WithFullMemory); } } } }
minidump を見る
実際に実行すると、ダンプファイルが生成されます。
Visual Studio でもいいですが、 windbg で開きます。
Windbg.exe -Q -z MiniDumpDemo_Mainline.dmp
開いたら、 sos をロードします。
(実験環境が .NET4 なのでモジュール名は clr です)
.loadby sos clr
ロードしたら、 !threads コマンドでスレッドの状況を確認。
結果はこんな感じに。
0:000> !threads ThreadCount: 10 UnstartedThread: 0 BackgroundThread: 5 PendingThread: 0 DeadThread: 3 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 1ce8 006307e8 201a220 Enabled 02272448:02273fe8 0062abb0 0 MTA 2 2 2128 00671720 b220 Enabled 00000000:00000000 0062abb0 0 MTA (Finalizer) XXXX 3 006b2588 10820 Enabled 00000000:00000000 0062abb0 0 Ukn XXXX 5 006be310 19820 Enabled 00000000:00000000 0062abb0 0 MTA XXXX 6 006df178 19820 Enabled 00000000:00000000 0062abb0 0 MTA 5 4 b44 006eb1f8 220 Enabled 0227fdbc:0227ffe8 0062abb0 0 Ukn 8 7 1de8 006e8470 b020 Enabled 0228a848:0228c798 0062abb0 0 MTA 6 8 620 006f04a8 220 Enabled 00000000:00000000 0062abb0 0 Ukn 9 9 1bfc 05085848 b220 Enabled 022843a0:02285fe8 0062abb0 0 MTA 10 a 21e4 0507e860 b020 Enabled 022a775c:022a7fe8 0062abb0 0 MTA System.DivideByZeroException (0228e99c)
ID 10 (a) を確認すると、Exception に DivideByZeroException 例外が出ていることを確認。
詳細を見るために、スレッドを切り替えます。
~10s
切り替えたら、今度は例外情報を出力します。
!pe
すると次のように結果が表示されます。
0:010> !pe Exception object: 0228e99c Exception type: System.DivideByZeroException Message: 0 で除算しようとしました。 InnerException: <none> StackTrace (generated): SP IP Function 06D0E9E8 003024BF MiniDumpSample!MiniDumpSample.Program.Main(System.String[])+0x7f StackTraceString: <none> HResult: 80020012
スタックトレースの結果から、 Program.Main メソッドで例外が発生した、ということが分かりました。
アセンブリでこの関数を追いかけてみます。
!U 003024BF
すると、次の実行結果が得られました。*1
0:010> !U 003024BF Normal JIT generated code MiniDumpSample.Program.Main(System.String[]) Begin 00302440, size 14f *** WARNING: Unable to verify checksum for MiniDumpSample.exe (略) C:\Users\xxxxx\Documents\Visual Studio 2010\Projects\MiniDumpSample\MiniDumpSample\Program.cs @ 19: 003024b6 33d2 xor edx,edx 003024b8 8955bc mov dword ptr [ebp-44h],edx C:\Users\xxxxx\Documents\Visual Studio 2010\Projects\MiniDumpSample\MiniDumpSample\Program.cs @ 20: 003024bb 8b45c0 mov eax,dword ptr [ebp-40h] 003024be 99 cdq >>> 003024bf f77dbc idiv eax,dword ptr [ebp-44h] 003024c2 8945b8 mov dword ptr [ebp-48h],eax (略)
例外が発生した該当のアドレスを見ると idiv 命令を実行しています。( >>> で強調されている行)
0 除算なので、この時の dword ptr [ebp-44h] が 0 になっているはずです。
直ぐ上を見ると「 xor edx edx 」の結果を代入していることが分かります。
お気づきの通り、ブール代数的には 同じ値でXOR(排他的論理和) をとると、 0 になります。
ということで、 0 除算している処理が Program.cs の 19 行目にあるみたいですよ。と言えそうです
今回はまさに自分でそういうコードを書いますから、これで無事解決。
実際のケースでは例外の内容を確認してからの調査方法はケース毎に異なるでしょうね。
お勉強はここまで。(夜が明け始めた!)
*1:私の名前の部分は xxxxx に置換してます。