Làm cách nào để tìm nơi có ngoại lệ trong C ++?


92

Tôi có một chương trình ném một ngoại lệ không cần thiết ở đâu đó. Tất cả những gì tôi nhận được là một báo cáo về một ngoại lệ được ném ra, và không có thông tin về nơi nó được ném. Có vẻ không hợp lý khi một chương trình được biên dịch để chứa các ký hiệu gỡ lỗi không thông báo cho tôi về vị trí trong mã của tôi, một ngoại lệ đã được tạo ra.

Có cách nào để biết các ngoại lệ của tôi đến từ đâu khi thiếu cài đặt 'catch throw' trong gdb và gọi backtrace cho mọi ngoại lệ được ném ra?



Nắm bắt ngoại lệ và xem thông báo nội bộ là gì. Vì nó là thực hành tốt cho một ngoại lệ được bắt nguồn từ một trong những trường hợp ngoại lệ chuẩn (std :: runtime_error) bạn shoudl có thể bắt nó với catch (std :: ngoại lệ const & e)
Martin York

1
Và std :: exception / Std :: runtime_error giải quyết vấn đề tìm ra "đường dẫn" và nguồn gốc của một ngoại lệ?
VolkerK

1
Như trạng thái câu hỏi của bạn là gdb, tôi nghĩ rằng giải pháp của bạn đã có trong SO: stackoverflow.com/questions/77005/… Tôi đã sử dụng giải pháp được mô tả ở đây và nó hoạt động hoàn hảo.
neuro

2
Bạn nên xem xét việc chỉ định hệ điều hành thông qua một thẻ. Vì bạn đề cập đến gdb, tôi cho rằng bạn đang tìm kiếm một giải pháp Linux chứ không phải Windows.
jschmier

Câu trả lời:


72

Đây là một số thông tin có thể được sử dụng để gỡ lỗi sự cố của bạn

Nếu không có ngoại lệ, hàm thư viện đặc biệt std::terminate()sẽ tự động được gọi. Chấm dứt thực sự là một con trỏ đến một chức năng và giá trị mặc định là thư viện chức năng tiêu chuẩn C std::abort(). Nếu không dọn dẹp xảy ra đối với một ngoại lệ uncaught , nó có thể thực sự là hữu ích trong việc gỡ rối vấn đề này vì không hủy được gọi.
† Nó được xác định thực thi cho dù ngăn xếp chưa được gắn kết trước đó std::terminate()có được gọi hay không.


Lệnh gọi tới abort()thường hữu ích trong việc tạo kết xuất lõi có thể được phân tích để xác định nguyên nhân của ngoại lệ. Đảm bảo rằng bạn bật kết xuất lõi qua ulimit -c unlimited(Linux).


Bạn có thể cài đặt terminate()chức năng của riêng mình bằng cách sử dụng std::set_terminate(). Bạn sẽ có thể đặt một điểm ngắt trên hàm kết thúc của mình trong gdb. Bạn thể tạo một dấu lùi ngăn xếp từ terminate()chức năng của mình và dấu vết này có thể giúp xác định vị trí của ngoại lệ.

Có một cuộc thảo luận ngắn gọn về các trường hợp ngoại lệ không cần thiết trong Tư duy của Bruce Eckel trong C ++, Phiên bản thứ hai cũng có thể hữu ích.


terminate()các cuộc gọi abort()theo mặc định (sẽ gây ra SIGABRTtín hiệu theo mặc định), bạn thể đặt một SIGABRTtrình xử lý và sau đó in một dấu vết ngăn xếp từ bên trong trình xử lý tín hiệu . Dấu vết này có thể giúp xác định vị trí của ngoại lệ.


Lưu ý: Tôi nói có thể vì C ++ hỗ trợ xử lý lỗi không cục bộ thông qua việc sử dụng các cấu trúc ngôn ngữ để tách mã báo cáo và xử lý lỗi khỏi mã thông thường. Khối bắt có thể, và thường là, nằm ở một chức năng / phương pháp khác với điểm ném. Nó cũng đã được chỉ ra cho tôi trong các nhận xét (cảm ơn Dan ) rằng nó được xác định thực thi cho dù ngăn xếp chưa được gắn kết trước đó terminate()được gọi hay không.

Cập nhật: Tôi đã tập hợp một chương trình thử nghiệm Linux có tên là tạo ra một dấu hiệu tồn đọng trong một terminate()hàm được đặt qua set_terminate()và một chương trình khác trong bộ xử lý tín hiệu cho SIGABRT. Cả hai dấu lùi đều hiển thị chính xác vị trí của ngoại lệ chưa được xử lý.

Cập nhật 2: Nhờ một bài đăng trên blog về Bắt các ngoại lệ không cần thiết trong vòng chấm dứt , tôi đã học được một vài thủ thuật mới; bao gồm việc ném lại ngoại lệ chưa được suy nghĩ trong trình xử lý kết thúc. Điều quan trọng cần lưu ý là throwcâu lệnh trống trong trình xử lý kết thúc tùy chỉnh hoạt động với GCC và không phải là một giải pháp di động.

Mã:

#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\n\n";

    // 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\n\n";

    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) << ")\n";
        exit(EXIT_FAILURE);
    }

    foo1();

    exit(EXIT_SUCCESS);
}

Đầu ra:

my_termina đã bắt gặp ngoại lệ không được xử lý. what (): RUNTIME ERROR!
my_termina backtrace trả lại 10 khung hình

[bt]: (0) ./test(my_termina__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]

tín hiệu 6 (Đã hủy bỏ), địa chỉ là 0x1239 từ 0x42029331
crit_err_hdlr backtrace trả về 13 khung hình

[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]


1
Rất thú vị. Tôi luôn nghi ngờ rằng một ngoại lệ chưa được xử lý sẽ giải phóng ngăn xếp cho đến khi nó lên đến cấp cao nhất ( main) và sau đó nó sẽ gọi terminate(). Nhưng ví dụ của bạn cho thấy rằng không có thao tác tháo cuộn nào được thực hiện, điều này rất tuyệt.
Dan

6
1) Thông throw(int)số kỹ thuật là không cần thiết. 2) Có uc->uc_mcontext.eiplẽ rất phụ thuộc vào nền tảng (ví dụ: sử dụng ...riptrên nền tảng 64-bit). 3) Biên dịch với -rdynamicđể bạn nhận được các ký hiệu backtrace. 4) Chạy ./a.out 2>&1 | c++filtđể nhận các biểu tượng backtrace đẹp.
Dan

2
"Không có quá trình dọn dẹp nào xảy ra cho một trường hợp ngoại lệ không cần thiết." - Trên thực tế, đó là triển khai-xác định. Xem 15.3 / 9 và và 15.5.1 / 2 trong thông số kỹ thuật C ++. "Trong trường hợp không tìm thấy trình xử lý phù hợp, nó được xác định bằng việc thực thi cho dù ngăn xếp chưa được liên kết trước khi chấm dứt () được gọi hay không." Tuy nhiên, đây là một giải pháp tuyệt vời nếu trình biên dịch của bạn hỗ trợ nó!
Dan

1
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;làm việc cho mục tiêu ARM của tôi
stephen

1
Một vài lưu ý: backtrace_symbols () thực hiện một malloc ... vì vậy, bạn có thể muốn cấp phát trước một khối bộ nhớ khi khởi động, sau đó phân bổ nó trong my_termina () ngay trước khi gọi backtrace_symbols () trong trường hợp bạn tình cờ xử lý một ngoại lệ std :: bad_alloc (). Ngoài ra, bạn có thể bao gồm <cxxabi.h> và sau đó sử dụng __cxa_demangle () để tạo điều gì đó hữu ích từ chuỗi con bị xáo trộn được hiển thị giữa '(' và '+' trong chuỗi thông báo đầu ra [].
K Scott Piel

51

Như bạn nói, chúng ta có thể sử dụng 'catch throw' trong gdb và gọi 'backtrace' cho mọi ngoại lệ được ném. Mặc dù điều đó thường quá tẻ nhạt để làm thủ công, nhưng gdb cho phép tự động hóa quá trình. Điều đó cho phép nhìn thấy dấu vết của tất cả các trường hợp ngoại lệ được ném ra, bao gồm cả trường hợp ngoại lệ cuối cùng chưa được hoàn thành:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

Nếu không có thêm sự can thiệp thủ công, điều này tạo ra rất nhiều dấu vết, bao gồm một dấu hiệu cho trường hợp ngoại lệ cuối cùng chưa được giải quyết:

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.

Đây là một bài đăng blog tuyệt vời tóm tắt lại vấn đề này: http://741mhz.com/throw-stacktrace [trên archive.org]


17

Bạn có thể tạo một macro như:

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

... và nó sẽ cung cấp cho bạn vị trí mà ngoại lệ được ném (phải thừa nhận là không phải dấu vết ngăn xếp). Bạn cần lấy các ngoại lệ của mình từ một số lớp cơ sở lấy hàm tạo ở trên.


18
-1 Bạn không throw new excation(...)nhưng throw exception(...)C ++ không phải là Java,
Artyom

7
Được rồi, tôi đã sửa nó. Có thể tha thứ cho một lập trình viên làm việc bằng cả Java và C ++ không?
Erik Hermansen

Trong khi tôi đã sử dụng cái này. Vấn đề với nó là nó không cho biết điều gì thực sự đã tạo ra ngoại lệ. Ví dụ: nếu bạn có 5 cuộc gọi stoi trong một khối thử, bạn sẽ không biết cái nào thực sự là thủ phạm.
Banjocat

5

Bạn đã không chuyển thông tin về hệ điều hành / trình biên dịch nào bạn sử dụng.

Trong Visual Studio C ++, các ngoại lệ có thể được bổ sung.

Xem "Công cụ xử lý ngoại lệ Visual C ++" trên ddj.com

Bài viết của tôi "Gỡ lỗi sau khi chết" , cũng trên ddj.com bao gồm mã sử dụng xử lý ngoại lệ có cấu trúc Win32 (được sử dụng bởi thiết bị đo đạc) để ghi nhật ký, v.v.


anh ấy nói gdb, điều này khá nhiều quy tắc đối với Windows / Visual Studio.
Ben Voigt

2
Anh ấy nói rằng anh ấy muốn thứ gì đó "thiếu gdb", nhưng anh ấy không đề cập rõ ràng đến bất kỳ Hệ điều hành / Trình biên dịch nào. Đó là vấn đề của mọi người không khai báo những thứ như vậy.
QUẢNG CÁO MỀM ĐỎ,

5

Bạn có thể đánh dấu các vị trí chặt chẽ chính trong mã của mình như noexceptđể tìm một ngoại lệ, sau đó sử dụng libunwind (chỉ cần thêm -lunwindvào các tham số của trình liên kết) (được thử nghiệm với clang++ 3.6):

demagle.hpp:

#pragma once

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

demangle.cpp:

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

backtrace.hpp:

#pragma once
#include <ostream>

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

backtrace.cpp:

#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 << ")\n\n";
        } else {
            _out << "-- error: unable to obtain symbol name for this frame\n\n";
        }
    }
    _out << std::flush;
}

backtrace_on_termina.hpp:

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

}

một bài báo hay liên quan đến vấn đề này.


1

Tôi có mã để thực hiện việc này trong Windows / Visual Studio, hãy cho tôi biết nếu bạn muốn có một phác thảo. Tuy nhiên, bạn không biết cách làm điều đó cho mã lùn2, một google nhanh chóng gợi ý rằng có một hàm _Unwind_Backtrace trong libgcc có thể là một phần của những gì bạn cần.


Có lẽ bởi vì "hãy cho tôi biết nếu bạn muốn một dàn ý" không phải là một câu trả lời hữu ích. Nhưng _Unwind_Backtrace là; bù đắp.
Thomas

Trên cơ sở OP đã đề cập đến gdb, tôi đoán rằng Windows không liên quan. Tất nhiên, Alex được tự do chỉnh sửa câu hỏi của mình thành Windows.
Ben Voigt

1

Kiểm tra chuỗi này, có lẽ nó sẽ giúp:

Bắt tất cả các ngoại lệ C ++ chưa được xử lý?

Tôi đã có những trải nghiệm tốt với phần mềm đó:

http://www.codeproject.com/KB/application/blackbox.aspx

Nó có thể in ra một dấu vết ngăn xếp vào một tệp cho bất kỳ ngoại lệ nào chưa được xử lý.


Tôi nghĩ vấn đề là Alex muốn có một stacktrace như thế nào exception thrown foo.c@54, ..., re-thrown bar.c@54, ....mà không cần phải làm thủ công.
VolkerK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.