İstisnada C ++ görüntü yığını izleme


204

Bir istisna atılırsa kullanıcıya yığın izleme raporlamak için bir yol var istiyorum. Bunu yapmanın en iyi yolu nedir? Büyük miktarda ekstra kod mu alıyor?

Soruları cevaplamak için:

Mümkünse taşınabilir olmasını istiyorum. Bilgilerin açılmasını istiyorum, böylece kullanıcı yığın izlemeyi kopyalayabilir ve bir hata oluşursa bana e-posta gönderebilir.

Yanıtlar:


76

Hangi platforma bağlı.

GCC'de oldukça önemsiz, bu yazıyı görün daha fazla ayrıntı için .

MSVC'de , Windows için gereken tüm temel API çağrılarını işleyen StackWalker kitaplığını kullanabilirsiniz .

Bu işlevi uygulamanıza entegre etmenin en iyi yolunu bulmanız gerekir, ancak yazmanız gereken kod miktarı minimum olmalıdır.


71
bağlandığınız yazı çoğunlukla bir segfaulttan iz oluşturmaya işaret ediyor, ancak asker özellikle oldukça farklı bir canavar olan istisnalardan bahsediyor.
Shep

8
@Shep ile aynı fikirdeyim - bu cevap gerçekten GCC'de kod atma işleminin yığın izini almanıza yardımcı olmaz. Olası bir çözüm için cevabımı görün.
Thomas Tempelmann

1
Bu cevap yanıltıcı. Bağlantı, Linuxdeğil özel bir cevabı gösterir gcc.
fjardon

Bu cevaptalibstdc++ açıklandığı gibi (GCC ve potansiyel olarak Clang tarafından kullanılan) atış mekanizmasını geçersiz kılabilirsiniz .
ingomueller.net

59

Andrew Grant cevabı yok değil yardım bir yığın izleme alma atma bir atış deyimi kendi başına geçerli yığın izleme tasarruf değil, çünkü en azından değil GCC ile, fonksiyonu ve catch işleyici de yığın izleme için erişemez bu nokta artık.

Bunu çözmenin tek yolu - GCC'yi kullanmak - atma talimatı noktasında bir yığın izlemesi oluşturmayı ve bunu istisna nesnesiyle kaydetmeyi sağlamaktır.

Bu yöntem, elbette, bir istisna atan her kodun söz konusu Exception sınıfını kullanmasını gerektirir.

Güncelleme 11 Temmuz 2017 : Bazı yararlı kodlar için http://stacktrace.sourceforge.net - Bu henüz kullanmadım ama umut verici görünüyor cahit beyaz'ın cevabına bir göz atın .


1
Maalesef bağlantı öldü. Başka bir şey verebilir misiniz?
warran

2
Ve archive.org da bilmiyor. Lanet olsun. Prosedür açık olmalıdır: atış sırasında yığın izlemesini kaydeden özel bir sınıf nesnesi atın.
Thomas Tempelmann

1
StackTrace'in ana sayfasında anlıyorum throw stack_runtime_error. Bu lib'in yalnızca std::exceptionüçüncü sınıf kütüphaneler için değil, o sınıftan türetilmiş istisnalar için çalıştığını çıkarırken doğru muyum ?
Thomas

3
Ne yazık ki cevap "Hayır, bir C ++ istisnasından yığın izini alamazsınız", tek seçenek, inşa edildiğinde yığın izlemesi oluşturan kendi sınıfınızı atmaktır. Örneğin, C ++ std :: kütüphanesinin herhangi bir bölümünü kullanmaktan mahrum kalırsanız, şansınız kalmaz. Üzgünüm, sen olmaktan berbatsın.
Kod Abominator

43

Boost 1.65 veya üstünü kullanıyorsanız boost :: stacktrace komutunu kullanabilirsiniz :

#include <boost/stacktrace.hpp>

// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

5
Boost dokümanlar sadece bir yığın izleme yakalayan değil açıklar, ama nasıl istisnalar için bunu ve iddia etmek. Harika şeyler.
moodboom

1
Bu stacktrace (), Başlarken kılavuzunda belirtildiği gibi kaynak dosyayı ve satır numaralarını yazdırır mı?
Gimhani


11

C ++ 11 ile kullanılabilir hale gelen istisna geri izleri oluşturmak için nasıl standart bir kütüphane seçeneği (yani çapraz platform) eklemek istiyorum :

Kullan std::nested_exceptionvestd::throw_with_nested

Bu size bir yığın gevşetmez, ama bence bir sonraki en iyi şey. Burada ve burada StackOverflow'da açıklanmıştır, istisnalarınız hakkında nasıl geri izleme alabilirsiniz hata ayıklayıcı veya hantal bir günlüklemeye gerek kalmadan kodunuzdaki iz alabileceğinizi, yalnızca iç içe geçmiş istisnaları yeniden düzenleyecek uygun bir istisna işleyicisi yazarak açıklayabilirsiniz.

Bunu herhangi bir türetilmiş istisna sınıfıyla yapabileceğiniz için, böyle bir geri izlemeye çok fazla bilgi ekleyebilirsiniz! Ayrıca , bir backtrace'ın şöyle görüneceği GitHub'daki MWE'mize de göz atabilirsiniz :

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Ekstra işi yapmak istiyorsanız, her zamanki aptal yığın izinden daha iyi olabilir.
Daha

4

AFAIK libunwind oldukça taşınabilir ve şu ana kadar kullanımı daha kolay bir şey bulamadım.


libunwind 1.1 işletim sistemi x üzerine kurulmaz.
xaxxon

4

Http://stacktrace.sourceforge.net/ projesini öneriyorum . Windows, Mac OS ve ayrıca Linux'u destekler


4
Ana sayfasında görüyorum throw stack_runtime_error. Bu lib'in yalnızca std::exceptionüçüncü sınıf kütüphaneler için değil, o sınıftan türetilmiş istisnalar için mi çalıştığı sonucunu çıkarıyor muyum ?
Thomas

4

C ++ kullanıyorsanız ve Boost'u kullanmak istemiyorsanız / kullanamıyorsanız, aşağıdaki kodu kullanarak [orijinal siteye bağlantı] demonte edilmiş adlarla geri izleme yazdırabilirsiniz .

Bu çözümün Linux'a özgü olduğunu unutmayın. Geri izlemeleri almak için GNU'nun libc işlevlerini backtrace () / backtrace_symbols () (execinfo.h) kullanır ve sonra geri izleme sembol adlarını çekmek için __cxa_demangle () (cxxabi.h) kullanır.

// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0

#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>

/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
    fprintf(out, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));

    if (addrlen == 0) {
    fprintf(out, "  <empty, possibly corrupt>\n");
    return;
    }

    // resolve addresses into strings containing "filename(function+address)",
    // this array must be free()-ed
    char** symbollist = backtrace_symbols(addrlist, addrlen);

    // allocate string which will be filled with the demangled function name
    size_t funcnamesize = 256;
    char* funcname = (char*)malloc(funcnamesize);

    // iterate over the returned symbol lines. skip the first, it is the
    // address of this function.
    for (int i = 1; i < addrlen; i++)
    {
    char *begin_name = 0, *begin_offset = 0, *end_offset = 0;

    // find parentheses and +address offset surrounding the mangled name:
    // ./module(function+0x15c) [0x8048a6d]
    for (char *p = symbollist[i]; *p; ++p)
    {
        if (*p == '(')
        begin_name = p;
        else if (*p == '+')
        begin_offset = p;
        else if (*p == ')' && begin_offset) {
        end_offset = p;
        break;
        }
    }

    if (begin_name && begin_offset && end_offset
        && begin_name < begin_offset)
    {
        *begin_name++ = '\0';
        *begin_offset++ = '\0';
        *end_offset = '\0';

        // mangled name is now in [begin_name, begin_offset) and caller
        // offset in [begin_offset, end_offset). now apply
        // __cxa_demangle():

        int status;
        char* ret = abi::__cxa_demangle(begin_name,
                        funcname, &funcnamesize, &status);
        if (status == 0) {
        funcname = ret; // use possibly realloc()-ed string
        fprintf(out, "  %s : %s+%s\n",
            symbollist[i], funcname, begin_offset);
        }
        else {
        // demangling failed. Output function name as a C function with
        // no arguments.
        fprintf(out, "  %s : %s()+%s\n",
            symbollist[i], begin_name, begin_offset);
        }
    }
    else
    {
        // couldn't parse the line? print the whole line.
        fprintf(out, "  %s\n", symbollist[i]);
    }
    }

    free(funcname);
    free(symbollist);
}

#endif // _STACKTRACE_H_

HTH!




3

Benzer bir sorunum var ve taşınabilirliği sevmeme rağmen, sadece gcc desteğine ihtiyacım var. Gcc'de execinfo.h ve backtrace çağrıları kullanılabilir. İşlev adlarını çözmek için Bay Bingmann'ın güzel bir kodu var. Bir istisna üzerine bir geri iz dökümü için, yapıcıda geri iz yazdırır bir özel durum oluşturun. Bunu bir kitaplıkta atılan bir istisna ile çalışmayı bekliyordum, geri çekme istisnasının kullanılması için yeniden oluşturma / bağlama gerektirebilir.

/******************************************
#Makefile with flags for printing backtrace with function names
# compile with symbols for backtrace
CXXFLAGS=-g
# add symbols to dynamic symbol table for backtrace
LDFLAGS=-rdynamic
turducken: turducken.cc
******************************************/

#include <cstdio>
#include <stdexcept>
#include <execinfo.h>
#include "stacktrace.h" /* https://panthema.net/2008/0901-stacktrace-demangled/ */

// simple exception that prints backtrace when constructed
class btoverflow_error: public std::overflow_error
{
    public:
    btoverflow_error( const std::string& arg ) :
        std::overflow_error( arg )
    {
        print_stacktrace();
    };
};


void chicken(void)
{
    throw btoverflow_error( "too big" );
}

void duck(void)
{
    chicken();
}

void turkey(void)
{
    duck();
}

int main( int argc, char *argv[])
{
    try
    {
        turkey();
    }
    catch( btoverflow_error e)
    {
        printf( "caught exception: %s\n", e.what() );
    }
}

Bunu gcc 4.8.4 ile derlemek ve çalıştırmak, güzel yönetilmeyen C ++ işlev adlarına sahip bir geri izleme sağlar:

stack trace:
 ./turducken : btoverflow_error::btoverflow_error(std::string const&)+0x43
 ./turducken : chicken()+0x48
 ./turducken : duck()+0x9
 ./turducken : turkey()+0x9
 ./turducken : main()+0x15
 /lib/x86_64-linux-gnu/libc.so.6 : __libc_start_main()+0xf5
 ./turducken() [0x401629]

3

Yakalama bloğuna girerken yığın zaten çözüldüğünden, benim durumumdaki çözüm belirli istisnaları yakalamak değildi daha sonra bir SIGABRT'ye yol açan . SIGABRT I için sinyal işleyicide fork () ve execl () ya gdb (hata ayıklama yapılarında) veya Google breakpads stackwalk (yayın yapılarında). Ayrıca sadece sinyal işleyici güvenli işlevlerini kullanmaya çalışıyorum.

GDB:

static const char BACKTRACE_START[] = "<2>--- backtrace of entire stack ---\n";
static const char BACKTRACE_STOP[] = "<2>--- backtrace finished ---\n";

static char *ltrim(char *s)
{
    while (' ' == *s) {
        s++;
    }
    return s;
}

void Backtracer::print()
{
    int child_pid = ::fork();
    if (child_pid == 0) {
        // redirect stdout to stderr
        ::dup2(2, 1);

        // create buffer for parent pid (2+16+1 spaces to allow up to a 64 bit hex parent pid)
        char pid_buf[32];
        const char* stem = "                   ";
        const char* s = stem;
        char* d = &pid_buf[0];
        while (static_cast<bool>(*s))
        {
            *d++ = *s++;
        }
        *d-- = '\0';
        char* hexppid = d;

        // write parent pid to buffer and prefix with 0x
        int ppid = getppid();
        while (ppid != 0) {
            *hexppid = ((ppid & 0xF) + '0');
            if(*hexppid > '9') {
                *hexppid += 'a' - '0' - 10;
            }
            --hexppid;
            ppid >>= 4;
        }
        *hexppid-- = 'x';
        *hexppid = '0';

        // invoke GDB
        char name_buf[512];
        name_buf[::readlink("/proc/self/exe", &name_buf[0], 511)] = 0;
        ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_START[0], sizeof(BACKTRACE_START));
        (void)r;
        ::execl("/usr/bin/gdb",
                "/usr/bin/gdb", "--batch", "-n", "-ex", "thread apply all bt full", "-ex", "quit",
                &name_buf[0], ltrim(&pid_buf[0]), nullptr);
        ::exit(1); // if GDB failed to start
    } else if (child_pid == -1) {
        ::exit(1); // if forking failed
    } else {
        // make it work for non root users
        if (0 != getuid()) {
            ::prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
        }
        ::waitpid(child_pid, nullptr, 0);
        ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_STOP[0], sizeof(BACKTRACE_STOP));
        (void)r;
    }
}

minidump_stackwalk:

static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
    int child_pid = ::fork();
    if (child_pid == 0) {
        ::dup2(open("/dev/null", O_WRONLY), 2); // ignore verbose output on stderr
        ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_START[0], sizeof(MINIDUMP_STACKWALK_START));
        (void)r;
        ::execl("/usr/bin/minidump_stackwalk", "/usr/bin/minidump_stackwalk", descriptor.path(), "/usr/share/breakpad-syms", nullptr);
        ::exit(1); // if minidump_stackwalk failed to start
    } else if (child_pid == -1) {
        ::exit(1); // if forking failed
    } else {
        ::waitpid(child_pid, nullptr, 0);
        ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_STOP[0], sizeof(MINIDUMP_STACKWALK_STOP));
        (void)r;
    }
    ::remove(descriptor.path()); // this is not signal safe anymore but should still work
    return succeeded;
}

Düzenleme: Breakpad için çalışması için ben de eklemek zorunda kaldı:

std::set_terminate([]()
{
    ssize_t r = ::write(STDERR_FILENO, EXCEPTION, sizeof(EXCEPTION));
    (void)r;
    google_breakpad::ExceptionHandler::WriteMinidump(std::string("/tmp"), dumpCallback, NULL);
    exit(1); // avoid creating a second dump by not calling std::abort
});

Kaynak: satır numarası bilgileri ile gcc kullanarak C ++ için bir yığın izleme nasıl alınır? ve gdb'yi çökmüş bir sürece eklemek mümkün mü (diğer adıyla "tam zamanında" hata ayıklama)


2

Poppy sadece yığın izlemesini değil, aynı zamanda parametre değerlerini, yerel değişkenleri vb. De toplayabilir - çökmeye yol açan her şey.


2

Aşağıdaki kod, bir istisna atıldıktan hemen sonra yürütmeyi durdurur. Bir sonlandırma işleyici ile birlikte bir windows_exception_handler ayarlamanız gerekir. Bunu MinGW 32 bit'te test ettim.

void beforeCrash(void);

static const bool SET_TERMINATE = std::set_terminate(beforeCrash);

void beforeCrash() {
    __asm("int3");
}

int main(int argc, char *argv[])
{
SetUnhandledExceptionFilter(windows_exception_handler);
...
}

Windows_exception_handler işlevi için aşağıdaki kodu kontrol edin: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


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.