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");
}
}
You can only block a SISGSEGV sent by
kill(2)
and friends. From the manpage ofsigprocmask(2)
:Since the signal isn't really blocked by
sigprocmask
- a prerequisite ofsignalfd
-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 withkill -11
.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 mimicksignalfd
by installing a signal handler writing on some pipe; butsignalfd
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 obsoletesignal(2)
to install a handling routine (so you can't usesignalfd
for SIGSEGV), and then you should eitherucontext_t
) to your handler installed bysigaction
withSA_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 yoursa_sigaction
function passed tosigaction
). 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 yourSIGSEGV
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.