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,这里面有很多内存以及磁盘相关的信息,方便我们了解崩溃时刻的内存情况。