English 中文(简体)
在C++中发现异常后,找出异常的来源?
原标题:
  • 时间:2008-08-30 16:16:32
  •  标签:

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

当调试一个大型C++应用程序时,不幸的是,它有非常广泛的C++异常使用。有时我发现异常的时间比我实际想要的要晚一点。

伪代码示例:

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

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

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

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

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

有没有一种更简单的方法可以减少工作量?不需要更改我的大型代码库?

用其他语言有更好的解决方案吗?

最佳回答

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

#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!");
问题回答

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

约翰·罗宾斯写了一本很棒的书,解决了许多调试难题。这本书被称为为Microsoft.NET和Microsoft Windows调试应用程序。尽管书名如此,这本书还是包含了大量关于调试本机C++应用程序的信息。

在这本书中,有一个很长的部分,全部是关于如何获得抛出的异常的调用堆栈。如果我没记错的话,他的一些建议包括使用结构化异常处理(SEH),而不是(或除此之外)C++异常。我真的无法对这本书给予足够高的推荐。

在异常对象构造函数中放置断点。您将在抛出异常之前获得断点。

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

你建议在构造函数中包含堆栈跟踪是最好的选择。是的,在构建过程中会花费时间,但你可能不应该经常抛出异常,这是一个值得关注的问题。让所有的异常从一个新的基础继承也可能超出您的需要。您可以简单地让相关的异常继承(谢谢,多重继承),并为这些异常单独捕获。

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

以下是我如何使用GCC库在C++中实现这一点:

#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;
}

异常的构造函数可以简单地调用这个函数并存储堆栈跟踪。它采用param<code>numskip</code>,因为我喜欢从堆栈跟踪中剥离异常的构造函数。

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

此外,调用堆栈通常必须在抛出的异常时进行记录;一旦被捕获,堆栈就会展开,因此您不再知道被抛出时发生了什么。

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

在本机代码中,您可以通过安装矢量异常处理程序。VC++在SEH异常之上实现C++异常,并且在任何基于帧的处理程序之前都会首先启动矢量异常处理器。但是,要非常小心,矢量异常处理引起的问题可能很难诊断。

此外Mike Stall有一些关于在具有托管代码的应用程序中使用它的警告。最后,请阅读Matt Pietrek的文章,并确保在尝试之前了解SEH和向量异常处理

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

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

如果您正在从IDE进行调试,请转到调试->;异常,单击抛出C++异常。

其他语言?好吧,在Java中,您可以调用e.printStackTrace();没有比这更简单的了。

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

Artem写道:

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

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

In fact, there are many dump types, maybe you could choose one small enough but still having more info http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx

不过,这些类型对调用堆栈没有帮助,它们只会影响您能够看到的变量数量。

我注意到我们使用的dbghelp.dll 5.1版本不支持其中一些转储类型。不过,我们可以将其更新到最新的6.9版本,我刚刚检查了MS调试工具的EULA——最新的dbghelp.dll仍然可以重新分发。

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

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

此外,我还有第二种异常格式:

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

然后使用FormatMessage将其从DWORD值转换为实际消息。使用where/what格式将显示发生了什么以及在什么函数中。

By now, it has been 11 years since this question was asked and today, we can solve this problem using only standard C++11, i.e. cross-platform and without the need for a debugger or cumbersome logging. You can trace the call stack that led to an exception

Use std::nested_exception and std::throw_with_nested

This won t give you a stack unwind, but in my opinion the next best thing. It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

It will, however, require that you insert try/catch statements at the functions you wish to trace (i.e. functions without this will not appear in your trace). You could automate this with macros, reducing the amount of code you have to write/change.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

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"




相关问题