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

<p>я работаю над linux с компилятором gcc. когда моя программа на c++ падает, я бы хотел, чтобы она автоматически генерировала трассировку стека.</p><p>моя программа запускается многими разными пользователями, а также работает на linux, windows и macintosh (все версии скомпилированы с использованием <code>gcc</code>).</p><p>я хотел бы, чтобы моя программа могла генерировать трассировку стека при сбое, и в следующий раз, когда пользователь запустит ее, она спросит, можно ли отправить мне трассировку стека, чтобы я мог отследить проблему. я могу обработать отправку информации мне, но я не знаю, как генерировать строку трассировки. есть идеи?</p>

<p>для linux, и я полагаю, что mac os x, если вы используете gcc или любой другой компилятор, который использует glibc, вы можете использовать функции backtrace () в <code>execinfo.h</code>, чтобы напечатать трассировку стека и корректно завершить работу, когда вы получите ошибку сегментации. документация может быть найдена <a href="http://www.gnu.org/software/libc/manual/html\_node/backtraces.html">в руководстве по libc</a> .</p><p>вот пример программы, которая устанавливает обработчик <code>sigsegv</code> и распечатывает трассировку стека в <code>stderr</code>, когда происходит сбой. функция <code>baz()</code> здесь вызывает segfault, который запускает обработчик:</p><pre><code>#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. } </code></pre><p>компиляция с <code>-g -rdynamic</code> дает вам информацию о символах в вашем выводе, которую glibc может использовать для создания трассировки стека nice:</p><pre><code>$ 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] </code></pre><p>здесь показаны модуль загрузки, смещение и функция, из которой получен каждый кадр в стеке. здесь вы можете увидеть обработчик сигналов в верхней части стека и функции libc перед <code>main</code> в дополнение к <code>main</code>, <code>foo</code>, <code>bar</code> и <code>baz</code>.</p>

<h2>linux</h2><p>хотя использование функций backtrace () в execinfo.h для печати трассировки стека и корректного выхода при возникновении ошибки сегментации <a href="https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/77336#77336">уже было предложено</a> , я не вижу упоминаний о тонкостях, необходимых для обеспечения того, чтобы полученная обратная трассировка указывала на фактическое местонахождение ошибки (по крайней мере, для некоторых архитектур - x86 и arm).</p><p>первые две записи в цепочке кадров стека, когда вы попадаете в обработчик сигнала, содержат адрес возврата внутри обработчика сигнала и один внутри sigaction () в libc. кадр стека последней функции, вызванной до того, как сигнал (который является местоположением ошибки) теряется.</p><h2>код</h2><pre><code>#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); } </code></pre><h2>результат</h2><pre><code>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] </code></pre><p>все опасности вызова функций backtrace () в обработчике сигналов все еще существуют, и их не следует упускать из виду, но я нахожу описанную здесь функциональность весьма полезной при отладке сбоев.</p><p>важно отметить, что приведенный мной пример разработан/протестирован на linux для x86. я также успешно реализовал это на arm, используя <code>uc\_mcontext.arm\_pc</code> вместо <code>uc\_mcontext.eip</code>. </p><p>вот ссылка на статью, где я узнал подробности этой реализации: <a href="http://www.linuxjournal.com/article/6391">http://www.linuxjournal.com/article/6391</a> </p>

<p>это даже проще, чем "man backtrace", есть немного документированная библиотека (специфичная для gnu), распространяемая с glibc как libsegfault.so, которая, как я полагаю, была написана ульрихом дреппером для поддержки программы catchsegv (см. "man catchsegv").</p><p>это дает нам 3 возможности. вместо запуска "программа -o хай":</p><ol> <li><p>запустить в catchsegv:</p> <pre><code>$ catchsegv program -o hai  ссылка с libsegfault во время выполнения: $ ld\_preload=/lib/libsegfault.so program -o hai </code></pre></li> <li><p>ссылка с libsegfault во время компиляции:</p> <pre><code>$ gcc -g1 -lsegfault -o program program.cc $ program -o hai </code></pre></li> </ol><p>во всех трех случаях вы получите более четкие трассировки с меньшим количеством символов оптимизации (gcc -o0 или -o1) и отладки (gcc -g). в противном случае вы можете просто получить кучу адресов памяти.</p><p>вы также можете поймать больше сигналов для трассировки стека что-то вроде:</p><pre><code>$ export segfault_signals="all" # "all" signals $ export segfault\_signals="bus abrt" # sigbus and sigabrt </code></pre><p>вывод будет выглядеть примерно так (обратите внимание на обратный след внизу):</p><pre><code>*** 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] </code></pre><p>если вы хотите узнать подробности, лучший источник, к сожалению, источник: см. <a href="http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c">http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c</a> и его родительский каталог <a href="http://sourceware.org/git/?p=glibc.git;a=tree;f=debug">http://sourceware.org/git/?p=glibc.git;a=tree;f=debug</a> </p>

<p>даже при том, что был предоставлен <a href="https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/77336#77336">правильный ответ</a> , который описывает, как использовать функцию gnu libc <code>backtrace()</code><sup>1</sup> и я предоставил <a href="https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/1925461#1925461">мой собственный ответ</a> , который описывает, как обеспечить обратную трассировку от обработчика сигнала к фактическому местоположению ошибки<sup>2</sup>я не вижу упоминания о <a href="http://en.wikipedia.org/wiki/name\_mangling">demangling</a> символах c++, выводимых из обратной трассировки.</p><p>при получении следов от программы на c++ вывод можно выполнить через <code>c++filt</code><sup>1</sup> чтобы разобрать символы или используя <a href="http://gcc.gnu.org/onlinedocs/libstdc++/manual/ext\_demangling.html"><code>abi::\_\_cxa\_demangle</code></a> <sup>1</sup> непосредственно.</p><ul> <li><sup>1</sup> linux & os x <sub>обратите внимание, что <code>c++filt</code> и <code>\_\_cxa\_demangle</code> специфичны для gcc</sub></li> <li><sup>2</sup> linux</li> </ul><hr><p>в следующем примере c++ linux используется тот же обработчик сигнала, что и у моего <a href="https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/1925461#1925461">другой ответ</a> , и демонстрируется, как <code>c++filt</code> можно использовать для разборки символов.</p><p><strong>код</strong>:</p><pre><code>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; } </code></pre><p><strong>output</strong> (<code>./test</code>):</p><pre><code>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] </code></pre><p><strong>demangled output</strong> (<code>./test 2>&1 | c++filt</code>):</p><pre><code>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] </code></pre><hr><p>следующее основано на обработчике сигнала из моего <a href="https://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/1925461#1925461">оригинальный ответ</a> и может заменить обработчик сигнала в приведенном выше примере, чтобы продемонстрировать, как <a href="http://gcc.gnu.org/onlinedocs/libstdc++/manual/ext\_demangling.html"><code>abi::\_\_cxa\_demangle</code></a> можно использовать для разборки символов. этот обработчик сигнала выдает тот же выходной сигнал, что и вышеприведенный пример.</p><p><strong>код</strong>:</p><pre><code>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); } </code></pre>

<p>возможно, стоит взглянуть на <a href="https://chromium.googlesource.com/breakpad/breakpad">google breakpad</a> , кроссплатформенный генератор аварийных дампов и инструменты для обработки дампов.</p>

<p>вы не указали свою операционную систему, поэтому трудно ответить. если вы используете систему, основанную на gnu libc, вы можете использовать функцию libc <code>backtrace()</code>.</p><p>gcc также имеет две встроенные функции, которые могут вам помочь, но которые могут или не могут быть полностью реализованы в вашей архитектуре, и это <code>\_\_builtin\_frame\_address</code> и <code>\_\_builtin\_return\_address</code>. оба из них требуют непосредственного целочисленного уровня (под непосредственным я имею в виду, что он не может быть переменной). если <code>\_\_builtin\_frame\_address</code> для данного уровня отличен от нуля, можно безопасно получить адрес возврата того же уровня.</p>

<p><code>ulimit -c <value></code> устанавливает ограничение размера основного файла в unix. по умолчанию ограничение размера основного файла равно 0. вы можете видеть ваши значения <code>ulimit</code> с помощью <code>ulimit -a</code>.</p><p>кроме того, если вы запустите свою программу из gdb, она остановит вашу программу из-за «нарушений сегментации» (<code>sigsegv</code>, как правило, когда вы обращаетесь к фрагменту памяти, который вы не выделили) или вы можете установить точки останова.</p><p>ddd и nemiver являются интерфейсом для gdb, что делает работу с ним намного проще для новичка.</p>

<p>я смотрел на эту проблему некоторое время.</p><p>и похоронен глубоко в инструментах google performance readme</p><p> <a href="http://code.google.com/p/google-perftools/source/browse/trunk/readme">http://code.google.com/p/google-perftools/source/browse/trunk/readme</a> </p><p>говорит о либунвинде</p><p> <a href="http://www.nongnu.org/libunwind/">http://www.nongnu.org/libunwind/</a> </p><p>хотелось бы услышать мнения этой библиотеки.</p><p>проблема с -rdynamic заключается в том, что в некоторых случаях он может относительно значительно увеличить размер двоичного файла.</p>

<p>спасибо энтузиасту за то, что обратили мое внимание на утилиту addr2line.</p><p>я написал быстрый и грязный скрипт для обработки вывода предоставленного ответа <a href="https://stackoverflow.com/a/1925461/1797414">здесь</a> : (большое спасибо jschmier!) с помощью утилиты addr2line.</p><p>скрипт принимает один аргумент: имя файла, содержащего вывод утилиты jschmier.</p><p>вывод должен напечатать что-то вроде следующего для каждого уровня трассировки:</p><pre><code>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; </code></pre><p>код:</p><pre><code>#!/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/.//' | 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 

некоторые версии libc содержат функции, которые имеют дело со следами стека; вы можете использовать их: <a href="http://www.gnu.org/software/libc/manual/html_node/backtraces.html">http://www.gnu.org/software/libc/manual/html_node/backtraces.html я помню, как использовал <a href="http://www.nongnu.org/libunwind/">libunwind давным-давно для получения трассировки стека, но он может не поддерживаться на вашей платформе.

важно отметить, что после того, как вы сгенерируете файл ядра, вам нужно использовать инструмент gdb, чтобы посмотреть на него. чтобы gdb имел смысл в вашем основном файле, вы должны указать gcc инструменту двоичный файл с отладочными символами: чтобы сделать это, вы скомпилируете с флагом -g:$ g++ -g prog.cpp -o prog </code></pre><p>затем вы можете либо установить «ulimit -c unlimited», чтобы он выгружал ядро, либо просто запустить вашу программу внутри gdb. мне больше нравится второй подход: </p><pre><code>$ gdb ./prog ... gdb startup output ... (gdb) run ... program runs and crashes ... (gdb) where ... gdb outputs your stack trace ... </code></pre><p>надеюсь, это поможет. </p>

<pre><code>ulimit -c unlimited </code></pre><p>системная переменная, которая позволит создать дамп ядра после сбоя вашего приложения. в этом случае неограниченное количество. найдите файл с именем core в том же каталоге. убедитесь, что вы скомпилировали свой код с включенной отладочной информацией!</p><p>с уважением</p>

<p>забудьте об изменении ваших источников и сделайте несколько хаков с функцией backtrace () или макросами - это просто плохие решения.</p><p>как правильно работающее решение, я бы посоветовал:</p><ol> <li>скомпилируйте вашу программу с флагом "-g" для встраивания отладочных символов в двоичный файл (не беспокойтесь, это не повлияет на вашу производительность). </li> <li>в linux выполните следующую команду: «ulimit -c unlimited» - чтобы система могла создавать большие аварийные дампы.</li> <li>когда ваша программа рухнула, в рабочем каталоге вы увидите файл «core».</li> <li>запустите следующую команду, чтобы напечатать backtrace в stdout: gdb -batch -ex "backtrace" ./your\_program\_exe ./core</li> </ol><p>это напечатает правильную читабельную трассировку вашей программы в удобочитаемом виде (с именами исходных файлов и номерами строк) . более того, этот подход даст вам свободу автоматизировать вашу систему: иметь короткий скрипт, который проверяет, создан ли процесс дамп ядра, а затем отправьте обратные следы по электронной почте разработчикам или войдите в какую-либо систему регистрации.</p>

<p>вы можете использовать <a href="https://github.com/vmarkovtsev/deathhandler">deathhandler</a> - небольшой класс c++, который делает все для вас, надежно.</p>

<p>победа: как насчет stackwalk64 <a href="http://msdn.microsoft.com/en-us/library/ms680650.aspx">http://msdn.microsoft.com/en-us/library/ms680650.aspx</a> </p>

<p>смотреть на:</p><p>man 3 backtrace</p><p>а также:</p><pre><code>#include <exeinfo.h> int backtrace(void **buffer, int size); </code></pre><p>это gnu расширения.</p>

<p>см. средство трассировки стека в <a href="http://www.cs.wustl.edu/~schmidt/ace.html">ace</a> (adaptive communication environment). он уже написан для всех основных платформ (и не только). библиотека лицензирована в стиле bsd, поэтому вы можете даже скопировать/вставить код, если не хотите использовать ace.</p>

<p>я могу помочь с версией для linux: можно использовать функции backtrace, backtrace\_symbols и backtrace\_symbols\_fd. смотрите соответствующие страницы руководства.</p>

<p>* nix: вы можете перехватить <a href="http://en.wikipedia.org/wiki/sigsegv">sigsegv</a> (обычно этот сигнал перед сбоем) и сохранить информацию в файле. (помимо основного файла, который вы можете использовать для отладки, например, с помощью gdb).</p><p>win: проверить <a href="http://msdn.microsoft.com/en-us/library/ms680659(vs.85).aspx">это</a> из msdn.</p><p>вы также можете посмотреть на хромовый код google, чтобы увидеть, как он обрабатывает сбои. он имеет хороший механизм обработки исключений.</p>

<p>я обнаружил, что решение @tgamblin не завершено . оно не может обрабатывать с помощью stackoverflow . я думаю, потому что по умолчанию обработчик сигналов вызывается с тем же стеком, а sigsegv генерируется дважды. для защиты вам необходимо зарегистрировать независимый стек для обработчика сигналов.</p><p>вы можете проверить это с помощью кода ниже. по умолчанию обработчик не выполняется. с определенным макросом stack\_overflow все в порядке.</p><pre><code>#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; } </code></pre>

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

<p>новый король в городе прибыл <a href="https://github.com/bombela/backward-cpp">https://github.com/bombela/backward-cpp</a> </p><p>1 заголовок для размещения в вашем коде и 1 библиотека для установки.</p><p>лично я называю это с помощью этой функции</p><pre><code>#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); } </code></pre>

<p>я бы использовал код, который генерирует трассировку стека для утечки памяти в <a href="http://www.codeproject.com/kb/applications/visualleakdetector.aspx">visual leak detector</a> . это работает только на win32, хотя.</p>

<p>как решение только для windows, вы можете получить эквивалент трассировки стека (с гораздо большей информацией), используя <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb513641.aspx">windows error reporting</a> . с помощью всего лишь нескольких записей реестра его можно настроить на <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181.aspx">сбор дампов пользовательского режима</a> :</p><blockquote> <p>начиная с windows server 2008 и windows vista с пакетом обновления 1 (sp1), отчеты об ошибках windows (wer) можно настроить так, чтобы полные дампы пользовательского режима собирались и сохранялись локально после сбоя приложения пользовательского режима. [...]</p> <p>эта функция не включена по умолчанию. включение этой функции требует прав администратора. чтобы включить и настроить эту функцию, используйте следующие параметры реестра в разделе <strong>hkey\_local\_machine\software\microsoft\windows\windows error reporting\localdumps</strong>.</p> </blockquote><p>вы можете установить записи реестра из вашего установщика, который имеет необходимые привилегии.</p><p>создание дампа в режиме пользователя имеет следующие преимущества по сравнению с генерацией трассировки стека на клиенте:</p><ul> <li>это уже реализовано в системе. вы можете использовать wer, как описано выше, или вызвать <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms680360.aspx">minidumpwritedump</a> самостоятельно, если вам нужен более детальный контроль над объемом информации, которую нужно выгрузить. (обязательно вызовите его из другого процесса.)</li> <li><strong>way</strong> более полный, чем трассировка стека. среди прочего он может содержать локальные переменные, аргументы функций, стеки для других потоков, загруженные модули и так далее. объем данных (и, следовательно, размер) очень настраиваемый.</li> <li>нет необходимости отправлять отладочные символы. это резко уменьшает размер вашего развертывания, а также усложняет обратную разработку приложения.</li> <li>во многом не зависит от используемого вами компилятора. использование wer даже не требует никакого кода. в любом случае, возможность получить базу данных символов (pdb) <em>очень</em> полезна для автономного анализа. я считаю, что gcc может либо генерировать pdb, либо существуют инструменты для преобразования базы данных символов в формат pdb.</li> </ul><p>обратите внимание, что wer может быть вызван только аварийным завершением приложения (т. е. системой, завершающей процесс из-за необработанного исключения). <code>minidumpwritedump</code> можно вызвать в любое время. это может быть полезно, если вам нужно вывести текущее состояние для диагностики других проблем, кроме сбоя.</p><p>обязательное чтение, если вы хотите оценить применимость мини-дампов:</p><ul> <li> <a href="http://www.debuginfo.com/articles/effminidumps.html">эффективные минидампы</a> </li> <li> <a href="http://www.debuginfo.com/articles/effminidumps2.html">эффективные минидампы (часть 2)</a> </li> </ul>

<p>в дополнение к приведенным выше ответам, здесь вы узнаете, как заставить ос debian linux генерировать дамп ядра </p><ol> <li>создайте папку «coredumps» в домашней папке пользователя</li> <li>перейдите в /etc/security/limits.conf. под строкой «» введите «soft core unlimited» и «root soft core unlimited», если разрешены дампы ядра для root, чтобы обеспечить неограниченное пространство для дампов ядра. </li> <li>примечание: «* soft core unlimited» не распространяется на root, поэтому root следует указывать в отдельной строке.</li> <li>чтобы проверить эти значения, выйдите из системы, снова войдите в систему и введите «ulimit -a». «основной размер файла» должен быть неограниченным.</li> <li>проверьте файлы .bashrc (пользователь и root, если применимо), чтобы убедиться, что ulimit там не установлен. в противном случае указанное выше значение будет перезаписано при запуске.</li> <li>откройте /etc/sysctl.conf. внизу введите следующее: «kernel.core\_pattern = /home//coredumps/%e\_%t.dump». (% e будет именем процесса, а% t будет системным временем)</li> <li>выйдите и введите «sysctl -p», чтобы загрузить новую конфигурацию проверьте/proc/sys/kernel/core\_pattern и убедитесь, что это соответствует тому, что вы только что набрали.</li> <li>дамп ядра можно проверить, запустив процесс в командной строке («&»), а затем убив его командой «kill -11». если дамп ядра успешен, вы увидите «(дамп ядра)» после индикации ошибки сегментации.</li> </ol>

<p>в linux/unix/macosx используйте основные файлы (вы можете включить их с помощью ulimit или <a href="http://www.opengroup.org/onlinepubs/009695399/functions/setrlimit.html">совместимого системного вызова</a> ). в windows используйте отчеты об ошибках microsoft (вы можете стать партнером и получить доступ к данным о сбое вашего приложения).</p>

<p>если вы все еще хотите сделать это в одиночку, как я, вы можете создать ссылку на <code>bfd</code> и избегать использования <code>addr2line</code>, как я сделал здесь:</p><p> <a href="https://github.com/gnif/lookingglass/blob/master/common/src/crash.linux.c">https://github.com/gnif/lookingglass/blob/master/common/src/crash.linux.c</a> </p><p>это производит вывод:</p><pre><code>[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] </code></pre>

<p>я забыл о технологии gnome "apport", но я не очень разбираюсь в ее использовании. он используется для генерации трассировки стека и другой диагностики для обработки и может автоматически регистрировать ошибки. это, безусловно, стоит проверить в.</p>

<p>если ваша программа аварийно завершает работу, то сама операционная система генерирует информацию о аварийном дампе. если вы используете операционную систему * nix, вам просто не нужно препятствовать этому (проверьте опции 'coredump' команды ulimit).</p>

<p>похоже, что в одной из последних появившихся библиотек c ++ boost версия предоставила именно то, что вы хотите, вероятно, код был бы мультиплатформенным . это <a href="https://www.boost.org/doc/libs/1\_69\_0/doc/html/stacktrace.html">boost :: stacktrace</a> , который вы можете использовать как <a href="https://www.boost.org/doc/libs/1\_69\_0/doc/html/stacktrace/getting\_started.html">как в boost образец</a> :</p><pre><code>#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 } </code></pre><p>в linux вы компилируете код выше:</p><pre><code>g++ --std=c++17 file.cpp -lstdc++fs -lboost\_stacktrace\_backtrace -ldl -lbacktrace </code></pre><p>пример обратного следа, скопированного из <a href="https://www.boost.org/doc/libs/1\_69\_0/doc/html/stacktrace/getting\_started.html">форсированной документации</a> :</p><pre><code>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 </code></pre>