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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…