Reputation: 4067
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
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