Автоматическое получение трассировок стека в системах Unix
-
09-06-2019 - |
Вопрос
Какие существуют методы для автоматического получения трассировки стека в системах Unix?Я имею в виду не просто получение основного файла или интерактивное подключение с помощью GDB, а наличие обработчика SIGSEGV, который сбрасывает обратную трассировку в текстовый файл.
Бонусные баллы за следующие дополнительные функции:
- Сбор дополнительной информации во время сбоя (например.конфигурационные файлы).
- Отправьте разработчикам пакет с информацией о сбое по электронной почте.
- Возможность добавить это в
dlopen
разделяемая библиотека ed - Не требующий графического интерфейса
Решение
Если вы работаете в системах с 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
Это не дает бонусных баллов за дополнительные функции (за исключением того, что не требует графического интерфейса пользователя), однако имеет то преимущество, что оно очень простое и не требует никаких дополнительных библиотек или программ.
Другие советы
К ТВОЕМУ сведению,
предлагаемое решение (использование backtrace_symbols в обработчике сигналов) опасно нарушено.НЕ ИСПОЛЬЗУЙТЕ ЕГО -
Да, backtrace и backtrace_symbols создадут backtrace и переведут его в символьные имена, однако:
backtrace_symbols выделяет память с помощью malloc, и вы используете free, чтобы освободить ее - если у вас сбой из-за повреждения памяти, ваша malloc-арена, скорее всего, повреждена и вызовет двойную ошибку.
malloc и free защищают арену malloc внутренним замком.Возможно, у вас произошла ошибка в середине malloc / free со снятой блокировкой, что приведет к тому, что эти функции или все, что их вызывает, будут заблокированы.
Вы используете puts, который использует стандартный поток, который также защищен блокировкой.Если вы допустили ошибку в середине printf, вы снова попали в тупик.
На 32 - битных платформах (например,ваш обычный КОМПЬЮТЕР 2-летней давности), ядро установит обратный адрес внутренней функции glibc вместо вашей функции обнаружения ошибок в вашем стеке, поэтому единственная наиболее важная информация, которая вас интересует - в какой функции произошла ошибка программы, на самом деле будет повреждена на этой платформе.
Итак, код в примере является наихудшим видом ошибки - кажется, что он работает, но на самом деле он неожиданным образом подведет вас в процессе производства.
Кстати, заинтересованы в том, чтобы сделать это правильно?проверить это вон.
Твое здоровье, Гилад.
Вот пример того, как получить дополнительную информацию с помощью деманглера.Как вы можете видеть, этот файл также записывает трассировку стека в файл.
#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);
}
Решение Dereks, вероятно, лучшее, но в любом случае вот альтернатива:
Последняя версия ядра Linux позволяет передавать дампы ядра в скрипт или программу.Вы могли бы написать скрипт для перехвата дампа ядра, сбора любой дополнительной информации, которая вам нужна, и отправки всего обратно по почте.Однако это глобальный параметр, поэтому он применим к любой аварийно завершающей программе в системе.Для настройки также потребуются права суперпользователя.Его можно настроить с помощью файла /proc/sys/kernel/core_pattern.Установите для этого значение что-то вроде ' | /home/myuser/bin/my-core-handler-script'.
Пользователи Ubuntu также используют эту функцию.