Автоматическое получение трассировки стека в системах Unix

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

Бонусные баллы за следующие дополнительные функции:

  • Сбор дополнительной информации во время сбоя (например, файлы конфигурации).
  • Отправьте разработчикам пакет с информацией о сбоях.
  • Возможность добавить это в разделяемую библиотеку dlopened
  • Не требует графического интерфейса
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
9 270
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Если вы работаете в системах с доступной функциональностью 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

Это не дает бонусных баллов за дополнительные функции (за исключением того, что не требует графического интерфейса), однако у него есть то преимущество, что он очень прост и не требует дополнительных библиотек или программ.

Это работает как игрушечный пример (например, когда вы вызываете SIGSEGV вручную), но, вероятно, нет будет работать, когда вам это нужно больше всего. См. Ответ @Gilad Ben-Yossef.

Andreas Magnusson 19.12.2019 01:23

Решение Дерекса, вероятно, лучшее, но в любом случае есть альтернатива:

Последняя версия ядра 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 05.01.2010 11:54

@ vitaly.v.ch Ошибка заключается не столько в использовании C++, сколько в использовании множества функций, небезопасных для сигнала. Все, что выделяет память, в значительной степени гарантирует, что нет будет работать тогда, когда вы этого больше всего хотите. Конечно, все будет хорошо, если вы используете kill -11 <pid>, но если это приводит к сбою вашего кода, просто прекратите это делать ...

Andreas Magnusson 19.12.2019 01:10

К вашему сведению,

предлагаемое решение (с использованием backtrace_symbols в обработчике сигналов) опасно нарушено. НЕ ИСПОЛЬЗУЙТЕ ЕГО -

Да, backtrace и backtrace_symbols будут создавать обратную трассировку и переводить ее в символические имена, однако:

  1. backtrace_symbols выделяет память с помощью malloc, и вы используете free для ее освобождения - если вы вылетаете из-за повреждения памяти, ваша арена malloc, скорее всего, будет повреждена и вызовет двойную ошибку.

  2. malloc и free защищают арену malloc внутренним замком. Возможно, вы ошиблись в середине malloc / free с взятой блокировкой, что приведет к тому, что эти функции или что-либо, что вызывает их, к мертвой блокировке.

  3. Вы используете put, который использует стандартный поток, который также защищен блокировкой. Если вы ошиблись в середине printf, вы снова попали в тупик.

  4. На 32-битных платформах (например, на вашем обычном ПК 2 года назад) ядро ​​установит обратный адрес во внутреннюю функцию glibc вместо вашей функции сбоя в стеке, так что единственная наиболее важная часть информации, которая вас интересует - в которой функция вызвала ошибку программы, будет фактически повреждена на этой платформе.

Таким образом, код в этом примере является наихудшим типом ошибок - он ВЫГЛЯДИТ так, как будто он работает, но он действительно неожиданно подведет вас в производственной среде.

Кстати, заинтересованы в том, чтобы сделать это правильно? проверьте это.

Ваше здоровье, Гилад.

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

LiquidityC 27.02.2018 00:40

@LiquidityC Если вы используете fork() внутри своего обработчика сигналов, ваш дочерний процесс будет в той же ситуации, что и процесс, который вы только что разветвили. fork() в обработчике сигналов, вероятно, наиболее полезен, если за ним следует execv() / execve(), иначе вы только что форкнули что-то.

Andreas Magnusson 19.12.2019 01:21

Как ни странно, ответы должны содержать ответ на заданный вопрос. Не объяснять никогда не применять другие ответы, которые работают в 99% случаев, или размещать неработающие ссылки. Так что просто укажите здесь свое решение.

calandoa 15.01.2020 13:44

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