अपवाद पर C ++ प्रदर्शन स्टैक ट्रेस


204

मैं एक अपवाद फेंके जाने पर उपयोगकर्ता को स्टैक ट्रेस की रिपोर्ट करने का एक तरीका चाहता हूं। इसे करने का बेहतरीन तरीका क्या है? क्या यह बड़ी मात्रा में अतिरिक्त कोड लेता है?

सवालों के जवाब देने के लिए:

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

जवाबों:


76

यह निर्भर करता है कि कौन सा मंच।

जीसीसी पर यह बहुत तुच्छ है, अधिक जानकारी के लिए इस पोस्ट को देखें।

MSVC पर फिर आप StackWalker लाइब्रेरी का उपयोग कर सकते हैं जो विंडोज के लिए आवश्यक सभी अंतर्निहित एपीआई कॉल को संभालती है।

आपको अपने ऐप में इस कार्यक्षमता को एकीकृत करने का सबसे अच्छा तरीका पता लगाना होगा, लेकिन आपको जो कोड लिखना होगा वह न्यूनतम होना चाहिए।


71
आप जिस पोस्ट को अधिकतर अंक से जोड़ते हैं, वह एक सेगफॉल्ट से ट्रेस उत्पन्न करने के लिए होता है, लेकिन प्रश्नकर्ता विशेष रूप से अपवादों का उल्लेख करता है, जो काफी अलग जानवर हैं।
शेप

8
मैं @ शीप से सहमत हूं - यह उत्तर वास्तव में जीसीसी पर फेंकने वाले कोड के ढेर का पता लगाने में मदद नहीं करता है। एक संभावित समाधान के लिए मेरा जवाब देखें।
थॉमस टेम्पेलमैन

1
यह उत्तर भ्रामक है। लिंक एक उत्तर के लिए इंगित करता है विशिष्ट Linuxनहीं gcc
fjardon

आप इस उत्तरlibstdc++ में बताए अनुसार (जीसीसी और संभावित रूप से क्लैंग द्वारा प्रयुक्त) के फेंक तंत्र को ओवरराइड कर सकते हैं ।
ingomueller.net 8

59

एंड्रयू ग्रांट का जवाब फेंकने वाले फ़ंक्शन के स्टैक ट्रेस को प्राप्त करने में मदद नहीं करता है , कम से कम जीसीसी के साथ नहीं, क्योंकि एक फेंक स्टेटमेंट अपने आप पर वर्तमान स्टैक ट्रेस को नहीं बचाता है, और कैच हैंडलर को स्टैक ट्रेस तक पहुंच नहीं होगी वह किसी भी बिंदु पर

एकमात्र तरीका - जीसीसी का उपयोग करना - इसे हल करने के लिए थ्रो इंस्ट्रक्शन के बिंदु पर स्टैक ट्रेस उत्पन्न करना सुनिश्चित करना है, और अपवाद ऑब्जेक्ट के साथ इसे सहेजना है।

इस विधि के लिए, निश्चित रूप से यह आवश्यक है कि हर कोड जो अपवाद फेंकता है, उस विशेष अपवाद वर्ग का उपयोग करता है।

अपडेट ११ जुलाई २०१ July : कुछ सहायक कोड के लिए, cahit beyaz के उत्तर पर एक नज़र डालें, जो http://stacktrace.sourceforge.net की ओर इशारा करता है - मैंने अभी तक इसका उपयोग नहीं किया है, लेकिन यह आशाजनक लग रहा है।


1
दुर्भाग्य से लिंक मृत है। क्या आप कुछ और प्रदान कर सकते हैं?
वारन

2
और आर्काइव.ऑर्ग इसे या तो नहीं जानता है। अरे नहीं। ठीक है, प्रक्रिया स्पष्ट होनी चाहिए: फेंकने के समय स्टैक ट्रेस को रिकॉर्ड करने वाली एक कस्टम क्लास 'ऑब्जेक्ट को फेंक दें।
थॉमस टेम्पेलमैन

1
StackTrace के होम पेज पर, मैं देखता हूं throw stack_runtime_error। क्या मैं यह सुनिश्चित करने में सही हूं कि यह दायित्व केवल उस वर्ग से प्राप्त अपवादों के लिए काम करता है, न कि std::exceptionतीसरे पक्ष के पुस्तकालयों से या अपवादों के लिए ?
थॉमस

3
तो दुख की बात यह है कि "नहीं, आपको C ++ अपवाद से स्टैक ट्रेस नहीं मिल सकता है", एकमात्र विकल्प अपनी कक्षा को फेंकना है जो निर्माण होने पर स्टैक ट्रेस उत्पन्न करता है। यदि आप चीजों का उपयोग करते हुए अटक जाते हैं, जैसे, C ++ std :: पुस्तकालय का कोई भी भाग, आप भाग्य से बाहर हैं। क्षमा करें, आप होने के लिए बेकार है।
कोड एबोमिनेटर

43

यदि आप बूस्ट 1.65 या उससे अधिक का उपयोग कर रहे हैं, तो आप बूस्ट का उपयोग कर सकते हैं :: स्टैकट्रेस :

#include <boost/stacktrace.hpp>

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

5
बढ़ावा डॉक्स केवल एक स्टैक ट्रेस कब्जा नहीं समझाते हैं, लेकिन अपवाद के लिए यह करते हैं और इस बात पर ज़ोर है। उत्तम सामग्री।
मूडबूम

1
क्या यह स्टैट्रेस () सोर्स फाइल और लाइन नंबर्स को प्रिंटस्टार्ट गाइड में दिया गया है?
गिन्हानी

14

2
मैक बैकट्रेस लिंक मृत है।
कोडी कोडमोंकी

मैक बैकट्रेस (iOS के लिए) के लिए सक्रिय लिंक । OSX 10.14.6 Mojave के साथ ठीक काम करने लगता है।
जेट बुलिट

11

मैं एक मानक लायब्रेरी विकल्प (यानी क्रॉस-प्लेटफ़ॉर्म) जोड़ना चाहूंगा कि अपवाद पृष्ठ कैसे उत्पन्न करें, जो C ++ 11 के साथ उपलब्ध हो गया है :

का उपयोग करें std::nested_exceptionऔरstd::throw_with_nested

यह तुम्हें एक ढेर खोल नहीं देगा, लेकिन मेरी राय में अगले सबसे अच्छी बात है। यह StackOverflow पर यहाँ और यहाँ वर्णित है , कि आप अपने अपवादों पर कैसे बैकट्रेस प्राप्त कर सकते हैं एक डिबगर या बोझिल लॉगिंग की आवश्यकता के बिना अपने कोड के अंदर , केवल एक उचित अपवाद हैंडलर लिखकर जो अपवादों को फिर से उखाड़ फेंकेंगे।

चूंकि आप इसे किसी भी व्युत्पन्न अपवाद वर्ग के साथ कर सकते हैं, इसलिए आप इस तरह के बैकट्रेस में बहुत सारी जानकारी जोड़ सकते हैं! आप GitHub पर मेरे MWE पर एक नज़र डाल सकते हैं , जहाँ एक बैकट्रेस कुछ इस तरह दिखेगी:

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"

यह शायद बहुत बेहतर है, यदि आप सामान्य डंब स्टैक ट्रेस की तुलना में अतिरिक्त काम करने के लिए तैयार हैं।
क्लीयर


4

मैं http://stacktrace.sourceforge.net/ प्रोजेक्ट की सलाह देता हूं । यह विंडोज, मैक ओएस और लिनक्स का भी समर्थन करता है


4
इसके होम पेज पर, मैं देख रहा हूं throw stack_runtime_error। क्या मैं यह सुनिश्चित करने में सही हूं कि यह दायित्व केवल उस वर्ग से प्राप्त अपवादों के लिए काम करता है, न कि std::exceptionतीसरे पक्ष के पुस्तकालयों से या अपवादों के लिए ?
थॉमस

4

यदि आप C ++ का उपयोग कर रहे हैं और Boost का उपयोग नहीं कर सकते हैं / नहीं कर सकते हैं, तो आप निम्नलिखित कोड [मूल साइट से लिंक] का उपयोग करते हुए ध्वस्त नामों के साथ बैकट्रेस प्रिंट कर सकते हैं ।

ध्यान दें, यह समाधान लिनक्स के लिए विशिष्ट है। यह GNU के लिबक फ़ंक्शंस backtrace () / backtrace_symbols () से (execinfo.h) का उपयोग करता है ताकि बैकट्रैक प्राप्त कर सके और फिर backtrace सिंबल के नामों को गिराने के लिए __cxa_demangle () (cxxxi.h) का उपयोग करता है।

// 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

विंडोज पर, BugTrap देखें । यह मूल लिंक पर अधिक समय तक नहीं है, लेकिन अभी भी कोडप्रोजेक्ट पर उपलब्ध है।


3

मुझे एक समान समस्या है, और यद्यपि मुझे पोर्टेबिलिटी पसंद है, मुझे केवल जीसीसी समर्थन की आवश्यकता है। Gcc में, execinfo.h और बैकट्रेस कॉल उपलब्ध हैं। फ़ंक्शन नामों को व्यवस्थित करने के लिए, श्री बिंगमैन के पास एक अच्छा कोड कोड है। एक अपवाद पर एक बैकट्रेस को डंप करने के लिए, मैं एक अपवाद बनाता हूं जो कंस्ट्रक्टर में बैकट्रेस प्रिंट करता है। अगर मैं लाइब्रेरी में फेंके गए अपवाद के साथ काम करने की उम्मीद कर रहा था, तो उसे पुनर्निर्माण / लिंकिंग की आवश्यकता हो सकती है ताकि बैकट्रैस्टिंग अपवाद का उपयोग किया जाए।

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

संकलन और इसे चलाने के साथ gcc 4.8.4 अच्छी तरह से अनमैल्ड C ++ फ़ंक्शन नामों के साथ एक बैकट्रेस देता है:

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

चूंकि स्टैक कैच ब्लॉक में प्रवेश करते समय पहले से ही खुला है, इसलिए मेरे मामले में समाधान कुछ अपवादों को नहीं पकड़ना था जो तब SIGABRT की ओर ले जाते हैं। SIGABRT I के लिए सिग्नल हैंडलर में तब fork () और execl () या तो gdb (डीबग बिल्ड में) या Google ब्रेकपैड्स स्टैकवॉक (रिलीज़ बिल्ड में)। इसके अलावा, मैं केवल सिग्नल हैंडलर सुरक्षित कार्यों का उपयोग करने का प्रयास करता हूं।

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

संपादित करें: इसे ब्रेकपैड के लिए काम करने के लिए मुझे यह भी जोड़ना पड़ा:

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

स्रोत: लाइन नंबर की जानकारी के साथ gcc का उपयोग करके C ++ के लिए स्टैक ट्रेस कैसे प्राप्त करें? और क्या यह संभव है कि एक दुर्घटनाग्रस्त प्रक्रिया में gdb संलग्न करें (उर्फ "इन-टाइम" डिबगिंग)


2

खसखस न केवल स्टैक ट्रेस को इकट्ठा कर सकता है, बल्कि पैरामीटर मान, स्थानीय चर, आदि - सब कुछ दुर्घटना के लिए अग्रणी हो सकता है।


2

एक अपवाद को फेंकने के बाद, निम्नलिखित कोड सही निष्पादन को रोक देता है। आपको समाप्ति हैंडलर के साथ windows_exception_handler सेट करने की आवश्यकता है। मैंने इसका परीक्षण MinGW 32बिट्स में किया।

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 फ़ंक्शन के लिए निम्न कोड की जांच करें: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


1

सीपीपी-टूल एक्स_डैग - इजीवेट, मल्टीप्लेट रिकॉर्डर, न्यूनतम संसाधन का उपयोग करना, ट्रेस पर सरल और लचीला।


मैंने 2017.12.24 को इस प्रोजेक्ट की जाँच की, स्रोत और डाउनलोड दोनों ही सुलभ नहीं हैं।
जहरौफ़ी

1
मैं सिर्फ जाँच code.google.com/archive/p/exception-diagnostic/source/default/... और स्रोत डाउनलोड करने योग्य है। क्या आप एक बार और कोशिश कर सकते हैं?
बोरिस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.