C ++ 'da bir istisnanın nereye atıldığını nasıl bulabilirim?


97

Bir yerde yakalanmamış bir istisna atan bir programım var. Aldığım tek şey, bir istisnanın atıldığına dair bir rapor ve bunun nereye atıldığına dair hiçbir bilgi yok. Derlenmiş bir programın hata ayıklama sembolleri içermesinin, kodumun neresinde bir istisnanın üretildiğini bana bildirmemesi mantıksız görünüyor.

İstisnalarımın gdb'de 'yakalama atışı' ayarlamadan ve atılan her istisna için bir geri izleme çağırmadan nereden geldiğini anlamanın bir yolu var mı?



İstisnayı yakalayın ve dahili mesajın ne olduğunu görün. Bir istisnanın standart istisnalardan (std :: runtime_error) birinden türetilmesi iyi bir uygulama olduğundan, onu catch (std :: exception const & e) ile yakalayabilmelisiniz
Martin York

1
Ve std :: exception / Std :: runtime_error, bir istisnanın "yolunu" ve kaynağını bulma problemini çözer mi?
VolkerK

1
Sorunuz gdb'yi ifade ettiği için, çözümünüzün zaten SO: stackoverflow.com/questions/77005/… ' de olduğunu düşünüyorum. Burada açıklanan çözümü kullandım ve mükemmel çalışıyor.
nöro

2
İşletim sistemini bir etiket aracılığıyla belirtmeyi düşünmelisiniz. Gdb'den bahsettiğinize göre, Windows değil bir Linux çözümü aradığınızı varsayıyorum.
jschmier

Yanıtlar:


74

Sorununuzun giderilmesinde faydalı olabilecek bazı bilgiler burada

Bir istisna yakalanırsa, özel kütüphane işlevi std::terminate()otomatik olarak çağrılır. Terminate aslında bir işlevin göstericisidir ve varsayılan değer Standart C kitaplığı işlevidir std::abort(). Yakalanmamış bir istisna için temizleme yapılmazsa , hiçbir yıkıcı çağrılmadığından bu sorunun hatalarının ayıklanmasında yardımcı olabilir .
† Çağrılmadan önce yığının çözülüp çözülmeyeceği uygulama tanımlıdır std::terminate().


Çağrısı abort(), istisnanın nedenini belirlemek için analiz edilebilecek bir çekirdek dökümü oluşturmada genellikle yararlıdır. ulimit -c unlimited(Linux) aracılığıyla temel dökümleri etkinleştirdiğinizden emin olun .


terminate()Kullanarak kendi fonksiyonunuzu kurabilirsiniz std::set_terminate(). Sonlandırma işleviniz için gdb'de bir kesme noktası ayarlayabilmelisiniz. Sen olabilir senin bir yığın geri izlemesi oluşturmak mümkün terminate()fonksiyonu ve bu Geriye dönük izleme edebilir istisna yerini belirlemede yardımcı olur.

Üzerine kısa bir tartışma vardır yakalanmamış istisnalar içinde C Bruce Eckel en Düşünce ++, 2. Baskı yanı yararlı olabilir.


Yana terminate()aramalar abort()varsayılan (a neden olacaktır tarafından SIGABRTvarsayılan olarak sinyal), sen olabilir bir ayarlayabilirsiniz SIGABRTişleyicisi ve daha sonra sinyal işleyici içinden bir yığın geri izlemesi yazdırmak . Bu geri izleme , istisnanın yerini belirlemede yardımcı olabilir .


Not: Diyorum may C ++ destekler yerel olmayan hata ayrı hata işleme dili yapıları kullanımı yoluyla taşıma ve sıradan kodundan kodunu haber verdiği. Yakalama bloğu, fırlatma noktasından farklı bir işlev / yöntemde bulunabilir ve genellikle bulunur. Ayrıca yorumlarda bana ( Dan teşekkürler ) yığının terminate()çağrılmadan önce çözülüp çözülmediğinin uygulama tanımlı olduğu belirtildi.

Güncelleme: Bir terminate()işlev kümesinde set_terminate()ve bir sinyal işleyicide başka bir işlev kümesinde bir geri izleme oluşturan adında bir Linux test programını bir araya getirdim SIGABRT. Her iki geri izleme de işlenmemiş özel durumun konumunu doğru şekilde gösterir.

Güncelleme 2: Sonlandırma içinde yakalanmamış istisnaları yakalama üzerine bir blog yazısı sayesinde birkaç yeni numara öğrendim; sonlandırma işleyicisi içinde yakalanmamış istisnanın yeniden atılması dahil. throwÖzel sonlandırma işleyicisindeki boş ifadenin GCC ile çalıştığını ve taşınabilir bir çözüm olmadığını unutmamak önemlidir .

Kod:

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

Çıktı:

my_terminate işlenmeyen istisna yakaladı. ne (): ÇALIŞMA SÜRESİ HATASI!
my_terminate backtrace 10 kare döndürdü

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

sinyal 6 (Durduruldu), adres 0x42029331'den 0x1239
crit_err_hdlr backtrace 13 kare döndürdü

[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
Çok ilginç. Her zaman işlenmemiş bir istisnanın yığını en üst seviyeye ( main) gelene kadar çözeceğinden ve sonra arayacağından şüphelenmiştim terminate(). Ancak sizin örneğiniz, hiç gevşemenin yapılmadığını gösteriyor ki bu çok güzel.
Dan

6
1) throw(int)Spesifikasyon gereksizdir. 2) uc->uc_mcontext.eipmuhtemelen platforma çok bağlıdır (örneğin, ...rip64 bitlik bir platformda kullanım). 3) -rdynamicGeri izleme sembollerini elde etmek için ile derleyin . 4) ./a.out 2>&1 | c++filtOldukça geri izleme sembolleri elde etmek için çalıştırın .
Dan

2
"Yakalanmamış bir istisna için temizleme yapılmaz." - Aslında bu uygulama tanımlı. C ++ spesifikasyonunda 15.3 / 9 ve 15.5.1 / 2'ye bakın. "Eşleşen işleyicinin bulunmadığı durumda, terminate () çağrılmadan önce yığının çözülüp çözülmeyeceği uygulama tanımlıdır." Yine de, derleyiciniz destekliyorsa bu harika bir çözümdür!
Dan

1
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;ARM hedefim için çalıştı
stephen

1
Birkaç not: backtrace_symbols () bir malloc yapar ... bu nedenle, başlangıçta bir bellek bloğunu önceden tahsis etmek isteyebilir, ardından geri trace_symbols () 'u çağırmadan hemen önce my_terminate () içinde serbest bırakabilirsiniz. std :: bad_alloc () istisnasının işlenmesi. Ayrıca, <cxxabi.h> 'yi ekleyebilir ve sonra __cxa_demangle () kullanarak çıktı mesajlarında [] dizelerinde' ('ve' + 'arasında görüntülenen karıştırılmış alt dizeden yararlı bir şeyler yapabilirsiniz.
K Scott Piel

51

Dediğiniz gibi, gdb'de 'catch throw' kullanabilir ve atılan her bir istisna için 'backtrace' çağırabiliriz. Bu genellikle elle yapmak için çok sıkıcı olsa da, gdb sürecin otomasyonuna izin verir. Bu, en son yakalanmayan dahil olmak üzere, atılan tüm istisnaların geri izini görmeyi sağlar:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

Daha fazla manuel müdahale olmadan, bu, en son yakalanmayan istisna dahil olmak üzere çok sayıda geri izleme üretir:

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.

İşte bunu özetleyen harika bir blog gönderisi: http://741mhz.com/throw-stacktrace [archive.org üzerinde]


17

Aşağıdaki gibi bir makro oluşturabilirsiniz:

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

... ve size istisnanın atıldığı yeri verecektir (kuşkusuz yığın izi değil). Yukarıdaki kurucuyu alan bazı temel sınıflardan istisnalarınızı türetmeniz gerekir.


18
-1 throw new excation(...)throw exception(...)
Değilsin

7
Tamam, düzelttim. Hem Java hem de C ++ ile çalışan bir programcıyı affedin belki?
Erik Hermansen

Bunu kullanırken. Bununla ilgili sorun, istisnayı gerçekte neyin attığını söylememesidir. Örneğin, bir deneme bloğunda 5 stoi çağrınız varsa, hangisinin gerçekten suçlu olduğunu bilemezsiniz.
Banjocat

5

Kullandığınız İşletim Sistemi / Derleyici hakkında bilgi vermediniz.

Visual Studio C ++ 'da Özel Durumlar çalıştırılabilir.

Bkz "Visual C ++ İstisna-Handling Yapısı'na" ddj.com üzerinde

Benim makale "Ölüm sonrası hata ayıklama" da ddj.com üzerinde, vb giriş için (enstrümantasyon tarafından kullanılır) Win32 yapılandırılmış özel durum işleme kullanmak kodu


Windows / Visual Studio'yu hemen hemen dışlayan gdb dedi.
Ben Voigt

2
"Gdb'den daha az" bir şey isteyeceğini söylüyor, ancak açıkça herhangi bir OS / Derleyiciye atıfta bulunmuyor. İnsanların böyle şeyleri beyan etmemelerinin sorunu budur.
KIRMIZI YUMUŞAK ADAIR

5

noexceptBir istisna bulmak için kodunuzdaki ana dar yerleri işaretleyebilir , ardından libunwind kullanabilirsiniz (sadece -lunwindbağlayıcı parametrelerine ekleyin ) (ile test edilir 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
}

}

Konuyla ilgili güzel bir makale var .


1

Bunu Windows / Visual Studio'da yapmak için kodum var, bir taslak istiyorsanız bana bildirin. Yine de dwarf2 kodu için nasıl yapılacağını bilmiyorum, hızlı bir google libgcc'de muhtemelen ihtiyacınız olan şeyin bir parçası olan _Unwind_Backtrace işlevi olduğunu öne sürer.


Muhtemelen, "bir taslak istiyorsanız bana bildirin" yararlı bir cevap olmadığı için. Ancak _Unwind_Backtrace; telafi edici.
Thomas

OP'nin gdb'den bahsetmesine dayanarak, Windows'un alakalı olmadığını tahmin ettim. Alex, tabii ki sorusunu Windows diyecek şekilde düzenlemekte özgürdü.
Ben Voigt

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.