-1
jmp_buf functjmp;

void sigsegv_handler(int sig) {
  sio_printf("Caught sigsegv!\n");
  siglongjmp(functjmp, 2);
  return;
}

void foo(unsigned val) {
  assert(0);
  sio_printf("entered!\n");
}

int main() {
  struct sigaction action;

  action.sa_handler = sigsegv_handler;
  sigemptyset(&action.sa_mask); /* Block sigs of type being handled */
  sigaddset(&action.sa_mask, SIGSEGV);
  action.sa_flags = SA_RESTART; /* Restart syscalls if possible */

  if (sigaction(SIGSEGV, &action, NULL) < 0) {
    sio_fprintf(stderr, "handler error!\n");
  }
  sigset_t prev_mask;
  sigprocmask(SIG_BLOCK, NULL, &prev_mask);
  if (sigsetjmp(functjmp, 0) == 0) {
    foo(*(unsigned *)0x8);
  } {
    sigprocmask(SIG_BLOCK, &prev_mask, NULL);
    sio_printf("jump handled!\n");
    foo(*(unsigned *)0x8);
  }
  sio_fprintf(stderr, "how did it come here?!\n");
}

I've been debugging this code using gdb, and I cannot figure out why the program will not handle the second SIGSEGV signal with my own handler, assuming no other signals were received or sent by the program? Any sio prefixed functions are async safe variants of the stdio counterparts. Currently, I surmise it has to do with something I'm missing in my conception about returning from the signal handler, which longjmp doesn't do at all.

Di W.
  • 175
  • 1
  • 1
  • 8

1 Answers1

1

Short answer: normally not possible to resume after SIGSEGV for C program. You might get more mileage with C++.

Long Answer: See discussions in Coming back to life after Segmentation Violation

Assuming OK to take the risk of undefined behavior:

It is possible to re-enable SEGV. The core issue is that during signal handler, the code explicitly blocks the SEGV signal from being triggered (with the sigaddset). In addition, the default behavior (of signal handlers) is that during signal handling, the same signal processing will be deferred until the signal handler returns. In the OP code, the signal handler never returns (because of the siglongjmp)

Both issues can be addressed by changing the original code.

  // Make sure all attributes are NULL.
  struct sigaction action = {} ;

  action.sa_handler = sigsegv_handler;
  sigemptyset(&action.sa_mask); /* Block sigs of type being handled */
  // Not Needed:: sigaddset(&action.sa_mask, SIGSEGV);
  // Add SA_NODEFER to disable the deferred processing of SIGSEGV.
  action.sa_flags = SA_RESTART | SA_NODEFER ; /* Restart syscalls if possible */

  // rest of code here
  if (sigaction(SIGSEGV, &action, NULL) < 0) {
    sio_fprintf(stderr, "handler error!\n");
  }
  ...
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
dash-o
  • 13,723
  • 1
  • 10
  • 37
  • I see, so is it because I'm never actually returning from the signal handler, so that some postprocessing in the kernel has not been done, or does the NODEFER flag serve a different functionality? Wouldn't the NODEFER flag enable a SIGSEGV signal to be received even before I siglongjmp prior to returning control flow to the desired place? – Di W. Jan 05 '20 at 06:33
  • 1
    @DiW. You are correct that NODEFER will allow SEGV even inside the signal handler. However, since it only performs the siglongjmp, the risk of getting SEGV is low. This can be protected using external flag to check for getting SEGV during the signal handler. – dash-o Jan 05 '20 at 06:43