【问题标题】:What is a good way to create a string for crash reporting Win32 C++ that reflects the cause of the crash?什么是创建用于反映崩溃原因的崩溃报告 Win32 C++ 的字符串的好方法?
【发布时间】:2011-01-06 19:10:34
【问题描述】:

我们正在使用 Fogbugz 来跟踪问题,我正在编写一个围绕 XML API for Fogbugz 的 C++ 包装器。

最佳做法似乎是使用“scout”字段,以便仅计算类似/相同的崩溃但不会再次报告。为此,我们需要一个用于特定崩溃原因的唯一字符串。

在 Win32 中 - 在获取 dmp 文件或其他崩溃处理程序之后,为崩溃创建唯一字符串的好方法是什么? (我们将创建一个 dmp 文件并将其发送到 fogbugz 服务器)

在之前的帖子/文章/等中,Joel 提出了各种建议,但其中大部分都依赖于像 C# 这样使用反射的语言,并且有很多难以获得或无法获得的信息。

有没有其他人得到了诸如堆栈跟踪或其他东西来在fogbugz中制作侦察条目?

编辑 澄清一下 - 我们不希望每个事件都有一个唯一的 ID - 可能存在具有相同代码路径的崩溃。我们想要捕捉到这一点。我在想我们会得到我们代码中的最后几个堆栈调用(不是来自 win32 DLL 的堆栈调用) - 但不知道如何去做。

报告每一次崩溃都是唯一的是不对的。报告同一案例下的所有崩溃是不对的。重复导致崩溃的场景的不同用户应映射到同一事件。

编辑

我认为我们想要的是崩溃的一般“签名” - 基于堆栈上的内容。类似的堆栈应该具有​​相同的签名。例如 - 采用我们应用程序中的前 5 个方法,然后我们将第一个调用(如果有)放入 MS DLL。这对于签名可能就足够了,并且可能会关联“相同”的崩溃。

那么如何获得堆栈中的方法列表呢?您如何判断它们是来自您自己的应用程序还是在另一个 DLL 中?

编辑 - 注意 我们想在异常处理程序中创建一个“bucket id”/签名,以便我们可以创建 minidump 并将其作为 scout 描述发送给fogbugz。或者,我们可以在应用程序下一次启动时加载转储,然后使用我们生成的签名发送它。

【问题讨论】:

    标签: c++ windows fogbugz crash-dumps crash-reports


    【解决方案1】:

    在我的项目中,我使用崩溃的地址内存作为“唯一”ID。

    【讨论】:

    • 这不能满足我们的需要 - 如果它们的原因相同,我们需要将一些崩溃映射到同一事件。
    • 例如 - 堆栈上的最后一项可能在 Windows DLL 中。并且地址内存取决于操作系统 - 它与我们应用程序中的任何内容无关。
    【解决方案2】:

    IMO 您可以使用的最好的东西将是来自转储分析的存储桶 id。使用正确配置的 Windows 调试工具 (windbg),可以执行 !analyze -v 并根据存储桶 ID 将转储分类到不同的存储桶中。 Bucket id 保证如果两个转储相同,则它们的桶 id 将相同。这解决了部分难题。

    很多时候,源于同一问题的两个转储会创建不同的存储桶 ID(可能是版本差异,比如您的 1.0 和 1.1 都在同一点崩溃)。您可以使用故障模块和堆栈签名来关联来自同一故障点的错误。

    有些事情会导致非常随机的转储(例如堆损坏,故障模块通常是受害者)。因此,转储分析应被视为尽力而为。当你不能时,你就不能。

    【讨论】:

    • 那么我如何获得一个bucket id?我想在异常处理程序中执行此操作。澄清一下 - 我希望在崩溃处理程序中这样做,以便我可以创建一个存储桶 id 并将其与 minidump 一起发送到我们的系统。
    • 最简单的方法是使用包含“!analyze -v”命令的脚本运行 ntsd/cdb 并 grep 其输出。然后,您将获得存储桶 ID 和故障线程的堆栈跟踪。您也可以通过编程方式执行此操作,请参阅 msdn.microsoft.com/en-us/library/ff540525%28v=VS.85%29.aspx 请注意,此处记录的 API 在 XP、Vista SP0、Vista SP1、Vista SP2 和 Windows 7 上的行为非常不同。是的,即使是服务包也会产生重大差异。
    【解决方案3】:

    我在我的上一个应用程序 (MSVC) 中使用了类似的东西来生成异常,因此每个错误都会记录在源文件和它发生的行中:

    class Error {
        //...
        public: Error(string file, string line, string error) ;
    };
    
    #define ERROR(err) Error(__FILE__, __LINE__, err)
    

    【讨论】:

    • 你会如何使用它?我在一个级别的异常/过滤器/崩溃处理程序中生成这些 - 在这种情况下,行和文件将一直相同,或者我错过了什么?
    • 嗯,是的,乍一看,我猜这可能不适用于您的场景。这只是我在代码中引发异常时使用的一种技术(即 throw ERROR("..");)。如果异常是在您的代码之外生成的,或者您无法更改它们在现有代码中的抛出方式,或者它们只是不会引发异常的崩溃,那么我想您需要其他东西。
    【解决方案4】:

    可能有点晚了,但我也会在这里添加我的解决方案,以防它可以帮助其他人。 您可以使用“Windows 调试工具”中的傻瓜来执行此操作,例如 windbg.exe 或更好的 kd.exe。 运行命令 "kd.exe -z "path_to_dump.dmp" -c "kd;q" >> dumpstack.txt,你可能会得到如下结果:

    Microsoft (R) Windows 调试器版本 10.0.15063.400 X86 版权所有 (c) 微软公司。保留所有权利。

    加载转储文件 [d:\work\bugs\14122\myexe.exe.2624.dmp] 具有完整内存的用户迷你转储文件:只有应用程序数据可用

    ************* Symbol Path validation summary **************
    Response                         Time (ms)     Location
    Deferred                                       srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
    Symbol search path is: srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
    Executable search path is: 
    Windows 10 Version 15063 MP (4 procs) Free x86 compatible
    Product: WinNt, suite: SingleUserTS
    15063.0.x86fre.rs2_release.170317-1834
    Machine Name:
    Debug session time: Fri Oct 13 00:09:01.000 2017 (UTC + 1:00)
    System Uptime: 0 days 0:18:33.797
    Process Uptime: 0 days 0:03:40.000
    ................................................................
    .....................................................
    Loading unloaded module list
    ..............................
    This dump file has an exception of interest stored in it.
    The stored exception information can be accessed via .ecxr.
    (a40.2580): Security check failure or stack buffer overrun - code c0000409 (first/second chance not available)
    eax=00000001 ebx=00000000 ecx=00000007 edx=77cc4350 esi=00000000 edi=00000000
    eip=62ae7666 esp=0b75e17c ebp=0b75e1a8 iopl=0         nv up ei pl nz na po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
    msvcr120!abort+0x28:
    62ae7666 cd29            int     29h
    0:068> kd: Reading initial command 'kb;q'
    ChildEBP RetAddr  Args to Child              
    0b75e178 62addc5f 935dda1f 00000000 00000000 msvcr120!abort+0x28
    0b75e1a8 0b75e7d4 62a9b436 0b75e1dc 62a52aa5 msvcr120!terminate+0x33
    WARNING: Frame IP not in any known module. Following frames may be wrong.
    0b75e1ac 62a9b436 0b75e1dc 62a52aa5 00000000 0xb75e7d4
    0b75e1b4 62a52aa5 00000000 62a59740 0b75e7d4 msvcr120!__FrameUnwindToState+0x89
    0b75e1c8 62a52b33 00000000 00000000 00000000 msvcr120!_EH4_CallFilterFunc+0x12
    0b75e1f4 62a5a0f3 62b1f7b8 62a4f7c6 0b75e324 msvcr120!_except_handler4_common+0x8e
    0b75e214 77cd6152 0b75e324 0b75e7c4 0b75e344 msvcr120!_except_handler4+0x1e
    0b75e238 77cd6124 0b75e324 0b75e7c4 0b75e344 ntdll!ExecuteHandler2+0x26
    0b75e30c 77cc4266 0b75e324 0b75e344 0b75e324 ntdll!ExecuteHandler+0x24
    0b75e30c 74cf28f2 0b75e324 0b75e344 0b75e324 ntdll!KiUserExceptionDispatcher+0x26
    0b75e684 62a59339 e06d7363 00000001 00000003 KERNELBASE!RaiseException+0x62
    0b75e6c4 6001821c 0b75e6e4 6004e1bc 946a8f2a msvcr120!_CxxThrowException+0x5b
    0b75e6f8 60018042 0b75e720 946a8efa ffffffff mymodule!FunctionC+0x7c
    0b75e730 60016544 946a8ece ffffffff 092889d8 mymodule!FunctionB+0x32
    0b75e754 600166b8 00842338 6000588d 00000001 myothermodule!FunctionB+0x44
    

    如果您仅从堆栈中获取方法并将它们连接到一个字符串中,则可以从此堆栈中创建一个唯一的存储桶:“mymodule!FunctionC+0x7c;mymodule!FunctionB+0x32;myothermodule!FunctionB +0x44”。为了使其工作,您需要使用环境变量 _NT_SYMBOL_PATH 或使用 -y 命令行开关来访问您的个人符号服务器。 可以可选地创建从返回地址唯一(第二列)的字符串:“ 62addc5f,0b75e7d4,62a9b436,62a52aa5,62a52b33,62a5a0f3,77cd6152,77cd6124,77cc4266,74cf28f2,62a59339,6001821c,60018042,60016544,600166b8 "

    【讨论】:

      【解决方案5】:

      只需使用从转储文件生成的 MD5 字符串,您就有可能在每次崩溃时获得唯一的字符串。

      【讨论】:

      • 我应该更清楚 - 我可以为所有事情找到一个独特的案例 - 但我想匹配类似的崩溃。整个转储可能不合适 - 仅来自我们自己的应用程序的前几个堆栈调用就足够了。
      【解决方案6】:

      我将首先收集有关代码中每个函数在崩溃报告堆栈跟踪中“闪现”的频率的数据。每个报告都必须添加到某种数据库中,并且每个函数都必须被索引,以便您以后可以查询,哪些函数似乎比其他函数更容易崩溃。 (当然,像 main() 这样的函数会出现在每个报告中,但这是可以理解的)。

      或者,您认为只有崩溃报告似乎是问题所在,您可以从崩溃堆栈跟踪中删除所有这些条目,然后对其余部分(您的函数)进行哈希处理。这样您就可以查看您自己函数的任何特定调用链是否会反复导致崩溃,无论中间调用了哪些外部函数。

      当然,一些更复杂的问题无论如何也不会被这样捕获,因为堆栈跟踪将完全不同。为了帮助实现这一点,您可以记录应用程序中的其他数据以及每个报告中的堆栈跟踪,例如缓冲区大小、计数器、应用程序不同部分的状态等等……然后对此进行一些统计。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-10
        • 1970-01-01
        • 1970-01-01
        • 2013-01-29
        • 1970-01-01
        • 2023-03-25
        • 2020-08-13
        • 2010-12-21
        相关资源
        最近更新 更多