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

538

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

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

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

  • 2
    Какая ОС, какая оболочка?
  • 2
    backtrace и backtrace_symbols_fd не безопасны для асинхронного сигнала. Вы не должны использовать эти функции в обработчике сигналов
Показать ещё 4 комментария
Теги:
gcc
stack-trace
crash
assert

30 ответов

462
Лучший ответ

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

Здесь приведен пример программы, которая устанавливает обработчик SIGSEGV и печатает stacktrace до stderr, когда он segfaults. Функция 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* 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 может использовать для создания красивой stacktrace:

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

  • 50
    Также есть /lib/libSegFault.so, который вы можете использовать с LD_PRELOAD.
  • 6
    Похоже, что первые две записи в выводе вашей обратной трассировки содержат адрес возврата внутри обработчика сигнала и, возможно, один внутри sigaction() в libc. Несмотря на то, что ваша обратная трассировка кажется правильной, я иногда обнаруживал, что необходимы дополнительные шаги, чтобы гарантировать, что фактическое местоположение ошибки появляется в sigaction() трассировке, поскольку оно может быть перезаписано sigaction() с помощью sigaction() .
Показать ещё 22 комментария
118

Linux

В то время как использование функций backtrace() в execinfo.h для печати stacktrace и изящное изложение, когда вы получаете ошибку сегментации, уже было предложено, я вижу не упоминать о тонкостях, необходимых для обеспечения того, чтобы получившиеся обратные точки указывали на фактическое местоположение ошибки (по крайней мере для некоторых архитектур - 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 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

  • 10
    В системах, использующих GNU ld, не забудьте скомпилировать с -rdynamic чтобы -rdynamic компоновщику добавить все символы, не только используемые, в таблицу динамических символов. Это позволяет backtrace_symbols() преобразовывать адреса в имена функций
  • 0
    Вывод в приведенном выше примере был взят из тестовой программы, скомпилированной с использованием кросс-цепочки gcc-3.4.5-glibc-2.3.6 и выполненной на платформе на основе ARMv6, работающей под управлением Linux Kernel 2.6.22.
Показать ещё 10 комментариев
105

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

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

  • Запуск в catchsegv:

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

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

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

Во всех трех случаях вы получите более четкие обратные трассы с меньшей оптимизацией (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]

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

  • 1
    «Возможность 3. Связь с libSegFault во время компиляции» не работает.
  • 4
    @crafter: Что вы имеете в виду "не работает". Что вы пробовали, на каком языке / компилятор / инструментарий / дистрибуция / оборудование? Не удалось скомпилировать? Чтобы поймать ошибку? Производить продукцию вообще? Производить сложный в использовании вывод? Спасибо за подробности, это поможет всем.
Показать ещё 3 комментария
78

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

При получении обратных трасс из программы на С++ выход может быть запущен через c++filt 1 для демонстрации символов или с помощью abi::__cxa_demangle 1.

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

Следующий пример С++ 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);
}
  • 0
    Спасибо за это, jschmier. Я создал небольшой скрипт bash, чтобы передать вывод этого в утилиту addr2line. См .: stackoverflow.com/a/15801966/1797414
  • 3
    Не забудьте #include <cxxabi.h>
Показать ещё 2 комментария
32

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

  • 0
    Новая ссылка для Breakpad: chromium.googlesource.com/breakpad/breakpad
  • 0
    Спасибо, обновлено!
Показать ещё 1 комментарий
20

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

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

12

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

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

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

  • 6
    Дампы ядра гораздо полезнее, чем трассировки стека, потому что вы можете загрузить дамп ядра в отладчике и увидеть состояние всей программы и ее данных в момент сбоя.
  • 1
    Функция обратного отслеживания, предложенная другими, вероятно, лучше, чем ничего, но она очень проста - она даже не дает номеров строк. Использование дампов ядра, с другой стороны, позволяет вам задним числом просматривать все состояние вашего приложения в момент его сбоя (включая детальную трассировку стека). Могут быть практические проблемы с попыткой использовать это для отладки в полевых условиях, но это определенно более мощный инструмент для анализа сбоев и утверждений во время разработки (по крайней мере, в Linux).
10

Благодарим вас за энтузиазм за привлечение внимания к утилите addr2line.

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

script принимает единственный аргумент: имя файла, содержащего вывод из утилиты 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 
10

Я смотрел на эту проблему некоторое время.

И погрузился глубоко в Google Performance Tools README

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

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

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

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

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

  • 2
    На x86 / 64, я не видел-динамического увеличения размера бинарника. Добавление -g приводит к гораздо большему увеличению.
  • 1
    Я заметил, что libunwind не имеет функциональности для получения номера строки, и я предполагаю (не проверял) unw_get_proc_name возвращает символ функции (который обфусцирован для перегрузки и тому подобное) вместо исходного имени.
Показать ещё 1 комментарий
10

Важно отметить, что как только вы создаете основной файл, вам нужно будет использовать инструмент 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 ...

Надеюсь, это поможет.

  • 3
    Вы также можете вызвать gdb прямо из вашей программы сбоя. Настройте обработчик для SIGSEGV, SEGILL, SIGBUS, SIGFPE, который будет вызывать gdb. Подробности: stackoverflow.com/questions/3151779/… Преимущество в том, что вы получаете красивую аннотированную обратную трассировку, как в bt full , а также вы можете получать трассировки стека всех потоков.
  • 0
    Вы также можете получить обратную трассировку проще, чем в ответе: gdb -silent ./prog core --eval-command = backtrace --batch -it покажет обратную трассировку и закроет отладчик
10

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

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

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

  • 7
    Вау, вы ответили на вопрос до того, как он был задан (посмотрите на время).
9

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

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

  • Скомпилируйте свою программу с помощью флага "-g" для встраивания символов отладки в двоичный файл (не беспокойтесь, это не повлияет на вашу производительность).
  • В linux выполните следующую команду: "ulimit -c unlimited" - чтобы система выдавала большие аварийные дампы.
  • Когда ваша программа разбилась, в рабочем каталоге вы увидите файл "core".
  • Выполнить следующую команду для печати backtrace в stdout: gdb -batch -ex "backtrace". /your _program_exe./core

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

9
ulimit -c unlimited

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

рассматривает

  • 4
    Пользователь не запрашивает дамп ядра. Он просит трассировку стека. См. Delorie.com/gnu/docs/glibc/libc_665.html.
  • 1
    дамп ядра будет содержать стек вызовов в момент сбоя, не так ли?
Показать ещё 2 комментария
8

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

  • 0
    Лучший показывает номера строк (правильные номера строк)
  • 1
    к сожалению, он использует execlp() для выполнения вызовов addr2line ... было бы неплохо полностью остаться в собственной программе (что возможно при включении кода addr2line в некоторой форме)
8

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

  • 0
    StackWalk64 требует, чтобы вы отправляли отладочные символы вместе с вашим кодом. Как правило, это нежелательно, поскольку это значительно упрощает реверс-инжиниринг вашего приложения. В Windows есть более простое решение, которое предоставляет гораздо лучшую информацию: используйте WER и напишите мини-дамп необработанных исключений. Единый реестр требуется, чтобы включить это.
7

Посмотрите:

человек 3 backtrace

и

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

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

  • 1
    Могут быть дополнительные примеры, чтобы помочь на этой странице, которую я создал некоторое время назад: charette.no-ip.com:81/programming/2010-01-25_Backtrace
6

Смотрите средство Stack Trace в ACE (ADAPTIVE Communication Environment). Он уже написан для охвата всех основных платформ (и более). Библиотека лицензирована в стиле BSD, поэтому вы можете даже скопировать/вставить код, если вы не хотите использовать ACE.

5

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

4

Я обнаружил, что решение @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* 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

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

выигрыш: Проверьте этот из msdn.

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

  • 0
    SEH не помогает в создании трассировки стека. Хотя это может быть частью решения, это решение сложнее реализовать и предоставляет меньше информации за счет раскрытия большей информации о вашем приложении, чем реальное решение: напишите мини-дамп. И настроить Windows, чтобы сделать это автоматически для вас.
3

Новый король в городе прибыл 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);
}
3

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

3
  • Скомпилируйте свой код с помощью флага -g, чтобы включить в двоичный код символы отладки.
  • Настройте свою систему так, чтобы ее основные файлы создавались при сбоях приложений (например, ulimit -c unlimited).
  • При сбое приложения вы можете использовать основной файл в отладчике (например, gdb, запустив, например, gdb ./core), чтобы получить обратную трассировку (команда gdb: bt).

Обратите внимание, что имена символов С++ иногда довольно искажены, а backtrace, вероятно, будет несколько непонятным.

Более полезные обратные вызовы, вероятно, потребуют злой хитрости (одно из решений, о котором я слышал, требует, чтобы вы добавили специальный макрос в начало всех методов, которые вы пишете).

3

Я бы использовал код, который генерирует трассировку стека для просочившейся памяти в Visual Leak Detector. Это работает только на Win32.

  • 0
    И требует, чтобы вы отправляли отладочные символы вместе с вашим кодом. В общем не желательно. Напишите мини-дамп и настройте Windows, чтобы он делал это автоматически за необработанными исключениями.
2

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

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

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

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

Эта функция не включена по умолчанию. Для включения функции необходимы права администратора. Чтобы включить и настроить эту функцию, используйте следующие значения реестра в разделе HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps.

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

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

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

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

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

1

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

0

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

Если ваша программа выходит из строя, сама операционная система генерирует информацию о дампе аварий. Если вы используете ОС * nix, вам просто не нужно этого мешать (ознакомьтесь с параметрами командной строки ulimit "coredump" ).

0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню