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




Если вы работаете в системах с доступной функциональностью BSD backtrace (Linux, OSX 1.5, BSD, конечно), вы можете сделать это программно в своем обработчике сигналов.
Например (Код backtrace, полученный из примера IBM):
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void sig_handler(int sig)
{
void * array[25];
int nSize = backtrace(array, 25);
char ** symbols = backtrace_symbols(array, nSize);
for (int i = 0; i < nSize; i++)
{
puts(symbols[i]);;
}
free(symbols);
signal(sig, &sig_handler);
}
void h()
{
kill(0, SIGSEGV);
}
void g()
{
h();
}
void f()
{
g();
}
int main(int argc, char ** argv)
{
signal(SIGSEGV, &sig_handler);
f();
}
Выход:
0 a.out 0x00001f2d sig_handler + 35
1 libSystem.B.dylib 0x95f8f09b _sigtramp + 43
2 ??? 0xffffffff 0x0 + 4294967295
3 a.out 0x00001fb1 h + 26
4 a.out 0x00001fbe g + 11
5 a.out 0x00001fcb f + 11
6 a.out 0x00001ff5 main + 40
7 a.out 0x00001ede start + 54
Это не дает бонусных баллов за дополнительные функции (за исключением того, что не требует графического интерфейса), однако у него есть то преимущество, что он очень прост и не требует дополнительных библиотек или программ.
Решение Дерекса, вероятно, лучшее, но в любом случае есть альтернатива:
Последняя версия ядра Linux позволяет передавать дампы ядра скрипту или программе. Вы можете написать сценарий, чтобы перехватить дамп ядра, собрать любую дополнительную информацию, которая вам нужна, и отправить ее обратно. Это глобальная настройка, поэтому она применима к любой сбойной программе в системе. Также для настройки потребуются права root. Его можно настроить с помощью файла / proc / sys / kernel / core_pattern. Установите что-нибудь вроде '| / домашний / myuser / bin / my-core-handler-script '.
Люди Ubuntu также используют эту функцию.
Вот пример того, как получить дополнительную информацию с помощью деманглера. Как видите, он также записывает трассировку стека в файл.
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <cxxabi.h>
void sig_handler(int sig)
{
std::stringstream stream;
void * array[25];
int nSize = backtrace(array, 25);
char ** symbols = backtrace_symbols(array, nSize);
for (unsigned int i = 0; i < size; i++) {
int status;
char *realname;
std::string current = symbols[i];
size_t start = current.find("(");
size_t end = current.find("+");
realname = NULL;
if (start != std::string::npos && end != std::string::npos) {
std::string symbol = current.substr(start+1, end-start-1);
realname = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
}
if (realname != NULL)
stream << realname << std::endl;
else
stream << symbols[i] << std::endl;
free(realname);
}
free(symbols);
std::cerr << stream.str();
std::ofstream file("/tmp/error.log");
if (file.is_open()) {
if (file.good())
file << stream.str();
file.close();
}
signal(sig, &sig_handler);
}
использование C++ в обработчиках сигналов - ошибка в геноме.
@ vitaly.v.ch Ошибка заключается не столько в использовании C++, сколько в использовании множества функций, небезопасных для сигнала. Все, что выделяет память, в значительной степени гарантирует, что нет будет работать тогда, когда вы этого больше всего хотите. Конечно, все будет хорошо, если вы используете kill -11 <pid>, но если это приводит к сбою вашего кода, просто прекратите это делать ...
К вашему сведению,
предлагаемое решение (с использованием backtrace_symbols в обработчике сигналов) опасно нарушено. НЕ ИСПОЛЬЗУЙТЕ ЕГО -
Да, backtrace и backtrace_symbols будут создавать обратную трассировку и переводить ее в символические имена, однако:
backtrace_symbols выделяет память с помощью malloc, и вы используете free для ее освобождения - если вы вылетаете из-за повреждения памяти, ваша арена malloc, скорее всего, будет повреждена и вызовет двойную ошибку.
malloc и free защищают арену malloc внутренним замком. Возможно, вы ошиблись в середине malloc / free с взятой блокировкой, что приведет к тому, что эти функции или что-либо, что вызывает их, к мертвой блокировке.
Вы используете put, который использует стандартный поток, который также защищен блокировкой. Если вы ошиблись в середине printf, вы снова попали в тупик.
На 32-битных платформах (например, на вашем обычном ПК 2 года назад) ядро установит обратный адрес во внутреннюю функцию glibc вместо вашей функции сбоя в стеке, так что единственная наиболее важная часть информации, которая вас интересует - в которой функция вызвала ошибку программы, будет фактически повреждена на этой платформе.
Таким образом, код в этом примере является наихудшим типом ошибок - он ВЫГЛЯДИТ так, как будто он работает, но он действительно неожиданно подведет вас в производственной среде.
Кстати, заинтересованы в том, чтобы сделать это правильно? проверьте это.
Ваше здоровье, Гилад.
Я не очень разбираюсь в этой области. Но если вы разветвляете и выполняете обратную трассировку в дочернем процессе, вы могли бы уйти, не так ли?
@LiquidityC Если вы используете fork() внутри своего обработчика сигналов, ваш дочерний процесс будет в той же ситуации, что и процесс, который вы только что разветвили. fork() в обработчике сигналов, вероятно, наиболее полезен, если за ним следует execv() / execve(), иначе вы только что форкнули что-то.
Как ни странно, ответы должны содержать ответ на заданный вопрос. Не объяснять никогда не применять другие ответы, которые работают в 99% случаев, или размещать неработающие ссылки. Так что просто укажите здесь свое решение.
Это работает как игрушечный пример (например, когда вы вызываете SIGSEGV вручную), но, вероятно, нет будет работать, когда вам это нужно больше всего. См. Ответ @Gilad Ben-Yossef.