IRIX Binary Compatibility, Part 4by Emmanuel Dreyfus
Native Implementation of Signals
Signals are the difficult of part IRIX emulation. However, before examining the way they work on IRIX, let us study the signals implementation in
A user process enters the kernel by a trap. When a trap is caught, the hardware transfers
control to the kernel. Assembly code in sys/arch/mips/mips/locore.S builds a trap frame
(this is a struct frame, defined in sys/arch/mips/include/proc.h) on the kernel stack, in which
CPU registers are saved. Then the
trap() function from sys/arch/mips/mips/trap.c is called to handle the trap.
When resuming the process execution, the kernel just restores the CPU registers from the trap frame. This restores the program counter, stack pointer, and so on.
When the kernel invokes a signal handler, it has to trick the user process so that on return to userland, it executes the signal handler instead of resuming normal execution where it was stopped when the trap occurred.
This is done by modifying the trap frame. The saved program counter is modified so that on
return to userland the signal handler is run. Registers
A2 are set to the signal
handler's arguments. This is done for native process in
In This Series
IRIX Binary Compatibility, Part 6
IRIX Binary Compatibility, Part 5
IRIX Binary Compatibility, Part 3
IRIX Binary Compatibility, Part 2
IRIX Binary Compatibility, Part 1
The difficult task is resuming the process normally once the signal handler is executed. To
achieve this, we must save the machine state from the trap frame somewhere, and restore it
correctly later. This is done by copying the machine state to a struct
sigcontext (defined in
sys/arch/mips/include/signal.h) on the process' user stack. The struct sigcontext address is
handed to the signal handler through its third argument, as documented in
Another requirement is to get control back after the signal handler is executed. On MIPS
processors, there is an
RA register, which holds the Return Address. Function returns are
implemented in assembly by a jump to
RA saved in the trap frame is used to control where the signal handler
will return. We want to return to the kernel in order to undo the trap frame modification so
that the process will be able to resume normally the next time it returns to
userland. Unfortunately, it is not possible to just jump to kernel code from a user program:
the user process must do a trap to return to the kernel.
Things are done by calling a dedicated system call:
RA is set to return to a small piece of code known as the signal trampoline, which is copied by the kernel onto the user stack. Then things work, because the signal handler returns to code in user space. The
signal trampoline calls
Here is the signal trampoline for NetBSD/mips, as defined in sys/arch/mips/mips/locore.S
addu a0, sp, 16 # address of sigcontext li v0, SYS___sigreturn14 # sigreturn(scp) syscall
If the signal handler did not screw up the stack pointer, the signal context structure will be
16 bytes above the stack pointer. The signal trampoline sets up
A0 with the address of the
sigcontext, which will be the first argument to
It is important to hand the struct sigcontext to
sigreturn(2), because it needs it in order to restore the process trap frame. This is done in sys/arch/mips/mips/mips_machdep.c:sigreturn().
sigreturn(2) is a system call that does not return: once the trap
frame is restored and we return to user space, execution will resume
after the trap that occurred before the signal handler execution. The
context of the signal handler does not exist anymore.
IRIX Implementation of Signal Delivery
On IRIX, things are much more complicated. Once again, doing things incrementally is a good solution, allowing different problems to be addressed separately.
Hence, implementing a
irix_sigreturn() as plain copies of the native
sigreturn is a good first step. The only original item we need is an IRIX signal trampoline. It can be made from the native signal trampoline, since the only thing that needs to change is the system call number for
addu $a0, $sp, 16 # address of sigcontext li $v0, IRIX_SYS_sigreturn + SYSCALL_SHIFT syscall # irix_sys_sigreturn(scp) break 0 # just in case sigreturn fails
Here we are storing into
A0 the address of
sigcontext, which is 16 bytes lower than the stack pointer.
A0 is used to store the first argument to the
irix_sys_sigreturn system call.
This is really basic, and we are far from emulating what really happens on IRIX, but, in fact, it will even "just work" for a lot of programs where the signal handler does not use its arguments.
Let us review the arguments of the signal handler on IRIX. According to the IRIX
sigaction(2) man page, we have two situations.
SA_SIGINFO flag was not set on
sigaction(2) call, then the signal handler is invoked with:
- the signal number
- a code indicating the cause of the signal (may be 0 for no known reason)
- a pointer to the struct
irix_sigcontext(defined in sys/compat/irix/irix_signal.h) where the process context was saved.
SA_SIGINFO was set:
- the signal number
- a pointer to a struct
irix_siginfo(defined in sys/compat/irix/irix_signalh) that explains in greater detail the causes of the signal. This pointer may be
- a pointer to the struct
irix_ucontext(again see sys/compat/irix/irix_signal.h) where the process context was saved
If the signal handler attempts to use its arguments, we must accurately
emulate them. When
SA_SIGINFO is not set, things are simple because this
is exactly what the native
sendsig() sets up. However, there is a problem
SA_SIGINFO is set.
It is easy to modify
irix_sendsig() so that it saves the context in a struct
irix_ucontext instead of struct
SA_SIGINFO was set. But the problem is in
irix_sys_sigreturn(): how to distinguish between the situation where the context is to be restored from a struct
irix_sigcontext or a struct
irix_ucontext. Assuming the wrong structure will lead to restoring a screwed context, and this means a crash for the process, because
execution will resume at the location the PC register was wrongly set to.
We have the beginning of an answer by looking at IRIX's
sigreturn expects in three arguments:
- a pointer to the struct sigcontext (called
scp) where the context was saved
- a mysterious pointer of type
void *, named
- the signal number
One more time, Linux has already implemented this, hence the temptation is here to pick up good
ideas from Linux. Looking at Linux source for
sigreturn is not very instructive: when the first
NULL, Linux uses the mysterious
ucp pointer to find the process context. Because we
have no real idea about how to handle this
ucp pointer and the
SA_SIGINFO flag, we will just
leave them behind for now. The easy thing to do is to modify
to make use of struct
irix_sigcontext instead of the native struct sigcontext. At least this
will make programs that do not set
SA_SIGINFO happy with the content of the signal context if
they happen to access it.