为什么我的信号处理程序在这里不被多次调用?

Why is my signal handler not invoked more than once here?

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

我一直在使用 gdb 调试这段代码,我无法弄清楚为什么程序不使用我自己的处理程序处理第二个 SIGSEGV 信号,假设程序没有接收或发送其他信号?任何带有 sio 前缀的函数都是 stdio 对应项的异步安全变体。 目前,我猜测它与我在关于从信号处理程序返回的概念中遗漏的东西有关,而 longjmp 根本没有这样做。

简短回答:对于 C 程序,通常无法在 SIGSEGV 之后恢复。您可能会使用 C++ 获得更多里程。

长答案:参见 Coming back to life after Segmentation Violation

中的讨论

假设可以承担未定义行为的风险:

可以重新启用 SEGV。核心问题是在信号处理程序期间,代码明确阻止 SEGV 信号被触发(使用 sigaddset)。此外,(信号处理程序的)默认行为是在信号处理期间,相同的信号处理将推迟到信号处理程序returns。在 OP 代码中,信号处理程序从不 returns (因为 siglongjmp)

这两个问题都可以通过更改原始代码来解决。

  // 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");
  }
  ...