Getting started with PInvoke: calling MiniDumpWriteDump to create minidumps

In my last post I briefly touched on what information is contained inside a minidump and the use of MiniDumpWriteDump to create your own dumps. In this post I’m going to elaborate on both of those and provide some code on how to call MiniDumpWriteDump.

The functions needed to interact with minidumps (and other debug data e.g. symbol files) are contained in the Debug Help Library (DbgHelp.dll). If you’re using C++ you can access these functions by simply including “DbgHelp.h”. Since I want to do this all in C# we’ll need to use PInvoke to access the DbgHelp functions (as DbgHelp.dll is unmanaged code i.e. a native library, not .Net).

When accessing unmanaged code there are 2 main pieces of information you need to know: the signature of the method you want to call and the file/dll that implements the function. This information is all available from the MSDN library.

If you look at the MSDN entry for MiniDumpWriteDump, you’ll see the method signature is defined as follows (just to state the obvious this is C++):

BOOL WINAPI MiniDumpWriteDump(
_In_ HANDLE hProcess,
_In_ DWORD ProcessId,
_In_ HANDLE hFile,
_In_ MINIDUMP_TYPE DumpType,
_In_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
_In_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
_In_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);

Towards the bottom of the page you’ll see the header file it’s declared in as well as the dll that implements it.

A quick side note – if you ever need to call any unmanaged code a good starting point is to search for the C# method signature at www.pinvoke.net. The library there is quite extensive, but it doesn’t contain everything you’ll look for. The DbhHelp functions are quite niche, so you’ll have mixed luck finding them. It turns out that MiniDumpWriteDump is actually on PInvoke (http://www.pinvoke.net/default.aspx/dbghelp/MiniDumpWriteDump.html), but I’ll take you through the steps of creating the signature so that you’ll be able to do it for yourself in future.

In order to use MiniDumpWriteDump from C# we’ll need to define a C# method that matches the method signature I mentioned above; so we need to match C# data types with the C/C++ data types. We also have to take into account whether the parameters are [In], [Out] or [In, Out]. For now all of the parameters are [In], so I’ll save that topic for another day. Once we’ve mapped the data types we then tell the .Net runtime that we won’t be defining the body of the method, it’ll be defined somewhere other than our code. After that you can try calling the function and see if you mapped everything correctly. Be prepared for a few crashes 😉

Mapping types between C++ and C# can be… challenging at times, but plenty of people have done it already so if you ever get stuck it’ll just take a bit of googling to fix your problem.

The first thing you need to know is what the definition of the C++ types are, you can find that here. Here’s a matching page for C# types.

First up on our list is the return type: “BOOL”. This one is easy: C++ BOOL maps to C# bool.

Next up is “hProcess” which is a HANDLE. This type is a core type in the Win32 API and is used throughout Win32 programming. The easiest way to describe it, is that it’s a reference to some kind of resource. It could reference a file, printer, bitmap, device context, font etc… it’s a long list. The important thing to note is that it’s a “reference to a resource”. That resource has memory associated with it and it’s up to you to make sure that you release that memory/handle when/if needed – there’s no magic .Net garbage collection in the unmanaged world. You can see from the link that HANDLE is actually a “void*” (in C++ you can declare typedefs, where one type is essentially an alias to another). So HANDLE is actually a pointer to a void. Previously we would use the C# type IntPtr to map to a HANDLE, but we now also have the option of using the SafeHandle class when possible (I’ll talk about the “when possible” part later).

Following “hProcess” is “ProcessId” which is a “DWORD”. The definition for DWORD says: “A DWORD is a 32-bit unsigned integer (range: 0 through 4294967295 decimal)”. We need to look for a matching C# type in our reference. DWORD is an integer type, so go to the “Integral Types Table”. Looking through that table we see that “uint” is an unsigned 32-bit integer, so that’s our match for a DWORD in C++.

I’m going to skip “hFile” since we’ve already covered handles.

After “hFile” we have “DumpType” which is of type “MINIDUMP_TYPE”. You won’t find MINIDUMP_TYPE in our list of Windows Data Types as it’s not a Windows Data Type, it’s a custom type specific to DbgHelp. If we look it up in MSDN we see that it’s actually an enum. Converting enums between C++ and C# is easy, they’re pretty much exactly the same. From the MSDN entry for MINIDUMP_TYPE we see it’s declared as follows:

typedef enum _MINIDUMP_TYPE {
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
//...
} MINIDUMP_TYPE;

In C# we have:

public enum MINIDUMP_TYPE
{
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
//...
}

As I said: almost exactly the same.

I’m not going to talk about the next 3 parameters as I don’t think many people will actually use them. Technically we should use ExceptionParam, but for now I’m going to leave it out. The quick version is: they’re structures, which means they’ll marshal as pointers, so we’ll just use IntPtr as a substitute for them. One thing to note here: it is absolutely vital that your C# data types are the same size as the C++ data types e.g. DWORD is 32-bits (4 bytes) so whatever C# type you use it has to, at the very least, be 32-bits (4 bytes) also. We used UInt as it is 32-bits. Technically we could have used Int32 also, since it’s also 32 bits. But Int32 is signed whereas UInt isn’t, so while they’re the same size, they have different meanings.

Given all that, here’s where what our parameter mapping looks like:

Parameter Type Parameter Name Description C# Type
BOOL return type Boolean bool
HANDLE hProcess void* IntPtr or SafeHandle
DWORD unsigned 32-bit integer UInt
MINIDUMP_TYPE DumpType enum enum
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam custom structure IntPtr
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam custom structure IntPtr
PMINIDUMP_CALLBACK_INFORMATION CallbackParam custom structure IntPtr

And this is what our C# function currently looks like:

bool MiniDumpWriteDump(IntPtr hProcess, UInt ProcessId, SafeHandle hFile, MINIDUMP_TYPE DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);

Now that we know what the C# method signature looks like we have to tell the C# compiler that this function is not implemented in our code. In C++ this is called a forward declaration: you’re telling the compiler that the function will exist somewhere, just not necessarily in our code and not at compile time. It should just carry on as if the function were there and the actual implementation will be resolved later. We do this in C# using 2 things: the extern keyword and the DllImport attribute.

The extern keyword tells the compiler that the function is implemented externally i.e. it’s not part of our code, we’re merely calling it from somewhere else. Your method has to be “static” when using “extern”.

The DllImport attribute lets us specify some details about where the function is implemented and some options to use when calling it. There is one very important property that you need to know about: SetLastError.

SetLastError defaults to false. If the function you are going to call will call the Win32 SetLastError() function, you need to set SetLastError=true. If you look at the “Return Value” section in the MSDN documentation for MiniDumpWriteDump you’ll notice they say that if the function returns FALSE then you should call GetLastError() to get the error code. This means we need to set SetLastError=true so that we can retrieve the error code from our C# code. I’ll show you how later.

While I’m mentioning SetLastError, have a look at PreserveSig also. We don’t need to worry about it now, but it’s important to understand for future use.

For now we just need to specify the name of the dll where the function can be found i.e. “dbghelp.dll” and SetLastError=true.

Our method now looks like this:

[DllImport(“dbghelp.dll”, SetLastError=true)]
static extern bool MiniDumpWriteDump(SafeHandle hProcess, UInt ProcessId, SafeHandle hFile, MINIDUMP_TYPE DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);

And that’s it, we’re good to go! Let’s try calling it now.

In order to call it we need to provide values for the following parameters:

Parameter Name Description
hProcess A handle to the process for which the information is to be generated.
ProcessId The identifier of the process for which the information is to be generated.
hFile A handle to the file in which the information is to be written.
DumpType The type of information to be generated. This parameter can be one or more of the values from the MINIDUMP_TYPE enumeration.

I decided to display a list of processes and let the user pick which one they wanted to create a minidump for. At first I was going to use the Win32 EnumProcesses to get a list of running processes, but then I remembered that .Net already has a Process class, which in turn has a method called GetProcesses() that you can use to list all processes on the local system. You can then use the Handle and Id properties which take care of the hProcess and ProcessId parameters respectively.

The last 2 parameters we need are hFile and DumpType. Both of those are up to us. hFile is a handle to a file we opened/created to save the dump to, and DumpType is the information you want saved.

You can use File.Create() to create a FileStream and then use it’s SafeFileHandle property for hFile.

DumpType is up to you; just use one or any combination of options from the enum.

With that done you’re good to go. I won’t release full code for the example this time as I’ve already worked ahead on starting to read the contents of minidumps. But here’s a quick peak at what it looks like:

[DllImport("DbgHelp.dll", SetLastError=true)]
public extern static bool MiniDumpWriteDump(
    IntPtr hProcess,
    UInt32 ProcessId,
    SafeHandle hFile,
    MINIDUMP_TYPE DumpType,
    IntPtr ExceptionParam,
    IntPtr UserStreamParam,
    IntPtr CallbackParam);

public enum MINIDUMP_TYPE
{
    MiniDumpNormal = 0x00000000,
    MiniDumpWithDataSegs = 0x00000001,
    //...
    MiniDumpWithFullAuxiliaryState = 0x00008000
}

using (FileStream crashDumpFileStream = File.Create(captureArguments.FilePath))
{
    bool success = NativeMethods.MiniDumpWriteDump(
        captureArguments.ProcessHandle,
        (uint)captureArguments.ProcessId,
        crashDumpFileStream.SafeFileHandle,
        captureArguments.MiniDumpType,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero);

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

        // Neat way to avoid the pain of calling FormatMessage.
        // You can create a new instance of Win32Exception without calling GetLast*Error first,
        // but I prefer this way as it's more obvious what's happening
        Win32Exception lastErrorException = new Win32Exception(lastError);

        MessageBox.Show(lastErrorException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}


Next time I’ll start talking about how to read the contents of minidumps.

Advertisements
This entry was posted in Crash Dumps and tagged , , . Bookmark the permalink.

One Response to Getting started with PInvoke: calling MiniDumpWriteDump to create minidumps

  1. Pingback: How to download Windows image files from the Microsoft symbol server using C# and DbgHelp | Greg's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s