Cara menghasilkan stacktrace secara otomatis ketika program saya mogok

590

Saya bekerja di Linux dengan kompiler GCC. Ketika program C ++ saya crash saya ingin secara otomatis menghasilkan stacktrace.

Program saya sedang dijalankan oleh banyak pengguna yang berbeda dan juga berjalan di Linux, Windows dan Macintosh (semua versi dikompilasi menggunakan gcc).

Saya ingin program saya dapat menghasilkan jejak stack ketika crash dan saat berikutnya pengguna menjalankannya, ia akan bertanya kepada mereka apakah boleh mengirimkan jejak stack kepada saya sehingga saya dapat melacak masalahnya. Saya dapat menangani pengiriman info kepada saya, tetapi saya tidak tahu cara membuat string jejak. Ada ide?

KPexEA
sumber
4
backtrace dan backtrace_symbols_fd bukan async-signal-safe. Anda sebaiknya tidak menggunakan fungsi ini dalam penangan sinyal
Parag Bafna
10
backtrace_symbols memanggil malloc, dan karenanya tidak boleh digunakan dalam penangan sinyal. Dua fungsi lainnya (backtrace dan backtrace_symbols_fd) tidak memiliki masalah ini, dan biasanya digunakan pada penangan sinyal.
cmccabe
3
@cmccabe yang salah backtrace_symbols_fd biasanya tidak memanggil malloc tetapi mungkin jika ada yang tidak beres dalam blok catch_error
Sam Saffron
6
Itu "mungkin" dalam arti bahwa tidak ada spesifikasi POSIX untuk backtrace_symbols_fd (atau backtrace apa pun); namun, backtrace_symbols_fd GNU / Linux ditentukan untuk tidak pernah memanggil malloc, seperti linux.die.net/man/3/backtrace_symbols_fd . Oleh karena itu, aman untuk berasumsi bahwa itu tidak akan pernah memanggil malloc di Linux.
codetaku
Bagaimana itu crash?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

509

Untuk Linux dan saya percaya Mac OS X, jika Anda menggunakan gcc, atau kompiler apa pun yang menggunakan glibc, Anda dapat menggunakan fungsi backtrace () execinfo.huntuk mencetak stacktrace dan keluar dengan anggun ketika Anda mendapatkan kesalahan segmentasi. Dokumentasi dapat ditemukan di manual libc .

Berikut adalah contoh program yang menginstal SIGSEGVhandler dan mencetak stacktrace stderrketika segfaults. The baz()Fungsi sini menyebabkan segfault yang memicu handler:

#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.
}

Mengkompilasi dengan -g -rdynamicmemberi Anda info simbol di output Anda, yang dapat digunakan glibc untuk membuat stacktrace yang bagus:

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

Menjalankan ini membuat Anda mendapatkan hasil ini:

$ ./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]

Ini menunjukkan modul muat, offset, dan fungsi yang berasal dari setiap frame dalam tumpukan. Di sini Anda dapat melihat penangan sinyal di atas tumpukan, dan fungsi libc sebelum mainselain main, foo, bar, dan baz.

Todd Gamblin
sumber
53
Ada juga /lib/libSegFault.so yang dapat Anda gunakan dengan LD_PRELOAD.
CesarB
6
Sepertinya dua entri pertama dalam output backtrace Anda berisi alamat pengirim di dalam pengendali sinyal dan mungkin satu di sigaction()dalam libc. Walaupun backtrace Anda tampaknya benar, saya terkadang menemukan bahwa langkah-langkah tambahan diperlukan untuk memastikan lokasi kesalahan yang sebenarnya muncul di backtrace karena dapat ditimpa sigaction()oleh kernel.
jschmier
9
Apa yang akan terjadi jika crash berasal dari dalam malloc? Tidakkah Anda kemudian memegang kunci dan kemudian macet ketika "backtrace" mencoba mengalokasikan memori?
Mattias Nilsson
7
catchsegvbukan apa yang dibutuhkan OP tetapi luar biasa untuk menangkap kesalahan segmentasi dan mendapatkan semua informasi.
Matt Clarkson
8
Untuk ARM, saya juga harus mengkompilasi dengan -funwind-tables. Kalau tidak, kedalaman tumpukan saya selalu 1 (kosong).
jfritz42
128

Ini bahkan lebih mudah daripada "man backtrace", ada perpustakaan kecil yang didokumentasikan (spesifik GNU) didistribusikan dengan glibc sebagai libSegFault.so, yang saya percaya ditulis oleh Ulrich Drepper untuk mendukung program catchsegv (lihat "man catchsegv").

Ini memberi kita 3 kemungkinan. Alih-alih menjalankan "program -o hai":

  1. Jalankan dalam catchsegv:

    $ catchsegv program -o hai
  2. Tautan dengan libSegFault saat runtime:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Tautan dengan libSegFault pada waktu kompilasi:

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

Dalam semua 3 kasus, Anda akan mendapatkan jejak balik yang lebih jelas dengan sedikit optimasi (gcc -O0 atau -O1) dan simbol debugging (gcc -g). Jika tidak, Anda mungkin berakhir dengan tumpukan alamat memori.

Anda juga dapat menangkap lebih banyak sinyal untuk jejak tumpukan dengan sesuatu seperti:

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

Outputnya akan terlihat seperti ini (perhatikan backtrace di bagian bawah):

*** 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]

Jika Anda ingin mengetahui detail darahnya, sayangnya sumber terbaik adalah sumbernya: Lihat http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c dan direktori induknya http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

jhclark
sumber
1
"Kemungkinan 3. Tautan dengan libSegFault pada waktu kompilasi" tidak berfungsi.
HHK
5
@crafter: Apa maksudmu "tidak bekerja". Apa yang sudah Anda coba, pada bahasa apa / kompiler / toolchain / distribusi / perangkat keras? Apakah gagal dikompilasi? Untuk menangkap kesalahan? Untuk menghasilkan output sama sekali? Untuk menghasilkan output yang sulit digunakan? Terima kasih untuk perinciannya akan membantu semua orang.
Stéphane Gourichon
1
'sumber terbaik sayangnya sumbernya' ... Semoga, suatu hari, halaman manual untuk catchsegv sebenarnya akan menyebutkan SEGFAULT_SIGNALS. Sampai saat itu, ada jawaban untuk merujuk.
greggo
Saya tidak percaya saya telah memprogram C selama 5 tahun dan tidak pernah mendengar hal ini: /
DavidMFrey
6
@ StéphaneGourichon @HansKratz Untuk menautkan dengan libSegFault, Anda harus menambahkan -Wl,--no-as-neededke flag kompiler. Jika tidak, ldmemang tidak akan terhubung libSegFault, karena ia mengenali bahwa biner tidak menggunakan simbolnya.
Phillip
122

Linux

Sementara penggunaan backtrace () berfungsi di execinfo.h untuk mencetak stacktrace dan keluar dengan anggun ketika Anda mendapatkan kesalahan segmentasi telah disarankan , saya tidak melihat menyebutkan seluk-beluk yang diperlukan untuk memastikan backtrace yang dihasilkan menunjuk ke lokasi aktual dari kesalahan (setidaknya untuk beberapa arsitektur - x86 & ARM).

Dua entri pertama dalam rantai bingkai tumpukan saat Anda masuk ke penangan sinyal berisi alamat kembali di dalam penangan sinyal dan satu di dalam sigaction () di libc. Bingkai tumpukan fungsi terakhir yang dipanggil sebelum sinyal (yang merupakan lokasi kesalahan) hilang.

Kode

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

Keluaran

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]

Semua bahaya memanggil fungsi backtrace () dalam pengatur sinyal masih ada dan tidak boleh diabaikan, tapi saya menemukan fungsionalitas yang saya jelaskan di sini cukup membantu dalam debugging crash.

Penting untuk dicatat bahwa contoh yang saya berikan dikembangkan / diuji di Linux untuk x86. Saya juga berhasil menerapkan ini pada ARM menggunakan uc_mcontext.arm_pcbukan uc_mcontext.eip.

Berikut tautan ke artikel tempat saya mempelajari detail untuk implementasi ini: http://www.linuxjournal.com/article/6391

Jschmier
sumber
11
Pada sistem yang menggunakan GNU ld, ingatlah untuk mengkompilasi dengan -rdynamicuntuk memerintahkan linker untuk menambahkan semua simbol, tidak hanya yang digunakan, ke tabel simbol dinamis. Ini memungkinkan backtrace_symbols()untuk mengonversi alamat menjadi nama fungsi
jschmier
1
Selain itu, Anda perlu menambahkan opsi "-mapcs-frame" ke baris perintah GCC untuk menghasilkan stack frame pada platform ARM
qehgt
3
Ini mungkin sudah terlambat tetapi bisakah kita menggunakan addr2lineperintah entah bagaimana untuk mendapatkan garis yang tepat di mana crash itu terjadi?
Antusiasme
4
Pada build terbaru glibc uc_mcontext, tidak berisi bidang bernama eip. Sekarang ada array yang perlu diindeks, uc_mcontext.gregs[REG_EIP]adalah setara.
mmlb
6
Untuk ARM, backtraces saya selalu memiliki kedalaman 1 sampai saya menambahkan opsi -funwind-tables ke kompiler.
jfritz42
84

Meskipun jawaban yang benar telah disediakan yang menjelaskan bagaimana cara menggunakan backtrace()fungsi libc GNU 1 dan saya memberikan jawaban saya sendiri yang menjelaskan bagaimana memastikan backtrace dari pengarah sinyal menunjuk ke lokasi aktual kesalahan 2 , saya tidak melihat penyebutan demangling C ++ simbol keluaran dari backtrace.

Ketika mendapatkan backtraces dari program C ++, output dapat dijalankan melalui c++filt1 untuk menghilangkan simbol-simbol atau dengan menggunakan 1 secara langsung.abi::__cxa_demangle

  • 1 Linux & OS X Perhatikan itu c++filtdan __cxa_demanglespesifik untuk GCC
  • 2 Linux

Contoh C ++ Linux berikut menggunakan penangan sinyal yang sama dengan jawaban saya yang lain dan menunjukkan bagaimana c++filtdapat digunakan untuk menghilangkan simbol.

Kode :

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

Output ( ./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]

Keluaran Demangled ( ./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]

Berikut ini dibangun pada penangan sinyal dari jawaban asli saya dan dapat menggantikan penangan sinyal dalam contoh di atas untuk menunjukkan bagaimana abi::__cxa_demangledapat digunakan untuk demangle simbol. Penangan sinyal ini menghasilkan output demangled yang sama seperti contoh di atas.

Kode :

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);
}
Jschmier
sumber
1
Terima kasih untuk ini, jschmier. Saya membuat skrip bash kecil untuk memberi makan output ini ke utilitas addr2line. Lihat: stackoverflow.com/a/15801966/1797414
arr_sea
4
Jangan lupa untuk mencantumkan <cxxabi.h>
Bamaco
1
Dokumentasi yang baik, dan file header langsung telah diposting di sini sejak 2008 ... panthema.net/2008/0901-stacktrace-demangled sangat mirip dengan pendekatan Anda :)
kevinf
abi :: __ cxa_demangle tampaknya bukan async-safe, jadi pengatur sinyal bisa menemui jalan buntu di malloc.
orcy
Penggunaan std::cerr, free()dan exit()semua melanggar pembatasan terhadap pemanggilan panggilan non-async-signal-safe pada sistem POSIX. Kode ini akan kebuntuan jika proses Anda gagal dalam panggilan apapun seperti free(), malloc() new, atau detete.
Andrew Henle
31

Mungkin layak untuk melihat Google Breakpad , generator lintas tempat kecelakaan dan alat untuk memproses pembuangan.

Simon Steele
sumber
Ini melaporkan hal-hal seperti kesalahan segmentasi, tetapi tidak melaporkan info apa pun tentang pengecualian C ++ yang tidak ditangani.
DBedrenko
21

Anda tidak menentukan sistem operasi Anda, jadi ini sulit dijawab. Jika Anda menggunakan sistem berdasarkan gnu libc, Anda mungkin dapat menggunakan fungsi libc backtrace().

GCC juga memiliki dua bawaan yang dapat membantu Anda, tetapi yang mungkin atau mungkin tidak diterapkan sepenuhnya pada arsitektur Anda, dan itu adalah __builtin_frame_addressdan __builtin_return_address. Keduanya menginginkan tingkat integer langsung (dengan segera, maksud saya tidak bisa menjadi variabel). Jika __builtin_frame_addressuntuk level tertentu tidak nol, harus aman untuk mengambil alamat pengirim level yang sama.

Brian Mitchell
sumber
13

Terima kasih untuk antusiasme telah menarik perhatian saya ke utilitas addr2line.

Saya telah menulis skrip yang cepat dan kotor untuk memproses output dari jawaban yang disediakan di sini : (terima kasih banyak kepada jschmier!) Menggunakan utilitas addr2line.

Script menerima argumen tunggal: Nama file yang berisi output dari utilitas jschmier.

Keluaran harus mencetak sesuatu seperti berikut untuk setiap tingkat jejak:

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;

Kode:

#!/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 
arr_sea
sumber
12

ulimit -c <value>menetapkan batas ukuran file inti pada unix. Secara default, batas ukuran file inti adalah 0. Anda dapat melihat ulimitnilai Anda ulimit -a.

juga, jika Anda menjalankan program dari dalam gdb, itu akan menghentikan program Anda pada "pelanggaran segmentasi" ( SIGSEGV, umumnya ketika Anda mengakses sepotong memori yang tidak Anda alokasikan) atau Anda dapat mengatur breakpoints.

ddd dan nemiver adalah ujung depan untuk gdb yang membuatnya bekerja dengan lebih mudah bagi pemula.

pengguna
sumber
6
Core dumps jauh lebih berguna daripada tumpukan jejak karena Anda dapat memuat core dump di debugger dan melihat status keseluruhan program dan datanya pada titik crash.
Adam Hawes
1
Fasilitas backtrace yang disarankan orang lain mungkin lebih baik daripada tidak sama sekali, tetapi sangat mendasar - bahkan tidak memberikan nomor baris. Sebaliknya, dengan menggunakan dump inti, Anda dapat melihat secara surut seluruh status aplikasi Anda pada saat crash (termasuk jejak stack terperinci). Ada mungkin ada masalah praktis dengan mencoba untuk menggunakan ini untuk bidang debugging, tapi itu jelas alat yang lebih kuat untuk crash menganalisis dan menegaskan selama pengembangan (setidaknya di Linux).
nobar
10

Penting untuk dicatat bahwa setelah Anda membuat file inti, Anda harus menggunakan alat gdb untuk melihatnya. Agar gdb memahami file inti Anda, Anda harus memberi tahu gcc untuk memasukkan biner dengan simbol debugging: untuk melakukan ini, Anda kompilasi dengan flag -g:

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

Kemudian, Anda dapat mengatur "ulimit -c unlimited" untuk membiarkannya membuang inti, atau jalankan saja program Anda di dalam gdb. Saya lebih suka pendekatan kedua:

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

Saya harap ini membantu.

Benson
sumber
4
Anda juga dapat menelepon gdblangsung dari program mogok Anda. Setup handler untuk SIGSEGV, SEGILL, SIGBUS, SIGFPE yang akan memanggil gdb. Detail: stackoverflow.com/questions/3151779/... Keuntungannya adalah Anda mendapatkan backtrace yang indah dan beranotasi seperti di bt full, juga Anda bisa mendapatkan jejak tumpukan semua utas.
Vi.
Anda juga bisa mendapatkan backtrace lebih mudah daripada di jawaban: gdb -silent ./prog core --eval-command = backtrace --batch -it akan menampilkan backtrace dan menutup debugger
baziorek
10

Saya sudah melihat masalah ini untuk sementara waktu.

Dan terkubur dalam di Google Performance Tools README

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

berbicara tentang libunwind

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

Senang mendengar pendapat perpustakaan ini.

Masalahnya dengan -inamik adalah ia dapat meningkatkan ukuran biner secara signifikan dalam beberapa kasus

Gregory
sumber
2
Pada x86 / 64, saya belum melihat -rincian meningkatkan ukuran biner. Menambahkan -g menghasilkan peningkatan yang jauh lebih besar.
Dan
1
Saya perhatikan bahwa libunwind tidak memiliki fungsionalitas untuk mendapatkan nomor baris, dan saya kira (tidak menguji) unw_get_proc_name mengembalikan simbol fungsi (yang dikaburkan karena kelebihan beban dan semacamnya) alih-alih nama aslinya.
Herbert
1
Itu benar. Ini menjadi sangat sulit untuk melakukan ini dengan benar, tetapi saya sudah sangat sukses dengan gaddr2line ada banyak informasi praktis di sini blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory
9

Anda dapat menggunakan DeathHandler - kelas C ++ kecil yang melakukan segalanya untuk Anda, andal.

markhor
sumber
1
sayangnya itu digunakan execlp()untuk melakukan panggilan addr2line ... akan menyenangkan untuk sepenuhnya tetap dalam program sendiri (yang dimungkinkan dengan memasukkan kode addr2line dalam beberapa bentuk)
contoh
9

Lupakan tentang mengubah sumber Anda dan lakukan beberapa peretasan dengan fungsi backtrace () atau macroses - ini hanyalah solusi yang buruk.

Sebagai solusi yang berfungsi dengan baik, saya akan menyarankan:

  1. Kompilasi program Anda dengan bendera "-g" untuk menyematkan simbol debug ke biner (jangan khawatir ini tidak akan mempengaruhi kinerja Anda).
  2. Di linux jalankan perintah berikutnya: "ulimit -c unlimited" - untuk memungkinkan sistem membuat crash crash besar.
  3. Ketika program Anda macet, di direktori kerja Anda akan melihat file "core".
  4. Jalankan perintah berikutnya untuk mencetak backtrace ke stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Ini akan mencetak jejak balik yang dapat dibaca yang tepat dari program Anda dengan cara yang dapat dibaca manusia (dengan nama file sumber dan nomor baris). Selain itu, pendekatan ini akan memberi Anda kebebasan untuk mengotomatiskan sistem Anda: memiliki skrip pendek yang memeriksa apakah proses membuat dump inti, dan kemudian mengirim jejak balik melalui email ke pengembang, atau login ini ke beberapa sistem logging.

loopzilla
sumber
Ini memberikan nomor baris yang salah. Bisakah ini diperbaiki?
HeyJude
7
ulimit -c unlimited

adalah variabel sistem, yang akan memungkinkan untuk membuat dump inti setelah aplikasi Anda crash. Dalam hal ini jumlah yang tidak terbatas. Cari file yang disebut core di direktori yang sama. Pastikan Anda mengkompilasi kode Anda dengan informasi debug yang diaktifkan!

salam

mana
sumber
5
Pengguna tidak meminta dump inti. Dia meminta jejak tumpukan. Lihat delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin
1
dump inti akan berisi tumpukan panggilan pada saat crash, bukan?
Mo
3
Anda menganggap dia menggunakan Unix, dan menggunakan Bash.
Paul Tomblin
2
Jika Anda menggunakan tcsh, Anda harus melakukannyalimit coredumpsize unlimited
sivabudh
6

Lihat fasilitas Stack Trace di ACE (ADAPTIVE Communication Environment). Ini sudah ditulis untuk mencakup semua platform utama (dan banyak lagi). Pustaka tersebut berlisensi BSD-style sehingga Anda bahkan dapat menyalin / menempelkan kode jika Anda tidak ingin menggunakan ACE.

Adam Mitz
sumber
Tautannya tampaknya sudah mati.
tglas
5

Saya dapat membantu dengan versi Linux: fungsi backtrace, backtrace_symbols dan backtrace_symbols_fd dapat digunakan. Lihat halaman manual yang sesuai.

ujung
sumber
5

Sepertinya di salah satu versi c ++ boost terakhir muncul library untuk memberikan apa yang Anda inginkan, mungkin kodenya akan multiplatform. Ini adalah boost :: stacktrace , yang dapat Anda gunakan seperti pada sampel 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
}

Di Linux Anda mengkompilasi kode di atas:

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

Contoh backtrace yang disalin dari dokumentasi boost :

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
baziorek
sumber
4

* nix: Anda dapat mencegat SIGSEGV (biasanya sinyal ini dinaikkan sebelum mogok) dan menyimpan info tersebut menjadi file. (selain file inti yang dapat Anda gunakan untuk debug menggunakan gdb misalnya).

win: Periksa ini dari msdn.

Anda juga dapat melihat kode chrome google untuk melihat bagaimana menangani crash. Ini memiliki mekanisme penanganan pengecualian yang bagus.

INS
sumber
SEH tidak membantu dalam menghasilkan jejak tumpukan. Meskipun itu bisa menjadi bagian dari solusi, solusi itu lebih sulit untuk diterapkan dan memberikan lebih sedikit informasi dengan mengorbankan mengungkapkan lebih banyak informasi tentang aplikasi Anda daripada solusi nyata : Menulis dump mini. Dan atur Windows untuk melakukan ini secara otomatis untuk Anda.
IInspectable
4

Saya menemukan bahwa solusi @tgamblin tidak lengkap. Itu tidak bisa menangani dengan stackoverflow. Saya pikir karena secara default handler sinyal dipanggil dengan tumpukan yang sama dan SIGSEGV dilemparkan dua kali. Untuk melindungi Anda perlu mendaftarkan tumpukan independen untuk penangan sinyal.

Anda dapat memeriksa ini dengan kode di bawah ini. Secara default pawang gagal. Dengan STACK_OVERFLOW makro yang didefinisikan, tidak apa-apa.

#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;
} 
Daneel S. Yaitskov
sumber
4

Raja baru di kota telah tiba https://github.com/bombela/backward-cpp

1 tajuk untuk ditempatkan dalam kode Anda dan 1 pustaka untuk dipasang.

Secara pribadi saya menyebutnya menggunakan fungsi ini

#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);
}
Roy
sumber
Wow! Akhirnya itulah yang harus dilakukan! Saya baru saja membuang dengan solusi sendiri untuk yang satu ini.
tglas
3

Saya akan menggunakan kode yang menghasilkan jejak stack untuk memori yang bocor di Visual Leak Detector . Ini hanya bekerja pada Win32.

Jim Buck
sumber
Dan mengharuskan Anda mengirimkan simbol debug dengan kode Anda. Secara umum tidak diinginkan. Tulis dump mini dan atur Windows untuk melakukannya secara otomatis untuk Anda pada pengecualian yang tidak ditangani.
IInspectable
3

Saya telah melihat banyak jawaban di sini melakukan penangan sinyal dan kemudian keluar. Itulah caranya, tapi ingat fakta yang sangat penting: Jika Anda ingin mendapatkan dump inti untuk kesalahan yang dihasilkan, Anda tidak bisa menelepon exit(status). Panggil abort()saja!

jard18
sumber
3

Sebagai solusi khusus Windows, Anda bisa mendapatkan yang setara dengan jejak tumpukan (dengan informasi jauh lebih banyak) menggunakan Pelaporan Kesalahan Windows . Dengan hanya beberapa entri registri, dapat diatur untuk mengumpulkan dump mode pengguna :

Dimulai dengan Windows Server 2008 dan Windows Vista dengan Paket Layanan 1 (SP1), Windows Error Reporting (WER) dapat dikonfigurasi sehingga dump mode pengguna penuh dikumpulkan dan disimpan secara lokal setelah aplikasi mode pengguna lumpuh. [...]

Fitur ini tidak diaktifkan secara default. Mengaktifkan fitur ini memerlukan hak administrator. Untuk mengaktifkan dan mengkonfigurasi fitur, gunakan nilai registri berikut di bawah HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumps key.

Anda dapat mengatur entri registri dari installer Anda, yang memiliki hak istimewa yang diperlukan.

Membuat dump mode-pengguna memiliki keuntungan berikut daripada menghasilkan jejak stack pada klien:

  • Ini sudah diimplementasikan dalam sistem. Anda dapat menggunakan WER seperti yang diuraikan di atas, atau hubungi MiniDumpWriteDump sendiri, jika Anda memerlukan kontrol yang lebih baik atas jumlah informasi yang akan dibuang. (Pastikan untuk memanggilnya dari proses yang berbeda.)
  • Jauh lebih lengkap dari tumpukan jejak. Antara lain dapat berisi variabel lokal, argumen fungsi, tumpukan untuk utas lainnya, modul yang dimuat, dan sebagainya. Jumlah data (dan ukurannya) sangat dapat disesuaikan.
  • Tidak perlu mengirim simbol debug. Ini secara drastis mengurangi ukuran penyebaran Anda, serta mempersulit rekayasa balik aplikasi Anda.
  • Sebagian besar independen dari kompiler yang Anda gunakan. Menggunakan WER bahkan tidak memerlukan kode apa pun. Either way, memiliki cara untuk mendapatkan basis data simbol (PDB) sangat berguna untuk analisis offline. Saya percaya GCC dapat menghasilkan PDB, atau ada alat untuk mengkonversi basis data simbol ke format PDB.

Perhatikan, bahwa WER hanya dapat dipicu oleh crash aplikasi (yaitu sistem mengakhiri proses karena pengecualian tidak tertangani). MiniDumpWriteDumpdapat dipanggil kapan saja. Ini mungkin membantu jika Anda perlu membuang kondisi saat ini untuk mendiagnosis masalah selain macet.

Bacaan wajib, jika Anda ingin mengevaluasi penerapan dump mini:

IInspectable
sumber
2

Selain jawaban di atas, berikut cara Anda membuat Debian Linux OS menghasilkan core dump

  1. Buat folder "coredumps" di folder home pengguna
  2. Pergi ke /etc/security/limits.conf. Di bawah garis '', ketik “soft core unlimited”, dan “root soft core unlimited” jika memungkinkan core dumps untuk root, untuk memberikan ruang tak terbatas untuk core dumps.
  3. CATATAN: "* soft core unlimited" tidak mencakup root, itulah sebabnya root harus ditentukan pada barisnya sendiri.
  4. Untuk memeriksa nilai-nilai ini, logout, masuk kembali, dan ketik "ulimit -a". "Ukuran file inti" harus ditetapkan menjadi tidak terbatas.
  5. Periksa file .bashrc (pengguna, dan root jika ada) untuk memastikan bahwa ulimit tidak diatur di sana. Jika tidak, nilai di atas akan ditimpa saat startup.
  6. Buka /etc/sysctl.conf. Masukkan yang berikut di bagian bawah: "kernel.core_pattern = /home//coredumps/%e_%t.dump". (% e akan menjadi nama proses, dan% t akan menjadi waktu sistem)
  7. Keluar dan ketik "sysctl -p" untuk memuat konfigurasi baru. Periksa / proc / sys / kernel / core_pattern dan verifikasi bahwa ini cocok dengan apa yang baru saja Anda ketikkan.
  8. Core dumping dapat diuji dengan menjalankan proses pada command line ("&"), dan kemudian membunuhnya dengan "kill -11". Jika core dumping berhasil, Anda akan melihat "(core dumped)" setelah indikasi kesalahan segmentasi.
antusiasme
sumber
2

Jika Anda masih ingin melakukannya sendiri seperti yang saya lakukan, Anda dapat menautkan bfddan menghindari penggunaan addr2lineseperti yang saya lakukan di sini:

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

Ini menghasilkan output:

[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]
Geoffrey
sumber
1

Di Linux / unix / MacOSX gunakan file inti (Anda dapat mengaktifkannya dengan ulimit atau panggilan sistem yang kompatibel ). Di Windows gunakan pelaporan kesalahan Microsoft (Anda bisa menjadi mitra dan mendapatkan akses ke data kerusakan aplikasi Anda).

Kasprzol
sumber
0

Saya lupa tentang teknologi "apport" GNOME, tetapi saya tidak tahu banyak tentang cara menggunakannya. Ini digunakan untuk menghasilkan stacktraces dan diagnostik lain untuk diproses dan dapat secara otomatis mengajukan bug. Ini pasti layak untuk dicoba.


sumber