zoukankan      html  css  js  c++  java
  • 向大厂看齐!为自己的程序增加自动转储的功能!

    如果你还不清楚什么是转储文件,不知道什么时候需要转储文件,请参考转储文件系列文章的第一篇 —— 转储文件知多少

    前言

    不知道各位小伙伴有没有遇到过 微信 或者 QQ 崩溃的情况。它们在崩溃的时候都会自动弹出一个对话框,提示用户上传相关文件,供开发人员分析问题的原因。有的小伙伴儿可能不太清楚我在说什么。没关系,下图就是微信崩溃后自动弹出的界面。

    微信 Crash Report 界面
    微信 Crash Report 界面

    如果勾选了 发送错误报告(S) 按钮,点击 确定(O) 按钮后,会把收集到的文件上传给开发人员。同样的,如果勾选了 重启程序(R),点击 确定(O) 按钮后,微信会自动重启。有没有觉得很酷?我们自己的程序可以做到类似的效果吗?答案是肯定的。如果感兴趣,就请继续阅读吧!

    MiniDumpWriteDump

    微软提供了专门的 API 来生成转储文件,这个 API 就是 MiniDumpWriteDump()

    BOOL MiniDumpWriteDump(
      HANDLE                            hProcess,
      DWORD                             ProcessId,
      HANDLE                            hFile,
      MINIDUMP_TYPE                     DumpType,
      PMINIDUMP_EXCEPTION_INFORMATION   ExceptionParam,
      PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
      PMINIDUMP_CALLBACK_INFORMATION    CallbackParam
    );

    简单介绍下每个参数:

    • hProcess:要转储的进程句柄。

      打开进程的时候,至少需要指定 PROCESS_QUERY_INFORMATIONPROCESS_VM_READ 权限。如果需要转储 句柄信息,还需要 PROCESS_DUP_HANDLE 权限。如果需要转储线程信息,需要 THREAD_ALL_ACCESS 权限。

    • ProcessId:要转储的进程ID

      有点不太理解为什么要额外传递一个进程ID的参数,调用GetProcessId() 就可以根据 hProcess 获取进程ID了,难道是为了效率?希望有知道的小伙伴儿指点一二。

    • hFile:通过 CreateFile()API 打开的,用来保存 dump 的文件句柄。

    • DumpType:转储类型。此参数会直接影响转储文件的大小,如果想自己写一个手动收集 dump 的工具,了解下这个参数会很有用。稍后介绍。

    • ExceptionParam:指向异常信息结构 MINIDUMP_EXCEPTION_INFORMATION 的指针。如果本参数为 NULL,则转储文件中不会包含异常信息。

      typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
        DWORD               ThreadId;
        PEXCEPTION_POINTERS ExceptionPointers;
        BOOL                ClientPointers;
      } MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
    • UserStreamParam:指向用户自定义信息结构 MINIDUMP_USER_STREAM_INFORMATION 的指针。如果本参数为 NULL,则转储文件中不会包含用户定义的信息。

      typedef struct _MINIDUMP_USER_STREAM_INFORMATION {
        ULONG                 UserStreamCount;
        PMINIDUMP_USER_STREAM UserStreamArray;
      } MINIDUMP_USER_STREAM_INFORMATION, *PMINIDUMP_USER_STREAM_INFORMATION;
    • CallbackParam:指向回调例程 MINIDUMP_CALLBACK_INFORMATION 的指针。如果此参数为NULL,转储过程中不会执行任何回调例程。

      typedef struct _MINIDUMP_CALLBACK_INFORMATION {
        MINIDUMP_CALLBACK_ROUTINE CallbackRoutine;
        PVOID                     CallbackParam;
      } MINIDUMP_CALLBACK_INFORMATION, *PMINIDUMP_CALLBACK_INFORMATION;

      本结构中的CallbackParam 是传递给回调函数的参数,用户可以指定一些自己需要传递的参数,很常见的做法。 CallbackRoutine 是回调函数,类型为 MINIDUMP_CALLBACK_ROUTINE 。原型如下:

      typedef
      BOOL
      (WINAPI * MINIDUMP_CALLBACK_ROUTINE) (
        _Inout_ PVOID CallbackParam,
        _In_    PMINIDUMP_CALLBACK_INPUT CallbackInput,
        _Inout_ PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
        );

    因为 DumpType会影响最后生成的转储文件的大小,这里介绍下这个参数。

    DumpType 参数

    DumpType 类型为 MINIDUMP_TYPE。以下定义摘自 10.0.18362.0 版本的 minidumpapiset.h,应该是比较全的了。如果你问我:你怎么知道 MINIDUMP_TYPE 定义在这个文件里?答案很简单:用 File Locator 搜的呗。

    搜索结果
    搜索结果

    typedef enum _MINIDUMP_TYPE {
        MiniDumpNormal                         = 0x00000000,
        MiniDumpWithDataSegs                   = 0x00000001,
        MiniDumpWithFullMemory                 = 0x00000002,
        MiniDumpWithHandleData                 = 0x00000004,
        MiniDumpFilterMemory                   = 0x00000008,
        MiniDumpScanMemory                     = 0x00000010,
        MiniDumpWithUnloadedModules            = 0x00000020,
        MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
        MiniDumpFilterModulePaths              = 0x00000080,
        MiniDumpWithProcessThreadData          = 0x00000100,
        MiniDumpWithPrivateReadWriteMemory     = 0x00000200,
        MiniDumpWithoutOptionalData            = 0x00000400,
        MiniDumpWithFullMemoryInfo             = 0x00000800,
        MiniDumpWithThreadInfo                 = 0x00001000,
        MiniDumpWithCodeSegs                   = 0x00002000,
        MiniDumpWithoutAuxiliaryState          = 0x00004000,
        MiniDumpWithFullAuxiliaryState         = 0x00008000,
        MiniDumpWithPrivateWriteCopyMemory     = 0x00010000,
        MiniDumpIgnoreInaccessibleMemory       = 0x00020000,
        MiniDumpWithTokenInformation           = 0x00040000,
        MiniDumpWithModuleHeaders              = 0x00080000,
        MiniDumpFilterTriage                   = 0x00100000,
        MiniDumpWithAvxXStateContext           = 0x00200000,
        MiniDumpWithIptTrace                   = 0x00400000,
        MiniDumpScanInaccessiblePartialPages   = 0x00800000,
        MiniDumpValidTypeFlags                 = 0x00ffffff,
    } MINIDUMP_TYPE;

    下面是每个选项的意义,主要翻译自官方帮助文档。

    名称 描述
    MiniDumpNormal 只包含调用栈相关信息
    MiniDumpWithDataSegs 包含已加载的模块的数据段信息,比如全局变量
    MiniDumpWithFullMemory 包含全部可访问的内存
    MiniDumpWithHandleData 包含句柄信息
    MiniDumpFilterMemory 过滤一些敏感信息,保护重建调用栈需要的信息
    MiniDumpScanMemory 扫描,以包含引用内存
    MiniDumpWithUnloadedModules 包含最近被卸载的模块信息
    MiniDumpWithIndirectlyReferencedMemory 包含未直接引用的内存
    MiniDumpFilterModulePaths 过滤某块的路径信息
    MiniDumpWithProcessThreadData 包含完整的进程和线程信息
    MiniDumpWithPrivateReadWriteMemory 包含页面属性为 PAGE_READWRITE 的页面
    MiniDumpWithoutOptionalData 不包含可选数据
    MiniDumpWithFullMemoryInfo 包含内存区信息
    MiniDumpWithThreadInfo 包含线程状态信息
    MiniDumpWithCodeSegs 包含所有代码和有关的内存段
    MiniDumpWithoutAuxiliaryState 关闭辅助内存收集
    MiniDumpWithFullAuxiliaryState 使用所有的内存收集器
    MiniDumpWithPrivateWriteCopyMemory 包含页面属性为 PAGE_WRITECOPY 的页面
    MiniDumpIgnoreInaccessibleMemory 忽略不可访问的页面
    MiniDumpWithTokenInformation 包含安全令牌相关信息。可以在调试的时候使用 "!token" 命令
    MiniDumpWithModuleHeaders 包含模块头相关信息
    MiniDumpFilterTriage 添加与筛选器分类相关的数据
    MiniDumpWithAvxXStateContext
    MiniDumpWithIptTrace
    MiniDumpValidTypeFlags 设置所有标志位

    {%note info%}
    说明:

    MINIDUMP_TYPE 的值是不断发展变化的(向后兼容),旧版本的 DbgHelp.dll 可能不支持某些值,具体可以参考 微软官方介绍 MINIDUMP_TYPE 的文档

    {%endnote%}

    关于MINIDUMP_TYPE 每一项的作用更为详细的介绍请参考 Effective minidumps (Part 1)Effective minidumps (Part 2)。真的是超级详细,强烈建议大家点开看一看!唯一的遗憾是作者写的比较早,很多新出现的标志没总结进来,但仍然是非常好的参考资料!

    MiniDumpWriteDump() 可以用来生成转储文件,我们应该怎么使用呢?

    使用场景

    通常,我们希望在自己的程序发生异常的时候,能自动保存一份转储文件,供我们事后分析。我们需要做的大概是:捕获各种异常,在异常处理函数中判断发生的异常是否能恢复,如果不能恢复就保存转储文件和其它一些关键文件(比如,程序的配置文件,出现问题时的屏幕截图等),并一起打包并保存,然后提示用户,让用户上传我们打包好的文件供我们分析。当程序发生异常的时候,我们很难确定发生异常的进程的运行状态。所以我们需要启动一个新的进程,并在新进程中调用 MiniDumpWriteDump() 来保存异常进程的信息。

    以上的操作看起来比较简单,但是处理起来,有很多细节需要考虑。比如,我们需要捕获哪些异常?怎么捕获这些异常?保存好的文件怎么上传?通过什么形式上传?等等等等…… 如果有可以直接拿来使用的框架,就太好了!别说,还真有!

    相关开源库

    以上的这些功能,早已经有开源软件可以用了。作为一个谦卑的程序员,尽量复用现有的轮子吧。给大家推荐一些不错的开源库。

    1. CrashRpt (之前在项目里用过,如果你想自己手动实现一个类似的,可以参考此项目的代码)。

    CrashRpt 官方介绍
    CrashRpt 官方介绍

    1. 除了 CrashRptchromium 里使用的 crashpad 也是一个非常好的选择。google 出品,质量有保障。

      crashpad 的前身是 breakpad(根据 google 官方介绍,breakpad 使用的是进程内报告机制,不再建议大家使用)。

    2. 适用于.NET程序的 CrashReporter

    3. 其它平台(AndroidiOS等)也有类似的开源库。可以在 github 上搜索 crash report。我就不截图了。

    {%note info%}

    说明: 本文介绍的方法只适用于我们自己的程序。如果我们想转储其它进程,我们需要借助现有工具(当然,如果有时间和精力,也可以自己写一个)。关于抓取转储的工具的介绍,可以参考之前的文章—— 你需要知道的 N 种抓取 dump 的工具

    {%endnote%}

    总结

    • 我们可以通过 MiniDumpWriteDump() 来保存转储文件。
    • 我们可以借助 CrashRpt, CrashPad 等开源框架来为我们的程序添加崩溃转储功能。

    参考资料

  • 相关阅读:
    hosts 文件妙用
    asp.net 各种路径
    正则表达式
    int.Parse()、int.TryParse()和Convert.ToInt32()的区别
    总结.NET 中什么时候用 Static
    利用.net的内部机制在asp.net中实现身份验证
    server.transfer 用法
    sql server Datetime格式转换
    如果在代码中使用JS
    js 添加广告
  • 原文地址:https://www.cnblogs.com/bianchengnan/p/12287936.html
Copyright © 2011-2022 走看看