Как автоматически сгенерировать stacktrace при сбое моей программы

StackOverflow https://stackoverflow.com/questions/77005

Вопрос

Я работаю над Linux с компилятором GCC.Когда моя программа на C ++ выходит из строя, я бы хотел, чтобы она автоматически генерировала stacktrace.

Моя программа запускается многими разными пользователями, а также работает в Linux, Windows и Macintosh (все версии скомпилированы с использованием gcc).

Я бы хотел, чтобы моя программа могла генерировать трассировку стека при сбое, и в следующий раз, когда пользователь запустит ее, она спросит, можно ли отправить мне трассировку стека, чтобы я мог отследить проблему.Я могу обработать отправку информации мне, но я не знаю, как сгенерировать строку трассировки.Есть какие-нибудь идеи?

Это было полезно?

Решение

Для Linux и, я полагаю, Mac OS X, если вы используете gcc или любой другой компилятор, использующий glibc, вы можете использовать функции backtrace() в execinfo.h чтобы напечатать stacktrace и корректно завершить работу при возникновении ошибки сегментации.Документацию можно найти в руководстве по libc.

Вот пример программы, которая устанавливает SIGSEGV обработчик и выводит трассировку стека в stderr когда он выходит из строя.Тот Самый baz() функция здесь вызывает segfault, который запускает обработчик:

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

Компиляция с помощью -g -rdynamic возвращает вам информацию о символах в ваших выходных данных, которую glibc может использовать для создания хорошей трассировки стека:

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

Выполняя это, вы получаете следующий результат:

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

Это показывает модуль загрузки, смещение и функцию, из которых был получен каждый кадр в стеке.Здесь вы можете увидеть обработчик сигналов поверх стека и функции libc перед ним main в дополнение к main, foo, bar, и baz.

Другие советы

Linux

В то время как использование функции backtrace() в execinfo.h для печати трассировки стека и корректного завершения при возникновении ошибки сегментации имеет уже было предложено, я не вижу упоминания о тонкостях, необходимых для обеспечения того, чтобы результирующая трассировка указывала на фактическое местоположение неисправности (по крайней мере, для некоторых архитектур - x86 и ARM).

Первые две записи в цепочке кадров стека, когда вы попадаете в обработчик сигнала, содержат адрес возврата внутри обработчика сигнала и один внутри sigaction() в libc.Фрейм стека последней функции, вызванной перед тем, как сигнал (который является местоположением неисправности) будет потерян.

Код

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

Выходной сигнал

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]

Все опасности, связанные с вызовом функций backtrace() в обработчике сигналов, все еще существуют, и их не следует упускать из виду, но я нахожу функциональность, которую я описал здесь, весьма полезной при отладке сбоев.

Важно отметить, что приведенный мной пример разработан / протестирован в Linux для x86.Я также успешно реализовал это на ARM, используя uc_mcontext.arm_pc вместо того, чтобы uc_mcontext.eip.

Вот ссылка на статью, из которой я узнал подробности этой реализации:http://www.linuxjournal.com/article/6391

Это даже проще, чем "man backtrace", есть малодокументированная библиотека (специфичная для GNU), распространяемая с glibc как libSegFault.so , который, как я полагаю, был написан Ульрихом Дреппером для поддержки программы catchsegv (см. "man catchsegv").

Это дает нам 3 возможности.Вместо запуска "program -o hai":

  1. Запуск в пределах catchsegv:

    $ catchsegv program -o hai
    
  2. Ссылка на libSegFault во время выполнения:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Ссылка на libSegFault во время компиляции:

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

Во всех 3 случаях вы получите более четкие обратные трассировки с меньшим количеством оптимизаций (gcc -O0 или -O1) и символов отладки (gcc -g).В противном случае вы можете просто получить кучу адресов памяти.

Вы также можете перехватить больше сигналов для трассировки стека с помощью чего-то вроде:

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

Результат будет выглядеть примерно так (обратите внимание на обратную трассировку внизу).:

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

Если вы хотите узнать кровавые подробности, лучший источник, к сожалению, это источник:Видишь http://sourceware.org/git/?p=glibc.git ;a=большой двоичный объект;f=отладка/segfault.c и его родительский каталог http://sourceware.org/git/?p=glibc.git ;a=дерево;f=отладка

Даже несмотря на то, что правильный ответ было предоставлено описание того, как использовать GNU libc backtrace() функция1 и я обеспечил мой собственный ответ в нем описывается, как обеспечить обратную трассировку от обработчика сигнала, указывающую на фактическое местоположение неисправности2, я не вижу никаких упоминаний о распутывание Символы C ++, выводимые из обратной трассировки.

При получении обратных следов из программы на C ++ выходные данные могут быть запущены через c++filt1 для распутывания символов или с помощью abi::__cxa_demangle1 напрямую.

  • 1 Linux и OS X Обратите внимание , что c++filt и __cxa_demangle специфичны ли для GCC
  • 2 Linux

Следующий пример C ++ Linux использует тот же обработчик сигналов, что и мой другой ответ и демонстрирует, как c++filt может использоваться для распутывания символов.

Код:

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

Выходной сигнал (./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]

Распутанный Выход (./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]

Следующее основано на обработчике сигналов из моего оригинальный ответ и может заменить обработчик сигнала в приведенном выше примере, чтобы продемонстрировать, как abi::__cxa_demangle может использоваться для распутывания символов.Этот обработчик сигнала выдает тот же искаженный выходной сигнал, что и в приведенном выше примере.

Код:

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

Возможно, на это стоит взглянуть Панель управления Google, кроссплатформенный генератор аварийных дампов и инструменты для обработки дампов.

Вы не указали свою операционную систему, поэтому на этот вопрос трудно ответить.Если вы используете систему, основанную на gnu libc, вы могли бы использовать функцию libc backtrace().

GCC также имеет два встроенных компонента, которые могут вам помочь, но которые могут быть или не быть полностью реализованы в вашей архитектуре, и это __builtin_frame_address и __builtin_return_address.Оба из которых требуют немедленного целочисленного уровня (под немедленным я подразумеваю, что это не может быть переменная).Если __builtin_frame_address поскольку данный уровень отличен от нуля, должно быть безопасно получить обратный адрес того же уровня.

ulimit -c <value> устанавливает ограничение на размер основного файла в unix.По умолчанию предельный размер основного файла равен 0.Вы можете видеть свой ulimit значения с ulimit -a.

кроме того, если вы запустите свою программу из gdb, она остановит вашу программу при "нарушениях сегментации" (SIGSEGV, как правило, когда вы обращались к фрагменту памяти, который вы не выделяли), или вы можете установить точки останова.

ddd и nemiver - это интерфейсы для gdb, которые значительно упрощают работу с ней новичку.

Спасибо enthusiasticgeek за то, что обратили мое внимание на утилиту addr2line.

Я написал быстрый и грязный скрипт для обработки выходных данных предоставленного ответа здесь:(большое спасибо jschmier!) с помощью утилиты addr2line.

Скрипт принимает один аргумент:Имя файла, содержащего выходные данные утилиты jschmier.

На выходе должно быть выведено что-то вроде следующего для каждого уровня трассировки:

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;

Код:

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

Важно отметить, что после создания основного файла вам нужно будет просмотреть его с помощью инструмента gdb.Чтобы gdb понял смысл вашего основного файла, вы должны сообщить gcc, чтобы он обработал двоичный файл символами отладки:чтобы сделать это, вы компилируете с флагом -g:

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

Затем вы можете либо установить "ulimit -c unlimited", чтобы позволить ему выгружать ядро, либо просто запустить вашу программу внутри gdb.Мне больше нравится второй подход:

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

Я надеюсь, что это поможет.

Я уже некоторое время занимаюсь этой проблемой.

И зарыт глубоко в Google Performance Tools README

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

рассказывает о libunwind

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

Хотелось бы услышать мнение об этой библиотеке.

Проблема с -rdynamic заключается в том, что в некоторых случаях он может относительно значительно увеличить размер двоичного файла

Некоторые версии libc содержат функции, которые имеют дело со трассировками стека;возможно, вы сможете ими воспользоваться:

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

Я помню, как использовал свободный ветер давным-давно удалось получить трассировки стека, но, возможно, это не поддерживается на вашей платформе.

Забудьте об изменении ваших источников и выполните несколько взломов с помощью функции backtrace() или макросов - это просто плохие решения.

В качестве правильно работающего решения я бы посоветовал:

  1. Скомпилируйте вашу программу с флагом "-g" для встраивания символов отладки в двоичный файл (не волнуйтесь, это не повлияет на вашу производительность).
  2. В Linux выполните следующую команду:"ulimit -c unlimited" - позволяет системе создавать большие аварийные дампы.
  3. Когда ваша программа выйдет из строя, в рабочем каталоге вы увидите файл "core".
  4. Запустите следующую команду, чтобы вывести обратную трассировку в стандартный вывод:gdb -batch -ex "обратная трассировка" ./your_programm_exe ./ядро

Это позволит напечатать надлежащую для чтения обратную трассировку вашей программы в удобочитаемом виде (с именами исходных файлов и номерами строк).Более того, такой подход даст вам свободу в автоматизации вашей системы:создайте короткий скрипт, который проверяет, создал ли процесс дамп ядра, а затем отправляет обратные трассировки по электронной почте разработчикам или регистрирует это в какой-либо системе ведения журнала.

ulimit -c unlimited

это системная переменная, которая позволит создать дамп ядра после сбоя вашего приложения.В данном случае неограниченное количество.Найдите файл с именем core в том же каталоге.Убедитесь, что вы скомпилировали свой код с включенной информацией об отладке!

с уважением

Конкурсы:Как насчет StackWalk64 http://msdn.microsoft.com/en-us/library/ms680650.aspx

Вы можете использовать Смертоносец - небольшой класс C ++, который делает все за вас, надежный.

Посмотрите на:

обратный путь человека 3

И:

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

Это расширения GNU.

Смотрите средство трассировки стека в туз (АДАПТИВНАЯ коммуникационная среда).Она уже написана для всех основных платформ (и не только).Библиотека лицензирована в стиле BSD, поэтому вы даже можете скопировать / вставить код, если не хотите использовать ACE.

Я могу помочь с версией Linux:можно использовать функции backtrace, backtrace_symbols и backtrace_symbols_fd.Смотрите соответствующие страницы руководства.

*никс:вы можете перехватить SIGSEGV (обычно этот сигнал подается перед сбоем) и сохраните информацию в файле.(помимо основного файла, который вы можете использовать для отладки, например, с помощью gdb).

Конкурсы:Проверить это из msdn.

Вы также можете посмотреть код Google Chrome, чтобы увидеть, как он справляется со сбоями.У него есть хороший механизм обработки исключений.

Я обнаружил, что решение @tgamblin не является полным.Он не может работать с stackoverflow.Я думаю, потому что по умолчанию обработчик сигнала вызывается с одним и тем же стеком и SIGSEGV выбрасывается дважды.Для защиты вам необходимо зарегистрировать независимый стек для обработчика сигналов.

Вы можете проверить это с помощью приведенного ниже кода.По умолчанию обработчик завершается с ошибкой.С определенным макросом STACK_OVERFLOW все в порядке.

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

Я бы использовал код, который генерирует трассировку стека для утечки памяти в Визуальный Течеискатель.Однако это работает только в Win32.

Я видел здесь много ответов на выполнение обработчика сигнала и затем завершение работы.Это правильный путь, но помните об очень важном факте:Если вы хотите получить дамп ядра для сгенерированной ошибки, вы не можете вызвать exit(status).Звонить abort() вместо этого!

В город прибыл новый король https://github.com/bombela/backward-cpp

1 заголовок для размещения в вашем коде и 1 библиотеку для установки.

Лично я вызываю это с помощью этой функции

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

В дополнение к приведенным выше ответам, вот как заставить ОС Debian Linux генерировать дамп ядра

  1. Создайте папку “coredumps” в домашней папке пользователя
  2. Перейдите в /etc/security/limits.conf.Под строкой " ' введите “soft core unlimited” и “root soft core unlimited”, если разрешены дампы ядра для root, чтобы предоставить неограниченное пространство для дампов ядра.
  3. ПРИМЕЧАНИЕ:“* soft core unlimited” не распространяется на root, поэтому root должен быть указан в отдельной строке.
  4. Чтобы проверить эти значения, выйдите из системы, войдите снова и введите “ulimit -a”.“Размер основного файла” должен быть установлен на неограниченный.
  5. Проверьте файлы .bashrc (user и root, если применимо), чтобы убедиться, что ulimit там не установлен.В противном случае указанное выше значение будет перезаписано при запуске.
  6. Откройте файл /etc/sysctl.conf.Введите следующее в нижней части поля:“kernel.core_pattern = /home//coredumps/%e_%t.дамп”.(%e будет именем процесса, а %t - системным временем)
  7. Завершите работу и введите “sysctl -p”, чтобы загрузить новую конфигурацию Проверьте /proc/sys/kernel/core_pattern и убедитесь, что это соответствует тому, что вы только что ввели.
  8. Сброс ядра можно протестировать, запустив процесс в командной строке (“ &”), а затем завершив его с помощью “kill -11 ”.Если сброс ядра прошел успешно, вы увидите “(сброс ядра)" после индикации ошибки сегментации.

В качестве решения только для Windows вы можете получить эквивалент трассировки стека (с гораздо, гораздо большим количеством информации), используя Отчеты об ошибках Windows.С помощью всего лишь нескольких записей в реестре его можно настроить на сбор дампов пользовательского режима:

Начиная с Windows Server 2008 и Windows Vista с пакетом обновления 1 (SP1), отчеты об ошибках Windows (WER) можно настроить таким образом, чтобы полные дампы пользовательского режима собирались и сохранялись локально после сбоя приложения пользовательского режима.[...]

По умолчанию эта функция не включена.Для включения этой функции требуются права администратора.Чтобы включить и настроить эту функцию, используйте следующие значения реестра в разделе HKEY_LOCAL_MACHINE\ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ\ Microsoft\ Windows\ Отчеты об ошибках Windows\LocalDumps Клавиша.

Вы можете настроить записи реестра с помощью программы установки, которая обладает необходимыми привилегиями.

Создание дампа пользовательского режима имеет следующие преимущества по сравнению с созданием трассировки стека на клиенте:

  • Это уже реализовано в системе.Вы можете либо использовать WER, как описано выше, либо вызвать MiniDumpWriteDump самостоятельно, если вам нужен более детальный контроль над объемом информации для сброса.(Обязательно вызовите его из другого процесса.)
  • Способ более полный, чем трассировка стека.Среди прочего, он может содержать локальные переменные, аргументы функции, стеки для других потоков, загруженные модули и так далее.Объем данных (и, следовательно, размер) легко настраивается.
  • Нет необходимости отправлять символы отладки.Это как резко уменьшает размер вашего развертывания, так и затрудняет реинжиниринг вашего приложения.
  • В значительной степени не зависит от используемого вами компилятора.Использование WER даже не требует какого-либо кода.В любом случае, наличие способа получить базу данных символов (PDB) является очень полезно для автономного анализа.Я полагаю, что GCC может либо генерировать PDB, либо есть инструменты для преобразования базы данных символов в формат PDB.

Обратите внимание, что WER может быть вызван только аварийным завершением работы приложения (т.е.система, завершающая процесс из-за необработанного исключения). MiniDumpWriteDump может быть вызван в любое время.Это может быть полезно, если вам нужно сбросить текущее состояние для диагностики проблем, отличных от сбоя.

Обязательное чтение, если вы хотите оценить применимость мини-дампов:

В Linux / unix / MacOSX используйте основные файлы (вы можете включить их с помощью ulimit или совместимый системный вызов).В Windows используйте отчеты об ошибках Microsoft (вы можете стать партнером и получить доступ к данным о сбое вашего приложения).

Я забыл о технологии GNOME "apport", но я мало что знаю о ее использовании.Он используется для генерации трассировок стека и другой диагностики для обработки и может автоматически регистрировать ошибки.Это, безусловно, стоит проверить.

Похоже, в одной из последних версий c ++ boost появилась библиотека, предоставляющая именно то, что Вы хотите, вероятно, код был бы мультиплатформенным.Это так boost:: отслеживание стека, который Вы можете использовать как как в примере 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
}

В Linux Вы компилируете приведенный выше код:

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

Пример обратной трассировки, скопированной из расширенная документация:

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

Если ты все еще хочешь действовать в одиночку, как это сделал я, ты можешь объединиться против bfd и избегайте использования addr2line как я уже сделал здесь:

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

Это приводит к получению выходных данных:

[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]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top