Why can't capture SIGSEGV using signalfd?

2020-01-27 06:41发布

问题:

My system is ubuntu 12.04. I modify the example from man 2 signalfd, and add sigaddset(&mask, SIGSEGV) in the exmaple. But I can't get the output when SIGSEGV is generated.

Is it a bug of glibc? The fragment of source code is following:

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

回答1:

See this and that answers for detailed explanations. Read carefully signal(7) and signal-safety(7). Remember also that the virtual address space of your process is common to, and shared between, all the threads of that process. See also proc(5) (and use pmap(1)) and try reading /proc/self/maps from inside your process to understand its actual virtual address space.

Grossly speaking, if you handle (an asynchronous) SIGSEGV (produced by the kernel after some exception fault) with signalfd(2), it is looking like you installed a "kernel" signal handler which magically "write"-s some bytes on some file descriptor (you almost could mimick signalfd by installing a signal handler writing on some pipe; but signalfd guarantees some "atomicity" that you won't have otherwise).

When you are back from that handling, the machine is in the same condition, so the SIGSEGV happens again.

If you want to handle SIGSEGV you need to use sigaction(2) or the obsolete signal(2) to install a handling routine (so you can't use signalfd for SIGSEGV), and then you should either

  • (more or less portably) avoid returning from your signal handler (e.g. by calling siglongjmp(3) from your signal handler installed with sigaction(2))
  • non-portably (in a processor and operating system specific way) change the machine context (given by the third argument (a pointer to some processor specific ucontext_t) to your handler installed by sigaction with SA_SIGINFO), e.g. by changing some registers, or change the address space (e.g. by calling mmap(2) from inside the handler).

The insight is that a SIGSEGV handler is entered with the program counter set to the faulting machine instruction. When you return from a SIGSEGV handler, the registers are in the state given to it (the pointer ucontext_t as the third argument of your sa_sigaction function passed to sigaction). If you don't change that state, the same machine instruction is re-executed, and since you didn't change anything the same fault happens and the same SIGSEGV signal is sent again by the kernel.

BTW, a nice example of a software handling cleverly and non-portably the SIGSEGV is the Ravenbrook MPS garbage collection library. Their write barrier (in GC parlance) is implemented by handling SIGSEGV. This is very clever (and non portable) code.

NB: in practice, if you just want to display backtrace information, you could do it from a SIGSEGV handler (e.g. by using GCC libbacktrace or backtrace(3) then _exit(2)-ing instead of returning from your SIGSEGV signal handler); it is not perfect and won't always work -e.g. if you corrupted the memory heap- because you will call non async-signal-safe functions, but in practice works well enough. Recent GCC is doing that (inside the compiler e.g. cc1plus and its plugins), and it helps a lot.



回答2:

You can only block a SISGSEGV sent by kill(2) and friends. From the manpage of sigprocmask(2):

If SIGBUS, SIGFPE, SIGILL, or SIGSEGV are generated while they are blocked, the result is undefined, unless the signal was generated by kill(2), sigqueue(3), or raise(3)


Since the signal isn't really blocked by sigprocmask - a prerequisite of signalfd - signalfd doesn't work. A simple way to test is to use your program but instead of causing a true segmentation fault, send a signal with kill -11.



标签: linux signals