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


204

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

질문에 대답하려면 :

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

답변:


76

플랫폼에 따라 다릅니다.

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

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

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


71
당신이 연결하는 게시물은 대부분 segfault에서 추적을 생성하는 지점이지만, asker는 예외가 있습니다. 예외는 상당히 다른 짐승입니다.
Shep

8
@Shep에 동의합니다.이 답변은 실제로 GCC에서 던지는 코드의 스택 추적을 얻는 데 도움이되지 않습니다. 가능한 해결책은 내 대답을 참조하십시오.
토마스 TEMPELMANN

1
이 답변은 잘못된 것입니다. 링크는 Linuxnot 에 대한 답변을 가리 킵니다 gcc.
fjardon

이 답변libstdc++ 에서 설명한대로 (GCC 및 잠재적으로 Clang에서 사용 하는) 던지기 메커니즘을 무시할 수 있습니다 .
ingomueller.net

59

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

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

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

2017 년 7 월 11 일 업데이트 : 유용한 코드를 보려면 cahit beyaz의 답변을 살펴보십시오. http://stacktrace.sourceforge.net을 - 나는 아직 사용하지 않은하지만 약속 보인다.


1
불행히도 연결이 끊어졌습니다. 다른 것을 제공 할 수 있습니까?
warran

2
그리고 archive.org도 그것을 모른다. 제길. 절차는 명확해야한다 : 던져 질 때 스택 트레이스를 기록하는 커스텀 클래스의 객체를 던져라.
토마스 템펠 만

1
StackTrace의 홈 페이지에서을 참조하십시오 throw stack_runtime_error. 이 라이브러리가 해당 클래스에서 파생 된 예외에 대해서만 작동하고 std::exception타사 라이브러리의 예외에 대해서는 작동하지 않는다고 추론하는 것이 맞 습니까?
토마스

3
슬프게도 대답은 "아니오, C ++ 예외에서 스택 추적을 얻을 수 없습니다"입니다. 유일한 옵션은 생성 될 때 스택 추적을 생성하는 자체 클래스를 던지는 것입니다. 예를 들어 C ++ std :: 라이브러리의 일부와 같은 것을 사용하지 않으면 운이 없습니다. 죄송합니다.
코드 어 보미 네이터

43

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

#include <boost/stacktrace.hpp>

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

5
부스트 문서는 단지 스택 추적을 캡처하지 설명,하지만 어떻게 예외 그것을하고 주장 할 수 있습니다. 좋은 물건.
moodboom

1
이 stacktrace ()는 시작 안내서에 제공된 소스 파일과 줄 번호를 인쇄합니까?
Gimhani


11

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"

일반적인 벙어리 스택 추적보다 추가 작업을 기꺼이 수행하려는 경우 아마도 훨씬 낫습니다.
Cleared


4

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


4
홈페이지에서을 참조하십시오 throw stack_runtime_error. 이 라이브러리가 해당 클래스에서 파생 된 예외에 대해서만 작동하고 std::exception타사 라이브러리의 예외에 대해서는 작동하지 않는다고 추론하는 것이 맞 습니까?
토마스

4

C ++를 사용 중이고 Boost를 사용하지 않거나 사용하지 않으려는 경우 다음 코드를 사용하여 얽힌 이름으로 역 추적을 인쇄 할 수 있습니다 [원래 사이트에 대한 링크] .

이 솔루션은 Linux에만 해당됩니다. execinfo.h에서 GNU의 libc 함수 backtrace () / backtrace_symbols ()를 사용하여 역 추적을 가져온 다음 __tracea_demangle () (cxxabi.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

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


3

비슷한 문제가 있으며 이식성을 좋아하지만 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]

3

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를 첨부 할 수 있습니까?



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


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.