Выдача исключения из обработчика сигнала - PullRequest
21 голосов
/ 11 ноября 2009

У нас есть библиотека, которая занимается многими аспектами сообщений об ошибках. Мне было поручено перенести эту библиотеку на Linux. При запуске через мой маленький набор тестов один из тестов не прошел. Упрощенная версия теста приведена ниже.

// Compiler: 4.1.1 20070105 RedHat 4.1.1-52
// Output: Terminate called after throwing an instance of 'int' abort

#include <iostream>
#include <csignal>
using namespace std;

void catch_signal(int signalNumber)
{
    signal(SIGINT, SIG_DFL);
    throw(signalNumber);
}

int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        raise(SIGINT);
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }
    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}

Я ожидаю, что Поймано исключение: сообщение будет отображаться. Что на самом деле происходит, так это то, что программа завершается, поскольку для обработчика int отсутствует обработчик catch.

Есть несколько вопросов по SO, которые кажутся связанными. Я нашел несколько страниц Google, которые были связаны. «Мудрость», кажется, сводится к следующему.

  1. Я не могу выбросить исключения из обработчиков сигналов, вызвать сигнал Обработчик работает со своим собственным стеком, поэтому в нем не определены обработчики.
  2. Я могу выбросить исключения из обработчиков сигналов, просто восстанови фальшивку кадр в стеке, и вы готовы идти.
  3. Да, мы делаем это все время. У меня работает на платформе X
  4. Да, раньше он был доступен с gcc, но, похоже, не работает Больше. Попробуйте опцию -fnon-call-exception * , возможно, это будет работать

    Код работает должным образом на нашем компиляторе / средах AIX / TRU64 / MSVC. Это терпит неудачу в нашей среде Linux.


Я ищу предложения, которые помогут решить эту проблему, чтобы поведение библиотеки в Linux совпадало с моими другими платформами или каким-то другим способом или обходным решением, которое могло бы достигнуть такой же функциональности.
Разрешение дампа ядра программы по сигналу не является жизнеспособным вариантом.

Ответы [ 7 ]

14 голосов
/ 12 ноября 2009

Сигналы полностью отличаются от исключений C ++. Вы не можете использовать блок C ++ try / catch для обработки сигнала. В частности, сигналы - это концепция POSIX, а не концепция языка C ++. Сигналы доставляются ядром асинхронно в ваше приложение, тогда как исключения C ++ являются синхронными событиями, определенными стандартом C ++.

Вы весьма ограничены в том, что вы можете делать переносимо в обработчике сигналов POSIX. Общая стратегия состоит в том, чтобы иметь глобальный флаг типа sig_atomic_t, который будет установлен в 1 в обработчике сигналов, а затем, возможно, longjmp в соответствующий путь выполнения.

См. здесь для помощи в написании правильных обработчиков сигналов.

9 голосов
/ 15 ноября 2014

Этот код демонстрирует технику, которая перемещает исключение из обработчика сигнала в код. Спасибо Чарльзу за идею.

#include <iostream>
#include <csignal>
#include <csetjmp>

using namespace std;

jmp_buf gBuffer;        // A buffer to hold info on where to jump to

void catch_signal(int signalNumber)
{
    //signal(SIGINT, SIG_DFL);          // Switch to default handling
    signal(SIGINT, catch_signal);       // Reactivate this handler.

    longjmp             // Jump back into the normal flow of the program
    (
        gBuffer,        // using this context to say where to jump to
        signalNumber    // and passing back the value of the signal.
    );
}


int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        int sig;
        if ((sig = setjmp(gBuffer)) == 0) 
        {
            cout << "before raise\n";
            raise(SIGINT);
            cout << "after raise\n";

        }
        else
        {
            // This path implies that a signal was thrown, and
            // that the setjmp function returned the signal
            // which puts use at this point.

            // Now that we are out of the signal handler it is
            // normally safe to throw what ever sort of exception we want.
            throw(sig);
        }
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }

    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}
5 голосов
/ 12 ноября 2009

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

4 голосов
/ 12 ноября 2009

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

Важное примечание следует учитывать для любого регистра, используемого ABI C ++, который сохраняется и повторно используется механизмом обработки сигналов.

3 голосов
/ 05 января 2014

google g ++ option

-fnon-call-exceptions

Это по сути то, что вы хотите. Я думаю, что это было разработано из-за давления со стороны Apple для их ОС. Я не уверен, насколько это поддерживается в Linux. И я не уверен, что можно поймать SIGINT - но все сигналы, запускаемые процессором (за исключением) могут быть перехвачены. Программисты, нуждающиеся в этой функции (и не заботящиеся об идеологии), должны оказать некоторое давление на сообщество разработчиков LINUX, так что она будет поддерживаться и в LINUX в ​​один прекрасный день - после того, как она поддерживалась в Windows почти два десятилетия.

1 голос
/ 24 мая 2019

Вот потенциальное решение. Это, вероятно, довольно сложно реализовать, и, конечно, по крайней мере, часть из них нуждается в повторной реализации для каждого сочетания архитектуры CPU и ОС и / или библиотеки C:

В обработчике сигналов стек содержит сохраненную копию всех регистров прерванного кода. Вы можете манипулировать этим, чтобы изменить состояние программы после выхода из обработчика сигнала. Вы бы хотели сделать что-то подобное в обработчике:

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

2) В свободном пространстве посередине стека придумайте новый кадр стека, как если бы выполнялась какая-то функция «вызова исключений», когда был поднят сигнал. Этот кадр должен быть расположен точно так же, как если бы прерванный код вызывал эту функцию обычным способом.

3) Измените ПК сохраненного состояния ЦП так, чтобы он указывал на эту функцию «вызова исключений».

4) Выход из обработчика сигнала.

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

Возможно, здесь есть несколько деталей; e.g.:

1) Функция "вызова исключений", вероятно, должна сохранять в стек кучу регистров, чего обычно не было бы; все регистры, сохраненные вызываемым абонентом, которые, возможно, использовал прерванный код. Вам может понадобиться написать (часть?) Функцию «вызова исключений» в ассемблере, чтобы помочь здесь. Возможно, шаг 2, описанный выше, может сохранить регистры как часть настройки стекового кадра.

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

3) Вам может потребоваться вручную сгенерировать некоторую информацию о размотке обработчика исключений C ++, чтобы код обработки исключений C ++ знал, как разматывать стек из этой функции «вызова исключений». Если вы можете написать функцию на C ++, вероятно, нет. Если не можете, то почти наверняка.

4) Вероятно, все виды неприятных деталей, которые я упустил из виду: -)

0 голосов
/ 28 мая 2019

По крайней мере в Ubuntu 16.04 x86-64, выброс из обработчика сигнала, кажется, работает нормально. Является ли это намерением (т.е. гарантированно работать, а не работать случайно как-то), я не исследовал. Я скомпилировал программу ниже, используя g++ -o sig-throw sig-throw.cpp:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern "C" void handler(int sig, siginfo_t *info, void *xxx)
{
    throw "Foo";
}

int main(int argc, char **argv)
{
    struct sigaction sa = {0};

    sa.sa_sigaction = handler;
    sigaction(SIGALRM, &sa, NULL);

    alarm(3);

    try {
        printf("Sleeping...\n");
        sleep(10);
        printf("Awoke\n"); // syscall interrupted
    }
    catch (...) {
        printf("Exception!\n");
    }

    return 0;
}

Вот оно работает:

[swarren@swarren-lx1 sig-throw]$ ./sig-throw 
Sleeping...
Exception!

Для справки:

[swarren@swarren-lx1 sig-throw]$ lsb_release -a
...
Description:    Ubuntu 16.04.6 LTS
...

[swarren@swarren-lx1 sig-throw]$ dpkg -l libc6
...
ii  libc6:amd64  2.23-0ubuntu11  amd64  GNU C Library: Shared libraries

[swarren@swarren-lx1 sig-throw]$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...