Can a C program continue execution after a signal

2019-01-23 16:13发布

问题:

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.

回答1:

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;



回答2:

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.)



回答3:

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


标签: c signals