c/c++语言里MiniDump是一个重要的调试手段,他们没有C#/java这样语言有很多异常输出信息(
JVM异常导出bug日志功能,通常在jdk目录,文件格式hs_err_%pid%.log,pid是进程id)。
我们通常在项目中都会把可预见性进行异常处理。常见的处理方法如下
try{ ... catch(Exception ex) { HandleExeption(ex); }
项目部署到客户机中运行在程序员无法评估的情况下,如(堆栈溢出、访问冲突)则无法处理
或者很难重现这种异常,这给程序调试带来一定程度上的障碍,而这个时候内存及当前机器环境的快
照信息对程序排错则至关重要。幸好我们.NET提供了应用程序域未捕获异常(不是所有异常)事件处理
接口AppDomain.UnhandledException,先贴出Minidump封装类:
public sealed class MiniDump { [Flags] public enum DumpType : uint { // From dbghelp.h: MiniDumpNormal = 0x00000000, MiniDumpWithDataSegs = 0x00000001, MiniDumpWithFullMemory = 0x00000002, MiniDumpWithHandleData = 0x00000004, MiniDumpFilterMemory = 0x00000008, MiniDumpScanMemory = 0x00000010, MiniDumpWithUnloadedModules = 0x00000020, MiniDumpWithIndirectlyReferencedMemory = 0x00000040, MiniDumpFilterModulePaths = 0x00000080, MiniDumpWithProcessThreadData = 0x00000100, MiniDumpWithPrivateReadWriteMemory = 0x00000200, MiniDumpWithoutOptionalData = 0x00000400, MiniDumpWithFullMemoryInfo = 0x00000800, MiniDumpWithThreadInfo = 0x00001000, MiniDumpWithCodeSegs = 0x00002000, MiniDumpWithoutAuxiliaryState = 0x00004000, MiniDumpWithFullAuxiliaryState = 0x00008000, MiniDumpWithPrivateWriteCopyMemory = 0x00010000, MiniDumpIgnoreInaccessibleMemory = 0x00020000, MiniDumpValidTypeFlags = 0x0003ffff, }; //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! struct MiniDumpExceptionInformation { public uint ThreadId; public IntPtr ExceptioonPointers; [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 // ); [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] static extern bool MiniDumpWriteDump( IntPtr hProcess, uint processId, IntPtr hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] static extern uint GetCurrentThreadId(); [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess", ExactSpelling = true)] static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcessId", ExactSpelling = true)] static extern uint GetCurrentProcessId(); public static bool Write(string fileName) { return Write(fileName, DumpType.MiniDumpWithFullMemory); } public static bool Write(string fileName, DumpType dumpType) { using (var fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) { MiniDumpExceptionInformation exp; exp.ThreadId = GetCurrentThreadId(); exp.ClientPointers = false; exp.ExceptioonPointers = System.Runtime.InteropServices.Marshal.GetExceptionPointers(); bool bRet = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), fs.SafeFileHandle.DangerousGetHandle(), (uint)dumpType, ref exp, IntPtr.Zero, IntPtr.Zero); return bRet; } }
以下以Winform演示这个事件的使用方法
先拖一个界面如下
class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> static void Main(string[] args) { //exception handler Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Application.ThreadException += Application_ThreadException; bool isRunWinService = args.Length > 0 && args[0].ToLower().Equals("-service"); //用户手工启动 if (!isRunWinService) { Application.Run(new frmSetup()); } else { ServiceBase.Run(new ServiceBase[] { new Daemon() }); } } private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { string dumpFile = System.IO.Path.Combine(System.Environment.CurrentDirectory, string.Format("crash-dump-{0}.dmp", DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss"))); MiniDump.Write(dumpFile); } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { string dumpFile = System.IO.Path.Combine(System.Environment.CurrentDirectory, string.Format("thread-dump-{0}.dmp", DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss"))); MiniDump.Write(dumpFile); } }
两个按钮事件代码分别如下:
private void button1_Click(object sender, EventArgs e) { new Thread(() => { throw new Exception("Other thread"); }).Start(); } private void button2_Click(object sender, EventArgs e) { string a = null; a.PadLeft(10); }
随便点击一个按钮都能触发异常处理,生成如下dump.dmp文件
拖到visual studio里面打开如下
“使用 仅托管进行调试”以下是打开dump文件后的效果,直接定位到异常处:
最近比较忙,时间紧张,文章写得比较粗糙,大家应该能明白什么意思了,有疑问欢迎留言。
参考:
AppDomain.CurrentDomain.UnhandledException not firing without debugging
application level global exception handler didn't get hit
How to create minidump of a .NET process when a certain first chance exception occurs
Should use both AppDomain.UnhandledException and Application.DispatcherUnhandledException?
The simplest way to generate minidump for mixed managed & unmanaged stack?
How to Use the Debug Diagnostic Tool v1.1 (DebugDiag) to Debug User Mode Processes