Sunday, July 31, 2011

Helper class for creating memory dumps of a Managed Process

I'm giving a talk at TechEd NZ 2011 in about a month. As part of that talk, I'll mention creating memory dumps using the MiniDumpWriteDump function, and show a helper class which P/Invokes it

Here is that helper class (Updated 19 Sept 2011 to fix some bugs).


using System.Runtime.InteropServices;
using System;
using System.IO;
using System.Diagnostics;
using System.Threading;

public static class DbgHelp
{
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    struct MINIDUMP_EXCEPTION_INFORMATION
    {
        public uint ThreadId;
        public IntPtr ExceptionPointers;

        [MarshalAs(UnmanagedType.Bool)]
        public bool ClientPointers;
    }

    [DllImport("Dbghelp.dll")]
    static extern bool MiniDumpWriteDump(
        IntPtr hProcess,
        uint ProcessId,
        IntPtr hFile,
        [MarshalAs(UnmanagedType.I4)] MiniDumpType DumpType,
        IntPtr ExceptionParam, // Ptr to MINIDUMP_EXCEPTION_INFORMATION
        IntPtr UserStreamParam,
        IntPtr CallbackParam);

    [DllImport("kernel32.dll")]
    static extern uint GetCurrentThreadId();

    enum MiniDumpType : int
    {
        MiniDumpNormal = 0x00000000,
        MiniDumpWithDataSegs = 0x00000001,
        MiniDumpWithFullMemory = 0x00000002, // required for .NET apps
        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,
        MiniDumpWithTokenInformation = 0x00040000
    };

    public static void WriteExceptionDump(string filePath)
    {
        var proc = Process.GetCurrentProcess();
        int win32Error;
        if(!TryCreateDump(filePath, proc.Handle, (uint)proc.Id, GetCurrentThreadId(), Marshal.GetExceptionPointers(), out win32Error))
            throw new Exception("Couldn't create dump file! Error: 0x" + win32Error.ToString("X8"));
    }

    public static bool TryCreateDump(string dumpFilePath, IntPtr processHandle, uint processId, uint threadId, IntPtr exceptionPointers, out int win32Error)
    {
        bool success = false;
        int lastError = 0;

        // Dump on a seperate thread - IsBackground=false is important to stop the process exiting while we write the dump
        // also works around an issue of VS not being able to walk the callstack of the crashing thread
        var thread = new Thread(new ThreadStart(() => {
            // In-process dumps must ClientPointers = false
            // If ClientPointers is false, or if there are no ExceptionPointers we must pass IntPtr.Zero as ExceptionInfo
            var exceptionParam = IntPtr.Zero;
            if (processId != Process.GetCurrentProcess().Id && exceptionPointers != IntPtr.Zero)
            {
                var ei = new MINIDUMP_EXCEPTION_INFORMATION {
                    ClientPointers = true, // in-process dump. True if we're dumping external processes
                    ExceptionPointers = exceptionPointers, // may be IntPtr.zero for CLR exceptions
                    ThreadId = threadId,
                };
                exceptionParam = Marshal.AllocHGlobal(Marshal.SizeOf(ei));
                Marshal.PtrToStructure(exceptionParam, ei);
            }

            using (var outputFile = new FileStream(dumpFilePath, FileMode.Create))
            {
                success = MiniDumpWriteDump(
                    processHandle,
                    processId,
                    outputFile.SafeFileHandle.DangerousGetHandle(),
                    MiniDumpType.MiniDumpWithFullMemory,
                    exceptionParam,
                    IntPtr.Zero,
                    IntPtr.Zero);
            }

            if (!success)
                lastError = Marshal.GetLastWin32Error();

            if (exceptionParam != IntPtr.Zero)
                Marshal.FreeHGlobal(exceptionParam);
        })) { IsBackground = false, Name = "MiniDump thread" };

        thread.Start();
        thread.Join();

        win32Error = lastError;
        return success;
    }
}




No comments: