关于调试:如何找到C ++中抛出异常的位置?

How do I find where an exception was thrown in C++?

我有一个程序在某个地方抛出一个未捕获的异常。我得到的只是一个异常被抛出的报告,没有关于它被抛出的位置的信息。对于一个编译为包含调试符号的程序来说,不通知我在代码中哪里生成了异常似乎是不合逻辑的。

如果没有在gdb中设置"catch throw"并为每个抛出的异常调用一个backtrace,是否有任何方法可以告诉我的异常来自何处?


以下是一些在调试问题时可能用到的信息

如果未捕获异常,则自动调用特殊库函数std::terminate()。terminate实际上是指向函数的指针,默认值是标准的C库函数std::abort()。如果未捕获异常没有发生清除?,它实际上可能有助于调试此问题,因为没有调用析构函数。< Sub >?在调用std::terminate()之前,是否释放堆栈是由实现定义的。

abort()的调用通常在生成核心转储时很有用,可以对其进行分析以确定异常的原因。确保通过ulimit -c unlimited(Linux)启用核心转储。

您可以使用std::set_terminate()安装自己的terminate()功能。您应该能够在gdb中的terminate函数上设置断点。您可以从terminate()函数生成堆栈回溯,此回溯可能有助于识别异常的位置。

简单地讨论了Bruce Eckel在C++中的思想中的例外情况,第二个Ed也有帮助。

由于terminate()默认调用abort()(默认情况下会导致SIGABRT信号),您可以设置SIGABRT处理程序,然后从信号处理程序中打印堆栈回溯。此回溯可能有助于识别异常的位置。

注意:我说可以,因为C++通过使用语言构造来支持非本地错误处理,以将错误处理和报告代码与普通代码分开。catch块可以且通常位于与抛出点不同的函数/方法中。在注释(谢谢dan)中还向我指出,在调用terminate()之前,是否释放堆栈是实现定义的。

更新:我构建了一个Linux测试程序,它在通过set_terminate()设置的terminate()函数集中生成回溯,在SIGABRT的信号处理程序中生成另一个回溯。两个回溯都正确地显示了未处理异常的位置。

更新2:多亏了一篇关于在Terminate中捕获未捕获异常的博客文章,我学到了一些新技巧;包括在Terminate处理程序中重新抛出未捕获异常。需要注意的是,自定义终止处理程序中的空throw语句与gcc一起工作,不是可移植的解决方案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <stdexcept>

void my_terminate(void);

namespace {
    // invoke set_terminate as part of global constant initialization
    static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}

// This structure mirrors the one found in /usr/include/asm/ucontext.h
typedef struct _sig_ucontext {
   unsigned long     uc_flags;
   struct ucontext   *uc_link;
   stack_t           uc_stack;
   struct sigcontext uc_mcontext;
   sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) {
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    // Get the address at the time the signal was raised from the EIP (x86)
    void * caller_address = (void *) uc->uc_mcontext.eip;

    std::cerr <<"signal" << sig_num
              <<" (" << strsignal(sig_num) <<"), address is"
              << info->si_addr <<" from"
              << caller_address << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    std::cerr << __FUNCTION__ <<" backtrace returned"
              << size <<" frames

"
;

    // overwrite sigaction with caller's address
    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i) {
        std::cerr <<"[bt]: (" << i <<")" << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

void my_terminate() {
    static bool tried_throw = false;

    try {
        // try once to re-throw currently active exception
        if (!tried_throw++) throw;
    }
    catch (const std::exception &e) {
        std::cerr << __FUNCTION__ <<" caught unhandled exception. what():"
                  << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << __FUNCTION__ <<" caught unknown/unhandled exception."
                  << std::endl;
    }

    void * array[50];
    int size = backtrace(array, 50);    

    std::cerr << __FUNCTION__ <<" backtrace returned"
              << size <<" frames

"
;

    char ** messages = backtrace_symbols(array, size);

    for (int i = 0; i < size && messages != NULL; ++i) {
        std::cerr <<"[bt]: (" << i <<")" << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    abort();
}

int throw_exception() {
    // throw an unhandled runtime error
    throw std::runtime_error("RUNTIME ERROR!");
    return 0;
}

int foo2() {
    throw_exception();
    return 0;
}

int foo1() {
    foo2();
    return 0;
}

int main(int argc, char ** argv) {
    struct sigaction sigact;

    sigact.sa_sigaction = crit_err_hdlr;
    sigact.sa_flags = SA_RESTART | SA_SIGINFO;

    if (sigaction(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) {
        std::cerr <<"error setting handler for signal" << SIGABRT
                  <<" (" << strsignal(SIGABRT) <<")
"
;
        exit(EXIT_FAILURE);
    }

    foo1();

    exit(EXIT_SUCCESS);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
my_terminate caught unhanded exception. what(): RUNTIME ERROR!
my_terminate backtrace returned 10 frames

[bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52]
[bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (5) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (6) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (7) ./test(main+0xc1) [0x8049121]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21]

signal 6 (Aborted), address is 0x1239 from 0x42029331
crit_err_hdlr backtrace returned 13 frames

[bt]: (1) ./test(kill+0x11) [0x42029331]
[bt]: (2) ./test(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./test [0x8048f9f]
[bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (8) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (10) ./test(main+0xc1) [0x8049121]
[bt]: (11) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21]


如您所说,我们可以在gdb中使用"catch throw",并为每个抛出的异常调用"backtrace"。虽然这通常过于繁琐,无法手动完成,但gdb允许流程自动化。这允许查看抛出的所有异常的回溯,包括最后一个未捕获的异常:

GDB >

1
2
3
4
5
6
7
set pagination off
catch throw
commands
backtrace
continue
end
run

如果不进行进一步的手动干预,这将生成大量的回溯,包括最后一个未捕获异常的回溯:

1
2
3
4
5
6
7
Catchpoint 1 (exception thrown), 0x00a30 in __cxa_throw () from libstdc++.so.6
#0  0x0da30 in __cxa_throw () from /usr/.../libstdc++.so.6
#1  0x021f2 in std::__throw_bad_weak_ptr () at .../shared_ptr_base.h:76
[...]
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
Program received signal SIGABRT, Aborted.

下面是一篇很棒的博客文章,总结了这一点:http://741Mhz.com/throw-stacktrace/


您可以创建如下宏:

1
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )

…它将给出抛出异常的位置(当然不是堆栈跟踪)。您有必要从采用上述构造函数的某个基类派生异常。


您可以将代码中的主要紧密位置标记为noexcept以定位异常,然后使用libunwind(只需将-lunwind添加到链接器参数中)(使用clang++ 3.6测试):

DEMAGLE

1
2
3
4
#pragma once

char const *
get_demangled_name(char const * const symbol) noexcept;

Dimangle CPP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include"demangle.hpp"

#include <memory>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wglobal-constructors"
#pragma clang diagnostic ignored"-Wexit-time-destructors"
std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free};
#pragma clang diagnostic pop

}

char const *
get_demangled_name(char const * const symbol) noexcept
{
    if (!symbol) {
        return"<null>";
    }
    int status = -4;
    demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status));
    return ((status == 0) ? demangled_name.get() : symbol);
}

回溯

1
2
3
4
5
#pragma once
#include <ostream>

void
backtrace(std::ostream & _out) noexcept;

RACTRAPE.CPP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include"backtrace.hpp"

#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>

#include <cstdint>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

namespace
{

void
print_reg(std::ostream & _out, unw_word_t reg) noexcept
{
    constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4;
    _out <<"0x" << std::setfill('0') << std::setw(address_width) << reg;
}

char symbol[1024];

}

void
backtrace(std::ostream & _out) noexcept
{
    unw_cursor_t cursor;
    unw_context_t context;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    _out << std::hex << std::uppercase;
    while (0 < unw_step(&cursor)) {
        unw_word_t ip = 0;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        if (ip == 0) {
            break;
        }
        unw_word_t sp = 0;
        unw_get_reg(&cursor, UNW_REG_SP, &sp);
        print_reg(_out, ip);
        _out <<": (SP:";
        print_reg(_out, sp);
        _out <<")";
        unw_word_t offset = 0;
        if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
            _out <<"(" << get_demangled_name(symbol) <<" + 0x" << offset <<")

"
;
        } else {
            _out <<"-- error: unable to obtain symbol name for this frame

"
;
        }
    }
    _out << std::flush;
}

backtrace_on_terminate.hpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include"demangle.hpp"
#include"backtrace.hpp"

#include <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

[[noreturn]]
void
backtrace_on_terminate() noexcept;

static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{});

#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wglobal-constructors"
#pragma clang diagnostic ignored"-Wexit-time-destructors"
std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate};
#pragma clang diagnostic pop

[[noreturn]]
void
backtrace_on_terminate() noexcept
{
    std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any
    backtrace(std::clog);
    if (std::exception_ptr ep = std::current_exception()) {
        try {
            std::rethrow_exception(ep);
        } catch (std::exception const & e) {
            std::clog <<"backtrace: unhandled exception std::exception:what():" << e.what() << std::endl;
        } catch (...) {
            if (std::type_info * et = abi::__cxa_current_exception_type()) {
                std::clog <<"backtrace: unhandled exception type:" << get_demangled_name(et->name()) << std::endl;
            } else {
                std::clog <<"backtrace: unhandled unknown exception" << std::endl;
            }
        }
    }
    std::_Exit(EXIT_FAILURE); // change to desired return code
}

}

关于这个问题有好文章。


您没有传递有关所使用的操作系统/编译器的信息。

在VisualStudioC++中,可以检测异常。

见DDC.NET上的"VisualC++异常处理工具"

我的文章"后期调试",也在ddj.com上,包括使用win32结构化异常处理(由工具使用)进行日志记录等的代码。


检查这个线程,也许它有助于:

捕获所有未处理的C++异常?

我在该软件方面有很好的经验:

http://www.codeproject.com/kb/applications/blackbox.aspx

对于任何未处理的异常,它都可以将堆栈跟踪输出到文件。


我有代码可以在Windows/Visual Studio中执行此操作,如果需要大纲,请通知我。不过,不知道如何处理dwarf2代码,一个快速的google建议在libgcc中有一个函数"unwind"backtrace,这可能是您需要的一部分。