为什么有了dmp,就可以解析出具体哪行发生的崩溃?

科技   2024-11-16 16:51   上海  
链接:https://www.cnblogs.com/xyh9039/p/18327652
大多数开发者应该都知道,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,这里面有很多内存以及磁盘相关的信息,方便我们了解崩溃时刻的内存情况,我贴个截图:

dmp整体的结构大概如图,图片太大了,这里显示可能不够清晰,可以加我微信(微信号cpp-father,加好友时务必备注dmp)获取原图:


参考资料
  • minidump的格式:https://formats.kaitai.io/windows_minidump/index.html
  • PDB https://www.debuginfo.com/articles/debuginfomatch.html#pdbfiles
  • 牛逼的一系列文章:https://www.debuginfo.com/articles.html
  • PDB的格式:https://llvm.org/docs/PDB/index.html
  • PDB是MSF格式:https://llvm.org/docs/PDB/MsfFile.html
  • Microsoft PDB repo:https://github.com/Microsoft/microsoft-pdb
  • https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/stack_walking.md
  • https://stackoverflow.com/questions/27648857/stack-trace-stack-scanning-vs-call-frame-vs-given-as-instruction-pointer-in-con


摘自良许Linux公众号

Qt教程
致力于Qt教程,Qt技术交流,研发
 最新文章