it-swarm-eu.dev

Jak automaticky generovat stacktrace při zhroucení programu

Pracuji na Linuxu s překladačem GCC. Když můj program C++ havaruje, rád bych, aby automaticky generoval stacktrace.

Můj program je spuštěn mnoha různými uživateli a také běží na Linuxu, Windows a Macintosh (všechny verze jsou kompilovány pomocí gcc).

Chtěl bych, aby byl můj program schopen generovat stopu zásobníku, když se zhroutí, a příště ho uživatel spustí, bude se jich zeptat, zda je v pořádku, aby mi poslal stopu zásobníku, abych mohl tento problém sledovat. Dokážu zvládnout zasílání informací ke mně, ale nevím, jak vygenerovat sledovací řetězec. Nějaké nápady?

518
KPexEA

Pro Linux a já věřím, že Mac OS X, pokud používáte gcc, nebo jakýkoliv kompilátor, který používá glibc, můžete použít funkce backtrace () v execinfo.h k tisku stacktrace a výstupu gracefully, když dostanete chybu segmentace. Dokumentaci lze nalézt v manuálu libc .

Zde je příklad programu, který nainstaluje obslužný program SIGSEGV a vytiskne stacktrace na stderr, když dojde k chybě. Funkce baz() zde způsobí, že segfault spustí 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.
}

Kompilace s -g -rdynamic vám ve vašem výstupu poskytne informace o symbolu, které glibc může použít k vytvoření Nice stacktrace:

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

Provedením tohoto výstupu získáte tento výstup:

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

To ukazuje modul zatížení, posun a funkci, ze které pochází každý snímek ve svazku. Zde můžete vidět popisovač signálu v horní části zásobníku a funkce libc před main navíc k main, foo, bar a baz.

447
Todd Gamblin

Linux

Zatímco použití funkce backtrace () v souboru execinfo.h k tisku stacktrace a výstupu gracefully, když se dostanete chybu segmentace má již bylo navrženo , nevidím žádnou zmínku o složitosti potřebné k zajištění výsledných zpětných stop body aktuální umístění poruchy (alespoň u některých architektur - x86 & ARM).

První dvě položky v řetězci rámce zásobníku, když se dostanete do obsluhy signálu, obsahují zpáteční adresu uvnitř obsluhy signálu a jednu uvnitř sigaction () v libc. Rámec stohů poslední funkce volaný před signálem (což je místo poruchy) je ztracen.

Kód

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

Výstup

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]

Všechna nebezpečí volání funkcí backtrace () v obslužném programu signálu stále existují a neměla by být přehlížena, ale zjistím, že funkce, kterou jsem zde popsal, je užitečná při ladění pádů.

Je důležité poznamenat, že příklad, který jsem uvedl, je vyvíjen/testován na Linuxu pro x86. Také jsem to úspěšně implementoval na ARM pomocí uc_mcontext.arm_pc místo uc_mcontext.eip

Zde je odkaz na článek, kde jsem se dozvěděl podrobnosti pro tuto realizaci: http://www.linuxjournal.com/article/6391

115
jschmier

Je to ještě jednodušší než "man backtrace", je zde málo zdokumentovaná knihovna (GNU specific) distribuovaná s glibc jako libSegFault.so, což jsem věřil, že napsal Ulrich Drepper na podporu programu catchsegv (viz "man catchsegv").

To nám dává 3 možnosti. Místo spuštění programu "program -o hai":

  1. Spustit v rámci catchsegv:

    $ catchsegv program -o hai
    
  2. Spojení s libSegFault za běhu:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Odkaz na libSegFault v době kompilace:

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

Ve všech 3 případech získáte jasnější zpětné vazby s menší optimalizací (gcc -O0 nebo -O1) a ladicími symboly (gcc -g). V opačném případě můžete skončit s hromadou adres paměti.

Můžete také zachytit více signálů pro stopy zásobníku s něčím podobným:

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

Výstup bude vypadat něco takového (všimněte si backtrace v dolní části):

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

Chcete-li znát podrobné údaje, je nejlepším zdrojem bohužel zdroj: Viz http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c a jeho nadřazený adresář http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

112
jhclark

I když byla poskytnuta správná odpověď , která popisuje, jak používat funkci GNU libc backtrace()1 a já jsem poskytl vlastní odpověď která popisuje, jak zajistit zpětný dohled od obsluhy signálu směřující ke skutečnému umístění chyby2, Nevidím žádnou zmínku o demanglingu C++ symbolech vystupujících z backtrace.

Při získávání zpětné vazby z programu C++ lze výstup spustit pomocí c++filt1 rozdělit symboly nebo pomocí abi::__cxa_demangle1 přímo.

  • 1 Linux & OS X Všimněte si, že c++filt a __cxa_demangle jsou specifické pro GCC
  • 2 Linux

Následující příklad C++ Linuxu používá stejný obslužný program signálu jako moje jiná odpověď a demonstruje, jak lze c++filt použít k demanglování symbolů.

Kód:

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

Výstup (./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]

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

Následující návaznost navazuje na obslužný program signálu z mé původní odpovědi a může nahradit obslužný program signálu ve výše uvedeném příkladu, aby demonstroval, jak abi::__cxa_demangle může být použito k demanglování symbolů. Tento obslužný program vydává stejný demangled výstup jako výše uvedený příklad.

Kód:

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

Mohlo by to být stojí za to podívat se na Google Breakpad , multiplatformní generátor havarijních výpisů a nástroje pro zpracování výpisů.

33
Simon Steele

Nezadali jste operační systém, takže je těžké odpovědět. Pokud používáte systém založený na gnu libc, můžete použít funkci libc backtrace().

GCC má také dvě vestavěné moduly, které vám mohou pomoci, ale které mohou nebo nemusí být plně implementovány ve vaší architektuře a ty jsou __builtin_frame_address a __builtin_return_address. Oba z nich chtějí okamžitou celočíselnou úroveň (okamžitou, myslím, že to nemůže být proměnná). Pokud je __builtin_frame_address pro danou úroveň nenulové, mělo by být bezpečné chytit zpáteční adresu stejné úrovně.

21
Brian Mitchell

ulimit -c <value> nastavuje limit velikosti základního souboru na unixu. Ve výchozím nastavení je limit velikosti souboru jádra 0. Hodnoty ulimit můžete zobrazit pomocí ulimit -a.

také, pokud spustíte program zevnitř gdb, bude zastavit váš program na "segmentace porušení" (SIGSEGV, obecně, když jste přistupovali k části paměti, kterou jste neměli přiděleny), nebo můžete nastavit zarážky.

ddd a nemiver jsou front-endy pro gdb, které usnadňují práci s ním nováčkem.

12
Joseph

Na tento problém jsem se na chvíli díval.

A pohřben hluboko v README nástroje Google Performance Tools

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

mluví o libunwindu

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

Rád bych slyšel názory této knihovny.

Problém s -rdynamic spočívá v tom, že v některých případech může relativně významně zvětšit velikost binárního souboru

10
Gregory

Děkuji enthusiasticgeek za to, že jsem upozornil na nástroj addr2line.

Napsal jsem rychlý a špinavý skript pro zpracování výstupu odpovědi zde : (Moc díky jschmier!) Pomocí nástroje addr2line.

Skript akceptuje jeden argument: Název souboru obsahující výstup z obslužného programu jschmier.

Výstup by měl vytisknout něco jako následující pro každou úroveň trasování:

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;

Kód:

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

Některé verze libc obsahují funkce, které se zabývají stopami zásobníku; můžete je používat:

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

Vzpomínám si, že používám libunwind už dávno, abych získal stopy stohů, ale nemusí být podporován na vaší platformě.

10
Stephen Deken

Je důležité si uvědomit, že jakmile vygenerujete základní soubor, budete k tomu muset použít nástroj gdb. Aby měl gdb smysl pro váš základní soubor, musíte gcc říci, že má binární soubor s ladicími symboly: pro tento účel zkompilujete s parametrem -g:

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

Potom můžete buď nastavit "ulimit -c unlimited", aby se dump vypustil do jádra, nebo jednoduše spusťte program uvnitř gdb. Mám rád druhý přístup: 

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

Doufám, že to pomůže. 

10
Benson
ulimit -c unlimited

je systémová proměnná, která umožní vytvořit výpis jádra po zhroucení aplikace. V tomto případě neomezené množství. Vyhledejte soubor nazvaný jádro ve stejném adresáři. Ujistěte se, že jste kód zkompilovali s povolenými ladicími informacemi!

pozdravy

9
mana

Zapomeňte na změnu zdrojů a proveďte některé hacky s funkcí backtrace () nebo makroseky - to jsou jen špatná řešení.

Jako řádně fungující řešení bych doporučil:

  1. Zkompilujte svůj program s příznakem "-g" pro vložení ladicích symbolů do binárních souborů (nebojte se, že to nebude mít vliv na výkon). 
  2. Na linuxu spusťte následující příkaz: "ulimit -c unlimited" - aby systém umožňoval skládat velké havárie.
  3. Když váš program havaroval, v pracovním adresáři uvidíte soubor "core".
  4. Spusťte další příkaz k tisku zpětné vazby na hodnotu stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

To bude tisknout řádně čitelné zpětné vazby vašeho programu lidsky čitelným způsobem (s názvy zdrojových souborů a čísly řádků). Tento přístup vám navíc poskytne svobodu automatizovat váš systém: Mají krátký skript, který zkontroluje, zda proces vytvořil dump jádra, a pak pošle backtraces e-mailem vývojářům, nebo je zapíše do nějakého logovacího systému.

9
loopzilla

Můžete použít DeathHandler - malá třída C++, která dělá vše pro vás, spolehlivě.

8
markhor
8
Roskoto

Podívat se na:

man 3 backtrace

A:

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

Jedná se o rozšíření GNU.

7
Stéphane

Viz zařízení Stack Trace v ACE (ADAPTIVE Communication Environment). Je to již napsáno, aby pokrylo všechny hlavní platformy (a další). Knihovna je licencována ve stylu BSD, takže můžete kód zkopírovat/vložit, pokud nechcete používat ACE.

6
Adam Mitz

Mohu pomoci s verzí Linuxu: lze použít funkci backtrace, backtrace_symbols a backtrace_symbols_fd. Viz příslušné manuálové stránky.

5
terminus

* nix: můžete zachytit SIGSEGV (obvykle tento signál je zvýšen před zhroucení) a udržet informace do souboru. (kromě základního souboru, který můžete použít například k ladění pomocí gdb).

win: Zaškrtněte to z msdn.

Můžete se také podívat na chromový kód Google, abyste zjistili, jak zpracovává zhroucení. Má mechanismus pro zpracování výjimek.

4
INS

Zjistil jsem, že řešení @tgamblin není kompletní. Nelze zvládnout stackoverflow. Myslím, že ve výchozím nastavení je obsluha signálu volána se stejným stackem a SIGSEGV je hozen dvakrát. Pro ochranu je třeba zaregistrovat nezávislý zásobník pro obsluhu signálu.

Toto můžete zkontrolovat pomocí níže uvedeného kódu. Ve výchozím nastavení selže obslužná rutina. S definovaným makro STACK_OVERFLOW je to v pořádku.

#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

Viděl jsem zde mnoho odpovědí, které provádějí obsluhovač signálu a pak opouštějí. To je způsob, jak jít, ale pamatujte si velmi důležitý fakt: Pokud chcete získat jádro výpisu pro vygenerovanou chybu, můžete ' t volání exit(status). Místo toho volejte abort()!

3
jard18

Nový král ve městě přišel https://github.com/bombela/backward-cpp

1 záhlaví umístit do kódu a 1 knihovna k instalaci.

Osobně tomu říkám pomocí této funkce

#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

Použil bych kód, který generuje trasování zásobníku pro uniklou paměť v Visual Leak Detector . To však funguje pouze na Win32.

3
Jim Buck

Jako řešení pouze pro Windows můžete získat ekvivalent trasování zásobníku (s mnohem více informací) pomocí Windows Error Reporting . Pouze s několika položkami registru lze nastavit sbírat výpisy uživatelského režimu :

Počínaje systémem Windows Server 2008 a Windows Vista s aktualizací Service Pack 1 (SP1) lze konfigurovat službu Windows Reporting chyb (WER) tak, aby byly úplné výpisy uživatelského režimu shromažďovány a ukládány lokálně po selhání aplikace uživatelského režimu. [...]

Tato funkce není ve výchozím nastavení povolena. Povolení funkce vyžaduje oprávnění správce. Chcete-li povolit a nakonfigurovat tuto funkci, použijte následující hodnoty registru v části SOFTWARE HKEY_LOCAL_MACHINE SOFTWARE Microsoft Windows Windows Error Reporting LocalDumps.

Můžete nastavit položky registru z instalačního programu, který má požadovaná oprávnění.

Vytvoření výpisu uživatelského režimu má oproti generování trasování zásobníku v klientovi následující výhody:

  • Je již implementován v systému. Můžete použít buď WER, jak je uvedeno výše, nebo zavolat MiniDumpWriteDump sami, pokud potřebujete jemnější kontrolu nad množstvím informací, které chcete vypsat. (Nezapomeňte jej zavolat z jiného procesu.)
  • Cesta je úplnější než trasování zásobníku. Mimo jiné může obsahovat lokální proměnné, argumenty funkcí, komíny pro další vlákna, načtené moduly a tak dále. Množství dat (a následně velikost) je vysoce přizpůsobitelné.
  • Není třeba dodávat ladicí symboly. To jak drasticky snižuje velikost vašeho nasazení, tak i znesnadňuje reverzní inženýrství vaší aplikace.
  • Velmi nezávislý na kompilátoru, který používáte. Použití WER nevyžaduje ani žádný kód. Ať tak či onak, mít způsob, jak získat databázi symbolů (PDB) je velmi užitečné pro offline analýzu. Věřím, že GCC může buď generovat PDB, nebo existují nástroje pro převod databáze symbolů do formátu PDB.

Všimněte si, že WER může být spuštěn pouze havárií aplikace (tj. Systém ukončí proces kvůli neošetřené výjimce). MiniDumpWriteDump lze volat kdykoliv. To může být užitečné, pokud potřebujete vyhodnotit aktuální stav a diagnostikovat problémy jiné než selhání.

Povinné čtení, pokud chcete vyhodnotit použitelnost mini výpisů:

2
IInspectable

Kromě výše uvedených odpovědí zde způsob, jak Debian Linux OS generuje jádro výpisu 

  1. V domovské složce uživatele vytvořte složku „coredumps“
  2. Přejděte do /etc/security/limits.conf. Pod řádkem '' zadejte "soft core unlimited" a "root soft core neomezené", pokud povolíte jádro dump pro root, aby bylo možné neomezené místo pro výpisy jádra. 
  3. POZNÁMKA: „* měkké jádro neomezené“ nepokrývá kořen, což je důvod, proč musí být kořen specifikován ve vlastním řádku.
  4. Chcete-li tyto hodnoty zkontrolovat, odhlaste se, přihlaste se a zadejte „ulimit -a“. „Velikost souboru jádra“ by měla být nastavena na neomezenou.
  5. Zkontrolujte soubory .bashrc (uživatel a root, pokud je to vhodné), abyste se ujistili, že tam není nastaven ulimit. V opačném případě bude výše uvedená hodnota přepsána při spuštění.
  6. Otevřete soubor /etc/sysctl.conf. V dolní části zadejte následující: „kernel.core_pattern = /home//coredumps/%e_%t.dump“. (% e bude název procesu a% t bude systémový čas)
  7. Ukončete a zadejte „sysctl -p“ pro načtení nové konfigurace. Zkontrolujte/proc/sys/kernel/core_pattern a ověřte, zda to odpovídá tomu, co jste právě zadali.
  8. Jádro dumpingu lze otestovat spuštěním procesu na příkazovém řádku („&“) a poté jeho zabitím „kill -11“. Pokud je úspěšné ukládání jádra, zobrazí se po indikaci poruchy segmentace „(jádro dumpingové)“.
2

V systému Linux/unix/MacOSX používejte základní soubory (můžete je povolit pomocí ulimit nebo kompatibilního systémového volání ). V systému Windows používejte hlášení chyb společnosti Microsoft (můžete se stát partnerem a získat přístup k datům havárie aplikace).

1
Kasprzol

Pokud stále chcete jít sám, jak jsem to udělal, můžete odkazovat proti bfd a vyhnout se použití addr2line, jak jsem to udělal zde:

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

Výsledkem je výstup:

[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

Zapomněl jsem na tech- nologii GNOME "apportu", ale o jeho používání moc nevím. Používá se pro generování stacktraces a další diagnostiku pro zpracování a může automaticky ukládat chyby. Určitě stojí za to se podívat.

0
Joseph

Pokud váš program havaruje, je to samotný operační systém, který generuje informace o výpisu pádu. Pokud používáte operační systém * nix, nemusíte tomu zabránit (podívejte se na volby příkazu coimump příkaz ulimit).

0
nsayer

Vypadá to, že v jedné z posledních verzí c ++ boost se objevila knihovna, která poskytuje přesně to, co chcete, pravděpodobně kód bude multiplatformový. Je to boost :: stacktrace , které můžete použít jako as ve vzestupném vzorku :

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

V Linuxu Zkompilujete výše uvedený kód:

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

Příklad zpětného kopírování zkopírované z dokumentace 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
0
Grzegorz Bazior