捕获后在C ++中查找异常的来源?

时间:2008-08-30 16:16:32

标签: c++ visual-studio winapi exception visual-c++

我在MS VC ++中寻找答案。

在调试大型C ++应用程序时,遗憾的是它具有非常广泛的C ++异常用法。有时我会比实际需要的时间稍晚一点地抓住异常。

伪代码中的示例:

FunctionB()
{
    ...
    throw e;
    ...
}

FunctionA()
{
    ...
    FunctionB()
    ...
}

try
{
    Function A()
}
catch(e)
{
    (<--- breakpoint)
    ...
}

我可以在调试时用断点捕获异常。但是我无法追溯FunctionA()FunctionB()或其他函数中是否发生异常。 (假设广泛的异常使用和上述示例的巨大版本)。

我的问题的一个解决方案是确定并保存调用堆栈在异常构造函数中(即在捕获之前)。但这需要我从这个基类异常类派生所有异常。它还需要大量代码,并且可能会减慢我的程序。

是否有更简单的方法需要更少的工作?无需更改我的大型代码库?

其他语言是否有更好的解决方案?

14 个答案:

答案 0 :(得分:12)

您在代码中指向了断点。由于您在调试器中,您可以在异常类的构造函数上设置断点,或者设置Visual Studio调试器以中断所有抛出的异常(Debug-&gt; Exceptions单击C ++异常,选择抛出和未捕获的选项)

答案 1 :(得分:11)

如果您只对异常的来源感兴趣,您可以编写一个简单的宏,如

#define throwException(message) \
    {                           \
        std::ostringstream oss; \
        oss << __FILE __ << " " << __LINE__ << " "  \
           << __FUNC__ << " " << message; \
        throw std::exception(oss.str().c_str()); \
    }

将文件名,行号和函数名添加到异常文本中(如果编译器提供相应的宏)。

然后使用

抛出异常
throwException("An unknown enum value has been passed!");

答案 2 :(得分:6)

John Robbins撰写了一本很好的书,解决了许多困难的调试问题。这本书叫做Debugging Applications for Microsoft .NET and Microsoft Windows。尽管有这个标题,但本书包含了大量有关调试本机C ++应用程序的信息。

在本书中,有一个冗长的部分,关于如何获取抛出异常的调用堆栈。如果我没记错的话,他的一些建议涉及使用结构化异常处理(SEH)而不是(或除了)C ++异常。我真的不能高度推荐这本书。

答案 3 :(得分:4)

被捕获之后无法找到异常的来源,除非您在抛出它时包含该信息。当您捕获异常时,堆栈已经解开,并且无法重建堆栈的先前状态。

您建议在构造函数中包含堆栈跟踪是您最好的选择。是的,它需要在施工期间花费时间,但是你可能不应该经常抛出异常,这是一个问题。使所有异常从新基础继承也可能比您需要的更多。你可以简单地让相关的异常继承(谢谢你,多重继承),并为那些有一个单独的catch。

您可以使用StackTrace64函数来构建跟踪(我相信还有其他方法)。查看this article代码示例。

答案 4 :(得分:4)

在异常对象构造函数中放置一个断点。你会在抛出异常之前得到你的断点。

答案 5 :(得分:2)

以下是我在C ++中使用GCC库的方法:

#include <execinfo.h> // Backtrace
#include <cxxabi.h> // Demangling

vector<Str> backtrace(size_t numskip) {
    vector<Str> result;
    std::vector<void*> bt(100);
    bt.resize(backtrace(&(*bt.begin()), bt.size()));
    char **btsyms = backtrace_symbols(&(*bt.begin()), bt.size());
    if (btsyms) {
        for (size_t i = numskip; i < bt.size(); i++) {
            Aiss in(btsyms[i]);
            int idx = 0; Astr nt, addr, mangled;
            in >> idx >> nt >> addr >> mangled;
            if (mangled == "start") break;
            int status = 0;
            char *demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);

            Str frame = (status==0) ? Str(demangled, demangled+strlen(demangled)) : 
                                      Str(mangled.begin(), mangled.end());
            result.push_back(frame);

            free(demangled);
        }
        free(btsyms);
    }
    return result;
}

您的异常构造函数可以简单地调用此函数并存储堆栈跟踪。它需要参数numskip,因为我喜欢从堆栈跟踪中切掉异常的构造函数。

答案 6 :(得分:1)

没有标准的方法可以做到这一点。

此外,通常必须在抛出异常时记录调用堆栈;一旦它被抓住,那么堆栈已经展开,所以你不再知道被扔的时候发生了什么。

在Win32 / Win64上的VC ++中,通过记录编译器内部_ReturnAddress()的值并确保您的异常类构造函数是__declspec(noinline),可能可以获得足够的结果。与调试符号库一起使用时,我认为您可以使用SymGetLineFromAddr64获取与返回地址对应的函数名称(和行号,如果您的.pdb包含它)。

答案 7 :(得分:1)

在本机代码中,您可以通过安装Vectored Exception handler来获取调用callstack的机会。 VC ++在SEH异常之上实现C ++异常,并且在任何基于帧的处理程序之前首先给出向量异常处理程序。但是要非常小心,矢量异常处理引入的问题可能难以诊断。

还有Mike Stall has some warnings关于在具有托管代码的应用中使用它。最后,阅读Matt Pietrek's article并确保在尝试此操作之前了解SEH和向量异常处理。 (没有什么比追踪一个关键问题更难以解决你添加帮助来追踪关键问题的代码了。)

答案 8 :(得分:1)

如果您正在从IDE进行调试,请转到Debug-&gt; Exceptions,单击Thrown for C ++ exception。

答案 9 :(得分:1)

我相信MSDev允许你在抛出异常时设置断点。

或者将断点放在异常对象的构造函数上。

答案 10 :(得分:0)

其他语言?好吧,在Java中你调用e.printStackTrace();它没有那么简单。

答案 11 :(得分:0)

如果有人有兴趣,一位同事通过电子邮件回复了这个问题:

Artem写道:

MiniDumpWriteDump()有一个标志,它可以做更好的崩溃转储,允许看到完整的程序状态,包含所有全局变量等。至于调用堆栈,我怀疑它们可以因为优化而更好......除非你关闭(可能是一些)优化。

另外,我认为禁用内联函数和整个程序优化会有很大帮助。

事实上,有很多转储类型,也许你可以选择一个足够小但仍然有更多信息 http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

但这些类型对调用堆栈没有帮助,它们只影响你能看到的变量数量。

我注意到我们使用的dbghelp.dll版本5.1中不支持某些转储类型。我们可以将它更新到最新的6.9版本,但我刚刚检查了EULA for MS Debugging Tools - 最新的dbghelp.dll仍可以重新分发。

答案 12 :(得分:0)

我使用自己的例外。你可以很简单地处理它们 - 它们也包含文本。我使用格式:

throw Exception( "comms::serial::serial( )", "Something failed!" );

我还有第二种例外格式:

throw Exception( "comms::serial::serial( )", ::GetLastError( ) );

然后使用FormatMessage将其从DWORD值转换为实际消息。使用where / what格式将显示发生的情况和功能。

答案 13 :(得分:0)

到目前为止,距提出此问题已有11年了,今天,我们可以仅使用标准C ++ 11 (即跨平台且无需调试器)解决此问题。或繁琐的日志记录。 您可以跟踪导致异常的调用堆栈

使用std::nested_exceptionstd::throw_with_nested

这不会给您带来很多好处,但我认为下一件好事。 在StackOverflow herehere中对此进行了描述,如何通过简单地编写适当的代码来在代码内获取异常的回溯,而无需调试器或繁琐的日志记录异常处理程序,它将抛出嵌套异常。

但是,这将要求您在要跟踪的函数中插入try / catch语句(即,没有此功能的函数将不会出现在跟踪中)。 您可以使用宏将其自动化,从而减少您必须编写/更改的代码量。

由于您可以使用任何派生的异常类执行此操作,因此可以向此类回溯中添加很多信息! 您也可以看看我的MWE on GitHub,回溯看起来像这样:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"