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

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

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

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

person Ted Percival    schedule 20.08.2008    source источник


Ответы (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

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

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

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

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

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

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

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

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

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

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

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

Здоровья, Гилад.

person Community    schedule 20.11.2008
comment
Я не очень разбираюсь в этой области. Но если вы разветвите и выполните обратную трассировку в дочернем процессе, вам это сойдет с рук, не так ли? - person LiquidityC; 27.02.2018
comment
@LiquidityC Если вы fork() внутри своего обработчика сигналов, ваш дочерний процесс окажется в той же ситуации, что и процесс, который вы только что разветвили. fork() в обработчике сигнала, вероятно, будет наиболее полезным, если за ним следует execv()/execve(), иначе вы только что разветвили что-то нерабочее. - person Andreas Magnusson; 19.12.2019
comment
Как ни странно, ответы должны содержать ответ на заданный вопрос. Не объяснять, чтобы никогда не применять другие ответы, которые работают в 99% случаев, или публиковать неработающие ссылки. Поэтому просто включите свое решение здесь. - person calandoa; 15.01.2020

Вот пример того, как получить дополнительную информацию с помощью деманглера. Как вы можете видеть, это также записывает трассировку стека в файл.

#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);
}
person AndersO    schedule 03.09.2008
comment
использование C++ в обработчиках сигналов - ошибка в геноме. - person vitaly.v.ch; 05.01.2010
comment
@vitaly.v.ch Ошибка заключается не столько в использовании C ++, сколько в использовании множества сигнальных небезопасных функций. Почти гарантировано, что все, что выделяет память, не будет работать, когда вы больше всего этого хотите. Конечно, это будет выглядеть нормально, если вы kill -11 <pid> но если это то, что приводит к сбою вашего кода, просто прекратите это делать... - person Andreas Magnusson; 19.12.2019

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

Последняя версия ядра Linux позволяет передавать дампы ядра сценарию или программе. Вы можете написать скрипт, чтобы поймать дамп ядра, собрать любую дополнительную информацию, которая вам нужна, и отправить все обратно по почте. Однако это глобальная настройка, поэтому она будет применяться к любой программе, вызывающей сбой в системе. Также для установки потребуются root-права. Его можно настроить через файл /proc/sys/kernel/core_pattern. Установите это что-то вроде ' | /home/myuser/bin/my-core-handler-script'.

Пользователи Ubuntu также используют эту функцию.

person Kristof Provost    schedule 23.08.2008