We have a library that deals with many aspects of error reporting. I have been tasked to port this library to Linux. When running though my little test suite, one of the
google g++ option
-fnon-call-exceptions
This is essentially what you want. I think this was developed due to pressure from apple for their OS. I'm not certain how supported it is on LINUX. And I'm not certain if one can catch SIGINT -- but all CPU triggered signals (aeh exceptions) can be caught. Coders needing this feature (and don't care about ideology) should create some pressure on the LINUX developer community so that it will be supported on LINUX as well one day -- after having been supported on Windows since nearly two decades.
Throwing out of a signal handler is probably not a good idea as the stack is not necessairly set up in the same way as for function calls thus unwinding from a signal handler may not work as expected.
Important note must be taken for any register used by the C++ ABI that are saved and re-used by the signal handling mechanism.
I would mask all signals in every thread, except one which would wait signals with sigwait (). This thread can handle signals without restriction, e.g. throw exceptions or use other communication mechanisms.
At least in Ubuntu 16.04 x86-64, throwing out of a signal handler seems to work fine. Whether this is by design (i.e. guaranteed to work, rather than working accidentally somehow), I have not researched. I compiled the program below using 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;
#if 0
// To ensure SIGALRM doesn't remain blocked once the signal handler raises
// an exception, either enable the following, or add enable the sigprocmask
// logic in the exception handler below.
sa.sa_flags = SA_NODEFER;
#endif
sigaction(SIGALRM, &sa, NULL);
alarm(3);
try {
printf("Sleeping...\n");
sleep(10);
printf("Awoke\n"); // syscall interrupted
}
catch (...) {
printf("Exception!\n");
#if 1
// To ensure SIGALRM doesn't remain blocked once the signal handler
// raises an exception, either enable the following, or add enable
// SA_NODEFER when registering the signal handler.
sigset_t sigs_alarm;
sigemptyset(&sigs_alarm);
sigaddset(&sigs_alarm, SIGALRM);
sigprocmask(SIG_UNBLOCK, &sigs_alarm, NULL);
#endif
}
alarm(3);
try {
printf("Sleeping...\n");
sleep(10);
printf("Awoke\n"); // syscall interrupted
}
catch (...) {
printf("Exception!\n");
}
return 0;
}
Here's it running:
[swarren@swarren-lx1 sig-throw]$ ./sig-throw
Sleeping...
Exception!
For reference:
[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
This code demonstrates a technique which moves the throwing of the exception out of the signal handler into the code. My thanks to Charles for the idea.
#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;
}
Signals are totally different than C++ exceptions. You can't use a C++ try/catch block to handle a signal. Specifically, signals are a POSIX concept, not a C++ language concept. Signals are delivered asynchronously to your application by the kernel, whereas C++ exceptions are synchronous events defined by the C++ standard.
You are quite limited in what you can do portably in a POSIX signal handler. A common strategy is to have a global flag of type sig_atomic_t
which will be set to 1 in the signal handler, and then possibly longjmp
to the appropriate execution path.
See here for help writing proper signal handlers.