Programing

예외에 대한 C ++ 디스플레이 스택 추적

lottogame 2020. 5. 21. 08:02
반응형

예외에 대한 C ++ 디스플레이 스택 추적


예외가 발생하면 스택 추적을 사용자에게보고하는 방법을 원합니다. 가장 좋은 방법은 무엇입니까? 엄청난 양의 추가 코드가 필요합니까?

질문에 대답하려면 :

가능하다면 휴대용으로 만들고 싶습니다. 정보가 팝업되기를 원하므로 오류가 발생하면 사용자가 스택 추적을 복사하여 전자 메일로 보낼 수 있습니다.


플랫폼에 따라 다릅니다.

GCC에서는 사소한 것이지만 자세한 내용 이 게시물 을 참조하십시오.

MSVC 에서는 Windows에 필요한 모든 기본 API 호출을 처리 하는 StackWalker 라이브러리를 사용할 수 있습니다 .

이 기능을 앱에 통합하는 가장 좋은 방법을 찾아야하지만 작성해야하는 코드의 양은 최소화되어야합니다.


앤드류 그랜트의 대답은하지 하지 도움이의 스택 추적을 받고 던지는 throw 문은 자신의 현재 스택 트레이스를 저장하지 않기 때문에, 적어도하지 GCC와 기능을하고, 캐치 처리기에서 스택 추적에 액세스 할 수 없습니다 더 이상은.

GCC를 사용하여이를 해결하는 유일한 방법은 throw 명령 시점에서 스택 추적을 생성하고 예외 객체와 함께 저장하는 것입니다.

물론이 메소드는 예외를 발생시키는 모든 코드가 해당 특정 예외 클래스를 사용해야합니다.

2017 (11) 7 월 업데이트 : 몇 가지 유용한 코드의 경우, 포인트 cahit beyaz의 대답을 살펴 취 http://stacktrace.sourceforge.net을 - 나는 아직 사용하지 않은하지만 약속 보인다.


Boost 1.65 이상을 사용하는 경우 boost :: stacktrace를 사용할 수 있습니다 .

#include <boost/stacktrace.hpp>

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

유닉스 : 역 추적

맥 : 역 추적

Windows : CaptureBackTrace


C ++ 11에서 사용할 수있는 예외 역 추적을 생성하는 방법에 표준 라이브러리 옵션 (예 : 크로스 플랫폼) 을 추가하고 싶습니다 .

사용 std::nested_exceptionstd::throw_with_nested

이렇게하면 스택이 풀리지 않지만 내 생각으로는 다음으로 가장 좋은 것이 있습니다. 여기여기 에 스택 오버플로에 설명되어 있으며 중첩 된 예외를 다시 던질 적절한 예외 처리기를 작성하여 디버거 또는 번거로운 로깅없이 코드 내부의 예외에 대한 역 추적을 얻는 방법에 대해 설명합니다 .

파생 된 예외 클래스로이 작업을 수행 할 수 있으므로 이러한 역 추적에 많은 정보를 추가 할 수 있습니다! 역 추적이 다음과 같은 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"

AFAIK libunwind는 이식성이 뛰어나서 지금까지 사용하기 쉬운 것을 찾지 못했습니다.


http://stacktrace.sourceforge.net/ 프로젝트를 권장 합니다. Windows, Mac OS 및 Linux도 지원합니다


g ++로 리눅스 에서이 lib를 확인하십시오.

https://sourceforge.net/projects/libcsdbg

그것은 당신을 위해 모든 작업을 수행


Windows에서는 BugTrap을 확인하십시오 . 더 이상 원래 링크에는 없지만 CodeProject에서 계속 사용할 수 있습니다.


catch 블록에 들어갈 때 스택이 이미 풀려 있기 때문에 필자의 경우 해결책은 특정 예외포착하지 못하여 SIGABRT로 이어졌습니다. SIGABRT의 신호 처리기에서 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를 첨부 할 수 있습니까?


양귀비 는 스택 추적뿐만 아니라 매개 변수 값, 로컬 변수 등을 수집하여 충돌을 일으키는 모든 것을 수집 할 수 있습니다.


비슷한 문제가 있으며 이식성을 좋아하지만 gcc 지원 만 필요합니다. 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]

Cpp-tool ex_diag- 간편하고 유연한 다중 플랫폼, 최소 리소스 사용, 단순하고 유연합니다.


The following code stops the execution right after an exception is thrown. You need to set a windows_exception_handler along with a termination handler. I tested this in MinGW 32bits.

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

Check the following code for the windows_exception_handler function: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


If you are using C++ and don't want/can't use Boost, you can print backtrace with demangled names using the following code [link to the original site].

Note, this solution is specific to Linux. It uses GNU's libc functions backtrace()/backtrace_symbols() (from execinfo.h) to get the backtraces and then uses __cxa_demangle() (from cxxabi.h) for demangling the backtrace symbol names.

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

참고URL : https://stackoverflow.com/questions/691719/c-display-stack-trace-on-exception

반응형