Programım çöktüğünde otomatik olarak bir yığın izi nasıl oluşturulur


590

GCC derleyicisiyle Linux üzerinde çalışıyorum. Benim C ++ programı çöktüğünde otomatik olarak bir stacktrace oluşturmak istiyorum.

Programım birçok farklı kullanıcı tarafından çalıştırılıyor ve Linux, Windows ve Macintosh üzerinde de çalışıyor (tüm sürümler kullanılarak derleniyor gcc).

Programım çöktüğünde bir yığın izlemesi yapabilmeyi ve kullanıcı bir sonraki çalıştırdığında, sorunu izleyebilmem için yığın izini bana göndermenin uygun olup olmadığını soracaktır. Bana bilgi gönderme işleyebilir ama izleme dizesi oluşturmak için nasıl bilmiyorum. Herhangi bir fikir?


4
backtrace ve backtrace_symbols_fd zaman uyumsuz sinyaller için güvenli değildir. bu işlevi sinyal işleyicide kullanmamalısınız
Parag Bafna

10
backtrace_symbols malloc'u çağırır ve bu nedenle bir sinyal işleyicide kullanılmamalıdır. Diğer iki işlevde (backtrace ve backtrace_symbols_fd) bu sorun yoktur ve sinyal işleyicilerinde yaygın olarak kullanılır.
cmccabe

3
@cmccabe yanlış olan backtrace_symbols_fd genellikle malloc çağırmaz, ancak catch_error bloğunda bir şeyler ters giderse
Sam Saffron

6
Backtrace_symbols_fd (veya herhangi bir backtrace) için POSIX spesifikasyonu olmaması anlamında "olabilir"; ancak, linux.die.net/man/3/backtrace_symbols_fd uyarınca, GNU / Linux'un backtrace_symbols_fd öğesinin asla malloc çağırmayacağı belirtilir . Bu nedenle, Linux'ta asla malloc demeyeceğini varsaymak güvenlidir.
codetaku

Nasıl çöküyor?
Ciro Santilli 法轮功 冠状

Yanıtlar:


509

Linux ve ben Mac OS X, gcc veya glibc kullanan herhangi bir derleyici kullanıyorsanız, execinfo.hbir yığın izlemesi yazdırmak ve bir segmentasyon hatası aldığınızda zarif bir şekilde çıkmak için backtrace () işlevlerini kullanabilirsiniz . Belgeleri libc kılavuzunda bulabilirsiniz .

İşte bir SIGSEGVişleyici yükleyen ve stderrsegfaults zaman için bir yığın izi yazdırmak örnek bir program . Buradaki baz()işlev, işleyiciyi tetikleyen segfault'a neden olur:

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

İle derlemek -g -rdynamic, çıktılarınızda glibc'nin güzel bir yığın izlemesi yapmak için kullanabileceği sembol bilgilerini alır:

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

Bunu yapmak size şu çıktıyı verir:

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

Bu, yığındaki her çerçevenin yük modülünü, ofsetini ve işlevini gösterir. Burada önce yığının en üstüne ve libc fonksiyonları üzerindeki sinyal işleyicisi görebilirsiniz mainek için main, foo, bar, ve baz.


53
LD_PRELOAD ile kullanabileceğiniz /lib/libSegFault.so da var.
CesarB

6
Geri izleme çıktınızdaki ilk iki giriş, sinyal işleyicisinin içinde ve muhtemelen sigaction()libc içinde bir giriş adresi içeriyor gibi görünüyor . Geri izlemeniz doğru gibi görünse de, bazen sigaction()çekirdeğin üzerine yazılabileceğinden, hatanın gerçek konumunun geri izlemede görünmesini sağlamak için ek adımlar gerektiğini bulduk .
jschmier

9
Kaza malloc içinden gelirse ne olur? Sonra bir kilit tutun ve "backtrace" bellek ayırmaya çalışırken takılıp olmaz?
Mattias Nilsson

7
catchsegvOP'nin ihtiyaç duyduğu şey değil, segmentasyon hatalarını yakalamak ve tüm bilgileri almak için harika.
Matt Clarkson

8
ARM için -funwind-tablolarıyla da derlemek zorunda kaldım. Aksi takdirde yığın derinliğim her zaman 1 (boş) idi.
jfritz42

128

"Adam geri izleme" bile daha kolay, glibc ile libSegFault.so olarak dağıtılan küçük belgelenmiş bir kütüphane (GNU'ya özgü) var, bu da Ulrich Drepper tarafından program catchsegv'yi desteklemek için yazılmış olduğuna inanıyorum (bkz. "Man catchsegv").

Bu bize 3 olasılık sunuyor. "Program -o hai" çalıştırmak yerine:

  1. Catchsegv içinde çalıştırın:

    $ catchsegv program -o hai
  2. Çalışma zamanında libSegFault ile bağlantı:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Derleme zamanında libSegFault ile bağlantı kurun:

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

Her üç durumda da, daha az optimizasyon (gcc -O0 veya -O1) ve hata ayıklama sembolleri (gcc -g) ile daha net geri çekimler elde edersiniz. Aksi takdirde, bir yığın bellek adresi alabilirsiniz.

Aşağıdaki gibi yığın izleri için daha fazla sinyal yakalayabilirsiniz:

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

Çıktı böyle bir şeye benzeyecektir (alttaki geri izlemeye dikkat edin):

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

Kanlı ayrıntıları bilmek istiyorsanız, en iyi kaynak maalesef kaynaktır: Bkz. Http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c ve üst dizini http://sourceware.org/git/?p=glibc.git;a=tree;f=debug


1
"Olasılık 3. Derleme zamanında libSegFault ile bağlantı" çalışmıyor.
HHK

5
@crafter: Ne demek "işe yaramıyor". Hangi dilde / derleyici / araç zinciri / dağıtım / donanımda ne denediniz? Derlenemedi mi? Hata yakalamak için? Hiç çıktı üretmek için? Kullanımı zor çıktı üretmek için? Herkese yardımcı olacak detaylar için teşekkür ederim.
Stéphane Gourichon

1
'en iyi kaynak maalesef kaynak' ... Umarım, bir gün, catchsegv için man sayfası aslında SEGFAULT_SIGNALS'tan bahsedecektir. O zamana kadar, atıfta bulunmak için bu cevap var.
greggo

C'yi 5 yıldır programladığımı
düşünemiyorum

6
@ StéphaneGourichon @HansKratz libSegFault ile bağlantı kurmak -Wl,--no-as-needediçin derleyici bayraklarına eklemelisiniz . Aksi takdirde, ldgerçekten de olacak değil karşı bağlamak libSegFaulto ikili onun sembollerinden birini kullanmayan kabul etmesi değil,.
Phillip

122

Linux

Bir yığın izlemesi yazdırmak ve zaten bir segmentasyon hatası aldığınızda zarif bir şekilde çıkmak için execinfo.h dosyasında backtrace () işlevlerinin kullanılması önerilmiş olsa da , sonuçta ortaya çıkan backtrace'in gerçek konumuna Arıza (en azından bazı mimariler için - x86 ve ARM).

Sinyal işleyiciye girdiğinizde yığın çerçeve zincirindeki ilk iki giriş, sinyal işleyicinin içinde bir dönüş adresi ve libc içinde bir iç sigaction () içerir. Sinyalden önce çağrılan son işlevin yığın çerçevesi (hatanın yeri) kaybolur.

kod

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

Çıktı

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]

Bir sinyal işleyicide backtrace () işlevlerini çağırmanın tüm tehlikeleri hala mevcuttur ve gözden kaçırılmamalıdır, ancak burada tarif ettiğim işlevselliği çökmeleri hata ayıklamada oldukça yararlı buluyorum.

Verdiğim örneğin Linux üzerinde x86 için geliştirildiğini / test edildiğini belirtmek önemlidir. Ben de başarıyla kullanarak ARM bu uygulamıştır uc_mcontext.arm_pcyerine uc_mcontext.eip.

İşte bu uygulamanın ayrıntılarını öğrendiğim makaleye bir link: http://www.linuxjournal.com/article/6391


11
GNU ld kullanan sistemlerde -rdynamic, bağlayıcıya yalnızca kullanılanları değil tüm sembolleri dinamik sembol tablosuna eklemesini bildirmek için derlemeyi unutmayın . Bu, backtrace_symbols()adresleri işlev adlarına dönüştürmeye izin verir
jschmier

1
Ayrıca, ARM platformunda yığın kareleri oluşturmak için GCC'nin komut satırına "-mapcs-frame" seçeneğini eklemeniz gerekiyor
qehgt

3
Bu çok geç olabilir, ancak addr2linekilitlenmenin meydana geldiği kesin satırı almak için bir şekilde komutu kullanabilir miyiz ?
enthusiasticgeek

4
Daha yeni yapılarda glibc uc_mcontextadlı bir alan içermiyor eip. Şimdi dizine eklenmesi gereken bir dizi var uc_mcontext.gregs[REG_EIP], eşdeğerdir.
mmlb

6
ARM için, derleyiciye -funwind-tables seçeneğini ekleyene kadar geri çekmelerim her zaman 1 derinliğe sahipti.
jfritz42

84

Her ne kadar GNU libc işlevi 1'in nasıl kullanılacağını açıklayan doğru bir cevap sağlanmış olsa da ve bir sinyal işleyiciden hatanın 2 gerçek konumuna işaret ettiğini nasıl geri izlemenin sağlanacağını açıklayan kendi cevabımı verdim, görmüyorum geri izinden çıkan C ++ sembollerinin demangling herhangi bir söz .backtrace()

C ++ programından geriye dönük izleme elde etmek, çıkış üzerinden çalıştırılabilir c++filt1 sembolleri demangle veya kullanarak 1 şirketinden.abi::__cxa_demangle

  • 1 Linux ve OS X GCC'ye özgü olduğunu c++filtve__cxa_demangle
  • 2 Linux

Aşağıdaki C ++ Linux örneği diğerc++filt cevabımla aynı sinyal işleyiciyi kullanıyor ve sembolleri sökmek için nasıl kullanılabileceğini gösteriyor .

Kod :

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

Çıktı ( ./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]

Demonte Çıktı ( ./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]

Aşağıdakiler, orijinalabi::__cxa_demangle yanıtımdan sinyal işleyici üzerine kurulur ve yukarıdaki örnekte sinyal işleyiciyi , sembolleri sökmek için nasıl kullanılabileceğini göstermek için değiştirebilir. Bu sinyal işleyici, yukarıdaki örnekle aynı demonte çıkışı üretir.

Kod :

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

1
Bunun için teşekkürler jschmier. Bunun çıktısını addr2line yardımcı programına beslemek için küçük bir bash betiği oluşturdum. Bakınız: stackoverflow.com/a/15801966/1797414
arr_sea

4
#İncxxq.h> 'i eklemeyi unutmayın
Bamaco

1
İyi belgeler ve 2008'den beri basit bir başlık dosyası yayınlanmıştır ... panthema.net/2008/0901-stacktrace- yaklaşımınıza çok benziyor :)
kevinf

abi :: __ cxa_demangle zaman uyumsuz sinyal için güvenli değil gibi görünüyor, bu nedenle sinyal işleyici malloc içinde bir yere kilitlenebilir.
orcy

Kullanımı std::cerr, free()ve exit()tüm POSIX sistemlerinde zaman uyumsuz olmayan-sinyal güvenli çağrıları çağırarak karşı kısıtlamalar ihlal eder. Sürecinizle gibi herhangi çağrısında başarısız olursa bu kod kilitlenmeye free(), malloc() newya detete.
Andrew Henle

31

Bir platformlar arası çarpışma dökümü oluşturucu ve dökümleri işlemek için araçlar olan Google Breakpad'e bakmaya değer olabilir .


Segmentasyon hataları gibi şeyler hakkında rapor verir, ancak işlenmemiş C ++ istisnaları hakkında herhangi bir bilgi rapor etmez.
DBedrenko

21

İşletim sisteminizi belirtmediniz, bu yüzden cevaplamak zor. Gnu libc tabanlı bir sistem kullanıyorsanız, libc işlevini kullanabilirsiniz backtrace().

GCC'nin ayrıca size yardımcı olabilecek ancak mimarinize tam olarak uygulanabilen veya uygulanamayan iki yerleşik vardır __builtin_frame_addressve bunlar ve __builtin_return_address. Her ikisi de hemen bir tamsayı seviyesi istiyor (derhal, bu bir değişken olamaz). __builtin_frame_addressBelirli bir seviye için sıfır değilse , aynı seviyenin dönüş adresini almak güvenli olmalıdır.


13

Addr2line yardımcı programına dikkatimi çektiği için hevesli olduğunuz için teşekkür ederiz.

Burada verilen yanıtın çıktısını işlemek için hızlı ve kirli bir komut dosyası yazdım : (çok teşekkürler jschmier!) Addr2line yardımcı programını kullanarak.

Komut dosyası tek bir bağımsız değişkeni kabul eder: jschmier'in yardımcı programından çıktıyı içeren dosyanın adı.

Çıktı, izin her düzeyi için aşağıdaki gibi bir şey yazdırmalıdır:

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;

Kod:

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

12

ulimit -c <value>unix'te çekirdek dosya boyutu sınırını belirler. Varsayılan olarak, çekirdek dosya boyutu sınırı 0'dır. ulimitDeğerlerinizi ile görebilirsiniz ulimit -a.

Ayrıca, programınızı gdb içinden çalıştırırsanız, programınızı "segmentasyon ihlallerinde" ( SIGSEGVgenellikle ayırmadığınız bir belleğe eriştiğinizde) durdurur veya kesme noktaları ayarlayabilirsiniz.

ddd ve nemiver, acemi için çalışmayı daha kolay hale getiren gdb'nin ön uçlarıdır.


6
Çekirdek dökümleri yığın izlerinden çok daha kullanışlıdır çünkü çekirdek dökümü hata ayıklayıcıya yükleyebilir ve tüm programın durumunu ve çökme noktasında verilerini görebilirsiniz.
Adam Hawes

1
Diğerlerinin önerdiği backtrace tesisi muhtemelen hiç yoktan iyidir, ancak çok basittir - hatta satır numaraları bile vermez. Diğer yandan çekirdek dökümlerini kullanarak, çöktüğü anda uygulamanızın tüm durumunu geriye dönük olarak görüntüleyelim (ayrıntılı yığın izlemesi dahil). Bunu alan hata ayıklaması için kullanmaya çalışmakla ilgili pratik sorunlar olabilir , ancak geliştirme sırasında (en azından Linux'ta) çökmeleri ve iddiaları analiz etmek için kesinlikle daha güçlü bir araçtır.
nobar

10

Bir çekirdek dosya oluşturduktan sonra, ona bakmak için gdb aracını kullanmanız gerektiğini unutmayın. Gdb'nin çekirdek dosyanızı anlamlandırması için, gcc'ye ikili dosyayı hata ayıklama simgeleriyle göstermesini söylemelisiniz: bunu yapmak için -g bayrağıyla derlersiniz:

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

Daha sonra, bir çekirdeği boşaltmasına izin vermek için "ulimit -c sınırsız" ayarlayabilir veya programınızı gdb içinde çalıştırabilirsiniz. İkinci yaklaşımı daha çok seviyorum:

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

Umarım bu yardımcı olur.


4
Ayrıca gdbkilitlenen programınızdan da arayabilirsiniz . Gdb'yi çağıran SIGSEGV, SEGILL, SIGBUS, SIGFPE için kurulum işleyicisi. Ayrıntılar: stackoverflow.com/questions/3151779/… Avantajı, olduğu gibi güzel, açıklamalı backtrace almanızdır bt full, ayrıca tüm iş parçacıklarının yığın izlerini alabilirsiniz.
Vi.

Ayrıca geri izlemeyi yanıttan daha kolay alabilirsiniz: gdb -silent ./prog core --eval-command = backtrace --batch -it geri izlemeyi gösterir ve hata ayıklayıcıyı
kapatır

10

Bir süredir bu soruna bakıyorum.

Google Performans Araçları README içinde derinlere gömüldü

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

libunwind hakkında konuşuyor

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

Bu kütüphanenin fikirlerini duymak isterim.

-Dinamik ile ilgili sorun, bazı durumlarda ikili büyüklüğünü nispeten önemli ölçüde artırabilmesidir


2
X86 / 64, ben görmedim -dinamik boyutu çok dinamik artış. -G eklemek çok daha büyük bir artış sağlar.
Dan

1
Libunwind'in satır numarasını almak için işlevselliği olmadığını fark ettim ve sanırım (test etmedim) unw_get_proc_name, orijinal ad yerine işlev sembolünü (aşırı yükleme için karıştırılmış ve böyle) döndürür.
Herbert

1
Bu doğru. Bunu doğru yapmak çok zor oluyor, ama gaddr2line ile mükemmel bir başarı elde ettim Burada birçok pratik bilgi var blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

DeathHandler'ı kullanabilirsiniz - küçük C ++ sınıfı sizin için her şeyi güvenilir yapar.


1
ne yazık ki execlp()addr2line çağrıları yapmak için kullanır ... tamamen kendi programında kalmak güzel olurdu (addr2line kodu bir biçimde dahil etmek mümkündür)
örnek

9

Kaynaklarınızı değiştirmeyi unutun ve backtrace () işlevi veya makrolarla bazı hackler yapın - bunlar sadece zayıf çözümlerdir.

Düzgün çalışan bir çözüm olarak tavsiye ederim:

  1. Hata ayıklama sembollerini ikili dosyaya gömmek için programınızı "-g" bayrağıyla derleyin (merak etmeyin, bu performansınızı etkilemez).
  2. Linux'ta bir sonraki komutu çalıştırın: "ulimit -c sınırsız" - sistemin büyük çökme dökümleri yapmasına izin vermek için.
  3. Programınız çöktüğünde, çalışma dizininde "çekirdek" dosyasını göreceksiniz.
  4. Geri izlemeyi stdout'a yazdırmak için sonraki komutu çalıştırın: gdb -batch -ex "backtrace" ./your_program_exe ./core

Bu, programınızın uygun okunabilir geri izini insan tarafından okunabilir şekilde yazdırır (kaynak dosya adları ve satır numaraları ile). Dahası bu yaklaşım size sisteminizi otomatikleştirme özgürlüğü verecektir: sürecin çekirdek dökümü oluşturup oluşturmadığını kontrol eden kısa bir komut dosyasına sahip olmak ve daha sonra geliştiricilere e-postayla geri izlemeler göndermek veya bunu bazı kayıt sistemlerine giriş yapmak.


Yanlış satır numaraları veriyor. Geliştirilebilir mi?
HeyJude

7
ulimit -c unlimited

uygulamanız çöktükten sonra çekirdek dökümü oluşturmanıza izin veren bir sistem değişkenidir. Bu durumda sınırsız bir miktar. Aynı dizinde core adında bir dosya arayın. Kodunuzu hata ayıklama bilgileri etkin olarak derlediğinizden emin olun!

Saygılarımızla


5
Kullanıcı bir çekirdek dökümü istemiyor. Bir yığın izi istiyor. Bkz. Delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin

1
çekirdek dökümü, kilitlenme anında çağrı yığınını içerecektir, değil mi?
Pzt

3
Unix'te olduğunu ve Bash kullandığını varsayıyorsun.
Paul Tomblin

2
limit coredumpsize unlimited
TCsh


6

İçinde Yığın İzleme tesis Bkz ACE (UYUMLU Haberleşme Çevre). Tüm büyük platformları (ve daha fazlasını) kapsayacak şekilde zaten yazılmıştır. Kütüphane BSD tarzı lisanslıdır, böylece ACE kullanmak istemiyorsanız kodu kopyalayıp yapıştırabilirsiniz.


Bağlantı ölü gibi görünüyor.
tglas

5

Linux sürümü ile yardımcı olabilirim: backtrace, backtrace_symbols ve backtrace_symbols_fd fonksiyonu kullanılabilir. İlgili kılavuz sayfalarına bakın.


5

Tam olarak ne istediğinizi sağlamak için son c ++ boost sürümü kitaplığından birinde göründüğü gibi görünüyor, muhtemelen kod çoklu platform olacaktır. Bu boost :: stacktrace , boost örneğinde olduğu gibi kullanabilirsiniz :

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

Linux'ta Yukarıdaki kodu derlersiniz:

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

Destek belgelerinden kopyalanan örnek geri izleme :

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

4

* nix: SIGSEGV'yi yakalayabilir (genellikle bu sinyal çökmeden önce kaldırılır) ve bilgileri bir dosyada saklayabilirsiniz . (örneğin gdb kullanarak hata ayıklamak için kullanabileceğiniz çekirdek dosyanın yanında).

win: Bunu msdn'den kontrol edin .

Ayrıca kilitlenmeleri nasıl ele aldığını görmek için Google'ın krom koduna da bakabilirsiniz. Güzel bir istisna yönetim mekanizması vardır.


SEH, yığın izinin üretilmesine yardımcı olmaz. Bir çözümün parçası olsa da, bu çözümü uygulamak daha zordur ve uygulamanız hakkında gerçek çözümden daha fazla bilgi ifşa etme pahasına daha az bilgi sağlar : Bir mini dökümü yazın. Ve Windows'u bunu sizin için otomatik olarak yapacak şekilde ayarlayın.
İnanılmaz

4

@Tgamblin çözümünün tam olmadığını gördüm. Stackoverflow ile işlem yapamaz. Bence varsayılan olarak işleyici aynı yığınla çağrılır ve SIGSEGV iki kez atılır. Korumak için sinyal işleyicisine bağımsız bir yığın kaydetmeniz gerekir.

Bunu aşağıdaki kodla kontrol edebilirsiniz. Varsayılan olarak işleyici başarısız olur. Tanımlanmış STACK_OVERFLOW makrosu ile sorun yok.

#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

Kasabadaki yeni kral geldi https://github.com/bombela/backward-cpp

Kodunuza yerleştirilecek 1 başlık ve yüklenecek 1 kitaplık.

Şahsen ben bu fonksiyonu kullanarak çağırıyorum

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

Vaov! Sonunda böyle yapılmalı! Ben sadece bu çözüm lehine kendi çözümüm ile terk ettim.
tglas

3

Visual Leak Detector sızdırılmış bellek için yığın izleme üreten kodu kullanır . Bu sadece Win32 üzerinde çalışır.


Ayrıca, hata ayıklama simgelerini kodunuzla birlikte göndermenizi gerektirir. Genel olarak arzu edilmez. Mini dökümü yazın ve Windows'u işlenmeyen özel durumlar için otomatik olarak yapacak şekilde ayarlayın.
İnanılmaz

3

Burada bir sinyal işleyici yapmak ve sonra çıkmak birçok cevap gördüm. Bu yol, ama çok önemli bir gerçeği hatırlayın: Oluşturulan hata için çekirdek dökümü almak istiyorsanız, arayamazsınız exit(status). abort()Bunun yerine arayın !


3

Yalnızca Windows çözümü olarak, Windows Hata Bildirimi'ni kullanarak yığın izlemesine eşdeğer (çok, daha fazla bilgi içeren) alabilirsiniz . Yalnızca birkaç kayıt defteri girdisiyle, kullanıcı modu dökümlerini toplamak üzere ayarlanabilir :

Windows Server 2008 ve Windows Vista Service Pack 1 (SP1) ile başlayarak, Windows Hata Bildirimi (WER), bir kullanıcı modu uygulaması çöktükten sonra tam kullanıcı modu dökümlerini yerel olarak toplanacak ve depolanacak şekilde yapılandırılabilir. [...]

Bu özellik varsayılan olarak etkin değildir. Özelliğin etkinleştirilmesi için yönetici ayrıcalıkları gerekir. Özelliği etkinleştirmek ve yapılandırmak için HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Hata Bildirimi \ LocalDumps anahtarının altındaki aşağıdaki kayıt defteri değerlerini kullanın .

Kayıt defteri girdilerini, gerekli ayrıcalıklara sahip yükleyicinizden ayarlayabilirsiniz.

Kullanıcı modu dökümü oluşturmanın, istemcide yığın izlemesi oluşturmaya göre aşağıdaki avantajları vardır:

  • Zaten sistemde uygulanmış. WER'i yukarıda belirtildiği gibi kullanabilir veya dökülecek bilgi miktarı üzerinde daha ayrıntılı bir denetime ihtiyacınız varsa MiniDumpWriteDump'ı kendiniz arayabilirsiniz . (Farklı bir işlemden aradığınızdan emin olun.)
  • Yol , bir yığın izleme daha tamamlandı. Diğerlerinin yanı sıra yerel değişkenler, fonksiyon argümanları, diğer evreler için yığınlar, yüklü modüller vb. İçerebilir. Veri miktarı (ve dolayısıyla boyutu) oldukça özelleştirilebilir.
  • Hata ayıklama sembolleri göndermenize gerek yoktur. Bu, hem dağıtımınızın boyutunu önemli ölçüde azaltır hem de uygulamanızı tersine mühendislik yapmayı zorlaştırır.
  • Kullandığınız derleyiciden büyük ölçüde bağımsız. WER kullanmak herhangi bir kod gerektirmez. Her iki durumda da, bir sembol veritabanı (PDB) elde etmenin bir yolu olması , çevrimdışı analiz için çok yararlıdır. GCC'nin ya PDB'ler üretebileceğine ya da sembol veritabanını PDB formatına dönüştürecek araçlar olduğuna inanıyorum.

WER'nin yalnızca bir uygulama kilitlenmesiyle (yani, işlenmemiş bir istisna nedeniyle bir işlemi sonlandıran) tetiklenebileceğini unutmayın. MiniDumpWriteDumpherhangi bir zamanda çağrılabilir. Bir çökme dışındaki sorunları teşhis etmek için mevcut durumu dökmeniz gerekiyorsa bu yararlı olabilir.

Mini dökümlerin uygulanabilirliğini değerlendirmek istiyorsanız zorunlu okuma:


2

Yukarıdaki yanıtlara ek olarak, Debian Linux OS'nin çekirdek dökümü oluşturmasını nasıl sağlıyorsunuz?

  1. Kullanıcının ana klasöründe bir "coredumps" klasörü oluşturun
  2. /Etc/security/limits.conf adresine gidin. '' Satırının altına, çekirdek dökümü için sınırsız alan sağlamak üzere "yumuşak çekirdek sınırsız" ve "kök yumuşak çekirdek sınırsız" yazın.
  3. NOT: “* yumuşak çekirdek sınırsız” kökü kapsamaz, bu nedenle kök kendi satırında belirtilmelidir.
  4. Bu değerleri kontrol etmek için oturumu kapatın, tekrar oturum açın ve “ulimit -a” yazın. “Temel dosya boyutu” sınırsız olarak ayarlanmalıdır.
  5. Ulimit'in orada ayarlanmadığından emin olmak için .bashrc dosyalarını (varsa kullanıcı ve kök) kontrol edin. Aksi takdirde, başlangıçta yukarıdaki değerin üzerine yazılacaktır.
  6. /Etc/sysctl.conf dosyasını açın. Aşağıya şunu girin: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (% e işlem adı ve% t sistem zamanı olacaktır)
  7. Yeni yapılandırmayı yüklemek için çıkın ve “sysctl -p” yazın / proc / sys / kernel / core_pattern öğesini kontrol edin ve bunun az önce yazdıklarınızla eşleştiğini doğrulayın.
  8. Çekirdek boşaltma, komut satırında (“&”) bir işlem yapıp daha sonra “kill -11” ile öldürülerek test edilebilir. Çekirdek boşaltma başarılı olursa, segmentasyon hatası göstergesinden sonra “(çekirdek boşaltma)” mesajını görürsünüz.

2

Hala yaptığım gibi yalnız gitmek istiyorsanız, bağlantı kurabilir bfdve kullanmaktan kaçınabilirsinizaddr2line :

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

Bu çıktıyı üretir:

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

1

Linux / unix / MacOSX'te çekirdek dosyaları kullanın (bunları ulimit veya uyumlu sistem çağrısı ile etkinleştirebilirsiniz ). Windows'da Microsoft hata raporlamasını kullanın (iş ortağı olabilir ve uygulama kilitlenme verilerinize erişebilirsiniz).


0

GNOME'un "apport" teknolojisini unuttum, ama onu kullanma hakkında çok fazla bilgim yok. İşleme için yığın izleri ve diğer tanılamaları oluşturmak için kullanılır ve hataları otomatik olarak dosyalayabilir. Kesinlikle kontrol etmeye değer.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.