В Linux гораздо проще использовать библиотеку libSegFault
, предоставляемую как часть библиотеки GNU C. На моей системе он установлен в /lib/x86_64-linux-gnu/libSegFault.so
.
Все, что вам нужно сделать, это установить для переменной среды SEGFAULT_SIGNALS
значение all
(чтобы можно было отследить все сбои, поддерживаемые библиотекой), дополнительно SEGFAULT_OUTPUT_NAME
, чтобы указать файл, в который записывается трассировка стека (по умолчанию) является стандартной ошибкой), а LD_PRELOAD
указывает на библиотеку segfault. Пока процесс не изменяет эти переменные среды, они также применяются ко всем дочерним процессам.
Например, если ./yourprog
была программа, которая разветвляет дочерний элемент, который падает, и вы хотите, чтобы трассировка стека равнялась ./yourprog.stacktrace
, запустите
SEGFAULT_SIGNALS=all \
SEGFAULT_OUTPUT_NAME=./yourprog.stacktrace \
LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so \
./yourprog
или все в одной строке без обратной косой черты (\
).
Единственным недостатком является то, что каждый сбой перезаписывает существующий файл, поэтому вы увидите только последний. Если вы установили /proc
, то дамп сбоя включает в себя как обратную трассировку, так и карту памяти процесса в момент сбоя.
Если вы настаиваете на том, чтобы сделать это в своей собственной программе на C, я рекомендую вам сначала взглянуть на libSegFault sources .
Дело в том, что трассировка стека должна быть выгружена самим процессом; это не доступно для родителя. Для этого вы вводите код в дочерний процесс, например, с помощью LD_PRELOAD
переменная окружения (которая является одной из переменных управления динамическим компоновщиком в Linux). (Обратите внимание, что трассировка стека и т. Д. Выполняется в контексте обработчика сигналов, поэтому следует использовать только функции, безопасные для асинхронных сигналов.)
Например, родительский процесс может создать канал и переместить его конец записи в определенный дескриптор в дочернем процессе перед выполнением целевого процесса с помощью пути к библиотеке предварительной загрузки помощника в LD_PRELOAD.
Библиотека предварительной загрузки помощника вставляет signal()
, sigaction()
и, возможно, sigprocmask()
, sigwait()
, sigwaitinfo()
, pthread_sigmask()
, чтобы гарантировать выполнение обработчиков сигналов аварийного дампа библиотек помощников, когда такой сигнал доставлено (SIGSEGV
, SIGBUS
, SIGILL
, возможно SIGTRAP
). Обработчик сигнала выполняет сброс стека (и печатает / proc / PID / maps), затем устанавливает расположение сигнала по умолчанию и повторно поднимает сигнал (используя raise()
).
По сути, это сводится к тому же, что и выше libSegFault, за исключением вашего собственного кода C.
Если вы не хотите внедрять код в дочерний процесс или управление обработчиками сигналов слишком сложное, вы можете использовать ptrace .
Когда трассировка уничтожается сигналом (кроме SIGKILL
), поток, принимающий сигнал, останавливается первым ( "signal-delivery-stop" ), поэтому трассировщик может проверить свой стек (и карта памяти трассировки), прежде чем позволить дочернему процессу продолжить / умереть.
На практике ptracing более инвазивен, так как есть много событий, которые приводят к остановке потоков трассировки. Это также намного сложнее для многопоточных процессов, чем подход LD_PRELOAD, потому что ptrace может управлять отдельными потоками в трассировке; Есть намного больше деталей, чтобы получить право.