Not Quite Research

Peter S. Housel's weblog

Chaining signal handlers on x86 linux
For some strange reason I can't comprehend, the SysV x86 ABI specifies that the INT 4 trap, invoked by the INTO (Interrupt on Overflow) instruction, is mapped to the SIGSEGV Unix signal, rather than the perhaps more intuitive SIGFPE. Linux for the x86 platform has followed suit (though FreeBSD did not.)

Open Dylan makes use of the INTO instruction to ensure that the <integer> abstraction is not violated. When an overflow trap is caught, the runtime is supposed to redirect program flow in order to cause a <arithmetic-overflow-error> exception to be signalled. This means that it needs to install a SIGSEGV handler.

The Memory Pool System garbage collector used by Open Dylan also installs its own SIGSEGV handler in order to implement a write barrier. (Linux aspects of the design are documented here.) In order for the two handlers to coexist, it is necessary to chain from one handler to the other if the condition it handles is not detected.

This is the tricky part. POSIX supports two different function signatures for signal handlers: the old style of handler,
void func(int signo);

and the SA_SIGINFO style handler,
void func(int signo, siginfo_t *info, void *context);

Chaining from one SA_SIGINFO handler to another is easy: just call it. Chaining to an old-style handler is not, however, because on Linux its real function signature is
void func(int signo, struct sigcontext context);

Moreover, any modifications made to the context structure passed on the stack are reflected in the program state on return from the signal handler. MPS uses this to simulate the execution of instructions that write to memory areas protected by its write barrier.

Fortunately, on x86 the contents of a struct sigcontext are identical to the content of uc_mcontext field of the ucontext_t structure type. This fact, along with a bit of inline assembly magic, allows us to write the following function for chaining to either type of signal handler:
inline void chain_sigaction(const struct sigaction *act,
                            int sig, siginfo_t *info, void *uap)
  if(act->sa_flags & SA_SIGINFO) {
    /* Inner handler uses the same (sa_sigaction) convention... call it */
    (*act->sa_sigaction)(sig, info, uap);
  else {
    /* Inner handler uses the old (sa_handler) convention, with a
     * struct sigcontext passed as a structure argument. The content
     * of struct sigcontext is identical to the content of the
     * ucontext_t uc_mcontext field.
    ucontext_t *uc = (ucontext_t *) uap;
    asm volatile(/* Preserve scratch registers on the stack */

                 /* Reserve stack space for the sigcontext argument */
                 /* Copy the sigcontext onto the stack as the second
                  * argument

                 /* Push the signal number onto the stack as the first
                  * argument

                 /* Call the handler */

                 /* Restore scratch registers */

                 /* Copy the sigcontext back into uc->uc_mcontext */

                 /* Restore the stack pointer */
                 : /* no outputs */
                 : [mcontext_bytes] "i" (sizeof(uc->uc_mcontext)),
                   [mcontext_words] "i" (sizeof(uc->uc_mcontext) / 4),
                   [mcontext] "m" (uc->uc_mcontext),
                   [handler] "g" (act->sa_handler),
                   [sig] "g" (sig)
                 : "memory", "cc", "ecx", "esi", "edi");
This code was added to Open Dylan in r12264.
Tags: ,


Log in