4

I live in an environment with Win7/MSVC 2010sp1, two different Linux boxes (Red Hat) with g++ versions (4.4.7, 4.1.2), and AIX with xlc++ (08.00.0000.0025).

Not so long ago it was requested that we move some code from AIX over to Linux. It didn't take too long to see that Linux was a bit different. Normally when a signal is thrown, we handle it and throw a C++ exception. That was not working as expected.

Long story short, throwing c++ exceptions from a signal handler isn't going to work.

Sometime later, I put together a fix that uses setjmp/longjmp to move the exception out of the handler. Aftersome testing and the dang thing works on all platforms. After an obligatory round of cubical happy dance I moved on to setting up some unit tests. Oops.

Some of my tests were failing on Linux. What I observed was that the raise function only worked once. With two tests using SIGILL, the first one passed, and the second one failed. I broke out an axe, and started chopping away at the code to remove as much cruft as possible. That yielded this smaller example.

#include <csetjmp>
#include <iostream>
#include <signal.h>

jmp_buf  mJmpBuf;
jmp_buf *mpJmpBuf = &mJmpBuf;
int status = 0;
int testCount = 3;

void handler(int signalNumber)
{
    signal(signalNumber, handler);
    longjmp(*mpJmpBuf, signalNumber);
}

int main(void)
{
    if (signal(SIGILL, handler) != SIG_ERR)
    {
        for (int test = 1; test <= testCount; test++)
        {
            try
            {
                std::cerr << "Test " << test << "\n";
                if ((status = setjmp(*mpJmpBuf)) == 0)
                {
                    std::cerr << "    About to raise SIGILL" << "\n";
                    int returnStatus = raise(SIGILL);
                    std::cerr << "    Raise returned value " << returnStatus
                              << "\n";
                }
                else
                {
                    std::cerr << "    Caught signal. Converting signal "
                              << status << " to exception" << "\n";
                    std::exception e;
                    throw e;
                }
                std::cerr << "    SIGILL should have been thrown **********\n";
            }
            catch (std::exception &)
            { std::cerr << "    Caught exception as expected\n"; }
        }
    }
    else
    { std::cerr << "The signal handler wasn't registered\n"; }

    return 0;
}

For the Windows and the AIX boxes I get the expected output.

Test 1
    About to raise SIGILL
    Caught signal. Converting signal 4 to exception
    Caught exception as expected Test 2
    About to raise SIGILL
    Caught signal. Converting signal 4 to exception
    Caught exception as expected Test 3
    About to raise SIGILL
    Caught signal. Converting signal 4 to exception
    Caught exception as expected

For both Linux boxes it looks like this.

Test 1
    About to raise SIGILL
    Caught signal. Converting signal 4 to exception
    Caught exception as expected
Test 2
    About to raise SIGILL
    Raise returned value 0
    SIGILL should have been thrown **********
Test 3
    About to raise SIGILL
    Raise returned value 0
    SIGILL should have been thrown **********

So, my real question is "What is going on here?"

My retorical questions are:

  • Is anyone else observing this behavior?
  • What should I do to try to troubleshoot this issue?
  • What other things should I be aware of?
Community
  • 1
  • 1
EvilTeach
  • 28,120
  • 21
  • 85
  • 141

1 Answers1

3

You must use sigsetjmp/siglongjmp to ensure the correct behavior when mixing signals and jumps. If you change your code it will correctly work under Linux.

You also used the old signal API which not recommended. I encourage you to use the much more reliable sigaction interface. The first benefits will be that you have no more need to reset the signal catch in the handler...

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69