Examining a crash dump

A crash dump is very helpful for diagnosing a problem with software. It can contain enough information to recreate a debug session, almost as if you’re debugging the problem live on the repro machine.

Last time I showed some code to create a crash dump with various settings. You can choose to include many different kinds of items, such as thread information, module lists, memory.

But what is inside a dump? How can you see what’s inside?

Below is a code sample that opens a dump and shows what’s inside. A dump can be very big: it represents the running state of the target process. It consists of multiple streams of data, each of which can be very big. The structure of a dump includes things like Location Descriptors and Directories.

A location descriptor for a stream is just a pair of numbers, indicating the relative offset (RVA) from the start of the file and the size of the stream.

You can see these MINIDUMP_LOCATION_DESCRIPTOR and MINIDUMP_DIRECTORY defined in c:\Program Files (x86)\Windows Kits\8.1\Include\um\DbgHelp.h

Because the dump file can be very big, we don’t want to read the whole thing into memory at once. We can use the RVA and Map just a small section of the file into memory at a time. The OS is very good at mapping sections of files or even memory (perhaps from another process). We only need to look at small sections at a time.

The sample code has a class called ”MiniDumpReader” that takes a dump file name, opens the file calling CreateFile, then calls CreateFileMapping, MapViewOfFileEx and MiniDumpReadDumpStream to read the streams from the dump.

The main program shows a WPF window with a TextBox for the user to enter a dump file name and a button to examine the specified dump. It only iterates through the different dump stream types to show whether or not that stream is present or absent from the dump. For modules (MINIDUMP_STREAM_TYPE.ModuleListStream), the code optionally shows the loaded modules (DLLs and EXEs) in the dump file.

clip_image001

See also:

Create your own crash dumps

Scan the Windows Event Log for your application crashes and hangs

<code>

 using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using static ExamineMiniDump.NativeMethods;

namespace ExamineMiniDump
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Loaded += (ol, el) =>
       {
         try
         {
           if (IntPtr.Size == 4)
           {
             this.Title = "ExamineDump Running as 32 bit";
           }
           else
           {
             this.Title = "ExamineDump Running as 64 bit";
           }
           this.Height = 800;
           this.Width = 800;
           var sp = new StackPanel() { Orientation = Orientation.Vertical };
           this.Content = sp;
           var dumpFileName = System.IO.Path.Combine(
                     System.IO.Path.GetTempPath(),
                     "testdump.dmp");
           dumpFileName = @"C:\Users\calvinh\AppData\Local\CrashDumps\t.dmp";
           var txtDumpFile = new TextBox()
           {
             Text = dumpFileName
           };
           sp.Children.Add(txtDumpFile);
           var chkModuleList = new CheckBox()
           {
             Content = "_Show ModuleList",
             IsChecked = true,
           };
           sp.Children.Add(chkModuleList);
           var txtStatus = new TextBox()
           {
             IsUndoEnabled = false,
             VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
             MaxHeight = 800
           };
           var btnGo = new Button()
           {
             Content = "_Examine Dump",
             HorizontalAlignment = HorizontalAlignment.Left,
             Width = 200
           };
           sp.Children.Add(btnGo);
           sp.Children.Add(txtStatus);
           btnGo.Click += (ob, eb) =>
            {
              txtStatus.Clear();
              try
              {
                dumpFileName = txtDumpFile.Text.Trim();
                using (var rdr = new MiniDumpReader(dumpFileName))
                {
                  foreach (MINIDUMP_STREAM_TYPE strmType in Enum.GetValues(typeof(MINIDUMP_STREAM_TYPE)))
                  {
                    var strmDir = rdr.ReadStreamType(strmType);
                    var locStream = strmDir.location;
                    if (locStream.Rva != 0)
                    {
                      var addrStream = rdr.MapStream(locStream);
                      switch (strmType)
                      {
                        case MINIDUMP_STREAM_TYPE.ModuleListStream:
                          var moduleStream = rdr.MapStream(strmDir.location);
                          var moduleList = Marshal.PtrToStructure<MINIDUMP_MODULE_LIST>(moduleStream);
                          var ndescSize = (uint)(Marshal.SizeOf<MINIDUMP_MODULE>() - 4);
                          var locRva = new MINIDUMP_LOCATION_DESCRIPTOR()
                          {
                            Rva = (uint)(locStream.Rva + Marshal.SizeOf<MINIDUMP_MODULE_LIST>()),
                            DataSize = (uint)(ndescSize + 4)
                          };
                          if (chkModuleList.IsChecked.Value)
                          {
                            for (int i = 0; i < moduleList.NumberOfModules; i++)
                            {
                              var ptr = rdr.MapStream(locRva);
                              var moduleinfo = Marshal.PtrToStructure<MINIDUMP_MODULE>(ptr);
                              var moduleName = rdr.GetNameFromRva(moduleinfo.ModuleNameRva);
                              txtStatus.AppendText(string.Format("  {0}\n", moduleName));

                              locRva.Rva += ndescSize;
                            }
                            break;
                          }
                          else
                          {
                            goto default;
                          }
                        default:
                          txtStatus.AppendText(
                                string.Format(
                                  "got {0} {1} Sz={2} Addr={3:x8}\r",
                                  strmType,
                                  locStream.Rva,
                                  locStream.DataSize,
                                  addrStream.ToInt32()));
                          break;
                      }
                    }
                    else
                    {
                      txtStatus.AppendText(
                                string.Format(
                                    "zero {0}\r",
                                    strmType
                                    ));
                    }
                  }
                }
              }
              catch (Exception ex)
              {
                txtStatus.Text = ex.ToString();
              }
            };
         }
         catch (Exception ex)
         {
           this.Content = ex.ToString();
         }
       };
    }
  }
  public class MiniDumpReader : IDisposable
  {
    public const int AllocationGranularity = 0x10000; // 64k

    ulong _minidumpFileSize;
    // a handle to the opened dump file
    IntPtr _hFile = IntPtr.Zero;
    // a handle to the file mapping
    IntPtr _hFileMapping = IntPtr.Zero;
    // the address of the mapping in our process
    IntPtr _addrFileMapping;
    // the current offset in the dump being mapped
    ulong _mapCurrentOffset;
    // the current size of the mapping
    UInt32 _mappedCurrentSize;

    public MiniDumpReader(string dumpFileName)
    {
      _minidumpFileSize = (ulong)(new FileInfo(dumpFileName)).Length;
      _hFile = CreateFile(
          dumpFileName,
          EFileAccess.GenericRead,
          EFileShare.Read,
          lpSecurityAttributes: IntPtr.Zero,
          dwCreationDisposition: ECreationDisposition.OpenExisting,
          dwFlagsAndAttributes: EFileAttributes.Readonly,
          hTemplateFile: IntPtr.Zero
          );
      if (_hFile == INVALID_HANDLE_VALUE)
      {
        var hr = Marshal.GetHRForLastWin32Error();
        var ex = Marshal.GetExceptionForHR(hr);
        throw ex;
      }
      // we create a File Mapping, from which we can 
      // Map and unmap Views
      _hFileMapping = CreateFileMapping(
          _hFile,
          lpAttributes: 0,
          flProtect: AllocationProtect.PAGE_READONLY,
          dwMaxSizeHi: 0, // default to max size of file
          dwMaxSizeLow: 0,
          lpName: null
          );
      if (_hFileMapping == INVALID_HANDLE_VALUE)
      {
        var hr = Marshal.GetHRForLastWin32Error();
        var ex = Marshal.GetExceptionForHR(hr);
        throw ex;
      }
    }
    public MINIDUMP_DIRECTORY ReadStreamType(MINIDUMP_STREAM_TYPE strmType)
    {
      MINIDUMP_DIRECTORY dir = new MINIDUMP_DIRECTORY();
      var initloc = new MINIDUMP_LOCATION_DESCRIPTOR() {
        Rva = 0,
        DataSize = AllocationGranularity
      };
      if (MapStream(initloc) == IntPtr.Zero)
      {
        throw new InvalidOperationException("MapViewOfFileFailed");
      }
      var dirPtr = IntPtr.Zero;
      dir.location = initloc;
      var _strmPtr = IntPtr.Zero;
      var _strmSize = 0u;
      if (MiniDumpReadDumpStream(
          _addrFileMapping,
          strmType,
          ref dirPtr,
          ref _strmPtr,
          ref _strmSize
          ))
      {
        dir = Marshal.PtrToStructure<MINIDUMP_DIRECTORY>(dirPtr);
      }
      else
      {
        dir.streamType = strmType;
        dir.location.Rva = 0;
        dir.location.DataSize = 0;
      }
      return dir;
    }
    // map a section of the dump specified by a location into memory 
    // return the address of the section
    public IntPtr MapStream(MINIDUMP_LOCATION_DESCRIPTOR loc)
    {
      IntPtr retval = IntPtr.Zero;
      ulong newbaseOffset = (uint)(loc.Rva / AllocationGranularity) * AllocationGranularity;
      uint mapViewSize = AllocationGranularity * 4;
      var nLeftover = loc.Rva - newbaseOffset;
      var fAlreadyMapped = loc.Rva >= _mapCurrentOffset &&
          loc.Rva + loc.DataSize <
          _mapCurrentOffset + _mappedCurrentSize;
      if (!fAlreadyMapped)
      {
        //  try to reuse the same address
        var preferredAddress = _addrFileMapping;
        if (_addrFileMapping != IntPtr.Zero) // unmap prior
        {
          var res = UnmapViewOfFile(_addrFileMapping);
          if (!res)
          {
            throw new InvalidOperationException("Couldn't unmap view of file");
          }
        }
        _addrFileMapping = IntPtr.Zero;
        uint hiPart = (uint)((newbaseOffset >> 32) & uint.MaxValue);
        uint loPart = (uint)newbaseOffset;
        if (loc.DataSize + nLeftover > mapViewSize)
        {
          mapViewSize = (uint)(loc.DataSize + nLeftover);
        }
        if (newbaseOffset + mapViewSize >= _minidumpFileSize)
        {
          mapViewSize = Math.Min((uint)(loc.DataSize + nLeftover), (uint)_minidumpFileSize);
        }
        while (_addrFileMapping == IntPtr.Zero)
        {
          _addrFileMapping = MapViewOfFileEx(
              _hFileMapping,
              FILE_MAP_READ,
              hiPart,
              loPart,
              mapViewSize,
              preferredAddress
              );
          if (_addrFileMapping == IntPtr.Zero)  // failure
          {
            // if we spec'd a preferred address, that addr might now be in use
            // so try again with no preferene
            if (preferredAddress != IntPtr.Zero)
            {
              preferredAddress = IntPtr.Zero;
              // loop
            }
            else
            {
              var hr = Marshal.GetHRForLastWin32Error();
              var ex = Marshal.GetExceptionForHR(hr);
              throw ex;
            }
          }
        }
        _mapCurrentOffset = newbaseOffset;
        _mappedCurrentSize = mapViewSize;
      }
      retval = _addrFileMapping +
          (int)(newbaseOffset - _mapCurrentOffset + nLeftover);
      return retval;
    }
    // get a string name from a relative address in the dump
    public string GetNameFromRva(uint rva)
    {
      var retstr = string.Empty;
      if (rva != 0)
      {
        var locDesc = new MINIDUMP_LOCATION_DESCRIPTOR()
        {
          Rva = rva,
          DataSize = 600
        };
        var locname = MapStream(locDesc);
        retstr = Marshal.PtrToStringBSTR(locname + 4); // ' skip length
      }
      return retstr;
    }
    public void Dispose()
    {
      if (_addrFileMapping != IntPtr.Zero)
      {
        UnmapViewOfFile(_addrFileMapping);
      }
      if (_hFileMapping != IntPtr.Zero)
      {
        CloseHandle(_hFileMapping);
      }
      if (_hFile != IntPtr.Zero)
      {
        CloseHandle(_hFile);
      }
    }
  }
  public static partial class NativeMethods
  {
    [DllImport("dbghelp.dll", SetLastError = true)]
    public static extern bool MiniDumpReadDumpStream(
                    IntPtr BaseOfDump,
                    MINIDUMP_STREAM_TYPE StreamNumber,
                    ref IntPtr DirPtr,
                    ref IntPtr StreamPointer,
                    ref uint StreamSize
            );

    [StructLayout(LayoutKind.Sequential)]
    public struct MINIDUMP_LOCATION_DESCRIPTOR
    {
      public uint DataSize;
      public uint Rva; /// relative byte offset
      public override string ToString()
      {
        return string.Format(
            "Off={0:x8} Sz={1:x8}",
            Rva,
            DataSize);
      }
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct MINIDUMP_DIRECTORY
    {
      public MINIDUMP_STREAM_TYPE streamType;
      public MINIDUMP_LOCATION_DESCRIPTOR location;
      public override string ToString()
      {
        return string.Format(
            "{0} {1}",
            streamType,
            location
            );
      }
    }
    public enum MINIDUMP_STREAM_TYPE
    {
      UnusedStream = 0,
      ReservedStream0 = 1,
      ReservedStream1 = 2,
      ThreadListStream = 3,
      ModuleListStream = 4,
      MemoryListStream = 5,
      ExceptionStream = 6,
      SystemInfoStream = 7,
      ThreadExListStream = 8,
      Memory64ListStream = 9,
      CommentStreamA = 10,
      CommentStreamW = 11,
      HandleDataStream = 12,
      FunctionTableStream = 13,
      UnloadedModuleListStream = 14,
      MiscInfoStream = 15,
      MemoryInfoListStream = 16, //   like VirtualQuery
      ThreadInfoListStream = 17,
      HandleOperationListStream = 18,
      TokenStream = 19,
      LastReservedStream = 0xFFFF
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MINIDUMP_MODULE_LIST
    {
      public uint NumberOfModules;
      //MINIDUMP_MODULE Modules[];
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MINIDUMP_MODULE
    {
      public long BaseOfImage;
      public uint SizeOfImage;
      public uint CheckSum;
      public uint TimeDateStamp;
      public uint ModuleNameRva;
      public VS_FIXEDFILEINFO VersionInfo;
      public MINIDUMP_LOCATION_DESCRIPTOR CvRecord;
      public MINIDUMP_LOCATION_DESCRIPTOR MiscRecord;
      public long Reserved0;
      public long Reserved1;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct VS_FIXEDFILEINFO
    {
      uint dwSignature;
      uint dwStrucVersion;
      uint dwFileVersionMS;
      uint dwFileVersionLS;
      uint dwProductVersionMS;
      uint dwProductVersionLS;
      uint dwFileFlagsMask;
      uint dwFileFlags;
      uint dwFileOS;
      uint dwFileType;
      uint dwFileSubtype;
      uint dwFileDateMS;
      uint dwFileDateLS;
    }

    [DllImport("kernel32.dll",
      SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseHandle(IntPtr hObject);
    public static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

    [Flags]
    public enum EFileAccess : uint
    {
      //
      // Standart Section
      //

      AccessSystemSecurity = 0x1000000,   // AccessSystemAcl access type
      MaximumAllowed = 0x2000000,     // MaximumAllowed access type

      Delete = 0x10000,
      ReadControl = 0x20000,
      WriteDAC = 0x40000,
      WriteOwner = 0x80000,
      Synchronize = 0x100000,

      StandardRightsRequired = 0xF0000,
      StandardRightsRead = ReadControl,
      StandardRightsWrite = ReadControl,
      StandardRightsExecute = ReadControl,
      StandardRightsAll = 0x1F0000,
      SpecificRightsAll = 0xFFFF,

      FILE_READ_DATA = 0x0001,        // file & pipe
      FILE_LIST_DIRECTORY = 0x0001,       // directory
      FILE_WRITE_DATA = 0x0002,       // file & pipe
      FILE_ADD_FILE = 0x0002,         // directory
      FILE_APPEND_DATA = 0x0004,      // file
      FILE_ADD_SUBDIRECTORY = 0x0004,     // directory
      FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
      FILE_READ_EA = 0x0008,          // file & directory
      FILE_WRITE_EA = 0x0010,         // file & directory
      FILE_EXECUTE = 0x0020,          // file
      FILE_TRAVERSE = 0x0020,         // directory
      FILE_DELETE_CHILD = 0x0040,     // directory
      FILE_READ_ATTRIBUTES = 0x0080,      // all
      FILE_WRITE_ATTRIBUTES = 0x0100,     // all

      //
      // Generic Section
      //

      GenericRead = 0x80000000,
      GenericWrite = 0x40000000,
      GenericExecute = 0x20000000,
      GenericAll = 0x10000000,

      SPECIFIC_RIGHTS_ALL = 0x00FFFF,
      FILE_ALL_ACCESS =
      StandardRightsRequired |
      Synchronize |
      0x1FF,

      FILE_GENERIC_READ =
      StandardRightsRead |
      FILE_READ_DATA |
      FILE_READ_ATTRIBUTES |
      FILE_READ_EA |
      Synchronize,

      FILE_GENERIC_WRITE =
      StandardRightsWrite |
      FILE_WRITE_DATA |
      FILE_WRITE_ATTRIBUTES |
      FILE_WRITE_EA |
      FILE_APPEND_DATA |
      Synchronize,

      FILE_GENERIC_EXECUTE =
      StandardRightsExecute |
        FILE_READ_ATTRIBUTES |
        FILE_EXECUTE |
        Synchronize
    }

    [Flags]
    public enum EFileShare : uint
    {
      /// <summary>
      /// 
      /// </summary>
      None = 0x00000000,
      /// <summary>
      /// Enables subsequent open operations on an object to request read access. 
      /// Otherwise, other processes cannot open the object if they request read access. 
      /// If this flag is not specified, but the object has been opened for read access, the function fails.
      /// </summary>
      Read = 0x00000001,
      /// <summary>
      /// Enables subsequent open operations on an object to request write access. 
      /// Otherwise, other processes cannot open the object if they request write access. 
      /// If this flag is not specified, but the object has been opened for write access, the function fails.
      /// </summary>
      Write = 0x00000002,
      /// <summary>
      /// Enables subsequent open operations on an object to request delete access. 
      /// Otherwise, other processes cannot open the object if they request delete access.
      /// If this flag is not specified, but the object has been opened for delete access, the function fails.
      /// </summary>
      Delete = 0x00000004
    }

    public enum ECreationDisposition : uint
    {
      /// <summary>
      /// Creates a new file. The function fails if a specified file exists.
      /// </summary>
      New = 1,
      /// <summary>
      /// Creates a new file, always. 
      /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
      /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
      /// </summary>
      CreateAlways = 2,
      /// <summary>
      /// Opens a file. The function fails if the file does not exist. 
      /// </summary>
      OpenExisting = 3,
      /// <summary>
      /// Opens a file, always. 
      /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
      /// </summary>
      OpenAlways = 4,
      /// <summary>
      /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
      /// The calling process must open the file with the GENERIC_WRITE access right. 
      /// </summary>
      TruncateExisting = 5
    }

    [Flags]
    public enum EFileAttributes : uint
    {
      Readonly = 0x00000001,
      Hidden = 0x00000002,
      System = 0x00000004,
      Directory = 0x00000010,
      Archive = 0x00000020,
      Device = 0x00000040,
      Normal = 0x00000080,
      Temporary = 0x00000100,
      SparseFile = 0x00000200,
      ReparsePoint = 0x00000400,
      Compressed = 0x00000800,
      Offline = 0x00001000,
      NotContentIndexed = 0x00002000,
      Encrypted = 0x00004000,
      Write_Through = 0x80000000,
      Overlapped = 0x40000000,
      NoBuffering = 0x20000000,
      RandomAccess = 0x10000000,
      SequentialScan = 0x08000000,
      DeleteOnClose = 0x04000000,
      BackupSemantics = 0x02000000,
      PosixSemantics = 0x01000000,
      OpenReparsePoint = 0x00200000,
      OpenNoRecall = 0x00100000,
      FirstPipeInstance = 0x00080000
    }

    [DllImport("kernel32.dll",
      SetLastError = true,
      CharSet = CharSet.Auto)]
    public static extern IntPtr CreateFile(
            string lpFileName,
            EFileAccess dwDesiredAccess,
            EFileShare dwShareMode,
            IntPtr lpSecurityAttributes,
            ECreationDisposition dwCreationDisposition,
            EFileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile
        );

    [DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateFileMapping(
                                     IntPtr hFile,
                                     UInt32 lpAttributes,
                                     AllocationProtect flProtect,
                                     UInt32 dwMaxSizeHi,
                                     UInt32 dwMaxSizeLow,
                                     string lpName
                                     );

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr MapViewOfFile(
                                           IntPtr hFileMappingObject,
                                           UInt32 dwDesiredAccess,
                                           UInt32 dwFileOffsetHigh,
                                           UInt32 dwFileOffsetLow,
                                           UInt32 dwNumberOfBytesToMap
        );

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr MapViewOfFileEx(IntPtr hFileMappingObject,
                                           UInt32 dwDesiredAccess,
                                           UInt32 dwFileOffsetHigh,
                                           UInt32 dwFileOffsetLow,
                                           UInt32 dwNumberOfBytesToMap,
                                           IntPtr PreferredAddress
                                           );

    [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
    public static extern bool UnmapViewOfFile(
          IntPtr lpBaseAddress
        );

    public const int FILE_MAP_READ = 4;
    public const int FILE_MAP_WRITE = 2;

    [Flags()]
    public enum AllocationProtect
    {
      PAGE_EXECUTE = 0x10,
      PAGE_EXECUTE_READ = 0x20,
      PAGE_EXECUTE_READWRITE = 0x40,
      PAGE_EXECUTE_WRITECOPY = 0x80,
      PAGE_NOACCESS = 0x1,
      PAGE_READONLY = 0x2,
      PAGE_READWRITE = 0x4,
      PAGE_WRITECOPY = 0x8,
      PAGE_GUARD = 0x100,
      PAGE_NOCACHE = 0x200,
      PAGE_WRITECOMBINE = 0x400
    }
  }

}

</code>