Minidump Explorer v0.2: Reading minidump MemoryListStream and Memory64ListStream

Version 0.2 of Minidump Explorer included 4 new data streams: MemoryListStream, Memory64ListStream, HandleDataStream and ThreadListStream. These are all fairly simple streams to read and use but 3 of them are going to be vitally important when we start hooking into CorDebug: MemoryListStream, Memory64ListStream and ThreadListStream. The 2 memory list streams will naturally give us access to some, or all, of the memory of the crashed process, while the thread list stream, amongst other things, give us access to the thread’s Context information. The handle data is interesting, but not essential at this point in time.

MemoryListStream and Memory64ListStream

MemoryListStream and Memory64ListStream provide you with a list of memory regions that are contained in the crash dump. The difference between the two is that Memory64ListStream is used for full-memory dumps, while MemoryListStream is used when only partial memory is available. You can see the difference when you look at the declaration of the structures they return:

MemoryListStream (C++)

typedef struct _MINIDUMP_LOCATION_DESCRIPTOR {
  ULONG32 DataSize;
  RVA     Rva;
} MINIDUMP_LOCATION_DESCRIPTOR;

typedef struct _MINIDUMP_MEMORY_DESCRIPTOR {
  ULONG64                      StartOfMemoryRange;
  MINIDUMP_LOCATION_DESCRIPTOR Memory;
} MINIDUMP_MEMORY_DESCRIPTOR, *PMINIDUMP_MEMORY_DESCRIPTOR;

typedef struct _MINIDUMP_MEMORY_LIST {
  ULONG32                    NumberOfMemoryRanges;
  MINIDUMP_MEMORY_DESCRIPTOR MemoryRanges[];
} MINIDUMP_MEMORY_LIST, *PMINIDUMP_MEMORY_LIST;

If you look at MINIDUMP_MEMORY_LIST you’ll notice that it contains an array of MINIDUMP_MEMORY_DESCRIPTOR’s (MemoryRanges). Each MINIDUMP_MEMORY_DESCRIPTOR represents a region of memory included with the minidump. It contains the starting address of the region of memory represented (StartOfMemoryRange) and a MINIDUMP_LOCATION_DESCRIPTOR (Memory) indicating how big the region is and where in the minidump file to find it. What’s important to note here, is that each region of memory could be in a different physical location inside the minidump file. Because of this each MINIDUMP_LOCATION_DESCRIPTOR has an Rva field which you need to use to find the correct location in the minidump to read from. Full memory dumps are different: the memory is all stored in one sequential block at the end of the dump. As a result you don’t need individual RVA’s for each region. You can see the difference here:

Memory64ListStream (C++)

typedef struct _MINIDUMP_MEMORY_DESCRIPTOR64 {
    ULONG64 StartOfMemoryRange;
    ULONG64 DataSize;
} MINIDUMP_MEMORY_DESCRIPTOR64, *PMINIDUMP_MEMORY_DESCRIPTOR64;

typedef struct _MINIDUMP_MEMORY64_LIST {
    ULONG64 NumberOfMemoryRanges;
    RVA64 BaseRva;
    MINIDUMP_MEMORY_DESCRIPTOR64 MemoryRanges [0];
} MINIDUMP_MEMORY64_LIST, *PMINIDUMP_MEMORY64_LIST;

You’ll notice that MINIDUMP_MEMORY64_LIST also has an array of descriptors (MINIDUMP_MEMORY_DESCRIPTOR64). The difference here is that MINIDUMP_MEMORY_DESCRIPTOR64 doesn’t include a location descriptor as MINIDUMP_MEMORY_DESCRIPTOR did. This is as a result of all of the memory being in one block at the end of the minidump: you don’t need individual RVA’s for each block since they all follow each other starting from MINIDUMP_MEMORY64_LIST.BaseRva.

Reading memory data from a minidump

So how do you go about reading from a location in memory?

If you have a crash dump containing partial memory (MINIDUMP_MEMORY_LIST) you would loop through each MINIDUMP_MEMORY_DESCRIPTOR and check for a region containing the address you were looking for: (myReadAddress >= StartOfMemoryRange) && (myReadAddress < (StartOfMemoryRange + Memory.DataSize)). If you find a matching MINIDUMP_MEMORY_DESCRIPTOR you would then add it’s Memory.Rva field (from MINIDUMP_LOCATION_DESCRIPTOR) to the address of the minidump file mapping in order to get the physical location to read from.

Reading from MINIDUMP_MEMORY64_LIST is a bit different. You would still loop through each MINIDUMP_MEMORY_DESCRIPTOR64 looking for the region that holds the address you want to read from, but the difference is in how you read the data once you’ve found the correct region. Since a full-memory dump has all of the memory stored sequentially at the end of the dump file there is only one RVA and that RVA points to the beginning of the memory data inside the minidump. In order to read the actual memory you need to keep a running total of the DataSize of each MINIDUMP_MEMORY_DESCRIPTOR64 that precedes the one that you need and add that to the BaseRva. Then add that to the address of the memory mapped file of the crash dump. So the end result logic would look similar to this:

// addressOfBlockToReadFrom is the address in the minidump file of 
// the start of the block you want to read from.
long addressOfBlockToReadFrom = memoryMappedFileAddress + memory64List.BaseRva + allPreceedingDataSizes;

// offsetToReadFrom is the offset from the beginning of the 
// block that you want to read from.
// e.g. if you want to read from 0x23 and the block starts 
// at 0x20 then the offset is 0x3.
long offsetToReadFrom = addressIWantToReadFrom - blockToReadFrom.StartOfMemoryRange;

// addressToReadFrom is the physical address in the minidump file
// where you should start reading from.
long addressToReadFrom = addressOfBlockToReadFrom + offsetToReadFrom;

Show me the code

I won’t go through the all of the field types individually as they’re all types I have covered previously. The only new one is RVA64 which is a ULONG64 and translates to a UInt64 in c#.

MemoryListStream (C#)

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct MINIDUMP_LOCATION_DESCRIPTOR
{
    public UInt32 DataSize;
    public uint Rva;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct MINIDUMP_MEMORY_DESCRIPTOR
{
    public UInt64 StartOfMemoryRange;
    public MINIDUMP_LOCATION_DESCRIPTOR Memory;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct MINIDUMP_MEMORY_LIST
{
    public UInt32 NumberOfMemoryRanges;
    public IntPtr MemoryRanges; // MINIDUMP_MEMORY_DESCRIPTOR[]
}

Memory64ListStream

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct MINIDUMP_MEMORY_DESCRIPTOR64
{
    public UInt64 StartOfMemoryRange;
    public UInt64 DataSize;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct MINIDUMP_MEMORY64_LIST
{
    public UInt64 NumberOfMemoryRanges;
    public UInt64 BaseRva;
    public IntPtr MemoryRanges; // MINIDUMP_MEMORY_DESCRIPTOR64[]
}

As far as reading the streams: you’ll follow the same steps as we did when reading the ModuleListStream. You can find the full source code on my CodePlex project (I’ve made a few small updates since the original article).

That’s it for the memory streams, next time I’ll cover the ThreadListStream.

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

One Response to Minidump Explorer v0.2: Reading minidump MemoryListStream and Memory64ListStream

  1. Pingback: Discovering the secrets of a pageant minidump | DiabloHorn

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