How to write out a minidump from try/catch in C#?
-
Friday, July 18, 2008 10:17 AM
Hi folk,
This is a hot topic I think. But please see the following code:
try { //string temp = null; //temp.ToString(); throw new NullReferenceException(); } catch (Exception e) { Utility.MinidumpWritter.SaveDumpToTempDirectory("minidump", Utility.MiniDumpType.Normal); }
When I throw an Exception object, the output of the minidump is in zero size (output nothing). But as long as I switch to above commented one (temp.ToString()), a null reference exception/access violation, can generate the output, which windbg can correctly parse the CLR stack. The root cause of this problem is the following code:
exceptionParam.ExceptionPointers = Marshal.GetExceptionPointers();
When I throw the Exception object, the return value of it is null, seems like we cannot get EXCEPTION_POINTERS at that moment. It's missing. (But why can I get it when in a case of access violation?). I know Exception Filter in MSIL can always do the right job, because it is the right phase where to determine if exception should be handled. But it is a pity that C# does not support this language feature. So, I use ildasm/ilasm to inject filter code manually. Sounds crazy in a productivity development. :S
In my case, I'm developing a add-on styled project. I cannot PInvoke to call SetUnhandledExceptionFilter, because the global handler should be registered by the application. And also, I don't use the one AppDomain provided.
So, is there a alternative way to work it around?
Thanks a lot!!
Work For Fun
Answers
-
Saturday, July 19, 2008 8:48 PMModerator
Your mei.ClientPointers setting is wrong. You are making an in-process dump so must set it to false. I noticed that creating a dump with mei.ExceptionPointers = null caused an AV in dbghelp.dll. You must pass a null pointer for the excepInfo argument if you don't have the EXCEPTION_POINTERS. That's not possible with the way MiniDumpCreateDump() is declared. Also, I noticed that the call stack for the crashing thread cannot be walked when you open the dump in Visual Studio. I came up with the following fixes for these problems:
using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Utility {
public static class MiniDump {
private static int mDumpError;
private static MakeDumpArgs mArgs;
private static MinidumpExceptionInfo mMei;
public static bool TryDump(String dmpPath, MiniDumpType dmpType) {
mArgs.path = dmpPath;
mArgs.type = dmpType;
mMei.ThreadId = GetCurrentThreadId();
mMei.ExceptionPointers = Marshal.GetExceptionPointers();
mMei.ClientPointers = false;
Thread t = new Thread(new ThreadStart(MakeDump));
t.Start();
t.Join();
return mDumpError == 0;
}
private static void MakeDump() {
using (FileStream stream = new FileStream(mArgs.path, FileMode.Create)) {
Process process = Process.GetCurrentProcess();
IntPtr mem = Marshal.AllocHGlobal(Marshal.SizeOf(mMei));
Marshal.StructureToPtr(mMei, mem, false);
Boolean res = MiniDumpWriteDump(
process.Handle,
process.Id,
stream.SafeFileHandle.DangerousGetHandle(),
mArgs.type,
mMei.ClientPointers ? mem : IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
mDumpError = res ? 0 : Marshal.GetLastWin32Error();
Marshal.FreeHGlobal(mem);
}
}
private struct MakeDumpArgs {
public string path;
public MiniDumpType type;
}
[DllImport("DbgHelp.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
private static extern Boolean MiniDumpWriteDump(
IntPtr hProcess,
Int32 processId,
IntPtr fileHandle,
MiniDumpType dumpType,
IntPtr excepInfo,
IntPtr userInfo,
IntPtr extInfo);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[StructLayout(LayoutKind.Sequential)]
struct MinidumpExceptionInfo {
public Int32 ThreadId;
public IntPtr ExceptionPointers;
public bool ClientPointers;
}
}
public enum MiniDumpType {
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
}
}
Hope it works.
Hans Passant.- Marked As Answer by 天鎖斬月 Sunday, July 20, 2008 4:12 AM
All Replies
-
Friday, July 18, 2008 10:51 AMModeratorNot sure what your problem is or what kind of IL you could have injected to make it work or what "MinidumpWritter" does. Marshal.GetExceptionPointers() returning null makes perfect sense to me. It could only return something when the .NET exception filter caught an SEH exception. Throwing your own managed exception doesn't qualify, dereferencing a null pointer does. That shouldn't affect the minidump, MiniDumpWriteDump() doesn't require a value for the ExceptionParam.ExceptionPointers argument to produce a dump. I'd guess the real problem is located in MinidumpWritter.
Hans Passant. -
Saturday, July 19, 2008 7:03 PM
Thanks for your reply^^
Yes, I agree with you. When in C++, I can use MiniDumpWriteDump() without ExceptionPointer, but I can't in managed code. Actually, Marshal.GetExceptionPointers() can be get besides SEH exception. I use the code as below, it should be no problem.
namespace Utility { public static class MiniDump { [DllImport("DbgHelp.dll", CallingConvention = CallingConvention.Winapi)] private static extern Boolean MiniDumpWriteDump( IntPtr hProcess, Int32 processId, IntPtr fileHandle, MiniDumpType dumpType, ref MinidumpExceptionInfo excepInfo, IntPtr userInfo, IntPtr extInfo); [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi)] private static internal extern int GetCurrentThreadId(); struct MinidumpExceptionInfo { public Int32 ThreadId; public IntPtr ExceptionPointers; public Boolean ClientPointers; } public static Boolean TryDump(String dmpPath, MiniDumpType dmpType) { using (FileStream stream = new FileStream(dmpPath, FileMode.Create)) { Process process = Process.GetCurrentProcess(); MinidumpExceptionInfo mei = new MinidumpExceptionInfo(); mei.ThreadId = GetCurrentThreadId(); mei.ExceptionPointers = Marshal.GetExceptionPointers(); mei.ClientPointers = true; Boolean res = MiniDumpWriteDump( process.Handle, process.Id, stream.SafeFileHandle.DangerousGetHandle(), dmpType, ref mei, IntPtr.Zero, IntPtr.Zero); stream.Flush(); stream.Close(); return res; } } } public enum MiniDumpType { None = 0x00010000, 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 } }
But, throwing NullReferenceException and dereferencing a null pointer (also it is a NullReferenceException, which CLR maps AV to it) yield different output. But in a exception filter (in MSIL, .try/filter statement block, C# does not support this feature), they have the same behavior. For MSIL inject, I use the same solution as the Link provided: http://code.msdn.microsoft.com/ExceptionFilterInjct
Any idea?
Thanks a lot!!
Work For Fun -
Saturday, July 19, 2008 8:48 PMModerator
Your mei.ClientPointers setting is wrong. You are making an in-process dump so must set it to false. I noticed that creating a dump with mei.ExceptionPointers = null caused an AV in dbghelp.dll. You must pass a null pointer for the excepInfo argument if you don't have the EXCEPTION_POINTERS. That's not possible with the way MiniDumpCreateDump() is declared. Also, I noticed that the call stack for the crashing thread cannot be walked when you open the dump in Visual Studio. I came up with the following fixes for these problems:
using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Utility {
public static class MiniDump {
private static int mDumpError;
private static MakeDumpArgs mArgs;
private static MinidumpExceptionInfo mMei;
public static bool TryDump(String dmpPath, MiniDumpType dmpType) {
mArgs.path = dmpPath;
mArgs.type = dmpType;
mMei.ThreadId = GetCurrentThreadId();
mMei.ExceptionPointers = Marshal.GetExceptionPointers();
mMei.ClientPointers = false;
Thread t = new Thread(new ThreadStart(MakeDump));
t.Start();
t.Join();
return mDumpError == 0;
}
private static void MakeDump() {
using (FileStream stream = new FileStream(mArgs.path, FileMode.Create)) {
Process process = Process.GetCurrentProcess();
IntPtr mem = Marshal.AllocHGlobal(Marshal.SizeOf(mMei));
Marshal.StructureToPtr(mMei, mem, false);
Boolean res = MiniDumpWriteDump(
process.Handle,
process.Id,
stream.SafeFileHandle.DangerousGetHandle(),
mArgs.type,
mMei.ClientPointers ? mem : IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
mDumpError = res ? 0 : Marshal.GetLastWin32Error();
Marshal.FreeHGlobal(mem);
}
}
private struct MakeDumpArgs {
public string path;
public MiniDumpType type;
}
[DllImport("DbgHelp.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
private static extern Boolean MiniDumpWriteDump(
IntPtr hProcess,
Int32 processId,
IntPtr fileHandle,
MiniDumpType dumpType,
IntPtr excepInfo,
IntPtr userInfo,
IntPtr extInfo);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[StructLayout(LayoutKind.Sequential)]
struct MinidumpExceptionInfo {
public Int32 ThreadId;
public IntPtr ExceptionPointers;
public bool ClientPointers;
}
}
public enum MiniDumpType {
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
}
}
Hope it works.
Hans Passant.- Marked As Answer by 天鎖斬月 Sunday, July 20, 2008 4:12 AM
-
Sunday, July 20, 2008 4:51 AM
Thanks a lot!! This is coming to work when ExceptionPointer is null^^
Then, the last question is why we can't get EXCEPTION_POINTERS for a managed Exception when calling Marshal.GetExceptionPointers() in a catch block? But we can get it from Exception filter (in MSIL .try/filter{// filter scope}{// handler scope}) block,
Try ' Catch ex As Exception When <filter expression> ' Code reacting to exception. End Try
which is supported in the language feature of VB.NET as above and managed C++? And what's interesting is that we actually can get EXCEPTION_POINTERS for an AV exception (even though it indeed has been mapped to managed Exception, NullReferenceException) wherever in the catch block.
I can not figure out the essential difference between SEH and CLR Exception, because CLR Exception is just a special exception to SEH, the exception code of which is 'COM + 1'. CLR just mapping some of the SEH to CLR managed Exception in its own global unhandledexceptionfilter. So, could please explain it more detials?
Compared with C++, GetExceptionInformation() can only be called in the filter expression of _except block, that is because compiler generates the instruction with using offset from stack to retrieve the EXCEPTION_POINTERS, which comes to be invalid when comes out of that filter expression. So, currently C++ forces GetExceptionInformation() to be called only in filter expression, otherwise compiler will spue an Error. So, if Marshal.GetExceptionPointers() just return null in catch block, why .NET doesn't prohibit this behavior just like C++ does?
Thanks in advance!!
Work For Fun -
Sunday, July 20, 2008 10:19 AMModeratorYou are asking questions that probably only Chris Brumme can answer. I'd guess that Marshal.GetExceptionPointers() simply returns the value of a global variable inside the CLR. That variable would be set in an __except() filter and reset in the __finally() block. Correct me if I'm wrong but with my fixes you don't need the EXCEPTION_POINTERS anymore right? The stack trace of the faulting thread should be fully available, it is stalled on the Thread.Join() call. The exception itself should be on the stack as well, allowing you a peek at its Exception.StackTrace property. I'm not familiar enough with Windbg.exe to know what the managed stacks looks like.
Hans Passant. -
Sunday, July 20, 2008 3:57 PMActually, my product needs crash information to be stored in the dump file, so that our subsequent business logical can be proceeded. Otherwise, I will face a lot of modifications on the current implementation :S
OK, I will do the further investigation myself, and let you know if I can find something useful for this topic. :)
Anyway, thank you very very much. :) Really appreciate.
Austin
Work For Fun -
Tuesday, June 29, 2010 5:42 AM
Hi Hans,
I'm tring to use this class but i don't see to be able to 'read' the dump file when i open the dump file in VS.
its say's 'No Native symbols in symbol file' when i try to load the pdb file..
i have written a simple c# console app like
class Program
{
class Foo
{
public void Print()
{
}
}static void Main(string[] args)
{
try
{
Foo p = null;
p.Print();
}
catch
{
string outputFileName = "c:\\temp\\dump.dmp";
Utility.MiniDump.TryDump(outputFileName, Utility.MiniDumpType.Normal);
}
}
}am i missing something??
Regards
Fred
-
Wednesday, July 07, 2010 1:16 PM
this is what i got when i try to show the clr stack
0:000> clrstack
*** WARNING: Unable to verify timestamp for ConsoleApplication1.exe
*** ERROR: Module load completed but symbols could not be loaded for ConsoleApplication1.exe
*** WARNING: Unable to verify checksum for System.ni.dll
Couldn't resolve error at 'lrstack' -
Thursday, January 20, 2011 7:35 AMThere's an error in Utility.MakeDump, simply change "mMei.ClientPointers ? mem: IntPtr.Zero" to "mem" and your problems will be resolved.
-
Saturday, April 14, 2012 12:31 AMYou have found a way to read the minidump? Help please, because I can not find the information