어딘가에 잡히지 않은 예외를 던지는 프로그램이 있습니다. 내가 얻는 것은 예외가 발생했다는보고 뿐이며 예외가 발생 된 위치에 대한 정보는 없습니다. 내 코드에서 예외가 생성 된 위치를 알려주지 않는 디버그 기호를 포함하도록 컴파일 된 프로그램이 비논리적으로 보입니다.
gdb에서 'catch throw'를 설정하고 throw되는 모든 예외에 대해 역 추적을 호출하지 않고 내 예외가 어디에서 오는지 알 수있는 방법이 있습니까?
어딘가에 잡히지 않은 예외를 던지는 프로그램이 있습니다. 내가 얻는 것은 예외가 발생했다는보고 뿐이며 예외가 발생 된 위치에 대한 정보는 없습니다. 내 코드에서 예외가 생성 된 위치를 알려주지 않는 디버그 기호를 포함하도록 컴파일 된 프로그램이 비논리적으로 보입니다.
gdb에서 'catch throw'를 설정하고 throw되는 모든 예외에 대해 역 추적을 호출하지 않고 내 예외가 어디에서 오는지 알 수있는 방법이 있습니까?
답변:
예외가 포착되지 않으면 특수 라이브러리 함수 std::terminate()
가 자동으로 호출됩니다. Terminate는 실제로 함수에 대한 포인터이며 기본값은 표준 C 라이브러리 함수 std::abort()
입니다. 더 정리가 잡히지 않은 예외 발생하지 않으면 † , 그것은 수 실제로 소멸자가 호출되지 않습니다으로이 문제를 디버깅하는 데 도움이 될.
† 스택 std::terminate()
이 호출 되기 전에 풀 렸는지 여부는 구현에서 정의됩니다 .
에 대한 호출 abort()
은 예외의 원인을 확인하기 위해 분석 할 수있는 코어 덤프를 생성하는 데 유용합니다. ulimit -c unlimited
(Linux) 를 통해 코어 덤프를 활성화했는지 확인하십시오 .
을 사용하여 자신 만의 terminate()
기능을 설치할 수 있습니다 std::set_terminate()
. gdb에서 종료 함수에 중단 점을 설정할 수 있어야합니다. 당신은 할 수 귀하의에서 스택 역 추적을 생성 할 수 terminate()
기능이 역 추적을 할 수 있습니다 예외의 위치를 식별하는 데 도움이.
Bruce Eckel의 Thinking in C ++, 2nd Ed에서도 잡히지 않은 예외 에 대한 간단한 토론 이 있습니다.
이후 terminate()
통화 abort()
기본 (A 발생할 수있는 SIGABRT
기본적으로 신호)를, 당신은 할 수있다 세트 할 수 SIGABRT
처리기를 다음 신호 처리기 내에서 스택 역 추적을 인쇄 . 이 역 추적 은 예외 위치를 식별하는 데 도움 이 될 수 있습니다 .
참고 : 내가 말할 수도 C ++ 지원 로컬이 아닌 오류가 별도의 오류 처리에 대한 언어 구조의 사용을 통해 처리하고 일반 코드에서 코드를보고 있기 때문이다. 캐치 블록은 던지는 지점과 다른 기능 / 방법에 위치 할 수 있으며 종종 있습니다. 또한 주석에서 ( Dan 에게 감사드립니다 ) 스택 terminate()
이 호출 되기 전에 풀 렸는지 여부에 관계없이 구현 정의 되어 있음을 지적했습니다 .
업데이트 :terminate()
를 통해 설정된 함수 set_terminate()
와 .NET 용 신호 처리기에서 다른 함수 세트 에서 역 추적을 생성하는 Linux 테스트 프로그램을 함께 던졌습니다 SIGABRT
. 두 역 추적 모두 처리되지 않은 예외의 위치를 올바르게 표시합니다.
업데이트 2 : 종료 내에서 포착되지 않은 예외 포착 에 대한 블로그 게시물 덕분에 몇 가지 새로운 트릭을 배웠습니다. 종료 처리기 내에서 포착되지 않은 예외의 다시 발생을 포함합니다. throw
사용자 지정 종료 처리기 내의 빈 문은 GCC에서 작동하며 이식 가능한 솔루션이 아니라는 점에 유의해야합니다 .
암호:
#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);
}
산출:
my_terminate가 처리되지 않은 예외를 포착했습니다. what () : 런타임 오류! my_terminate backtrace는 10 개의 프레임을 반환했습니다. [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] 신호 6 (중단됨), 주소는 0x42029331에서 0x1239입니다. crit_err_hdlr 역 추적이 13 프레임을 반환했습니다. [bt] : (1) ./test(kill+0x11) [0x42029331] [bt] : (2) ./test (중단 + 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]
main
)와 다음 이 부를 것이다 terminate()
. 그러나 귀하의 예는 풀기가 전혀 수행되지 않음을 보여줍니다.
throw(int)
사양은 불필요합니다. 2) uc->uc_mcontext.eip
아마도 매우 플랫폼 의존적 일 것입니다 (예 : ...rip
64 비트 플랫폼에서 사용). 3) 컴파일하여 -rdynamic
역 추적 기호를 얻습니다. 4) ./a.out 2>&1 | c++filt
예쁜 역 추적 기호를 얻으려면 실행하십시오 .
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;
내 ARM의 대상에 대해 일
당신이 말했듯이, 우리는 gdb에서 'catch throw'를 사용하고 throw되는 모든 예외에 대해 'backtrace'를 호출 할 수 있습니다. 일반적으로 수동으로 수행하기에는 너무 지루하지만 gdb를 사용하면 프로세스를 자동화 할 수 있습니다. 이를 통해 마지막으로 잡히지 않은 예외를 포함하여 발생한 모든 예외의 역 추적을 볼 수 있습니다.
gdb>
set pagination off
catch throw
commands
backtrace
continue
end
run
추가 수동 개입이 없으면 마지막 포착되지 않은 예외에 대한 하나를 포함하여 많은 역 추적이 생성됩니다.
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 [archive.org]
다음과 같은 매크로를 만들 수 있습니다.
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
... 그리고 예외가 발생한 위치를 알려줄 것입니다 (스택 추적이 아님). 위의 생성자를 사용하는 일부 기본 클래스에서 예외를 파생시키는 것이 필요합니다.
throw new excation(...)
않지만 throw exception(...)
C ++는 자바가 아닙니다.
사용하는 OS / 컴파일러에 대한 정보를 전달하지 않았습니다.
Visual Studio C ++에서 예외를 계측 할 수 있습니다.
참조 "비주얼 C ++ 예외 처리 계측을" ddj.com에
ddj.com의 내 기사 " Postmortem Debugging" 에는 로깅 등을 위해 Win32 구조적 예외 처리 (계측에서 사용)를 사용하는 코드가 포함되어 있습니다.
noexcept
예외를 찾기 위해 코드의 주요 좁은 위치를 표시 한 다음 libunwind 를 사용합니다 ( -lunwind
링커 매개 변수에 추가 하기 만하면 됨 ) (으로 테스트 됨 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_terminate.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
}
}
문제에 관한 좋은 기사 가 있습니다 .
Windows / Visual Studio에서이 작업을 수행하는 코드가 있습니다. 개요가 필요한지 알려주세요. dwarf2 코드에 대해 수행하는 방법을 모르지만 빠른 Google은 libgcc에 아마도 필요한 것의 일부인 _Unwind_Backtrace 함수가 있다고 제안합니다.
이 스레드를 확인하면 도움이 될 것입니다.
나는 그 소프트웨어로 좋은 경험을했습니다.
http://www.codeproject.com/KB/applications/blackbox.aspx
처리되지 않은 예외에 대해 파일에 스택 추적을 인쇄 할 수 있습니다.
exception thrown foo.c@54, ..., re-thrown bar.c@54, ....
수동으로 수행하지 않고도 스택 추적을 원한다는 것입니다.