Windows Dump文件解析:从崩溃到定位 | 调试技巧

Windows Dump文件解析:从崩溃到定位 #

Dump文件是Windows平台上分析程序崩溃的重要工具。本文将深入解析dump文件的内部结构,帮助你理解如何通过dump文件准确定位程序崩溃的原因。

大多数开发者应该都知道,Windows程序崩溃,可以自己调用API或者系统生成对应的dmp,我们拿到dmp后,可以用于调试定位问题,一般情况下都可以定位到具体是哪行代码出现的崩溃。拿到了dmp,为什么可以定位到具体哪里出现的崩溃呢?dmp具体都包含哪些信息呢?看下dmp的结构。先看header:

typedef struct _MINIDUMP_HEADER {
  ULONG32 Signature;
  ULONG32 Version;
  ULONG32 NumberOfStreams;
  RVA     StreamDirectoryRva;
  ULONG32 CheckSum;
  union {
    ULONG32 Reserved;
    ULONG32 TimeDateStamp;
  };
  ULONG64 Flags;
} MINIDUMP_HEADER, *PMINIDUMP_HEADER;

header算是整个dmp的引导头,从header中可以看到dmp中包含很多stream,header中描述了每个stream在文件中的位置,我们可以去对应的offset处,读取对应的stream。dmp中包含了很多stream,每个stream存储对应的信息,具体信息在Win的官方文档上几乎都可以看到。https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_stream_type,这里有给出 dmp 中 stream 的类型:

typedef 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,
  ThreadInfoListStream = 17,
  HandleOperationListStream = 18,
  TokenStream = 19,
  JavaScriptDataStream = 20,
  SystemMemoryInfoStream = 21,
  ProcessVmCountersStream = 22,
  IptTraceStream = 23,
  ThreadNamesStream = 24,
  ceStreamNull = 0x8000,
  ceStreamSystemInfo = 0x8001,
  ceStreamException = 0x8002,
  ceStreamModuleList = 0x8003,
  ceStreamProcessList = 0x8004,
  ceStreamThreadList = 0x8005,
  ceStreamThreadContextList = 0x8006,
  ceStreamThreadCallStackList = 0x8007,
  ceStreamMemoryVirtualList = 0x8008,
  ceStreamMemoryPhysicalList = 0x8009,
  ceStreamBucketParameters = 0x800A,
  ceStreamProcessModuleMap = 0x800B,
  ceStreamDiagnosisList = 0x800C,
  LastReservedStream = 0xffff
} MINIDUMP_STREAM_TYPE;

有些stream比较冷门,先不关注,这里只关注一些对我们来说非常有意义的stream:

  • ThreadListStream
  • ModuleListStream
  • ExceptionStream
  • SystemInfoStream
  • MiscInfoStream

下面具体看下每个Stream中都包含哪些信息:

ThreadListStream #

typedef struct _MINIDUMP_THREAD {
  ULONG32                   ThreadId;
  ULONG32                   SuspendCount;
  ULONG32                   PriorityClass;
  ULONG32                   Priority;
  ULONG64                   Teb;
  MINIDUMP_MEMORY_DESCRIPTOR Stack;
  MINIDUMP_LOCATION_DESCRIPTOR ThreadContext;
} MINIDUMP_THREAD, *PMINIDUMP_THREAD;

其中ThreadId和Priority以及Stack对我们排查问题都有帮助,我们可以知道崩溃时刻整个程序有多少个线程,比如崩溃时有10000个线程,那这程序不崩才怪。

ModuleListStream #

typedef struct _MINIDUMP_MODULE {
  ULONG64                   BaseOfImage;
  ULONG32                   SizeOfImage;
  ULONG32                   CheckSum;
  ULONG32                   TimeDateStamp;
  RVA                       ModuleNameRva;
  VS_FIXEDFILEINFO          VersionInfo;
  MINIDUMP_LOCATION_DESCRIPTOR CvRecord;
  MINIDUMP_LOCATION_DESCRIPTOR MiscRecord;
  ULONG64                   Reserved0;
  ULONG64                   Reserved1;
} MINIDUMP_MODULE, *PMINIDUMP_MODULE;

这个Stream很重要,它包含了崩溃进程加载过的所有PE模块的信息,信息包括PE文件的各种ID、以及版本号以及对应的符号表的信息。根据这个Stream,我们debug时,才能准确的匹配到加载的DLL所对应的pdb文件。

ExceptionStream #

typedef struct _MINIDUMP_EXCEPTION {
  ULONG32 ExceptionCode;
  ULONG32 ExceptionFlags;
  ULONG64 ExceptionRecord;
  ULONG64 ExceptionAddress;
  ULONG32 NumberParameters;
  ULONG32 __unusedAlignment;
  ULONG64 ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} MINIDUMP_EXCEPTION, *PMINIDUMP_EXCEPTION;
typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
  DWORD           ThreadId;
  PEXCEPTION_POINTERS ExceptionPointers;
  BOOL            ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;

这个stream就是崩溃线程的那个stream,它包含了崩溃线程的ID、以及崩溃的Code以及Address,解析工具根据这个address,再配合符号表,就可以解析出对应的调用栈,也就是知道崩溃时当前线程整个调用的链路,以及对应的代码位置,行号,函数名,一般情况下都可以获得。

SystemInfoStream #

typedef struct _MINIDUMP_SYSTEM_INFO {
  USHORT          ProcessorArchitecture;
  USHORT          ProcessorLevel;
  USHORT          ProcessorRevision;
  union {
    USHORT Reserved0;
    struct {
      UCHAR NumberOfProcessors;
      UCHAR ProductType;
    };
  };
  ULONG32         MajorVersion;
  ULONG32         MinorVersion;
  ULONG32         BuildNumber;
  ULONG32         PlatformId;
  RVA             CSDVersionRva;
  union {
    ULONG32 Reserved1;
    struct {
      USHORT SuiteMask;
      USHORT Reserved2;
    };
  };
  CPU_INFORMATION Cpu;
} MINIDUMP_SYSTEM_INFO, *PMINIDUMP_SYSTEM_INFO;

这个stream会标识崩溃进程所在的系统环境,最主要的就是系统版本号和cpu信息了。

MiscInfoStream #

typedef struct _MINIDUMP_MISC_INFO {
  ULONG32 SizeOfInfo;
  ULONG32 Flags1;
  ULONG32 ProcessId;
  ULONG32 ProcessCreateTime;
  ULONG32 ProcessUserTime;
  ULONG32 ProcessKernelTime;
} MINIDUMP_MISC_INFO, *PMINIDUMP_MISC_INFO;

包括进程ID、进程创建时间等等。在dmp的header中其实有个时间戳,我们可以理解为是创建dmp的时间,有了dmp创建时间,又有进程创建时间,我们还可以计算出程序的运行时间。

typedef struct _MINIDUMP_HEADER {
  ULONG32 Signature;
  ULONG32 Version;
  ULONG32 NumberOfStreams;
  RVA     StreamDirectoryRva;
  ULONG32 CheckSum;
  union {
    ULONG32 Reserved;
    ULONG32 TimeDateStamp;
  };
  ULONG64 Flags;
} MINIDUMP_HEADER, *PMINIDUMP_HEADER;

更多内容可以查看Windows官方文档:https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/其实官网介绍的还不够全面,dmp里还有很多有用的信息,比如MemoryInfoStream,这里面有很多内存以及磁盘相关的信息,方便我们了解崩溃时刻的内存情况。

参考资料 #