मुझे कैसे पता चलेगा कि C ++ में एक अपवाद कहां फेंका गया था?


92

मेरा एक कार्यक्रम है जो एक अनकहा अपवाद कहीं फेंकता है। मुझे जो कुछ भी मिला है वह एक अपवाद की एक रिपोर्ट है, और यह फेंके जाने के बारे में कोई जानकारी नहीं है। ऐसा लगता है कि डिबग प्रतीकों को संकलित करने के लिए संकलित कार्यक्रम के लिए मुझे यह सूचित नहीं करना चाहिए कि मेरे कोड में मुझे अपवाद कहां से उत्पन्न हुआ।

क्या यह बताने का कोई तरीका है कि मेरे अपवाद जीडीबी में 'कैच थ्रो' की कमी से आ रहे हैं और हर एक फेंके गए अपवाद के लिए बैकट्रेस कह रहे हैं?



अपवाद को पकड़ें और देखें कि आंतरिक संदेश क्या है। चूंकि यह एक मानक अपवाद (std :: runtime_error) में से किसी एक से प्राप्त होने के लिए एक अपवाद के लिए अच्छा अभ्यास है, आप इसे जोर से पकड़ने में सक्षम हो सकते हैं (std :: अपवाद const & e)
मार्टिन

1
और std :: अपवाद / Std :: runtime_error "पथ" और एक अपवाद की उत्पत्ति का पता लगाने की समस्या को हल करता है?
वोल्कर मार्क

1
जैसा कि आपका प्रश्न राज्य gdb, मुझे लगता है कि आपका समाधान पहले से ही SO में है: stackoverflow.com/questions/77005/… मैंने यहाँ वर्णित समाधान का उपयोग किया है और यह पूरी तरह से काम करता है।
न्यूरो

2
आपको एक टैग के माध्यम से ओएस को निर्दिष्ट करने पर विचार करना चाहिए। चूँकि आप gdb का उल्लेख करते हैं, मैं मानता हूँ कि आप एक लिनक्स समाधान की तलाश कर रहे हैं न कि विंडोज की।
jschmier

जवाबों:


72

यहाँ कुछ जानकारी है जो आपकी समस्या को डीबग करने में काम आ सकती है

यदि अपवाद अपवाद नहीं है, तो विशेष लाइब्रेरी फ़ंक्शन std::terminate()को स्वचालित रूप से कहा जाता है। समाप्त करना वास्तव में एक फ़ंक्शन का सूचक है और डिफ़ॉल्ट मान मानक सी लाइब्रेरी फ़ंक्शन है std::abort()। कोई सफाई न आया हुआ अपवाद के लिए हो तो , यह हो सकता है वास्तव में इस समस्या को दूर करने के रूप में कोई विनाशकर्ता कहा जाता है में सहायक हो।
† यह कार्यान्वयन-परिभाषित है कि क्या स्टैक को पहले std::terminate()कहा गया है या नहीं ।


एक कॉल abort()अक्सर एक कोर डंप पैदा करने में उपयोगी होता है जिसे अपवाद का कारण निर्धारित करने के लिए विश्लेषण किया जा सकता है। सुनिश्चित करें कि आप ulimit -c unlimited(Linux) के माध्यम से कोर डंप सक्षम करें ।


आप अपने खुद के terminate()फ़ंक्शन का उपयोग करके स्थापित कर सकते हैं std::set_terminate()। आपको gdb में अपने समाप्ति कार्य पर एक ब्रेकपॉइंट सेट करने में सक्षम होना चाहिए। आप अपने फ़ंक्शन से एक स्टैक बैकट्रेस उत्पन्न करने में सक्षम हो सकते हैं terminate()और यह बैकट्रेस अपवाद के स्थान की पहचान करने में मदद कर सकता है।

ब्रूस एकेल की थिंकिंग सी +, 2 डी एड में अनकैप्ड अपवादों पर एक संक्षिप्त चर्चा है जो सहायक भी हो सकती है।


चूंकि डिफ़ॉल्ट रूप से terminate()कॉल abort()(जो डिफ़ॉल्ट रूप से एक SIGABRTसिग्नल का कारण होगा ), आप एक हैंडलर सेट करने में सक्षम हो सकते हैं SIGABRTऔर फिर सिग्नल हैंडलर के भीतर से एक स्टैक बैकट्रेस प्रिंट कर सकते हैं । यह बैकट्रेस अपवाद के स्थान की पहचान करने में मदद कर सकता है।


नोट: मैं कह सकता हूं क्योंकि C ++ गैर-स्थानीय त्रुटि हैंडलिंग का समर्थन करता है, भाषा निर्माण के उपयोग के माध्यम से सामान्य कोड से त्रुटि हैंडलिंग और रिपोर्टिंग कोड को अलग करने के लिए। पकड़ ब्लॉक हो सकता है, और अक्सर, फेंकने के बिंदु से अलग फ़ंक्शन / विधि में स्थित होता है। यह भी मुझे टिप्पणियों में धन्यवाद दिया गया है (धन्यवाद डैन ) कि यह कार्यान्वयन-परिभाषित है कि क्या स्टैक अनबाउंड है या नहीं, terminate()यह कहा जाता है।

अद्यतन: मैंने एक साथ एक लिनक्स टेस्ट प्रोग्राम को फेंक दिया जो कि एक terminate()फ़ंक्शन के माध्यम से set_terminate()और दूसरे के लिए सिग्नल हैंडलर में एक बैकट्रेस उत्पन्न करता है SIGABRT। दोनों backtraces बिना किसी अपवाद के स्थान को सही ढंग से दिखाते हैं।

अद्यतन 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 ने बिना किसी अपवाद के पकड़ा। क्या (): RUNTIME ERROR!
my_terminate backtrace में 10 फ़्रेम वापस आए

[bt]: (०) ।/estest(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]: (४) ./estest(throw_exception__Fv+0x68) [०x ]०४ ९ ४x]
[bt]: (५) ./estest(foo2__Fv+0xb) [0x8049043]
[bt]: (६) ./estest(foo1__Fv+0xb) [0x8049057]
[bt]: (।) ./test(main+0xc1) [0x8049121]
[bt]: (।) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (९) ./test(__eh_alloc+0x3d) [0x80bb21]

सिग्नल 6 (निरस्त), पता 0x2029331 से 0x1239 है
crit_err_hdlr बैकट्रेस ने 13 फ़्रेम वापस किए

[bt]: (१) ./test(kill+0x11) [0x42029331]
[bt]: (2)। /est(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./est [0x8048f9f]
[bt]: (४) /rr/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]: (।) ./test(throw_exception__Fv+0x68) [०x ]०४ ९ ४x]
[bt]: (।) ./estest(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./estest(foo1__Fv+0xb) [0x8049057]
[bt]: (१०) ./test(main+0xc1) [0x8049121]
[bt]: (११) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (१२) ./estest(__eh_alloc+0x3d) [0x80bb21]


1
बहुत ही रोचक। मुझे हमेशा संदेह था कि एक अखंड अपवाद स्टैक को तब तक खोल देगा, जब तक कि यह शीर्ष स्तर ( main) तक न पहुंच जाए और फिर यह कॉल करेगा terminate()। लेकिन आपके उदाहरण से पता चलता है कि कोई भी ऐसा नहीं किया गया है, जो बहुत अच्छा हो।
डैन

6
1) throw(int)युक्ति अनावश्यक है। 2) uc->uc_mcontext.eipशायद बहुत ही प्लेटफॉर्म पर निर्भर है (जैसे, ...rip64-बिट प्लेटफॉर्म पर उपयोग )। 3) संकलन करें -rdynamicताकि आपको बैकट्रेस प्रतीक मिलें। 4) ./a.out 2>&1 | c++filtसुंदर बैकट्रेस प्रतीक पाने के लिए दौड़ें।
डैन

2
"बिना किसी अपवाद के कोई सफाई नहीं होती है।" - दरअसल, यह कार्यान्वयन-परिभाषित है। सी ++ कल्पना में 15.3 / 9 और 15.5.1 / 2 देखें। "उस स्थिति में जहां कोई मिलान करने वाला हैंडलर नहीं मिलता है, यह कार्यान्वयन-परिभाषित है कि समाप्त होने से पहले स्टैक अनवाउंड है या नहीं) () कहा जाता है।" फिर भी, यह एक शानदार समाधान है यदि आपका कंपाइलर इसका समर्थन करता है!
डैन

1
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;मेरे एआरएम लक्ष्य के लिए काम किया
स्टीफन

1
नोटों की एक जोड़ी: backtrace_symbols () एक मॉलोक करता है ... इसलिए, आप स्टार्टअप पर मेमोरी के एक ब्लॉक को पूर्व-आवंटित करना चाहते हैं, तो उस स्थिति में my_terminate () में backt_symbols () कॉल करने से ठीक पहले इसे डीललेट कर सकते हैं। एक std को हैंडल करना :: bad_alloc () अपवाद। इसके अलावा, आप <cxxabi.h> को शामिल कर सकते हैं और फिर आउटपुट संदेशों में '(' और '+' के बीच प्रदर्शित आम के प्रतिस्थापन से बाहर कुछ उपयोगी बनाने के लिए __cxa_demangle () का उपयोग कर सकते हैं [] स्ट्रिंग्स
K स्कॉट Piel

51

जैसा कि आप कहते हैं, हम 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 [पर संग्रहीत ।.org]


17

आप एक मैक्रो बना सकते हैं जैसे:

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

... और यह आपको वह स्थान देगा जहाँ अपवाद फेंका गया है (मूलतः स्टैक ट्रेस नहीं)। आपके लिए यह आवश्यक है कि आप कुछ आधार वर्ग से अपने अपवादों को प्राप्त करें जो उपरोक्त निर्माणकर्ता को लेते हैं।


18
-1 आप नहीं हैं throw new excation(...)लेकिन throw exception(...)C ++ जावा नहीं है,
Artyom

7
ठीक है, मैंने इसे ठीक कर दिया। एक प्रोग्रामर को क्षमा करें जो जावा और सी ++ दोनों में काम करता है हो सकता है?
एरिक हर्मेनसेन

जबकि मैंने इसका इस्तेमाल किया है। इसके साथ समस्या यह है कि यह नहीं बताती है कि वास्तव में अपवाद क्या है। यदि उदाहरण के लिए आपके पास एक कोशिश ब्लॉक में 5 स्टॉइ कॉल हैं, तो आपको पता नहीं चलेगा कि वास्तव में कौन सा अपराधी है।
बंजोकट

5

आपने ओएस / कंपाइलर का उपयोग करने के बारे में जानकारी नहीं दी।

विजुअल स्टूडियो सी ++ में इंस्ट्रूमेंट्स को इंस्ट्रूमेंट किया जा सकता है।

देखें "विजुअल C ++ अपवाद-हैंडलिंग इंस्ट्रुमेंटेशन" ddj.com पर

मेरा लेख "पोस्टमॉर्टम डिबगिंग" , ddj.com पर भी लॉगिंग आदि के लिए Win32 संरचित अपवाद हैंडलिंग (इंस्ट्रूमेंटेशन द्वारा प्रयुक्त) का उपयोग करने के लिए कोड शामिल है।


उन्होंने कहा कि gdb, जो विंडोज / विजुअल स्टूडियो को बहुत अधिक नियमित करता है।
बेन वोइगट

2
वैसे उनका कहना है कि उन्हें "जीडीबी की कमी" कुछ पसंद है, लेकिन वे स्पष्ट रूप से किसी भी ओएस / कंपाइलर का उल्लेख नहीं करते हैं। लोगों को इस तरह के सामान की घोषणा नहीं करने की समस्या।
लाल सॉफ्ट एडेयर

5

आप 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
}

}

नहीं है अच्छा लेख मुद्दे से संबंधित।


1

मुझे विंडोज / विजुअल स्टूडियो में ऐसा करने के लिए कोड मिल गया है, मुझे पता है कि क्या आप एक रूपरेखा चाहते हैं। पता नहीं कैसे यह dwarf2 कोड के लिए करना है, हालांकि, एक त्वरित Google बताता है कि libgcc में एक फ़ंक्शन _Unwind_Backtrace है जो संभवतः आपकी आवश्यकता का हिस्सा है।


शायद इसलिए क्योंकि "मुझे पता है कि आप एक रूपरेखा चाहते हैं" एक उपयोगी जवाब नहीं है। लेकिन _Unwind_Backtrace है; प्रतिकारी।
थॉमस

इस आधार पर कि ओपी ने जीडीबी का उल्लेख किया है, मैंने अनुमान लगाया कि विंडोज प्रासंगिक नहीं था। बेशक, विंडोज कहने के लिए अपने सवाल को संपादित करने के लिए स्वतंत्र था।
बेन वोइगट

1

इस धागे की जाँच करें, शायद यह मदद करता है:

सभी सीएचडी अपवादों को पकड़ना?

मैंने उस सॉफ्टवेयर के साथ अच्छे अनुभव किए:

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

यह बिना किसी अपवाद के किसी फ़ाइल के स्टैक ट्रेस को प्रिंट कर सकता है।


मुझे लगता है कि बिंदु यह है कि एलेक्स एक स्टैकट्रेस exception thrown foo.c@54, ..., re-thrown bar.c@54, ....को मैन्युअल रूप से करने के लिए बिना चाहता है ।
VolkerK
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.