Подтвердить что ты не робот

Выбрасывание исключения из обработчика сигнала

У нас есть библиотека, которая занимается многими аспектами отчетов об ошибках. Мне было поручено перенести эту библиотеку в 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;
}

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

Есть несколько вопросов о SO, которые кажутся связанными. Я нашел несколько страниц Google, которые были связаны. Кажется, что "мудрость" сводится к.

  • Я не могу генерировать исключения из обработчиков сигналов, вызывать сигнал обработчик работает со своим собственным стеком, поэтому на нем не определены обработчики.
  • Ya может генерировать исключения из обработчиков сигналов, просто восстановить подделку   кадр в стеке, и вам хорошо идти.
  • Я, мы делаем это все время. Это работает для меня на платформе X
  • Ya, который раньше был доступен с gcc, но, похоже, не работает Больше. Попробуйте опцию -fnon-call-exceptions, возможно, она будет работать

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


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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

#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;
}

Ответ 3

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

Ответ 4

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

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

Ответ 5

опция google g++

-fnon-call-exceptions

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

Ответ 6

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

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

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

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

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

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

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

Здесь, вероятно, есть несколько деталей; например:

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

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

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

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