UPDATE: I’m looking into using SafeMemoryMappedViewHandle as the return value for MapViewOfFile() instead of IntPtr. Either will work, but since Microsoft provides a class to specifically wrap memory mapped view handles I thought I’d have a look at using it. I’ll report later on what my preference ends up being.
- Part 1 of 4: Accessing memory mapped files from C#
- Part 2 of 4: Using MiniDumpReadDumpStream
- Part 3 of 4: Reading stream data (ModuleListStream) returned from MiniDumpReadDumpStream
- Part 4 of 4: Putting it all together
All code is available on my CodePlex project.
In my previous post I explained how to use PInvoke to call MiniDumpWriteDump in order to create minidump files. Now it’s time to start reading data out of them. I’m going to break this topic into a few posts as this is the first time we’re going to be reading data and there is quite a bit to cover. I’ll start with the mechanics of opening the file and creating a memory mapped view of it. Following that I’ll talk about the actual read function. Next I’ll talk about how to interpret the data you read and finally I’ll put it all together in an application. Once we’ve successfully completed our first read the rest will follow easily and with much less effort.
In my previous post I mentioned the
DumpType parameter of the
MiniDumpWriteDump function and how we would use that parameter to determine which information was saved into our minidump. Using MiniDumpReadDumpStream we can now choose which of those pieces of information we want to read out of the minidump.
The method signature of MiniDumpReadDumpStream is as follows (from MSDN):
BOOL WINAPI MiniDumpReadDumpStream( _In_ PVOID BaseOfDump, _In_ ULONG StreamNumber, _Out_ PMINIDUMP_DIRECTORY *Dir, _Out_ PVOID *StreamPointer, _Out_ ULONG *StreamSize );
|BaseOfDump [in]||A pointer to the base of the mapped minidump file. The file should have been mapped into memory using the MapViewOfFile function.|
|StreamNumber [in]||The type of data to be read from the minidump file. This member can be one of the values in the MINIDUMP_STREAM_TYPE enumeration.|
|Dir [out]||A pointer to a MINIDUMP_DIRECTORY structure.|
|StreamPointer [out]||A pointer to the beginning of the minidump stream. The format of this stream depends on the value of StreamNumber. For more information, see MINIDUMP_STREAM_TYPE.|
|StreamSize [out]||The size of the stream pointed to by StreamPointer, in bytes.|
Simply put: we tell the function where to read from (BaseOfDump), what we want it to read (StreamNumber) and where we want the read information put (StreamPointer and StreamSize). Don’t worry about “Dir” for now, we won’t be using it.
For the rest of this post I’m going to talk about the
BaseOfDump parameter as this is the whole reason we need to talk about mapped file views in the first place.
You’ll see from the MSDN description of the
BaseOfDump parameter that it’s a pointer to the memory mapped view of the file. What does a memory mapped file do? It lets us read a portion (or all) of a file as if it were a block of memory in our address space. So our code will read the file as if it were a piece of memory and not a file on the disk. Another way to about it is that we’ll using memory functions to read data, not file I/O functions.
The .Net Framework actually provides a wrapper around the CreateFileMapping() and MapViewOfFile() calls. You can use MemoryMappedFile.CreateFromFile() to create a MemoryMappedFile and then use MemoryMappedFile.CreateViewAccessor() to get a MemoryMappedViewAccessor.
Using these classes would make life a lot easier for us as all of the PInvoke hard work has already been done. The only problem is that neither provides us with access to the address the file was mapped to. The MemoryMappedViewAccessor only provides Read() and Write() methods to access the view and since we’re not the ones doing the reading (MiniDumpReadDumpStream() is) that doesn’t really help us. MemoryMappedFile does however provide a handle to the file mapping that it created so we’ll use that and call the unmanaged MapViewOfFile() ourselves; at least we save a bit of work.
Given that let’s figure out how to call
MapViewOfFile() from C#. Here’s the method signature from MSDN:
LPVOID WINAPI MapViewOfFile( _In_ HANDLE hFileMappingObject, _In_ DWORD dwDesiredAccess, _In_ DWORD dwFileOffsetHigh, _In_ DWORD dwFileOffsetLow, _In_ SIZE_T dwNumberOfBytesToMap );
Before I continue: I’ve create a page that I’ll use to keep track of all of the C++/C# type mappings for future use. You can access it here.
First up is the
LPVOID return type. This is a pointer type so we use IntPtr in C#.
The first parameter is
hFileMappingObject. This is a HANDLE which we know from last time is an IntPtr (or SafeHandle if possible). We’ll be using MemoryMappedFile.SafeMemoryMappedFileHandle to get access to the handle of the file mapping, so we’ll use SafeHandle.
Next up is
dwDesiredAccess. This is a DWORD which we also used last time; the equivalent in C# is
uint. That part was easy; figuring out what value to pass in is a bit more challenging. You’ll notice from the MSDN entry that they tell you exactly what the possible options are e.g.
FILE_MAP_ALL_ACCESS, FILE_MAP_COPY etc. The only problem is that they don’t say what the value of any of the options are, only what they are called .This is because these are all defined in the C++ header files. If you were calling this function from C++ you wouldn’t care what the value is, you only need to know what it’s called. We’re doing all of this in C# though, which means we don’t have access to those header files from our code, so we’ll need to find the definition in the header file and redefine it in our C# code.
If you need to find something in a C++ header file, you can either search through the files in the header files folder (“C:\Program Files (x86)\Windows Kits\8.0\Include\um” on my machine) or keep a “spare” C++ console app project on the side and just use the Visual Studio “Go to definition” command to find it. I started out just searching through the header files on disk, but I’ve switched to using the “Go to definition” command now as its much quicker and easier.
Looking at the options for
dwDesiredAccess we can see that the option we need is
FILE_MAP_READ. This is defined in “memoryapi.h” (line 49) as “
SECTION_MAP_READ in turn is defined in “winnt.h” (line 11500) as “0x0004” (you can see the value of “Go to definition” now – most types/defines end up as alias’s to other ones).
Here is the end result of the mapping:
public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000; public const uint SECTION_QUERY = 0x0001; public const uint SECTION_MAP_WRITE = 0x0002; public const uint SECTION_MAP_READ = 0x0004; public const uint SECTION_MAP_EXECUTE = 0x0008; public const uint SECTION_EXTEND_SIZE = 0x0010; public const uint SECTION_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | SECTION_MAP_WRITE | SECTION_MAP_READ | SECTION_MAP_EXECUTE | SECTION_EXTEND_SIZE); public const uint FILE_MAP_COPY = SECTION_QUERY; public const uint FILE_MAP_WRITE = SECTION_MAP_WRITE; public const uint FILE_MAP_READ = SECTION_MAP_READ; public const uint FILE_MAP_EXECUTE = SECTION_MAP_EXECUTE; public const uint FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS;
dwDesiredAccess we have
dwFileOffsetLow. Both are DWORD’s so in C# they’re both
uint’s. Our view starts at the beginning of the file, so there is no offset and we set both of these to 0.
Finally we have
dwNumberOfBytesToMap which is a
SIZE_T. SIZE_T is defined here as a ULONG_PTR which is in turn defined as a __int3264. As you can see from the documentation __int3264 varies depending on the platform you’re using: 32-bits if you’re executing in 32bit mode, 64-bits otherwise. We’ll use IntPtr for our C# call as it is also platform specific i.e. either 32 bits or 64 bits.
Our parameters now look like this:
|Parameter Type||Parameter Name||Description||C# Type|
|HANDLE||hFileMappingObject||void*||IntPtr or SafeHandle|
|DWORD||dwDesiredAccess||unsigned 32-bit integer||UInt|
|DWORD||dwFileOffsetHigh||unsigned 32-bit integer||UInt|
|DWORD||dwFileOffsetLow||unsigned 32-bit integer||UInt|
|SIZE_T||dwNumberOfBytesToMap||32-bit or 64-bit integer depending on execution environemtn||IntPtr|
Our method signature now looks like this:
IntPtr MapViewOfFile(SafeHandle hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, IntPtr dwNumberOfBytesToMap)
Adding our DllImport attribute we end up with the final method:
[DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr MapViewOfFile( SafeHandle hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, IntPtr dwNumberOfBytesToMap);
There’s one very important thing to take note of before going on: cleaning up after ourselves! I mentioned previously that there is no magic .Net garbage collection in the unmanaged world and this is a perfect example of that. We called the unmanaged function MapViewOfFile to map a view for us. This call allocated resources and it’s up to us to free those resources when we’re done using them. So once we’re done using the view we need to close it using UnmapViewOfFile:
[DllImport("kernel32.dll", SetLastError = true)] public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
With that done we now have access to a memory mapped view of our minidump file. In the next part I’ll talk about invoking MiniDumpReadDumpStream to read some data.