I'm new at signal handling in Unix through C and I have been looking at some tutorials on it (out of pure interest).
My questions is, is it possible to continue execution of a program past the point where a signal is handled?
I understand that the signal handling function does the cleanup but in the spirit of exception handling (such as in C++), is it possible for that signal to be handled in the same fashion and for the program to continue running normally?
At the moment catch
goes in an infinite loop (presumably a way to quit would be to call exit(1)
).
My intention would be for b
to be assigned 1 and for the program to finish gracefully (if that is possible of course).
Here's my code:
#include <signal.h>
#include <stdio.h>
int a = 5;
int b = 0;
void catch(int sig)
{
printf("Caught the signal, will handle it now\n");
b = 1;
}
int main(void)
{
signal(SIGFPE, catch);
int c = a / b;
return 0;
}
Also, as C is procedural, how come the signal handler declared before the offending statement is actually called after the latter has executed?
And finally, in order for the handling function to do its clean up properly, all the variables than need to be cleaned up in the event of an exception need to be declared prior to the function, right?
Thanks in advance for your answers and apologies if some of the above is very obvious.
Yes, that's what signal handlers are for. But some signals need to be handled specially in order to allow the program to continue (e.g. SIGSEGV, SIGFPE, …).
See the manpage of sigaction
:
According to POSIX, the behavior of a process is undefined after it ignores a SIGFPE, SIGILL, or SIGSEGV signal that was not
generated by kill(2) or raise(3). Integer division by zero has undefined result. On some architectures it will generate a
SIGFPE signal. (Also dividing the most negative integer by -1 may generate SIGFPE.) Ignoring this signal might lead to an
endless loop.
Right now, you are ignoring the signal, by not doing anything to prevent it from happening (again). You need the execution context in the signal handler and fix it up manually, which involves overwriting some registers.
If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
sa_handler) specifies the signal-handling function for signum. This
function receives the signal number as its first argument, a pointer
to a siginfo_t as its second argument and a pointer to a ucontext_t
(cast to void *) as its third argument. (Commonly, the handler
function doesn't make any use of the third argument. See
getcontext(2) for further information about ucontext_t.)
The context allows access to the registers at the time of fault and needs to be changed to allow your program to continue. See this lkml post. As mentioned there, siglongjmp
might also be an option. The post also offers a rather reusable solution for handling the error, without having to make variables global etc.:
And because you handle it youself, you have any flexibility you want
to with error handling. For example, you can make the fault handler
jump to some specified point in your function with something like
this:
__label__ error_handler;
__asm__("divl %2"
:"=a" (low), "=d" (high)
:"g" (divisor), "c" (&&error_handler))
... do normal cases ...
error_handler:
... check against zero division or overflow, so whatever you want to ..
Then, your handler for SIGFPE needs only to do something like
context.eip = context.ecx;
In general, yes, execution continues after the handler returns. But if the signal was caused by a hardware error (such as a floating point exception or a segmentation fault), you have no way of undoing that error, and so your program will be terminated regardless.
In other words, you have to distinguish between signals and things that cause signals. Signals by themselves are perfectly fine and handlable, but they don't always let you fix errors that cause signals.
(Some signals are special, such as ABRT and STOP, in the sense that even if you just raise such a signal manually with kill
, you still can't "prevent its effects". And of course KILL cannot even be handled at all.)
If you know what you are doing, you can set the instruction pointer to point right after the offending instruction. Below is my example for x86 (32bit and 64bit). Don't try at home or in real products !!!
#define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>
static void sigaction_segv(int signal, siginfo_t *si, void *arg)
{
ucontext_t *ctx = (ucontext_t *)arg;
/* We are on linux x86, the returning IP is stored in RIP (64bit) or EIP (32bit).
In this example, the length of the offending instruction is 6 bytes.
So we skip the offender ! */
#if __WORDSIZE == 64
printf("Caught SIGSEGV, addr %p, RIP 0x%lx\n", si->si_addr, ctx->uc_mcontext.gregs[REG_RIP]);
ctx->uc_mcontext.gregs[REG_RIP] += 6;
#else
printf("Caught SIGSEGV, addr %p, EIP 0x%x\n", si->si_addr, ctx->uc_mcontext.gregs[REG_EIP]);
ctx->uc_mcontext.gregs[REG_EIP] += 6;
#endif
}
int main(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sigaction_segv;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
/* Generate a seg fault */
*(int *)NULL = 0;
printf("Back to normal execution.\n");
return 0;
}