为什么不能使用signalfd 捕获SIGSEGV?

Tod*_*.Lu 4 linux signals

我的系统是 ubuntu 12.04。我从 修改了示例man 2 signalfd,并添加了示例sigaddset(&mask, SIGSEGV)。但是在SIGSEGV生成时我无法获得输出。

它是一个错误glibc吗?源代码片段如下:

       sigemptyset(&mask);
       sigaddset(&mask, SIGINT);
       sigaddset(&mask, SIGQUIT);
       sigaddset(&mask, SIGSEGV);

       /* Block signals so that they aren't handled
          according to their default dispositions */

       if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
           handle_error("sigprocmask");

       sfd = signalfd(-1, &mask, 0);
       if (sfd == -1)
           handle_error("signalfd");
        int* a = NULL;
       for (;;) {
           s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
           if (s != sizeof(struct signalfd_siginfo))
               handle_error("read");

           if (fdsi.ssi_signo == SIGINT) {
               printf("Got SIGINT\n");
               (*a) = 1;
           } else if (fdsi.ssi_signo == SIGQUIT) {
               printf("Got SIGQUIT\n");
               exit(EXIT_SUCCESS);
           } else {
               printf("Read unexpected signal\n");
           }
       }
Run Code Online (Sandbox Code Playgroud)

Bas*_*tch 6

有关详细说明请参阅这个那个答案。仔细阅读signal(7)signal-safety(7)。还要记住,您的进程虚拟地址空间是该进程的所有线程共有的,并在它们之间共享。另请参阅proc(5)(并使用pmap(1))并尝试 从进程内部读取以了解其实际虚拟地址空间。/proc/self/maps

粗略地说,如果您SIGSEGV使用signalfd(2)处理(异步)(由内核在某些异常故障后产生,看起来您安装了一个“内核”信号处理程序,它神奇地“写入”某些文件上的一些字节描述符(您几乎可以signalfd通过在某些管道上安装信号处理程序来模仿;但signalfd保证一些“原子性”,否则您将不会拥有)。

当你从那个处理中回来时,机器处于相同的状态,所以 SIGSEGV 再次发生。

如果你想处理SIGSEGV你需要使用sigaction(2)或过时的signal(2)来安装处理例程(所以你不能signalfd用于 SIGSEGV),然后你应该要么

  • (或多或少可移植)避免从您的信号处理程序返回(例如,通过从安装了sigaction(2) 的信号处理程序调用siglongjmp(3 )
  • 不可移植地(以处理器和操作系统特定的方式)将机器上下文(由第三个参数(指向某些特定处理器的指针ucontext_t)提供给由sigactionwith安装的处理程序SA_SIGINFO),例如通过更改某些寄存器或更改地址空间(例如,通过从处理程序内部调用mmap(2))。

洞察力是进入 SIGSEGV 处理程序,程序计数器设置为故障机器指令。当您从 SIGSEGV 处理程序返回时,寄存器处于提供给它的状态(指针ucontext_t作为sa_sigaction传递给的函数的第三个参数sigaction)。如果您不更改该状态,则会重新执行相同的机器指令,并且由于您没有更改任何内容,因此会发生相同的错误,并且内核会再次发送相同的 SIGSEGV 信号。

顺便说一句,软件巧妙且不可移植地处理 SIGSEGV 的一个很好的例子是Ravenbrook MPS垃圾收集库。它们的写屏障(用 GC 的说法)是通过处理 SIGSEGV 来实现的。这是非常聪明(且不可移植)的代码。

注意:在实践中,如果您只想显示回溯信息,您可以从SIGSEGV处理程序中完成(例如,通过使用GCC libbacktracebacktrace(3)然后_exit(2) -ing 而不是从您的SIGSEGV信号处理程序返回);它并不完美,也不会总是有效——例如,如果你破坏了内存堆——因为你将调用非异步信号安全函数,但实际上工作得很好。最近的 GCC 正在这样做(在编译器内部,例如cc1plus及其插件),它有很大帮助。