backtrace_symbols не может распечатать ту самую функцию, которая вызвала сигнал

Я реализую простой регистратор сбоев для своего приложения на C++:

static void handler(int, siginfo_t * info, void *) {
    void *array[1000];

    switch (info->si_signo) {
    case SIGILL:
        Logger() << "Received SIGILL";
        break;
    case SIGSEGV:
        Logger() << "Received SIGSEGV";
        break;
    case SIGBUS:
        Logger() << "Received SIGBUS";
        break;
    case SIGSYS:
        Logger() << "Received SIGSYS";
        break;
    default:
        break;
    }

    // get void*'s for all entries on the stack
    const size_t size = backtrace(array, 1000);

    // print out all the frames
    char ** symbols = backtrace_symbols(array, size);
    for(size_t i = 0; i < size; ++i)
    {
        Logger() << symbols[i];
    }

    free(symbols);
    exit(EXIT_FAILURE);
}

struct sigaction SignalAction;
SignalAction.sa_flags = SA_SIGINFO;
SignalAction.sa_sigaction = handler;
sigemptyset(&SignalAction.sa_mask);
sigaction(SIGSEGV, &SignalAction, NULL);
sigaction(SIGBUS, &SignalAction, NULL);
sigaction(SIGILL, &SignalAction, NULL);

Я еще не тестировал его в Linux, но в OS X конкретный элемент трассировки, который меня интересует, тот, который вызвал сигнал, не печатается (запись номер 2):

: " Received SIGSEGV" 
: " 0   App                       0x0000000100253d15 _ZL7handleriP9__siginfoPv + 229" 
: " 1   libsystem_platform.dylib  0x00007fff8ff0f5aa _sigtramp + 26" 
: " 2   ???                       0x000000000000000c 0x0 + 12" 
: " 3   App                       0x000000010000dfa7 _ZN11CMainWindow13initShortcutsEv + 231" 
: " 4   App                       0x000000010000d059 _ZN11CMainWindowC2EP7QWidget + 1001" 
: " 5   App                       0x00000001000091d9 main + 6217" 
: " 6   App                       0x00000001000070a5 _start + 227" 
: " 7   App                       0x0000000100006fc1 start + 33" 

Почему это происходит, и можно ли это исправить?

P.S. Это отладочная сборка. Фактического сегментирования не было, оно было смоделировано с помощью raise(SIGSEGV). raise был вызван из метода , который, в свою очередь, был вызван из MainWindow::initShortcuts.


person Violet Giraffe    schedule 23.01.2014    source источник
comment
Какого результата вы ожидаете и как узнать, что эта трассировка неверна? Возможно, либо произошло повреждение стека (искажение обратной трассировки), либо процесс фактически пытался перейти к адресу 0xC (из-за повреждения памяти, ошибки программирования и т. д.).   -  person nobody    schedule 25.01.2014
comment
Что ж, поскольку вы используете C++, вы также можете получить от std::exception, построить информацию о трассировке стека в std::list из std::string в конструкторе этого производного класса исключений и бросить этот производный класс. Как показывают ответы, вы никогда не можете полагаться на обработчик сигналов, чтобы дать правильную трассировку стека.   -  person rwols    schedule 25.01.2014
comment
@AndrewMedico: см. обновленный вопрос (нижний колонтитул - раздел P.S.).   -  person Violet Giraffe    schedule 25.01.2014


Ответы (2)


Запись номер 2 имеет особый адрес. Это 0x000000000000000c, и вы должны знать, что существует таблица описания прерываний. Согласно http://en.wikipedia.org/wiki/Interrupt_Descriptor_Table, это может быть ошибка стека.

Однако, как указал Кен, делать то, что вы делаете, плохо. Это все равно, что пытаться вспомнить номер автомобиля, который сбил вас, когда у вас разорвалась аорта. Можно, но зачем?

Возможно, вместо этого попробуйте научиться использовать strace http://en.wikipedia.org/wiki/Strace< /а>.

person omikron    schedule 31.01.2014
comment
Strace, кажется, не делает то, что мне нужно. - person Violet Giraffe; 01.02.2014
comment
Хм, насколько я понимаю этот журнал трассировки, ошибка стека была вызвана : " 3 App 0x000000010000dfa7 _ZN11CMainWindow13initShortcutsEv + 231", процессор просмотрел таблицу дескрипторов прерываний (запись 2) и перешел к процедуре, обрабатывающей этот сигнал в libsystem_platform.dylib (запись 1), которая снова вызвала ваш код (запись 0), потому что вы установите свой собственный обработчик сигнала. IDT — это список инструкций перехода к обработчикам прерываний. - person omikron; 01.02.2014
comment
Не было ошибки стека. Был сигнал SIGSEGV. И отправил не ZN11CMainWindow13initShortcutsEv, а другим методом, вызванным оттуда. - person Violet Giraffe; 01.02.2014
comment
Отладочная сборка, без оптимизаций. Не должен был быть встроен. - person Violet Giraffe; 01.02.2014
comment
Неважно, что это Debug, если бы вы определили код в заголовке, он, вероятно, был бы встроенным. Методы также могут быть встроенными. Сделайте его виртуальным, чтобы он точно не был встроен. - person omikron; 01.02.2014
comment
Ты не прав. Нет оптимизации = нет встраивания, иначе вы не сможете правильно отлаживать. - person Violet Giraffe; 01.02.2014

Идея создания аварийного журнала с обработчиком сигналов в корне ошибочна. Из раздела «ПРИМЕЧАНИЕ» документа sigaction. справочная страница:

Следующие функции являются либо реентерабельными, либо не прерываемыми сигналами и безопасными для асинхронных сигналов. Поэтому приложения могут без ограничений вызывать их из функций захвата сигналов:

[… список безопасных функций асинхронного сигнала опущен…]

Все функции, не вошедшие в вышеперечисленные списки, считаются небезопасными по отношению к сигналам. То есть поведение таких функций при вызове из обработчика сигнала не определено. Однако в целом обработчики сигналов должны делать немного больше, чем просто устанавливать флаг; большинство других действий небезопасны.

Ни один из backtrace(), backtrace_symbols(), free() или exit() нельзя безопасно вызывать в обработчике сигнала. Почти наверняка содержимое вашего Logger или operator<< также небезопасно.

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

На самом деле обработчики сигналов могут работать в совершенно другом стеке, чем обычный код. См. sigaltstack().

person Ken Thomases    schedule 25.01.2014
comment
Ссылка в самом конце вашего сообщения, которая является единственной частью, которая действительно может быть кому-то полезна, теперь мертва. - person Stuntddude; 16.09.2019
comment
ссылка больше не работает, см. man sigaltstack или gnu.org/software/ libc/manual/html_node/Signal-Stack.html. - person kgbook; 04.06.2020