【问题标题】:Qt C++ Unhandled exceptions stack traceQt C++ 未处理的异常堆栈跟踪
【发布时间】:2017-05-08 14:38:57
【问题描述】:

我正在尝试找出一种方法来在发生崩溃时获取已部署的 c++ 应用程序的堆栈跟踪。我尝试了几种方法,我认为我的问题与发生异常后的堆栈有关。

我在 Qt 中创建了一个测试应用程序。这是代码。

void miniDumpFunc()
{
    MiniDump dump;
    dump.Create(L"C:\\dmp\\dmp.dmp");
}

void anotherFunc()
{
    miniDumpFunc();
}

LONG WINAPI OurCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
{
    miniDumpFunc();

    return EXCEPTION_EXECUTE_HANDLER;
}

void badFunc()
{
    int *myNull = NULL;
    *myNull = 42;
}

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    ::SetUnhandledExceptionFilter(OurCrashHandler);

    anotherFunc();

    return app.exec();
}

如果我调用 anotherFunc() 然后查看 WinDbg 中的堆栈跟踪,我会得到以下堆栈。

我认为这里有一些内联,但看起来是正确的。

如果我调用 badFunc() 但是这就是我得到的。

堆栈跟踪从 UnhandledExceptionFilter 开始。堆栈似乎被异常弄乱了。

这是我获得生成迷你转储的代码的地方。 http://blog.aaronballman.com/2011/05/generating-a-minidump/

【问题讨论】:

  • 如果您不将EXCEPTION_POINTERS 传递给MiniDumpWriteDump,则无法从引发 SEH 异常的代码开始编写转储。传递这些(使用nullptr,以防您想在不处理异常的情况下编写转储)。关于您正在使用的实现的警告:这在某些时候死锁。您绝对肯定想在自己的进程中调用MiniDumpWriteDump
  • 您链接到的文章(和实现)是如此破碎,无法再生,我强烈建议您编写自己的文章(来自可靠的信息,如DebugInfo.com)。就此而言,作者完全不知道他在做什么,或者他正在使用的 API 做了什么(例如,MiniDumpWriteDump 已经暂停了所有线程)。保持清醒。

标签: c++ qt winapi exception-handling dump


【解决方案1】:

您为badFunc 发布的堆栈跟踪非常好,并且考虑到您正在使用的实现,这是预期的结果。 MiniDumpWriteDump 使用当前指令指针运行堆栈跟踪,除非您通过 MINIDUMP_EXCEPTION_INFORMATION 结构传递 SEH 异常的 EXCEPTION_POINTERS。由于未处理的异常过滤器安装在异常帧的底部,因此从那里调用MiniDumpWriteDump 会产生您观察到的堆栈跟踪。

要获得更有用的堆栈跟踪,您需要将EXCEPTION_POINTERS 传递给MiniDumpWriteDump

这只是implementation you are using 的众多问题之一。还有更多:

  • 作者似乎很清楚,他们可以为ExceptionParam 参数传递nullptr,但随后决定始终传递nullptr。该接口实际上应该提供两种重载:一种采用EXCEPTION_POINTERS 参数,另一种没有。这允许从任何地方调用该功能,但当从未处理的异常过滤器(后者是迄今为止最常见的用例)调用时,还保留堆栈跟踪。
  • 作者提出了一个“有趣的问题:应用程序中的其他线程应该怎么做?” 就目前而言,MiniDumpWriteDump 已经暂停了 所有 线程在前进之前的过程。你甚至没有选择那里。挂起线程的整个实现(包括排除辅助线程的过滤器)是多余的。该走了。
  • 作者未能解决一个实际问题(原版MiniDumpWriteDump 以及他们自己的线程挂起实现):线程在任意点被挂起。如果这些线程中的任何一个持有任何锁(如全局堆分配互斥锁),那么您将立即陷入死锁。毕竟,MiniDumpWriteDump 也希望从进程堆中分配内存。这里的解决方案涉及更多:对MiniDumpWriteDump 的调用必须在其自己的进程中执行,并具有适当的 IPC。

以上任何一个都是相当严重的错误,需要解决。作为published,代码既无用又危险。如果您想同时实施自己的解决方案,请查看以下资源以获取可靠信息:

【讨论】:

  • 你能发布一个官方来源。虽然其中大部分可能是有道理的,但 MSFT 自己的文档有些不清楚。例如,“MiniDumpWriteDump()”的“备注”部分声明应该从单独的进程中调用它,但强调它对于崩溃的应用程序更重要。然后他们似乎默认通过声明“......您可以从新的工作线程调用该函数并从转储中过滤该工作线程”来默认从尚未崩溃的应用程序调用它。他们不能同时拥有它。从“未崩溃”的应用程序调用是否安全......
  • ... 但是,如果它可能对“未崩溃”的应用程序不安全,开发人员怎么能真正知道。他们不能,因为这样的假设是非常脆弱的。因此,从一个单独的应用程序调用它似乎是唯一的方法(无论目标应用程序是否崩溃),但同样,MSFT 自己的文档混淆了这种情况(他们自己关于过滤工作线程的引用呈现了你关于暂停线程的声明“多余的”,不完全是故事的全部,我恭敬地说——如果一个单独的过程实际上是唯一可靠的事情,那么这一点甚至无关紧要)
  • @Larry:我没有更多官方信息可以提供,希望 MSDN 对这个主题更清楚。我必须提供的是个人经验。我确实调试了由故障程序调用MiniDumpWriteDump 引起的死锁。这是由持有进程堆锁的挂起线程之一引起的,当时MiniDumpWriteDump 本身试图分配内存。这可能随时发生,因此在程序中调用MiniDumpWriteDump永远不会安全。
  • @Larry:不过,考虑一下,可能会出现这样一种情况,从故障应用程序调用MiniDumpWriteDump 确实是安全的:如果应用程序是静态链接的反对 CRT。在此设置中,应用程序使用与系统调用不同的堆(无论如何我相信)。这可能就是为什么 MSDN 在该主题上如此不透明的原因。即使那样,我也不建议这样做。还有另一个死锁,我没有分析到确定的程度,所以失败的可能性更大。如果您想安全起见,我强烈建议您将通话解耦。
  • @Inspectable:好的,感谢您提供的信息。我只是想确保它实际上是“MiniDumpWriteDump()”挂起线程而不是你。您提供的信息对我的调查非常有价值。不要担心概念验证,我可以从这里拿东西(不过谢谢)。顺便说一句,如果您(或其他人)不知道它(我上次查看时 MSFT 没有广泛发布它),您可以直接在 Visual Studio 本身中打开一个小型转储。它可以让您在崩溃线上有效地中断,就好像崩溃发生在调试会话期间一样(具有完整的堆栈信息等)。如果您需要详细信息,请告诉我。
猜你喜欢
  • 2023-03-14
  • 1970-01-01
  • 2017-08-06
  • 1970-01-01
  • 2013-09-25
  • 2012-09-21
  • 2015-06-23
  • 2011-01-05
  • 2013-07-31
相关资源
最近更新 更多