it-swarm-vi.tech

Cách tự động tạo stacktrace khi chương trình của tôi gặp sự cố

Tôi đang làm việc trên Linux với trình biên dịch GCC. Khi chương trình C++ của tôi gặp sự cố, tôi muốn nó tự động tạo một stacktrace.

Chương trình của tôi đang được điều hành bởi nhiều người dùng khác nhau và nó cũng chạy trên Linux, Windows và Macintosh (tất cả các phiên bản được biên dịch bằng cách sử dụng gcc).

Tôi muốn chương trình của tôi có thể tạo ra dấu vết ngăn xếp khi nó gặp sự cố và lần sau khi người dùng chạy nó, nó sẽ hỏi họ xem có thể gửi theo dõi ngăn xếp cho tôi để tôi có thể theo dõi vấn đề không. Tôi có thể xử lý việc gửi thông tin cho tôi nhưng tôi không biết cách tạo chuỗi theo dõi. Có ý kiến ​​gì không?

518
KPexEA

Đối với Linux và tôi tin rằng Mac OS X, nếu bạn đang sử dụng gcc hoặc bất kỳ trình biên dịch nào sử dụng glibc, bạn có thể sử dụng các hàm backtrace () trong execinfo.h để in stacktrace và thoát một cách duyên dáng khi bạn gặp lỗi phân đoạn. Tài liệu có thể được tìm thấy trong hướng dẫn libc .

Đây là một chương trình ví dụ cài đặt trình xử lý SIGSEGV và in một stacktrace thành stderr khi nó phân tách. Hàm baz() ở đây gây ra segfault kích hoạt trình xử lý:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

Biên dịch với -g -rdynamic giúp bạn có thông tin biểu tượng trong đầu ra của mình, mà glibc có thể sử dụng để tạo một stacktrace Nice:

$ gcc -g -rdynamic ./test.c -o test

Thực hiện điều này giúp bạn có đầu ra này:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

Điều này cho thấy mô-đun tải, bù và chức năng mà mỗi khung trong ngăn xếp đến từ. Tại đây, bạn có thể thấy trình xử lý tín hiệu ở trên cùng của ngăn xếp và các hàm libc trước main ngoài main, foo, barbaz.

447
Todd Gamblin

Linux

Mặc dù việc sử dụng các hàm backtrace () trong execinfo.h để in một stacktrace và thoát một cách duyên dáng khi bạn gặp lỗi phân đoạn đã đã được đề xuất , tôi không thấy đề cập đến những rắc rối cần thiết để đảm bảo các điểm backtrace vị trí thực tế của lỗi (ít nhất là đối với một số kiến ​​trúc - x86 & ARM).

Hai mục đầu tiên trong chuỗi khung ngăn xếp khi bạn vào trình xử lý tín hiệu chứa địa chỉ trả về bên trong trình xử lý tín hiệu và một mục bên trong sigaction () trong libc. Khung ngăn xếp của chức năng cuối cùng được gọi trước khi tín hiệu (là vị trí của lỗi) bị mất.

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 struct ucontext   *uc_link;
 stack_t           uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#Elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other Arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

Đầu ra

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

Tất cả các mối nguy hiểm khi gọi các hàm backtrace () trong trình xử lý tín hiệu vẫn tồn tại và không nên bỏ qua, nhưng tôi thấy chức năng mà tôi mô tả ở đây khá hữu ích trong việc gỡ lỗi.

Điều quan trọng cần lưu ý là ví dụ tôi cung cấp được phát triển/thử nghiệm trên Linux cho x86. Tôi cũng đã thực hiện thành công điều này trên ARM bằng cách sử dụng uc_mcontext.arm_pc thay vì uc_mcontext.eip

Đây là một liên kết đến bài viết mà tôi đã tìm hiểu chi tiết cho việc triển khai này: http://www.linuxjournal.com/article/6391

115
jschmier

Nó thậm chí còn dễ dàng hơn "man backtrace", có một thư viện tài liệu nhỏ (cụ thể là GNU) được phân phối với glibc là libSegFault.so, mà tôi tin là do Ulrich Drepper viết để hỗ trợ chương trình Catchsegv (xem "man Catchsegv").

Điều này cho chúng ta 3 khả năng. Thay vì chạy "chương trình -o hai":

  1. Chạy trong Catchsegv:

    $ catchsegv program -o hai
    
  2. Liên kết với libSegFault khi chạy:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Liên kết với libSegFault tại thời gian biên dịch:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    

Trong cả 3 trường hợp, bạn sẽ nhận được backtraces rõ ràng hơn với ít tối ưu hóa hơn (gcc -O0 hoặc -O1) và các biểu tượng gỡ lỗi (gcc -g). Nếu không, bạn có thể chỉ cần một đống địa chỉ bộ nhớ.

Bạn cũng có thể bắt được nhiều tín hiệu hơn cho dấu vết ngăn xếp với thứ gì đó như:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

Đầu ra sẽ trông giống như thế này (chú ý phần nền ở phía dưới):

*** Segmentation fault Register dump:

 EAX: 0000000c   EBX: 00000080   ECX:
00000000   EDX: 0000000c  ESI:
bfdbf080   EDI: 080497e0   EBP:
bfdbee38   ESP: bfdbee20

 EIP: 0805640f   EFLAGS: 00010282

 CS: 0073   DS: 007b   ES: 007b   FS:
0000   GS: 0033   SS: 007b

 Trap: 0000000e   Error: 00000004  
OldMask: 00000000  ESP/signal:
bfdbee20   CR2: 00000024

 FPUCW: ffff037f   FPUSW: ffff0000  
TAG: ffffffff  IPOFF: 00000000  
CSSEL: 0000   DATAOFF: 00000000  
DATASEL: 0000

 ST(0) 0000 0000000000000000   ST(1)
0000 0000000000000000  ST(2) 0000
0000000000000000   ST(3) 0000
0000000000000000  ST(4) 0000
0000000000000000   ST(5) 0000
0000000000000000  ST(6) 0000
0000000000000000   ST(7) 0000
0000000000000000

Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]

Nếu bạn muốn biết thông tin chi tiết, nguồn tốt nhất không may là nguồn: Xem http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c và thư mục mẹ của nó http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

112
jhclark

Mặc dù một câu trả lời đúng đã được cung cấp mô tả cách sử dụng hàm GNU libc backtrace()1 và tôi đã cung cấp câu trả lời của riêng tôi mô tả cách đảm bảo quay lui từ bộ xử lý tín hiệu chỉ đến vị trí thực tế của lỗi2, Tôi không thấy bất kỳ đề cập nào về demangling Biểu tượng C++ xuất ra từ backtrace.

Khi nhận được backtraces từ chương trình C++, đầu ra có thể được chạy qua c++filt1 để giải mã các ký hiệu hoặc bằng cách sử dụng abi::__cxa_demangle1 trực tiếp.

  • 1 Linux & OS X Lưu ý rằng c++filt__cxa_demangle là dành riêng cho GCC
  • 2 Linux

Ví dụ C++ Linux sau đây sử dụng trình xử lý tín hiệu tương tự như câu trả lời khác của tôi và giải thích cách c++filt có thể được sử dụng để giải mã các ký hiệu.

:

class foo
{
public:
    foo() { foo1(); }

private:
    void foo1() { foo2(); }
    void foo2() { foo3(); }
    void foo3() { foo4(); }
    void foo4() { crash(); }
    void crash() { char * p = NULL; *p = 0; }
};

int main(int argc, char ** argv)
{
    // Setup signal handler for SIGSEGV
    ...

    foo * f = new foo();
    return 0;
}

Đầu ra (./test):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Đầu ra bị biến dạng (./test 2>&1 | c++filt):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Phần sau đây xây dựng trình xử lý tín hiệu từ câu trả lời gốc và có thể thay thế trình xử lý tín hiệu trong ví dụ trên để giải thích cách abi::__cxa_demangle có thể được sử dụng để tháo gỡ các ký hiệu. Trình xử lý tín hiệu này tạo ra đầu ra được khử giống như ví dụ trên.

:

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " << caller_address 
              << std::endl << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);    

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i)
    {
        char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;

        // find parantheses and +address offset surrounding mangled name
        for (char *p = messages[i]; *p; ++p)
        {
            if (*p == '(') 
            {
                mangled_name = p; 
            }
            else if (*p == '+') 
            {
                offset_begin = p;
            }
            else if (*p == ')')
            {
                offset_end = p;
                break;
            }
        }

        // if the line could be processed, attempt to demangle the symbol
        if (mangled_name && offset_begin && offset_end && 
            mangled_name < offset_begin)
        {
            *mangled_name++ = '\0';
            *offset_begin++ = '\0';
            *offset_end++ = '\0';

            int status;
            char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);

            // if demangling is successful, output the demangled function name
            if (status == 0)
            {    
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << real_name << "+" << offset_begin << offset_end 
                          << std::endl;

            }
            // otherwise, output the mangled function name
            else
            {
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << mangled_name << "+" << offset_begin << offset_end 
                          << std::endl;
            }
            free(real_name);
        }
        // otherwise, print the whole line
        else
        {
            std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
        }
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}
76
jschmier

Có thể đáng xem Google Breakpad , một công cụ tạo bãi đổ đa nền tảng và các công cụ để xử lý các bãi chứa.

33
Simon Steele

Bạn đã không chỉ định hệ điều hành của mình, vì vậy điều này rất khó trả lời. Nếu bạn đang sử dụng một hệ thống dựa trên gnu libc, bạn có thể sử dụng chức năng libc backtrace().

GCC cũng có hai nội dung có thể hỗ trợ bạn, nhưng có thể được thực hiện đầy đủ trên kiến ​​trúc của bạn và đó là __builtin_frame_address__builtin_return_address. Cả hai đều muốn một mức nguyên ngay lập tức (bởi ngay lập tức, ý tôi là nó không thể là một biến). Nếu __builtin_frame_address cho một mức nhất định khác không, thì sẽ an toàn khi lấy địa chỉ trả về cùng cấp.

21
Brian Mitchell

ulimit -c <value> đặt giới hạn kích thước tệp lõi trên unix. Theo mặc định, giới hạn kích thước tệp lõi là 0. Bạn có thể thấy các giá trị ulimit của mình với ulimit -a.

đồng thời, nếu bạn chạy chương trình của mình từ bên trong gdb, nó sẽ tạm dừng chương trình của bạn về "vi phạm phân khúc" (SIGSEGV, nói chung khi bạn truy cập vào một phần bộ nhớ mà bạn đã phân bổ) hoặc bạn có thể đặt điểm dừng.

ddd và nemiver là giao diện người dùng cho gdb, giúp làm việc với người mới dễ dàng hơn nhiều.

12
Joseph

Tôi đã xem xét vấn đề này một thời gian.

Và chôn sâu trong Công cụ hiệu suất Google README

http://code.google.com.vn/p/google-perftools/source/browse/trunk/README

nói về libunwind

http://www.nongnu.org/libunwind/

Rất thích nghe ý kiến ​​của thư viện này.

Vấn đề với -rdynamic là nó có thể tăng kích thước của nhị phân tương đối đáng kể trong một số trường hợp

10
Gregory

Cảm ơn bạn nhiệt tình đã thu hút sự chú ý của tôi đến tiện ích addr2line.

Tôi đã viết một tập lệnh nhanh và bẩn để xử lý kết quả đầu ra của câu trả lời được cung cấp tại đây :

Kịch bản chấp nhận một đối số duy nhất: Tên của tệp chứa đầu ra từ tiện ích của jschmier.

Đầu ra sẽ in một cái gì đó như sau cho mỗi cấp độ của dấu vết:

BACKTRACE:  testExe 0x8A5db6b
FILE:       pathToFile/testExe.C:110
FUNCTION:   testFunction(int) 
   107  
   108           
   109           int* i = 0x0;
  *110           *i = 5;
   111      
   112        }
   113        return i;

Mã số:

#!/bin/bash

LOGFILE=$1

NUM_SRC_CONTEXT_LINES=3

old_IFS=$IFS  # save the field separator           
IFS=$'\n'     # new field separator, the end of line           

for bt in `cat $LOGFILE | grep '\[bt\]'`; do
   IFS=$old_IFS     # restore default field separator 
   printf '\n'
   EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
   ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
   echo "BACKTRACE:  $EXEC $ADDR"
   A2L=`addr2line -a $ADDR -e $EXEC -pfC`
   #echo "A2L:        $A2L"

   FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
   FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
   echo "FILE:       $FILE_AND_LINE"
   echo "FUNCTION:   $FUNCTION"

   # print offending source code
   SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
   LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
   if ([ -f $SRCFILE ]); then
      cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
   else
      echo "File not found: $SRCFILE"
   fi
   IFS=$'\n'     # new field separator, the end of line           
done

IFS=$old_IFS     # restore default field separator 
10
arr_sea

Một số phiên bản của libc chứa các hàm xử lý dấu vết ngăn xếp; bạn có thể sử dụng chúng:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Tôi nhớ sử dụng libunwind cách đây rất lâu để có được dấu vết ngăn xếp, nhưng nó có thể không được hỗ trợ trên nền tảng của bạn.

10
Stephen Deken

Điều quan trọng cần lưu ý là một khi bạn tạo tệp lõi, bạn sẽ cần sử dụng công cụ gdb để xem xét nó. Để gdb hiểu ý nghĩa của tệp cốt lõi của bạn, bạn phải nói với gcc để tạo thành nhị phân với các ký hiệu gỡ lỗi: để thực hiện việc này, bạn biên dịch bằng cờ -g:

$ g++ -g prog.cpp -o prog

Sau đó, bạn có thể đặt "ulimit -c không giới hạn" để cho phép kết xuất lõi hoặc chỉ chạy chương trình của bạn bên trong gdb. Tôi thích cách tiếp cận thứ hai hơn: 

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

Tôi hi vọng cái này giúp được. 

10
Benson
ulimit -c unlimited

là một biến hệ thống, sẽ cho phép tạo kết xuất lõi sau khi ứng dụng của bạn gặp sự cố. Trong trường hợp này một số lượng không giới hạn. Hãy tìm một tập tin gọi là core trong cùng thư mục. Hãy chắc chắn rằng bạn đã biên dịch mã của mình với thông tin gỡ lỗi được kích hoạt!

trân trọng

9
mana

Hãy quên việc thay đổi nguồn của bạn và thực hiện một số hack với hàm backtrace () hoặc macro - đây chỉ là những giải pháp kém.

Là một giải pháp làm việc đúng đắn, tôi sẽ tư vấn:

  1. Biên dịch chương trình của bạn với cờ "-g" để nhúng các biểu tượng gỡ lỗi thành nhị phân (đừng lo điều này sẽ không ảnh hưởng đến hiệu suất của bạn). 
  2. Trên linux chạy lệnh tiếp theo: "ulimit -c không giới hạn" - để cho phép hệ thống tạo ra các sự cố lớn.
  3. Khi chương trình của bạn bị sập, trong thư mục làm việc, bạn sẽ thấy tệp "lõi".
  4. Chạy lệnh tiếp theo để in backtrace sang stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Điều này sẽ in backtrace thích hợp của chương trình của bạn theo cách dễ đọc của con người (với tên tệp nguồn và số dòng) . Ngoài ra, cách tiếp cận này sẽ cho bạn tự do tự động hóa hệ thống của mình: .__ một bãi chứa cốt lõi, và sau đó gửi backtraces qua email cho các nhà phát triển hoặc đăng nhập nó vào một số hệ thống đăng nhập.

9
loopzilla

Bạn có thể sử dụng DeathHandler - lớp C++ nhỏ, mọi thứ cho bạn, đáng tin cậy.

8
markhor

giành chiến thắng: Làm thế nào về StackWalk64 http://msdn.Microsoft.com/en-us/l Library/ms680650.aspx

8
Roskoto

Nhìn vào:

người đàn ông 3 backtrace

Và:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

Đây là các phần mở rộng GNU.

7
Stéphane

Xem tiện ích Stack Trace trong ACE (Môi trường giao tiếp ADAPTIVE). Nó đã được viết để bao gồm tất cả các nền tảng chính (và nhiều hơn nữa). Thư viện được cấp phép theo kiểu BSD để bạn thậm chí có thể sao chép/dán mã nếu bạn không muốn sử dụng ACE.

6
Adam Mitz

Tôi có thể giúp với phiên bản Linux: có thể sử dụng chức năng backtrace, backtrace_symbols và backtrace_symbols_fd. Xem các trang hướng dẫn tương ứng.

5
terminus

* nix: bạn có thể chặn SIGSEGV (thông thường tín hiệu này được đưa ra trước khi gặp sự cố) và giữ thông tin vào một tệp. (bên cạnh tệp lõi mà bạn có thể sử dụng để gỡ lỗi bằng gdb chẳng hạn).

thắng: Kiểm tra this từ msdn.

Bạn cũng có thể xem mã chrome của google để xem cách xử lý sự cố. Nó có một cơ chế xử lý ngoại lệ Nice.

4
INS

Tôi thấy rằng giải pháp @tgamblin chưa hoàn tất . Nó không thể xử lý với stackoverflow . Tôi nghĩ bởi vì trình xử lý tín hiệu mặc định được gọi với cùng ngăn xếp và SIGSEGV được ném hai lần. Để bảo vệ bạn cần đăng ký một ngăn xếp độc lập cho trình xử lý tín hiệu.

Bạn có thể kiểm tra điều này với mã dưới đây. Theo mặc định, trình xử lý không thành công. Với macro được xác định STACK_OVERFLOW, mọi thứ đều ổn.

#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>

using namespace std;

//#define STACK_OVERFLOW

#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif

static struct sigaction sigseg_handler;

void handler(int sig) {
  cerr << "sig seg fault handler" << endl;
  const int asize = 10;
  void *array[asize];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, asize);

  // print out all the frames to stderr
  cerr << "stack trace: " << endl;
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  cerr << "resend SIGSEGV to get core dump" << endl;
  signal(sig, SIG_DFL);
  kill(getpid(), sig);
}

void foo() {
  foo();
}

int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
  sigseg_stack.ss_sp = stack_body;
  sigseg_stack.ss_flags = SS_ONSTACK;
  sigseg_stack.ss_size = sizeof(stack_body);
  assert(!sigaltstack(&sigseg_stack, nullptr));
  sigseg_handler.sa_flags = SA_ONSTACK;
#else
  sigseg_handler.sa_flags = SA_RESTART;  
#endif
  sigseg_handler.sa_handler = &handler;
  assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
  cout << "sig action set" << endl;
  foo();
  return 0;
} 
4
Daneel S. Yaitskov

Tôi đã thấy rất nhiều câu trả lời ở đây thực hiện một trình xử lý tín hiệu và sau đó thoát ra. Đó là cách để đi, nhưng hãy nhớ một thực tế rất quan trọng: Nếu bạn muốn nhận kết xuất lõi cho lỗi được tạo, bạn không thể gọi exit(status). Thay vào đó, hãy gọi abort()!

3
jard18

Vị vua mới trong thị trấn đã đến https://github.com/bombela/backward-cpp

1 tiêu đề để đặt trong mã của bạn và 1 thư viện để cài đặt.

Cá nhân tôi gọi nó bằng chức năng này

#include "backward.hpp"
void stacker() {

using namespace backward;
StackTrace st;


st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace

Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}
3
Roy

Tôi sẽ sử dụng mã tạo ra dấu vết ngăn xếp cho bộ nhớ bị rò rỉ trong Trình phát hiện rò rỉ trực quan . Điều này chỉ hoạt động trên Win32, mặc dù.

3
Jim Buck

Là một giải pháp chỉ dành cho Windows, bạn có thể nhận được tương đương với theo dõi ngăn xếp (với nhiều thông tin hơn, nhiều hơn nữa) bằng cách sử dụng Báo cáo Lỗi Windows . Chỉ với một vài mục đăng ký, nó có thể được thiết lập để thu thập các kết xuất chế độ người dùng :

Bắt đầu với Windows Server 2008 và Windows Vista với Gói dịch vụ 1 (SP1), Báo cáo lỗi Windows (WER) có thể được định cấu hình để các bản ghi chế độ người dùng đầy đủ được thu thập và lưu trữ cục bộ sau khi ứng dụng ở chế độ người dùng gặp sự cố. [...]

Tính năng này không được bật theo mặc định. Kích hoạt tính năng yêu cầu quyền quản trị viên. Để bật và định cấu hình tính năng, hãy sử dụng các giá trị đăng ký sau trong khóa HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps.

Bạn có thể đặt các mục đăng ký từ trình cài đặt của mình, có các đặc quyền bắt buộc.

Tạo kết xuất chế độ người dùng có các ưu điểm sau so với việc tạo theo dõi ngăn xếp trên máy khách:

  • Nó đã được thực hiện trong hệ thống. Bạn có thể sử dụng WER như đã nêu ở trên hoặc gọi MiniDumpWriteDump chính bạn, nếu bạn cần kiểm soát chi tiết hơn đối với lượng thông tin cần kết xuất. (Đảm bảo gọi nó từ một quy trình khác.)
  • Cách đầy đủ hơn dấu vết ngăn xếp. Trong số những thứ khác, nó có thể chứa các biến cục bộ, đối số hàm, ngăn xếp cho các luồng khác, các mô-đun được tải, v.v. Lượng dữ liệu (và do đó kích thước) có khả năng tùy biến cao.
  • Không cần phải gửi biểu tượng gỡ lỗi. Điều này vừa làm giảm đáng kể quy mô triển khai của bạn, vừa khiến việc thiết kế ngược ứng dụng của bạn trở nên khó khăn hơn.
  • Phần lớn độc lập với trình biên dịch bạn sử dụng. Sử dụng WER thậm chí không yêu cầu bất kỳ mã. Dù bằng cách nào, có một cách để có được cơ sở dữ liệu ký hiệu (PDB) là rất hữu ích cho phân tích ngoại tuyến. Tôi tin rằng GCC có thể tạo PDB hoặc có các công cụ để chuyển đổi cơ sở dữ liệu ký hiệu sang định dạng PDB.

Xin lưu ý rằng WER chỉ có thể được kích hoạt bởi sự cố ứng dụng (nghĩa là hệ thống chấm dứt một quá trình do một ngoại lệ chưa được xử lý). MiniDumpWriteDump có thể được gọi bất cứ lúc nào. Điều này có thể hữu ích nếu bạn cần bỏ trạng thái hiện tại để chẩn đoán các vấn đề khác ngoài sự cố.

Đọc bắt buộc, nếu bạn muốn đánh giá khả năng ứng dụng của các bãi rác nhỏ:

2
IInspectable

Ngoài các câu trả lời ở trên, đây là cách bạn tạo hệ điều hành Debian Linux tạo kết xuất lõi 

  1. Tạo một thư mục Coredumps vào trong thư mục nhà của người dùng
  2. Truy cập /etc/security/limits.conf. Bên dưới dòng '', nhập vào lõi mềm không giới hạn, và không giới hạn lõi mềm không giới hạn, nếu cho phép các lõi lõi cho root, để cho phép không gian không giới hạn cho các bãi lõi. 
  3. LƯU Ý: Lõi * không giới hạn lõi mềm không bao gồm root, đó là lý do tại sao root phải được chỉ định trong dòng riêng của nó.
  4. Để kiểm tra các giá trị này, hãy đăng xuất, đăng nhập lại và nhập vào ul ulitit -a. Kích thước tập tin lõi Core nên được đặt thành không giới hạn.
  5. Kiểm tra các tệp .bashrc (người dùng và root nếu có) để đảm bảo rằng ulimit không được đặt ở đó. Nếu không, giá trị trên sẽ được ghi đè khi khởi động.
  6. Mở /etc/sysctl.conf. Nhập nội dung sau vào dưới cùng: kernel kernel.core_potype = /home//coredumps/%e_%t.dump tựa. (% e sẽ là tên quy trình và% t sẽ là thời gian hệ thống)
  7. Thoát và gõ vào sysctl -pv để tải cấu hình mới Kiểm tra/Proc/sys/kernel/core_potype và xác minh rằng điều này phù hợp với những gì bạn vừa nhập.
  8. Việc bán phá giá cốt lõi có thể được kiểm tra bằng cách chạy một quy trình trên dòng lệnh (Nhận & Trực), và sau đó tiêu diệt nó bằng cách giết chết -11. Nếu việc bán phá giá lõi thành công, bạn sẽ thấy tập tin (lõi bị đổ) sau dấu hiệu lỗi phân đoạn.
2
enthusiasticgeek

Trên Linux/unix/MacOSX sử dụng các tệp lõi (bạn có thể kích hoạt chúng bằng ulimit hoặc gọi hệ thống tương thích ). Trên Windows sử dụng báo cáo lỗi của Microsoft (bạn có thể trở thành đối tác và có quyền truy cập vào dữ liệu sự cố ứng dụng của mình).

1
Kasprzol

Nếu bạn vẫn muốn đi một mình như tôi đã làm, bạn có thể liên kết với bfd và tránh sử dụng addr2line như tôi đã làm ở đây:

https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c

Điều này tạo ra đầu ra:

[E]        crash.linux.c:170  | crit_err_hdlr                  | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E]        crash.linux.c:171  | crit_err_hdlr                  | signal 11 (Segmentation fault), address is (nil)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]
0
Geoffrey

Tôi đã quên công nghệ "apport" của Gnome, nhưng tôi không biết nhiều về việc sử dụng nó. Nó được sử dụng để tạo stacktraces và các chẩn đoán khác để xử lý và có thể tự động báo lỗi. Đó chắc chắn là giá trị kiểm tra.

0
Joseph

Nếu chương trình của bạn gặp sự cố, chính hệ điều hành sẽ tạo ra thông tin kết xuất sự cố. Nếu bạn đang sử dụng HĐH * nix, bạn chỉ cần không ngăn nó làm như vậy (kiểm tra các tùy chọn 'coredump' của lệnh ulimit).

0
nsayer

Có vẻ như trong một trong những phiên bản thư viện tăng cường c ++ cuối cùng đã cung cấp chính xác những gì bạn muốn, có lẽ mã sẽ là đa nền tảng . Đó là boost :: stacktrace , mà bạn có thể sử dụng như như trong mẫu boost :

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

Trong Linux Bạn biên dịch mã ở trên:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

Ví dụ backtrace được sao chép từ tài liệu tăng cường :

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start
0
Grzegorz Bazior