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

时间:2014-01-18 12:38:55

标签: linux signals

我的系统是ubuntu 12.04。 我从man 2 signalfd修改了示例,并在exmaple中添加了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");
           }
       }

2 个答案:

答案 0 :(得分:4)

有关详细说明,请参阅thisthat个答案。仔细阅读signal(7)signal-safety(7)。另请注意,virtual address spaceprocess对该流程的所有threads都是通用的,并且在该proc(5)之间共享。另请参阅pmap(1)(并使用signalfd(2))并尝试从流程内部阅读/proc/self/maps以了解其实际虚拟地址空间。

粗略地说,如果你使用sigaction(2)处理(异步)SIGSEGV(由于内核在发生异常故障后产生),看起来你已经安装了一个"内核"信号处理程序神奇地"写一些文件描述符上的某些字节(你几乎可以通过在某个管道上安装信号处理程序来模仿signalfd;但signalfd保证一些&# 34;原子性"你没有赢得其他)。

当您退出处理时,机器处于相同状态,因此SIGSEGV再次发生。

如果您要处理SIGSEGV,则需要使用siglongjmp(3)或过时的signal(2)来安装处理例程(因此您无法使用signalfd SIGSEGV),然后你应该

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

洞察力是输入SIGSEGV处理程序,程序计数器设置为错误机器指令。当您从SIGSEGV处理程序返回时,寄存器处于给定状态(指针ucontext_t作为传递给sa_sigaction的{​​{1}}函数的第三个参数。如果你没有改变那个状态,那么重新执行相同的机器指令,并且由于你没有改变任何东西,同样的故障发生了,内核再次发送相同的SIGSEGV信号。

BTW,一个很好的软件处理示例,SIGSEGV是Ravenbrook MPS垃圾收集库。他们的写屏障(用GC说法)是通过处理SIGSEGV来实现的。这是非常聪明(和非便携)的代码。

注意:在实践中,如果您只想显示回溯信息,可以从sigaction处理程序执行此操作(例如,使用GCC libbacktracebacktrace(3)然后_exit(2) -ing而不是从SIGSEGV信号处理程序返回;它不完美,不会一直工作 - 例如。如果你corrupted the memory heap - 因为你会调用非异步信号安全函数,但实际上运行得很好。最近的GCC正在这样做(在编译器内部,例如SIGSEGV及其插件),它有很多帮助。

答案 1 :(得分:2)

您只能阻止kill(2)和朋友发送的SISGSEGV。来自sigprocmask(2)的联机帮助页:

  

如果生成SIGBUS,SIGFPE,SIGILL或SIGSEGV   阻止,结果是未定义的,除非信号是由...生成的   kill(2),sigqueue(3)或raise(3)


由于信号未被sigprocmask真正阻止 - signalfd - signalfd的先决条件不起作用。一种简单的测试方法是使用您的程序,但不是引起真正的分段错误,而是发送kill -11的信号。