views:

122

answers:

2

Im looking for a way to generate minidump files in my applications simular to what ProcDump does but prefarably with code and not having to extract a 3dparty tool to do it.

The main reasons for not wanting to use ProcDump is:
1) Size of the binary would increase greatly ( This is a problem because my apps are freeware, and bandwith is not free).
2) Feels dirty.
3) No way i can port that app to run inn windows mobile.

My requirements are:
1) Ability to generate mdump files in a fatale crash.
2) Abilityt to do "pause" the app do a dump, and contiune would be a bonus
.
If this is not realy a option, is there a way to get the values of local varibales in the current context dynamicly?

Side note: I did find this article, but its very old so i so im hesitant to base my work of it.
There seems to either be a issue with IE 9 or with the site, so i had problems with tags.

+5  A: 

You can call MiniDumpWriteDump from AppDomain.UnhandledException or Application.ThreadException event handler to create the minidump. This article explains the function in great details: Effective minidumps

You can also use this library which has other functionality too: Catch All Bugs with BugTrap!

Edit

Looks like getting a useful minidump is not that easy. First of all sos.dll complains when the dump is not full (full dumps are about 100-150MB). Secondly, writing dump in catch block is not recommended: Getting good dumps when an exception is thrown.

If you have a winforms application this question has some usefull info: How does SetUnhandledExceptionFilter work in .NET WinForms applications?

Giorgi
Im already trapping unhanlded Exeptions, but i need to get local variable values. And im basicly looking for a way of doing that dynamicly. And i dont think i can create full dumps, if thats the only option i will do that, but would prefere minidumps yes.
EKS
@EKS - you will need dump for that.
Giorgi
I figured that, gonna test out what you posted tomorrow. At a lan party atm :)
EKS
@giorgi: can you please take a look at this: http://stackoverflow.com/questions/3963542/net-runtime-2-0-error-user-machine-what-next I have all the exception handles in place, but they aren't caught anyway.... any experience?
Daniel Mošmondor
+4  A: 

So there is one solution that comes to mind and meets the following goals:

  • Size of the binary would increase by around 300k
  • Ability to generate mdump files in a fatale crash.
  • Ability to do "pause" the app do a dump, and contiune would be a bonus

I'll give this requirement a complete unknown:

  • No way i can port that app to run inn windows mobile.

So what's the solution?

Integrate the required parts you need from the Microsoft Sample for MDbg.exe to provide you with a 'just-in-time' debugger that attaches, dumps, and detaches from the crashing process.

Step 1 - Start by downloading the source code to the mdbg from here: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=38449a42-6b7a-4e28-80ce-c55645ab1310&DisplayLang=en

Step 2 - Create a 'crash' handler that spawns a debugger process and waits for completion. I used the following few lines of code to re-launch the same exe with a few extra arguments to invoke the debugger and output an xml file to std::out.

string tempFile = Path.GetTempFileName();
Mutex handle = new Mutex(true, typeof(Program).Namespace + "-self-debugging");
try
{
    Process pDebug = Process.Start(typeof(Program).Assembly.Location,
        "debug-dump " + Process.GetCurrentProcess().Id + " " + tempFile);
    if (pDebug != null)
        pDebug.WaitForExit();
}
catch { }
finally
{
    handle.ReleaseMutex();
}

Console.WriteLine(File.ReadAllText(tempFile));

Step 3 - Write the debug dump routine, this can be in the same exe or in a different exe. You will need to reference (or include the source from) the 'raw', 'corapi', and 'mdbgeng' modules from the sample. Then add a few lines to your Main():

public static void Main(string[] args)
{
    if (args.Length > 0 && args[0] == "debug-dump")
    {   //debug-dump process by id = args[1], output = args[2]
        using (XmlTextWriter wtr = new XmlTextWriter(args[2], Encoding.ASCII))
        {
            wtr.Formatting = Formatting.Indented;
            PerformDebugDump(Int32.Parse(args[1]), wtr);
        }
        return;
    }
    //... continue normal program execution
}

static void PerformDebugDump(int process, XmlWriter x)
{
    x.WriteStartElement("process");
    x.WriteAttributeString("id", process.ToString());
    x.WriteAttributeString("time", XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.RoundtripKind));

    MDbgEngine e = new MDbgEngine();
    MDbgProcess me = e.Attach(process);
    me.Go().WaitOne();

    try
    {
        x.WriteStartElement("modules");
        foreach (MDbgModule mod in me.Modules)
            x.WriteElementString("module", mod.CorModule.Name);
        x.WriteEndElement();

        foreach (MDbgThread thread in me.Threads)
        {
            x.WriteStartElement("thread");
            x.WriteAttributeString("id", thread.Id.ToString());
            x.WriteAttributeString("number", thread.Number.ToString());
            int ixstack = -1;

            foreach (MDbgFrame frame in thread.Frames)
            {
                x.WriteStartElement("frame");
                x.WriteAttributeString("ix", (++ixstack).ToString());
                x.WriteAttributeString("loc", frame.ToString(String.Empty));
                string valueText = null;

                x.WriteStartElement("args");
                try
                {
                    foreach (MDbgValue value in frame.Function.GetArguments(frame))
                    {
                        x.WriteStartElement(value.Name);
                        x.WriteAttributeString("type", value.TypeName);
                        try { x.WriteAttributeString("value", value.GetStringValue(1, false)); }
                        finally { x.WriteEndElement(); }
                    }
                }
                catch { }
                x.WriteEndElement();

                x.WriteStartElement("locals");
                try
                {
                    foreach (MDbgValue value in frame.Function.GetActiveLocalVars(frame))
                    {
                        x.WriteStartElement(value.Name);
                        x.WriteAttributeString("type", value.TypeName);
                        try { x.WriteAttributeString("value", value.GetStringValue(1, false)); }
                        finally { x.WriteEndElement(); }
                    }
                }
                catch { }
                x.WriteEndElement();
                x.WriteEndElement();
            }
            x.WriteEndElement();
        }
    }
    finally
    {
        me.Detach().WaitOne();
    }

    x.WriteEndElement();
}

Example Output

<process id="8276" time="2010-10-18T16:03:59.3781465-05:00">
<modules>
<module>C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll</module>
...etc
</modules>
<thread id="17208" number="0">
<frame ix="0" loc="System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop (source line information unavailable)">
    <args>
        <this type="System.Windows.Forms.Application.ComponentManager" value="System.Windows.Forms.Application.ComponentManager&#xA;    oleComponents=System.Collections.Hashtable&#xA; cookieCounter=1&#xA;    activeComponent=System.Windows.Forms.Application.ThreadContext&#xA; trackingComponent=&lt;null&gt;&#xA; currentState=0" />
        <dwComponentID type="N/A" value="&lt;N/A&gt;" />
        <reason type="System.Int32" value="-1" />
        <pvLoopData type="System.Int32" value="0" />
    </args>
    <locals>
        <local_0 type="System.Int32" value="0" />
        <local_1 type="System.Boolean" value="True" />
        <local_2 type="System.Windows.Forms.UnsafeNativeMethods.IMsoComponent" value="&lt;null&gt;" />
        <local_3 type="N/A" value="&lt;N/A&gt;" />
        <local_4 type="N/A" value="&lt;N/A&gt;" />
        <local_5 type="N/A" value="&lt;N/A&gt;" />
        <local_6 type="System.Windows.Forms.Application.ThreadContext" value="System.Windows.Forms.Application.ThreadContext&#xA;   contextHash=System.Collections.Hashtable&#xA;   tcInternalSyncObject=System.Object&#xA; totalMessageLoopCount=1&#xA;    baseLoopReason=-1&#xA;  currentThreadContext=System.Windows.Forms.Application.ThreadContext&#xA;    threadExceptionHandler=System.Threading.ThreadExceptionEventHandler&#xA;    idleHandler=&lt;null&gt;&#xA;   enterModalHandler=&lt;null&gt;&#xA; leaveModalHandler=&lt;null&gt;&#xA; applicationContext=System.Windows.Forms.ApplicationContext&#xA; parkingWindow=&lt;null&gt;&#xA; marshalingControl=System.Windows.Forms.Application.MarshalingControl&#xA;   culture=&lt;null&gt;&#xA;   messageFilters=&lt;null&gt;&#xA;    messageFilterSnapshot=&lt;null&gt;&#xA; handle=912&#xA; id=17208&#xA;   messageLoopCount=1&#xA; threadState=1&#xA;  modalCount=0&#xA;   activatingControlRef=&lt;null&gt;&#xA;  componentManager=System.Windows.Forms.Application.ComponentManager&#xA; externalComponentManager=False&#xA; fetchingComponentManager=False&#xA; componentID=1&#xA;  currentForm=Program.MainForm&#xA;   threadWindows=&lt;null&gt;&#xA; tempMsg=System.Windows.Forms.NativeMethods.MSG&#xA; disposeCount=0&#xA; ourModalLoop=False&#xA; messageLoopCallback=&lt;null&gt;&#xA;   __identity=&lt;null&gt;" />
        <local_7 type="N/A" value="&lt;N/A&gt;" />
        <local_8 type="N/A" value="&lt;N/A&gt;" />
        <local_9 type="N/A" value="&lt;N/A&gt;" />
        <local_10 type="N/A" value="&lt;N/A&gt;" />
        <local_11 type="N/A" value="&lt;N/A&gt;" />
        <local_12 type="N/A" value="&lt;N/A&gt;" />
        <local_13 type="System.Boolean" value="False" />
        <local_14 type="System.Windows.Forms.NativeMethods.MSG[]" value="array [1]&#xA; [0] = System.Windows.Forms.NativeMethods.MSG" />
    </locals>
</frame>
<frame ix="1" loc="System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (source line information unavailable)">
    <args>
        <this type="System.Windows.Forms.Application.ThreadContext" value="System.Windows.Forms.Application.ThreadContext&#xA;  contextHash=System.Collections.Hashtable&#xA;   tcInternalSyncObject=System.Object&#xA; totalMessageLoopCount=1&#xA;    baseLoopReason=-1&#xA;  currentThreadContext=System.Windows.Forms.Application.ThreadContext&#xA;    threadExceptionHandler=System.Threading.ThreadExceptionEventHandler&#xA;    idleHandler=&lt;null&gt;&#xA;   enterModalHandler=&lt;null&gt;&#xA; leaveModalHandler=&lt;null&gt;&#xA; applicationContext=System.Windows.Forms.ApplicationContext&#xA; parkingWindow=&lt;null&gt;&#xA; marshalingControl=System.Windows.Forms.Application.MarshalingControl&#xA;   culture=&lt;null&gt;&#xA;   messageFilters=&lt;null&gt;&#xA;    messageFilterSnapshot=&lt;null&gt;&#xA; handle=912&#xA; id=17208&#xA;   messageLoopCount=1&#xA; threadState=1&#xA;  modalCount=0&#xA;   activatingControlRef=&lt;null&gt;&#xA;  componentManager=System.Windows.Forms.Application.ComponentManager&#xA; externalComponentManager=False&#xA; fetchingComponentManager=False&#xA; componentID=1&#xA;  currentForm=Program.MainForm&#xA;   threadWindows=&lt;null&gt;&#xA; tempMsg=System.Windows.Forms.NativeMethods.MSG&#xA; disposeCount=0&#xA; ourModalLoop=False&#xA; messageLoopCallback=&lt;null&gt;&#xA;   __identity=&lt;null&gt;" />
        <reason type="System.Int32" value="-1" />
        <context type="System.Windows.Forms.ApplicationContext" value="System.Windows.Forms.ApplicationContext&#xA; mainForm=Program.MainForm&#xA;  userData=&lt;null&gt;&#xA;  ThreadExit=System.EventHandler" />
    </args>
    <locals>
        <local_0 type="System.Windows.Forms.Form" value="&lt;null&gt;" />
        <local_1 type="System.Boolean" value="False" />
        <local_2 type="N/A" value="&lt;N/A&gt;" />
        <local_3 type="N/A" value="&lt;N/A&gt;" />
        <local_4 type="N/A" value="&lt;N/A&gt;" />
    </locals>
</frame>
... etc
</thread>
</process>

Since you have the full power of a debugger there is nothing stopping you from writing as much or as little as you like, but the above example should get you started.

UPDATE

This works with the .Net 2.0 and/or 3.5 runtime without any further dependencies.

This can debug .Net 2.0/3.5 code running in a .Net 4.0 process; however, it does not work with 4.0 (yet).

For 4.0 CLR see this post: http://blogs.msdn.com/b/rmbyers/archive/2008/10/27/icordebug-re-architecture-in-clr-4-0.aspx

csharptest.net
@csharptest.net - According to the page you linked it requires .Net 2.0 SDK to be installed which might not be available on the client computer. Also, does it support .Net 4 programs?
Giorgi
this is a real thing to consider - maybe someone knows minimal requirements for end-user deployment?
Daniel Mošmondor
Though I have not verified it I do not believe there are any requirements besides the .net runtime. As to .net 4.0 you should only need the sample from that version of the SDK (assuming MS has updated it? IDK, it may even work as it's all COM based. I'll try it without the sdk installed when I get to work.
csharptest.net
See update, no additional dependencies required.
csharptest.net
@csharptest.net - I've been playing with it and the values of local variables are most of the time N/A (like in your example). Any idea why this is happening and how to avoid it?
Giorgi
@Giorgi, you might want to make this a separate question. Post a link to the follow-up question here and I'll see what I can come up with in the mean time.
csharptest.net
@csharptest.net - I'm playing with it so if I don't find anything myself I'll post a question.
Giorgi
Here is one discussion on the topic: http://social.msdn.microsoft.com/Forums/en-US/netfxtoolsdev/thread/85f54cb7-38d1-42be-b430-d137745b9937Seems that it may be a result of JIT optimizations, since your not launching the process with a debugger.
csharptest.net