在catch块中获取有关C++异常抛出位置的信息?

8
我有一个C++应用程序,它在许多代码块中包装了try块。当我捕获异常时,我可以将用户返回到稳定状态,这很好。但是我不再收到崩溃转储。我非常想知道异常在代码中的位置,以便我可以记录并修复它。
能够在不停止应用程序的情况下获取转储将是理想的,但我不确定是否可能。
有没有办法在catch块内找出抛出异常的位置?如果有用的话,我在Windows XP及更高版本上使用本机MSVC++。我的计划是将崩溃日志记录到各个用户机器上的文件中,然后一旦达到一定大小就上传崩溃日志。

2
可能是C++在异常时显示堆栈跟踪的重复问题。 - sth
5个回答

6

使用SEH(结构化异常处理)可以实现这一点。关键在于MSVC通过SEH实现了C++异常。另一方面,纯SEH更加强大和灵活。

你应该这样做。不要使用像这样的纯C++ try/catch块:

try
{
    DoSomething();
} catch(MyExc& exc)
{
    // process the exception
}

您需要使用SEH块包装内部代码块DoSomething

void DoSomething()
{
    __try {
        DoSomethingInner();
    }
    __except (DumpExc(GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) {
        // never get there
    }
}

void DumpEx(EXCEPTION_POINTERS* pExc)
{
    // Call MiniDumpWriteDump to produce the needed dump file
}

也就是说,在C++的try/catch块内部,我们放置了另一个原始的SEH块,它只会转储所有异常而不捕获它们。

请参见这里,了解使用MiniDumpWriteDump的示例。


1
如果您不关心可移植性,直接使用SEH是可以的。标准C++异常更具可移植性(嗯,除了一些不支持异常的嵌入式系统编译器)。 - George
2
这是处理C++异常的有效方法。如果您想获取有关操作系统异常(例如堆栈溢出或访问冲突)的有用信息,您应该安排从另一个进程调用MiniDumpWriteDump - Ben Voigt
@valdo:MiniDumpWriteDump 需要加载其他 DLL,因此如果在持有加载器锁时遇到异常,最好从外部进程调用它。此外,如果您遇到内存损坏(导致错误的常见原因),您也应该从外部进程调用 MiniDumpWriteDump - Ben Voigt
1
@23W:你是在说未处理的异常(崩溃而不是警告),还是第一次机会异常调试器消息?如果是后者,那么很可能是因为 SendMessage 有自己的 SEH 处理程序,可以处理该异常。 - valdo
@valdo,关于第一次机会异常调试器消息。 - 23W
显示剩余5条评论

4

您可以设计自己的异常,包括源文件名称和行号。为了实现这一点,您需要创建一个派生自 std::exception 的类来包含该信息。在下面的示例中,我有一个异常库,用于我的应用程序,包括my_exception。我还有一个traced_error,它是从我的应用程序级别异常派生的模板异常类。 traced_error 异常保存有关文件名和行号的信息,并调用应用程序级别异常类的what()方法来获取详细的错误信息。

#include <cstdlib>
#include <string>
#include <stdexcept>
#include <iostream>
using namespace std;


template<class EX>
class traced_error : virtual public std::exception, virtual public EX
{
public:
    traced_error(const std::string& file, int line, const EX& ex)
    :   EX(ex),
        line_(line),
        file_(file)
    {       
    }

    const char* what() const
    {
        std::stringstream ss;
        static std::string msg;
        ss << "File: " << file_ << " Line: " << line_ << " Error: " << EX::what();
        msg = ss.str().c_str();
        return msg.c_str();
    }

    int line_;
    std::string file_;
};

template<class EX> traced_error<EX> make_traced_error(const std::string& file, int line, const EX& ex)
{
    return traced_error<EX>(file, line, ex);
}


class my_exception : virtual public std::exception
{
public:
    my_exception() {};

    const char* what() const
    {
        return "my_exception's what";
    }
};

#define throwx(EX) (throw make_traced_error(__FILE__,__LINE__,EX))


int main()
{
    try
    {
        throwx(my_exception());
    }
    catch( const std::exception& ex )
    {
        cout << ex.what();
    }
    return 0;
}

这个程序的输出结果是:

文件:.\main.cpp 行数:57 错误: my_exception's what

你也可以重新设计这个程序,使应用级别的异常从traced_error派生而来,以便更容易捕获特定的应用级别异常。在catch语句中,你可以将错误记录到日志文件中,并使用MiniDumpWriteDump()创建一个转储文件。

2
你可以使用MiniDumpWriteDump函数来编写转储文件。
如果你正在使用C++异常,那么你可以在抛出该异常的任何位置(使用____FUNCTION____、____FILE____和____LINE____宏)包括文件/行/函数信息(因此如果调用std::exception.what(),则会将其视为文本)。
如果你试图捕获操作系统异常,那么崩溃应用程序可能是更好的选择。

1
你需要做的是分析堆栈以确定异常来自哪里。对于 msvc,有一个名为 dbghelp.dll 的库可以帮助您记录异常。通常我会记录一个minidump文件并使用它来重现问题,除了使用正确的程序数据库(pdb文件)。这适用于没有源代码或不想提供 pdb 的客户系统。

1
一个与编译器无关的技巧是将 throw 语句包装在一个函数中。该函数可以在抛出异常之前执行其他任务,例如记录到日志文件中。它还可以成为放置断点的便捷位置。如果您创建一个宏来调用该函数,则可以自动包含抛出异常发生的 __FILE__ 和 __LINE__。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接