向用户显示异常调试信息

时间:2009-12-26 21:55:51

标签: c++ debugging boost exception debug-information

我目前正致力于为我的OSS应用程序添加异常和异常处理。例外从一开始就是一般的想法,但我想找到一个很好的异常框架,并且在开始使用它们之前,要完全理解C ++异常处理约定和习惯用法。我在使用C#/ .Net,Python和其他使用异常的语言方面有很多经验。我对这个想法并不陌生(但远非主人)。

在C#和Python中,当发生未处理的异常时,用户会得到一个很好的堆栈跟踪,并且通常会有很多非常有用的无价的调试信息。如果您正在使用OSS应用程序,让用户将该信息粘贴到问题报告中......那么我只是说我发现没有它就很难生存。对于这个C ++项目,我得到“应用程序崩溃”,或者来自更明智的用户,“我做了X,Y和Z,然后它崩溃了”。但我也想要调试信息!

我已经(并且非常困难)让我感到安心的是,我永远不会看到跨平台和交叉编译的方式获得C ++异常堆栈跟踪,但我知道我可以获得该功能名称和其他相关信息。

现在我想要那些未经处理的例外情况。我正在使用boost::exception,他们有一个非常好的diagnostic_information thingamajig可以打印出(未编码的)函数名,文件,行,最重要的是,程序员添加到该异常的其他异常特定信息

当然,我会尽可能地处理代码中的异常,但我认为我不会让一对夫妇溜走(当然是无意的)。

所以我想要做的是将我的主入口点包含在try块中,其中catch创建一个特殊的对话框,通知用户应用程序中发生了错误,包含更多内容用户单击“更多”或“调试信息”或其他内容时显示的详细信息。这将包含来自diagnostic_information的字符串。然后,我可以指示用户将此信息粘贴到问题报告中。

但是一种唠叨的直觉感觉告诉我,将一切都包装在试块中是一个非常糟糕的主意。我将要做什么愚蠢的事情?如果是(并且即使它不是),那么实现我想要的更好的方法是什么?

4 个答案:

答案 0 :(得分:33)

在main()中放置一个try / catch块是可以的,它不会导致任何问题。无论如何,该程序已经死于未处理的异常。在你寻求获得最重要的堆栈跟踪时,它根本没有帮助。当catch块捕获异常时,该信息是奇闻趣事。

捕获C ++异常也不是很有帮助。程序死于从std :: exception派生的异常的几率非常小。虽然它可能发生。在C / C ++应用程序中更有可能是由于硬件异常导致死亡,AccessViolation是数字uno。捕获它们需要main()方法中的__try和__except关键字。同样,很少有上下文可用,你基本上只有一个异常代码。 AV还会告诉您哪个确切的内存位置导致异常。

这不仅仅是一个跨平台的问题btw,你无法在任何平台上获得良好的堆栈跟踪。没有可靠的方法来进行堆叠,有太多的优化(如framepointer遗漏)使这成为一个危险的旅程。这是C / C ++的方式:尽可能快地使它,不知道它爆炸时发生了什么。

您需要做的是以C / C ++方式调试这些问题。您需要创建一个minidump。它大致类似于旧的“核心转储”,即异常发生时过程映像的快照。那时候,你实际上已经完全转储了核心。已经取得了进展,现在它是“迷你”,有些必要,因为完整的核心转储将需要接近2千兆字节。它实际上很适合诊断程序状态。

在Windows上,通过调用SetUnhandledExceptionFilter()开始,您提供一个回调函数指针,该函数将在程序在未处理的异常上死亡时运行。任何异常,C ++以及SEH。您的下一个资源是dbghelp.dll,可在Windows调试工具下载中找到。它有一个名为MiniDumpWriteDump()的入口点,它创建一个minidump。

一旦你获得了由MiniDumpWriteDump()创建的文件,你就会非常高兴。您可以在Visual Studio中加载.dmp文件,几乎就像它是一个项目。按F5和VS研磨掉一段时间,试图为进程中加载​​的DLL加载.pdb文件。您需要设置符号服务器,非常对于获得良好的堆栈跟踪非常重要。如果一切正常,您将在抛出异常的确切位置获得“调试中断”。使用堆栈跟踪。

为使这项工作顺利进行您需要做的事情:

  • 使用构建服务器创建二进制文件。它需要将调试符号(.pdb文件)推送到符号服务器,以便在调试minidump时随时可用。
  • 配置调试器,以便找到所有模块的调试符号。您可以从Microsoft获取Windows的调试符号,代码的符号需要来自上面提到的符号服务器。
  • 编写代码以捕获未处理的异常并创建minidump。我提到了SetUnhandledExceptionFilter(),但是创建minidump的代码不应该在崩溃的程序中。它能成功编写minidump的可能性相当小,程序的状态尚未确定。最好的办法是运行一个“守护”过程,密切关注一个命名的互斥锁。您的异常过滤器可以设置互斥锁,后卫可以创建小型转储。
  • 为minidump创建一种从客户端计算机转移到您的计算机的方法。我们使用亚马逊的S3服务,以合理的价格使用太字节。
  • 将minidump处理程序连接到调试数据库。我们使用Jira,它有一个Web服务,允许我们使用相同的“签名”来验证崩溃桶与早期崩溃的数据库。当它是唯一的或没有足够的命中时,我们要求崩溃管理器代码将minidump上传到Amazon并创建bug数据库条目。

嗯,这就是我为我工作的公司所做的。工作得非常好,它将碰撞斗频率从数千减少到数十。给开源ffdshow组件创建者的个人信息:我热爱你。但是你不再崩溃了我们的应用程序!坏蛋。

答案 1 :(得分:3)

将所有代码包装在一个try/catch块中是可以的。例如,它不会减慢其中任何内容的执行速度。事实上,我的所有程序都有(代码类似)这个框架:

int execute(int pArgc, char *pArgv[])
{
    // do stuff
}

int main(int pArgc, char *pArgv[])
{
    // maybe setup some debug stuff,
    // like splitting cerr to log.txt

    try
    {
        return execute(pArgc, pArgv);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Unhandled exception:\n" << e.what() << std::endl;
        // or other methods of displaying an error

        return EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Unknown exception!" << std::endl;

        return EXIT_FAILURE;
    }
}

答案 2 :(得分:1)

不,这不是愚蠢的。这是一个非常好的主意,当然在你遇到未处理的异常之前它几乎没有任何成本。

请注意,操作系统已经提供了一个包装线程的异常处理程序(我认为是C-runtime的另一个)。您可能需要将某些异常传递给这些处理程序以获得正确的行为。在某些体系结构中,访问错误对齐的数据由异常处理程序处理。所以你可能想要特殊情况EXCEPTION_DATATYPE_MISALIGNMENT并让它传递给更高级别的异常处理程序。

我包含寄存器,应用版本和内部版本号,异常类型和十六进制的堆栈转储,带有模块名称和十六进制值的偏移量,可以是代码的地址。请务必包含exe的版本号和内部版本号/日期。

您还可以使用VirtualQuery将堆栈值轻松转换为“ModuleName + Offset”。而且,结合.MAP文件通常会告诉您确切的崩溃位置。

我发现我可以培训beta测试人员很容易地发送我的文本,但在早期我得到的是错误对话框而不是文本的图片。我认为这是因为很多用户不知道你可以右键单击任何编辑控件来获得一个菜单,其中包含“全选”和“复制”。如果我要再做一次,我会添加一个按钮 将该文本复制到剪贴板,以便可以轻松地将其粘贴到电子邮件中。

如果您想要设置“发送错误报告”按钮的麻烦,那就更好了,但只是让用户有办法将文本放入自己的电子邮件中,这样就可以获得大部分内容,并且不会提高任何关于“我与他们分享什么信息?”的红旗?

答案 3 :(得分:0)

实际上,boost :: diagnostic_information专门设计用于“全局”catch(...)块,以显示有关不应达到的异常的信息。但请注意,boost :: diagnostic_information返回的字符串不是用户友好的。