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


590

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

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

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


4
backtrace и backtrace_symbols_fd не безопасны для асинхронного сигнала. Вы не должны использовать эти функции в обработчике сигналов
Parag Bafna

10
backtrace_symbols вызывает malloc и поэтому не должен использоваться в обработчике сигналов. Две другие функции (backtrace и backtrace_symbols_fd) не имеют этой проблемы и обычно используются в обработчиках сигналов.
cmccabe

3
@cmccabe, который является неправильным backtrace_symbols_fd, обычно не вызывает malloc, но может, если что-то пойдет не так в его блоке catch_error
Сэм Саффрон

6
Это «может» в том смысле, что не существует спецификации POSIX для backtrace_symbols_fd (или какой-либо обратной трассировки); однако в GNU / Linux backtrace_symbols_fd указывается, чтобы никогда не вызывать malloc, как указано в linux.die.net/man/3/backtrace_symbols_fd . Поэтому можно с уверенностью предположить, что он никогда не будет вызывать malloc в Linux.
Codetaku

Ответы:


509

Для Linux, и я считаю, что Mac OS X, если вы используете gcc или любой другой компилятор, который использует glibc, вы можете использовать функции backtrace () execinfo.hдля печати трассировки стека и корректного выхода при возникновении ошибки сегментации. Документация может быть найдена в руководстве по 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.


53
Также есть /lib/libSegFault.so, который вы можете использовать с LD_PRELOAD.
CesarB

6
Похоже, что первые две записи в выводе вашей обратной трассировки содержат адрес возврата внутри обработчика сигнала и, возможно, один из них внутри sigaction()libc. Хотя ваша обратная трассировка кажется правильной, я иногда обнаруживал, что необходимы дополнительные шаги для обеспечения того, чтобы фактическое местоположение ошибки отображалось в обратной трассировке, поскольку оно может быть перезаписано sigaction()ядром.
jschmier

9
Что произойдет, если сбой произойдет из-за malloc? Разве вы не удерживаете блокировку, а затем застреваете, когда «обратная трассировка» пытается выделить память?
Маттиас Нильссон

7
catchsegvэто не то, что нужно OP, но он отлично подходит для выявления ошибок сегментации и получения всей информации.
Мэтт Кларксон

8
Для ARM мне пришлось также скомпилировать с -funwind-таблицами. В противном случае глубина моего стека всегда была 1 (пусто).
jfritz42

128

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

Это дает нам 3 возможности. Вместо запуска "программа -o хай":

  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

Во всех трех случаях вы получите более четкие трассировки с меньшим количеством символов оптимизации (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=blob;f=debug/segfault.c и его родительский каталог. http://sourceware.org/git/?p=glibc.git;a=tree;f=debug


1
«Возможность 3. Связь с libSegFault во время компиляции» не работает.
HHK

5
@crafter: Что вы имеете в виду "не работает". Что вы пробовали, на каком языке / компилятор / инструментарий / дистрибуция / оборудование? Не удалось скомпилировать? Чтобы поймать ошибку? Производить продукцию вообще? Производить сложный в использовании вывод? Спасибо за подробности, это поможет всем.
Стефан Гурихон

1
«лучший источник - это, к сожалению, источник» ... Надеюсь, когда-нибудь на странице man для catchsegv будет упоминаться SEGFAULT_SIGNALS. До тех пор, есть этот ответ для ссылки.
Грегго

Я не могу поверить, что я программировал на C в течение 5 лет и никогда не слышал об этом: /
DavidMFrey

6
@ StéphaneGourichon @HansKratz Чтобы связать с libSegFault, вам нужно добавить -Wl,--no-as-neededфлаги компилятора. В противном случае, ldдействительно не будет ссылаться на libSegFault, потому что он признает, что двоичный файл не использует ни один из своих символов.
Филипп

122

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


11
В системах, использующих GNU ld, не забудьте скомпилировать, -rdynamicчтобы указать компоновщику добавить все символы, а не только используемые, в таблицу динамических символов. Это позволяет backtrace_symbols()преобразовывать адреса в имена функций
jschmier

1
Кроме того, вам нужно добавить опцию "-mapcs-frame" в командную строку GCC для генерации стековых фреймов на платформе ARM
qehgt

3
Это может быть слишком поздно, но можем ли мы addr2lineкак-то использовать команду, чтобы получить точную строку, где произошел сбой?
энтузиазм

4
На более поздних сборках glibc uc_mcontextотсутствует поле с именем eip. Теперь есть массив, который нужно проиндексировать, uc_mcontext.gregs[REG_EIP]это эквивалентно.
mmlb

6
Для ARM мои обратные трассировки всегда имели глубину 1, пока я не добавил опцию -funwind-tables в компилятор.
jfritz42

84

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

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

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

1
Спасибо вам за это, jschmier. Я создал небольшой скрипт bash, чтобы передать результат этого в утилиту addr2line. См .: stackoverflow.com/a/15801966/1797414
arr_sea

4
Не забудьте #include <cxxabi.h>
Bamaco

1
Хорошая документация и простой заголовочный файл были опубликованы здесь с 2008 года ... panthema.net/2008/0901-stacktrace-demangled очень похож на ваш подход :)
kevinf

abi :: __cxa_demangle, по-видимому, не безопасен для асинхронного сигнала, поэтому обработчик сигнала может заблокироваться где-нибудь в malloc.
Орси

Использование std::cerr, free()и exit()все нарушают ограничения в отношении вызова не Асинхры-сигнал безопасных вызовов на системах POSIX. Этот код будет тупиковым , если ваш процесс терпит неудачу в любом вызове , такие как free(), malloc() newили detete.
Эндрю Хенле

31

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


Он сообщает о таких вещах, как ошибки сегментации, но не сообщает никакой информации о необработанных исключениях C ++.
Д.Бедренко

21

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

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


13

Спасибо энтузиасту за то, что обратили мое внимание на утилиту 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 

12

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

также, если вы запустите вашу программу из GDB, она остановит вашу программу из-за «нарушений сегментации» (SIGSEGV обычно, когда вы получили доступ к фрагменту памяти, который вы не выделили) или вы можете установить точки останова.

DDD и Nemiver являются интерфейсом для GDB, что делает работу с ним намного проще для новичка.


6
Дампы ядра гораздо полезнее, чем трассировки стека, потому что вы можете загрузить дамп ядра в отладчике и увидеть состояние всей программы и ее данных в момент сбоя.
Адам Хос

1
Функция обратного отслеживания, предложенная другими, вероятно, лучше, чем ничего, но она очень проста - она ​​даже не дает номеров строк. С помощью дампов ядра, с другой стороны, вы сможете задним числом просматривать все состояние вашего приложения в момент его сбоя (включая детальную трассировку стека). Там может быть практические проблемы с попыткой использовать это для отладки поля, но это, безусловно , более мощный инструмент для анализа аварий и утверждает в процессе разработки (по крайней мере на Linux).
Нобар

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

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


4
Вы также можете позвонить gdbпрямо из вашей программы сбоя. Настройте обработчик для SIGSEGV, SEGILL, SIGBUS, SIGFPE, который будет вызывать GDB. Подробности: stackoverflow.com/questions/3151779/… Преимущество в том, что вы получаете красивую аннотированную обратную трассировку, как в bt full, а также вы можете получать трассировки стека всех потоков.
Ви.

Вы также можете получить обратную трассировку проще, чем в ответе: gdb -silent ./prog core --eval-command = backtrace --batch -it покажет обратную трассировку и закроет отладчик
baziorek

10

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

И похоронен глубоко в Google Performance Tools README

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

говорит о либунвинде

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

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

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


2
На x86 / 64 я не видел, чтобы динамическое увеличение размера двоичного файла значительно. Добавление -g приводит к гораздо большему увеличению.
Дан

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

1
Это правильно. Это очень сложно сделать правильно, но я добился отличного успеха с gaddr2line. Здесь много полезной
Григорий

9

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

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

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


9

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


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

9

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

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

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

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


Это дает неправильные номера строк. Можно ли это улучшить?
HeyJude

7
ulimit -c unlimited

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

С уважением


5
Пользователь не запрашивает дамп ядра. Он просит трассировку стека. См. Delorie.com/gnu/docs/glibc/libc_665.html
Тодд Гамблин,

1
основной дамп будет содержать стек вызовов в момент сбоя, не так ли?
Пн.

3
Вы предполагаете, что он на Unix, и используете Bash.
Пол Томблин

2
Если вы используете tcsh, вы должны сделатьlimit coredumpsize unlimited
sivabudh


6

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


Ссылка кажется мертвой.
tglas

5

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


5

Похоже, что в одной из последних появившихся библиотек 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

4

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

win: проверьте это из msdn.

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


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

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

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

Вот Это Да! Вот наконец то, как это должно быть сделано! Я только что бросил собственное решение в пользу этого.
Tglas

3

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


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

3

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


3

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

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

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

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

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

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

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

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


2

В дополнение к приведенным выше ответам, здесь вы узнаете, как заставить ОС 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 (пользователь и root, если применимо), чтобы убедиться, что ulimit там не установлен. В противном случае указанное выше значение будет перезаписано при запуске.
  6. Откройте /etc/sysctl.conf. Введите следующее внизу: «kernel.core_pattern = /home//coredumps/%e_%t.dump». (% e будет именем процесса, а% t будет системным временем)
  7. Выйдите и введите «sysctl -p», чтобы загрузить новую конфигурацию. Проверьте / proc / sys / kernel / core_pattern и убедитесь, что это соответствует тому, что вы только что набрали.
  8. Дамп ядра можно проверить, запустив процесс в командной строке («&»), а затем убив его командой «kill -11». Если дамп ядра успешен, вы увидите «(дамп ядра)» после индикации ошибки сегментации.

2

Если вы все еще хотите сделать это в одиночку, как я, вы можете ссылаться 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]

1

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


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.