Dấu vết ngăn xếp hiển thị C ++ trên ngoại lệ


204

Tôi muốn có một cách để báo cáo theo dõi ngăn xếp cho người dùng nếu một ngoại lệ được ném ra. Cách tốt nhất để làm việc này là gì? Liệu nó có mất một lượng lớn mã bổ sung?

Trả lời câu hỏi:

Tôi muốn nó có thể mang theo nếu có thể. Tôi muốn thông tin bật lên, vì vậy người dùng có thể sao chép dấu vết ngăn xếp và gửi email cho tôi nếu có lỗi xảy ra.

Câu trả lời:


76

Nó phụ thuộc vào nền tảng nào.

Trên GCC, nó khá tầm thường, xem bài đăng này để biết thêm chi tiết.

Trên MSVC sau đó bạn có thể sử dụng thư viện StackWalker xử lý tất cả các lệnh gọi API cơ bản cần thiết cho Windows.

Bạn sẽ phải tìm ra cách tốt nhất để tích hợp chức năng này vào ứng dụng của mình, nhưng số lượng mã bạn cần viết phải ở mức tối thiểu.


71
bài đăng bạn liên kết đến hầu hết các điểm để tạo ra dấu vết từ segfault, nhưng người hỏi đặc biệt đề cập đến các ngoại lệ, đó là một con thú hoàn toàn khác.
Cừu

8
Tôi đồng ý với @Shep - câu trả lời này không thực sự hữu ích với việc theo dõi ngăn xếp của mã ném trên GCC. Xem câu trả lời của tôi cho một giải pháp có thể.
Thomas Tempelmann

1
Câu trả lời này là sai lệch. Các liên kết chỉ đến một câu trả lời cụ thể để Linuxkhông gcc.
fjardon

Bạn có thể ghi đè cơ chế ném của libstdc++(được sử dụng bởi GCC và có khả năng Clang) như được giải thích trong câu trả lời này .
ingomueller.net

59

Câu trả lời của Andrew Grant không giúp lấy dấu vết ngăn xếp của chức năng ném , ít nhất là không phải với GCC, bởi vì câu lệnh ném không tự lưu dấu vết ngăn xếp hiện tại và trình xử lý bắt sẽ không có quyền truy cập vào dấu vết ngăn xếp tại điểm đó nữa.

Cách duy nhất - sử dụng GCC - để giải quyết điều này là đảm bảo tạo ra dấu vết ngăn xếp tại điểm của lệnh ném và lưu nó với đối tượng ngoại lệ.

Tất nhiên, phương thức này yêu cầu rằng mọi mã ném ngoại lệ đều sử dụng lớp Exception cụ thể đó.

Cập nhật ngày 11 tháng 7 năm 2017 : Đối với một số mã hữu ích, hãy xem câu trả lời của cahit beyaz, trỏ đến http://stacktrace.sourceforge.net - Tôi chưa sử dụng nó nhưng có vẻ đầy hứa hẹn.


1
Thật không may liên kết đã chết. Bạn có thể cung cấp một số khác?
warran

2
Và archive.org cũng không biết điều đó. Chỉ trích. Chà, thủ tục nên rõ ràng: ném một đối tượng của lớp tùy chỉnh ghi lại dấu vết ngăn xếp tại thời điểm ném.
Thomas Tempelmann

1
Trên trang chủ của StackTrace, tôi thấy throw stack_runtime_error. Tôi có đúng không khi suy luận rằng lib này chỉ hoạt động đối với các ngoại lệ xuất phát từ lớp đó, chứ không phải cho std::exceptionhoặc ngoại lệ từ các thư viện của bên thứ ba?
Thomas

3
Thật đáng buồn, câu trả lời là "Không, bạn không thể có được dấu vết ngăn xếp từ ngoại lệ C ++", tùy chọn duy nhất là ném lớp của chính bạn tạo ra dấu vết ngăn xếp khi nó được xây dựng. Nếu bạn bị mắc kẹt khi sử dụng những thứ như, giả sử, bất kỳ phần nào của thư viện C ++ std ::, bạn sẽ không gặp may. Xin lỗi, hút là bạn.
Mã số Abominator

43

Nếu bạn đang sử dụng Boost 1.65 trở lên, bạn có thể sử dụng boost :: stacktrace :

#include <boost/stacktrace.hpp>

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

5
Các tài liệu tăng explain không chỉ chụp một chồng dấu vết, nhưng làm thế nào để làm điều đó cho trường hợp ngoại lệ và khẳng định. Công cụ tuyệt vời.
psychboom

1
Liệu stacktrace () này có in tệp nguồn và số dòng như được đưa ra trong hướng dẫn Bắt đầu không?
Gimhani


11

Tôi muốn thêm một tùy chọn thư viện tiêu chuẩn (tức là đa nền tảng) cách tạo backtraces ngoại lệ, đã có sẵn với C ++ 11 :

Sử dụng std::nested_exceptionstd::throw_with_nested

Điều này sẽ không cung cấp cho bạn một ngăn xếp thư giãn, nhưng theo tôi điều tốt nhất tiếp theo. Nó được mô tả trên StackOverflow tại đâyđây , làm thế nào bạn có thể nhận được một phản hồi về các ngoại lệ của bạn bên trong mã của bạn mà không cần trình gỡ lỗi hoặc ghi nhật ký rườm rà, bằng cách viết một trình xử lý ngoại lệ thích hợp sẽ lấy lại các ngoại lệ lồng nhau.

Vì bạn có thể làm điều này với bất kỳ lớp ngoại lệ dẫn xuất nào, bạn có thể thêm rất nhiều thông tin vào một backtrace như vậy! Bạn cũng có thể xem MWE của tôi trên GitHub , nơi một backtrace sẽ trông giống như thế này:

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"

Điều này có lẽ tốt hơn rất nhiều, nếu bạn sẵn sàng làm thêm, hơn là dấu vết ngăn xếp câm thông thường.
Rõ ràng hơn vào

4

AFAIK libunwind khá di động và cho đến nay tôi chưa tìm thấy thứ gì dễ sử dụng hơn.


libunwind 1.1 không xây dựng trên os x.
xaxxon

4

Tôi khuyên bạn nên dùng dự án http://stacktrace.sourceforge.net/ . Nó hỗ trợ Windows, Mac OS và cả Linux


4
Trên trang chủ của nó, tôi thấy throw stack_runtime_error. Tôi có đúng không khi suy luận rằng lib này chỉ hoạt động đối với các ngoại lệ xuất phát từ lớp đó, chứ không phải cho std::exceptionhoặc ngoại lệ từ các thư viện của bên thứ ba?
Thomas

4

Nếu bạn đang sử dụng C ++ và không muốn / không thể sử dụng Boost, bạn có thể in backtrace với các tên được đánh dấu bằng cách sử dụng mã sau [liên kết đến trang web gốc] .

Lưu ý, giải pháp này dành riêng cho Linux. Nó sử dụng các hàm libc của GNU backtrace () / backtrace_symbols () (từ execinfo.h) để lấy backtraces và sau đó sử dụng __cxa_demangle () (từ cxxabi.h) để đánh dấu tên biểu tượng backtrace.

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

Trên Windows, hãy kiểm tra BugTrap . Nó không còn ở liên kết ban đầu, nhưng nó vẫn có sẵn trên CodeProject.


3

Tôi có một vấn đề tương tự, và mặc dù tôi thích tính di động, tôi chỉ cần hỗ trợ gcc. Trong gcc, execinfo.h và các cuộc gọi backtrace có sẵn. Để giải mã các tên hàm, ông Bingmann có một đoạn mã đẹp. Để kết xuất một backtrace trên một ngoại lệ, tôi tạo một ngoại lệ in backtrace trong hàm tạo. Nếu tôi đang mong đợi nó hoạt động với một ngoại lệ được ném vào thư viện, thì nó có thể yêu cầu xây dựng lại / liên kết để ngoại lệ backtracing được sử dụng.

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

Biên dịch và chạy nó với gcc 4.8.4 mang lại một nền tảng với các tên hàm C ++ không bị thay đổi độc đáo:

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

Vì ngăn xếp đã không có sẵn khi vào khối bắt, giải pháp trong trường hợp của tôi là không bắt được một số ngoại lệ nhất định dẫn đến SIGABRT. Trong trình xử lý tín hiệu cho SIGABRT I sau đó fork () và execl () hoặc gdb (trong bản dựng gỡ lỗi) hoặc stackwalks breakpad của Google (trong bản dựng phát hành). Ngoài ra tôi cố gắng chỉ sử dụng các chức năng xử lý tín hiệu an toàn.

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

Chỉnh sửa: Để làm cho nó hoạt động cho breakpad tôi cũng phải thêm cái này:

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

Nguồn: Làm thế nào để có được dấu vết ngăn xếp cho C ++ bằng gcc với thông tin số dòng? Có thể đính kèm gdb vào một quy trình bị lỗi (hay còn gọi là gỡ lỗi "chỉ trong thời gian")


2

Poppy có thể thu thập không chỉ dấu vết ngăn xếp, mà cả các giá trị tham số, biến cục bộ, v.v. - mọi thứ dẫn đến sự cố.


2

Đoạn mã sau dừng thực thi ngay sau khi ném ngoại lệ. Bạn cần đặt windows_exception_handler cùng với trình xử lý kết thúc. Tôi đã thử nghiệm điều này trong MinGW 32 bit.

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

Kiểm tra mã sau đây cho chức năng windows_exception_handler: http://www.codingisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.