Prateek Joshi
Prateek Joshi

Reputation: 4067

Using setjmp() and longjmp() to prevent segmentation fault in a program

I have written a program to prevent segfault using setjmp() and longjmp(), but the program that I have written prevents segfault from happening only one time (I'm running my code inside a while loop).

Here is my code:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

jmp_buf buf;

void my_sig_handler(int sig)
{
    if( sig )
    {
        printf("Received SIGSEGV signl \n");
        longjmp(buf,2);
    }
}

int main()
{
    while( 1)
    {
        switch( setjmp(buf) )                       // Save the program counter
        {
        case 0:
            signal(SIGSEGV, my_sig_handler);        // Register SIGSEGV signal handler function
            printf("Inside 0 statement \n");
            int *ptr = NULL;
            printf("ptr is  %d ", *ptr);            // SEG fault will happen here
            break;
        case 2:
            printf("Inside 2 statement \n");        // In case of SEG fault, program should execute this statement
            break;
        default:
            printf("Inside default statement \n");
            break;
        }
    }
    return 0;
}

Output:

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
Inside 0 statement 
Segmentation fault

Expected Output:

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
.
.(Infinite times)
.
Inside 0 statement 
Received SIGSEGV signal
Inside 2 statement 

Can someone please explain why this is only running as expected the first time only? Also, what I am missing here to run my code as expected?

Upvotes: 3

Views: 2210

Answers (1)

Marco Bonelli
Marco Bonelli

Reputation: 69276

Long story short: longjump is (obviously) not an async-signal-safe function, and printf too. Therefore calling those functions from a signal handler will cause undefined behavior. Refer to man 7 signal-safety for more information and a list of async-signal-safe functions.

What's most probably happening is that the longjump(buf, 2) is causing the program to "escape" the signal handler abnormally, and this causes another segmentation fault after executing the second switch case. Since another segmentation fault occurs, the signal handler is called again, and you do another longjump(buf, 2), getting back where you was, causing another segfault, and so on and so forth... indefinitely.


EDIT: as suggested by Andrew Henle in the comments below, there also are the two POSIX functions sigsetjmp() and siglongjmp(). I however prefer the approach described below since it looks cleaner to me and safely returns from the signal handler leaving the dirty work to the kernel.

If you want your code to run as expected, you can have your signal receive information about the context at the moment of the segfault:

static void signal_handler(int sig, siginfo_t *info, void *ucontext) {
    /* Assuming your architecture is Intel x86_64. */
    ucontext_t *uc = (ucontext_t *)ucontext;
    greg_t *rip = &uc->uc_mcontext.gregs[REG_RIP];

    /* Assign a new value to *rip somehow, which will be where the
       execution will continue after the signal handler returns. */
}

int main(void) {
    struct sigaction sa;
    int err;

    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = signal_handler;

    err = sigemptyset(&sa.sa_mask);
    if (err)
        return 1;

    err = sigaddset(&sa.sa_mask, SIGSEGV);
    if (err)
        return 1;

    err = sigaction(SIGSEGV, &sa, NULL);
    if (err)
        return 1;

    /* ... */

    return 0;
}

This will allow you to resume execution basically anywhere you want, provided that you actually know where to exactly resume. To set rip to the right value though, you will probably have to use a global label defined with inline asm or some other dirty trick.

Something like this should work (tested on my machine):

/* In main, where you want to retums after SIGSEGV: */
asm voaltile ("checkpoint: .global checkpoint" : );

/* In your signal handler: */
asm volatile (
    "movabs $checkpoint, %0"
    : "=r" (*rip)
);

If you are wondering about why this isn't that easy that's because it shouldn't even be done in the first place, it's basically an abomination that serves no purpose other than maybe having fun discovering how stuff can be broken in the most absurd ways.

You will need at least the following headers and feature test macros for the above to work:

#define _GNU_SOURCE
#define __USE_GNU
#include <signal.h>
#include <ucontext.h>

Note that this is (of course) both architecture and platform dependent.

Upvotes: 4

Related Questions