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

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

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

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

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

Parag Bafna 14.06.2012 13:08

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

cmccabe 03.08.2012 00:01

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

Sam Saffron 18.12.2013 02:24

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

codetaku 17.07.2014 18:42

Как это вылетает?

Ciro Santilli TRUMP BAN IS BAD 19.03.2017 15:00

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

ingomueller.net 06.09.2019 10:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
621
6
391 851
28
Перейти к ответу Данный вопрос помечен как решенный

Ответы 28

ulimit -c unlimited

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

С уважением

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

Todd Gamblin 17.09.2008 00:54

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

Mo. 17.09.2008 00:57

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

Paul Tomblin 17.09.2008 00:58

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

sivabudh 10.11.2010 22:46

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

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

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

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

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

Смотреть на:

человек 3 след

И:

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

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

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

Stéphane 10.10.2010 11:05

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

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

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

Vi. 01.07.2010 02:20

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

baziorek 02.01.2019 11:00

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

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

IInspectable 17.02.2018 17:29

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

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

победить: Проверьте это из msdn.

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

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

IInspectable 17.02.2018 17:28

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

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

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

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

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

Adam Hawes 04.02.2009 16:07

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

Brent Bradburn 26.10.2010 17:36

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

Ответ принят как подходящий

Для 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.

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

CesarB 23.10.2008 19:05

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

jschmier 27.03.2010 22:11

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

Mattias Nilsson 17.04.2012 10:39

Затем вы можете попробовать какой-нибудь другой API для обхода стека, например: DynInst StackwalkerAPI dyninst.org/stackwalkerapi или nongnu.org/libunwind. Как правило, если вы ожидаете выхода из фреймов стека или прерывания фреймов внутри malloc, вам нужно делать специальные вещи для обработки этого. Многие инструменты используют свой собственный распределитель арены, чтобы избежать конфликта с libc malloc в подобных ситуациях.

Todd Gamblin 18.04.2012 04:19

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

Parag Bafna 14.06.2012 11:45

@ParagBafna, тогда Какие можем ли мы использовать для обратной трассировки, безопасную для асинхронных сигналов?

lurscher 19.10.2012 06:16

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

Matt Clarkson 30.01.2013 14:45

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

jfritz42 11.04.2013 00:17

Вы не можете безопасно вызвать exit() из обработчика сигналов. Используйте _exit() или _Exit().

Carl Norum 30.05.2013 21:35

@Olshansk - то же самое и со мной

ducin 20.01.2014 14:27

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

Paul Coccoli 05.03.2014 18:17

Если вы планируете использовать приведенный выше код где-то еще, а не в функции, которая в конечном итоге будет exit(), обратите внимание, что для backtrace_symbol_fd(3) вам необходимо освободить array после того, как вы закончите с ним.

Marco83 22.05.2014 13:46

Согласно man 7 signal, каждая из функций, которые вызывает ваш обработчик, небезопасна для асинхронных сигналов, что приводит к неопределенному поведению. На практике ваш процесс может зайти в тупик (с -lpthread) или повредить его память (без).

mic_e 19.08.2014 16:18

@mic_e: Верно! Но это обработчик, который вызывается при сбое вашего приложения. Другими словами, ваш процесс уже укусил пыль, и вы находитесь на очень небезопасной территории. Вы вызываете это в обработчике сигнала, потому что хотите знать, где произошла ошибка, и это работает на практике. Если это не удается (а это случается нечасто), вы ничего не теряете, потому что ваш процесс уже умирает с самого начала.

Todd Gamblin 23.08.2014 03:19

@anyone, как сгенерировать stacktrace (в моих журналах) и coredump вместе? Если мы определим обработчик сигнала, мы не получим coredump. Я хочу и то, и другое, предложите мне хорошее решение. Заранее спасибо.

sree 24.07.2015 10:18

@sree: вам нужно добавить код, чтобы обработчик отменил регистрацию и вернулся к обработке сигналов по умолчанию, а затем убил себя. Добавление «signal (sig, SIG_DFL); kill (getpid (), sig);» в конце обработчик должен работать. Пример здесь: alexonlinux.com/…

Todd Gamblin 24.07.2015 11:22

@tgamblin: Я пробовал эту технику, но она полностью указывает не на то место, когда я попадаю в аварию! :(; Но мой backtrace () работает нормально, он указывает на точное местоположение. Но мне нужно иметь core-дамп с точной информацией вместе с backtrace, напечатанным в моих журналах. И каковы все недостатки, которые моя программа могла бы получить, используя signal (signum, SIG_DFL)? Спасибо за ответ.

sree 25.07.2015 15:33

А есть альтернатива windows?

Jack 06.02.2016 23:29

*(int*)0=1; будет достаточно для генерации segfault вместо 2 строк

user2763554 17.11.2016 06:57

@sree для SIGSEGV, SIGILL, SIGFPE и SIGBUS, просто возвращаясь из обработчика сигнала, повторно поднимет сигнал в исходном месте, что даст вам достойный дамп ядра. Для SIGABRT то же самое произойдет, если и только если вы получили его в результате abort(). В качестве другого варианта: попробуйте зарегистрировать обработчик сигнала в sigactionбез, используя SA_NODEFER (signal обычно эквивалентен SA_RESETHAND | SA_NODEFER). Тогда сигнал будет доставлен только после, возвращающимся от обработчика сигнала.

Giel 07.06.2018 17:41

fprintf(stderr, "Error: signal %d:\n", sig); в обработчике SIGSEGV зайдет в тупик, если fprintf() вызовет malloc() или free(), а SIGSEGV возникнет одновременно с вызовом malloc() или free() (или аналогичным), что довольно часто. Так что нет, на самом деле это не «работает».

Andrew Henle 19.12.2019 13:04

Помимо использования -rdynamic, также убедитесь, что ваша система сборки не добавляет параметр -fvisibility=hidden! (так как это полностью отбросит эффект -rdynamic)

Dima Litvinov 13.05.2020 03:42

@ToddGamblin Истинный! Но это обработчик, который вызывается при сбое вашего приложения. Нет, это обработчик, который вызывается, когда ваше приложение вот-вот рухнет. Он еще не разбился, но наиболее вероятный сбой, который перехватит этот дерьмовый код, - это SIGSEGV, вызванный поврежденной кучей, которая, вероятно, будет обнаружена при вызове чего-то вроде malloc() или free(). И поскольку этот код зависит от кода, который будет вызывать malloc(), он зайдет в тупик и оставит ваше приложение застрявшим там, где оно НИКОГДА не выйдет.

Andrew Henle 21.01.2021 23:49

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

Ссылка вроде мертвая.

tglas 01.12.2019 22:48

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

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

DBedrenko 02.08.2016 14:15

Некоторое время я занимался этой проблемой.

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

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

говорит о libunwind

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

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

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

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

Dan 24.03.2010 09:46

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

Herbert 24.11.2014 23:10

Это правильно. Очень сложно сделать это правильно, но у меня был отличный успех с gaddr2line, здесь много практической информации blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gc‌ c

Gregory 25.11.2014 23:53

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;
 ucontext_t        *uc_link;
 stack_t           uc_stack;
 sigcontext_t      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

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

jschmier 26.03.2010 23:00

Выходные данные в приведенном выше примере были взяты из тестовой программы, скомпилированной с использованием кросс-инструментальной цепочки gcc-3.4.5-glibc-2.3.6 и выполненной на платформе на базе ARMv6 под управлением Linux Kernel 2.6.22.

jschmier 25.05.2010 03:54

включение поддержки обратной трассировки имеет смысл только при компиляции для режима Thumb в ARM

manav m-n 17.10.2011 10:58

Кроме того, вам необходимо добавить параметр «-mapcs-frame» в командную строку GCC для создания кадров стека на платформе ARM.

qehgt 01.02.2012 19:53

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

enthusiasticgeek 24.10.2012 22:26

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

mmlb 14.12.2012 18:57

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

arr_sea 04.04.2013 07:26

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

jfritz42 10.04.2013 20:10

@letmaik, я давно не занимался этим, но в x86_64 указатель инструкции - RIP, а не EIP. Возможно, вам нужно проиндексировать массив uc_mcontext как uc_mcontext.gregs[REG_RIP].

jschmier 19.01.2017 02:45

struct sigcontext не может быть найден в macOS 10.14, которая совместима с POSIX, поэтому это программа, специфичная для glibc. Кроме того, в Ubuntu 16.04 (Linux 4.4.0 в качестве ядра) строка, содержащая символ crash(), появляется дважды, а не только один раз, как показано в ответе.

Leedehai 10.10.2018 10:02

На x86_64 (Ubuntu 18.04) Это утверждение не применимо? The stack frame of the last function called before the signal (which is the location of the fault) is lost. Без введения указателя, полученного через uc_mcontext, местоположение segfault показано как четвертая запись в трассировке.

Steven Lu 21.11.2019 03:41

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

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

  • 1 Linux и OS X Note that c++filt and __cxa_demangle are GCC specific
  • 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);
}

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

arr_sea 05.04.2013 23:02

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

Bamaco 07.07.2014 23:52

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

Kevin 24.10.2014 00:25

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

orcy 27.11.2015 09:41

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

Andrew Henle 24.02.2020 20:37

@AndrewHenle Замечательно! Что нам теперь делать с этим?

Andrew 10.11.2020 09:47

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

Andrew 10.11.2020 10:27

@Andrew Замечательно! Что нам теперь делать с этим? Доктор, больно, когда я это делаю? Так что не делай этого! Не используйте вызовы, не защищенные от асинхронного сигнала, такие как std::cerr, free() или exit(). Это несложно. Или даже backtrace_symbols(). Используйте от write() до STDERR_FILENO низкого уровня. Справочная страница backtrace_symbols() даже утверждает, что «backtrace_symbols_fd() не вызывает malloc (3), поэтому его можно использовать в ситуациях, когда последняя функция может дать сбой».

Andrew Henle 10.11.2020 13:27

@AndrewHenle Я думаю, вы немного теряете из виду предполагаемое использование этого кода - он печатает стек при сбое программы, поэтому, если при определенных обстоятельствах он может заблокировать программу, это не имеет большого значения, поскольку программа уже имеет не удалось. На практическом уровне я бы посоветовал выдать какое-то сообщение перед вызовом, который может заблокировать, чтобы разработчик знал, что происходит (что, я думаю, делает приведенный выше код).

Mike Moreton 09.02.2021 11:20

@MikeMoreton поэтому, если при определенных обстоятельствах он может заблокировать программу, это не имеет большого значения, поскольку программа уже потерпела неудачу Замечательно - теперь ваш критический процесс зависает и не перезапускается. Вау, это здорово. Вы выступаете за ужасно низкий стандарт надежности. Каждый раз, когда вы пишете такой код, вы снижаете надежность своей системы. Когда вы пишете такой дерьмовый код из тысяч строк кода, вы получаете дерьмовый продукт. Но эй, если тебе этого достаточно ...

Andrew Henle 09.02.2021 13:06

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

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

  1. Выполнить в catchsegv:

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

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

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

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

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

HHK 23.01.2013 22:05

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

Stéphane Gourichon 31.03.2014 13:33

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

greggo 03.07.2014 20:06

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

DavidMFrey 16.03.2016 15:44

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

Phillip 28.07.2016 11:49

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

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

example 26.08.2014 18:39

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

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 

В дополнение к приведенным выше ответам, вот как вы заставляете ОС 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». Если выгрузка ядра прошла успешно, вы увидите «(дамп ядра)» после индикации ошибки сегментации.

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

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

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

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

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

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

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

OfirD 10.05.2019 17:17

Новый король в городе прибыл 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 01.12.2019 23:09

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

Starting with Windows Server 2008 and Windows Vista with Service Pack 1 (SP1), Windows Error Reporting (WER) can be configured so that full user-mode dumps are collected and stored locally after a user-mode application crashes. [...]

This feature is not enabled by default. Enabling the feature requires administrator privileges. To enable and configure the feature, use the following registry values under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps key.

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

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

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

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

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

Похоже, что в одной из последних буст-версий C++ появилась библиотека, которая предоставит именно то, что вы хотите, возможно, код будет мультиплатформенным. Это boost :: stacktrace, который вы можете использовать как как в буст-сэмпле:

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

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

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

Пример обратной трассировки, скопированной из увеличить документацию:

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

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

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

Это дает результат:

[E]        crash.linux.c:170  | crit_err_hdlr                  | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E]        crash.linux.c:171  | crit_err_hdlr                  | signal 11 (Segmentation fault), address is (nil)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]

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