IRIX Binary Compatibility, Part 4
Pages: 1, 2, 3, 4
In the CPU register values, the content of register RA
is also interesting: this is the address of the signal trampoline. An x/20i $ra
command typed in gdb
would show it, but I cannot
reproduce it here because of the copyright SGI holds on it. There are several things to note
about this signal trampoline.
First, dumping it would show that it does not start at RA
but a few instructions before. If you
happen to run the above test program, you can see the whole IRIX 6.5 signal trampoline by
issuing an x/32i $ra-100
. This suggests two things: there are several entry points to the
signal trampoline, or the signal trampoline is invoked before the signal handler.
It is easy to find out what is happening: we just have to set a breakpoint at the beginning of
the signal trampoline and run the test program again (do not pay attention to the file names,
gdb
is obviously confused):
(gdb) b *$ra-100
Breakpoint 2 at 0xfadce9c: file iconv_converter.c, line 21556.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /tmp/signal10
Program received signal SIGHUP, Hangup.
0xfa44cc0 in kill () at regcomp.c:575
575 regcomp.c: No such file or directory.
(gdb) c
Continuing.
Breakpoint 2, <signal handler called>
(gdb)
We do break at the beginning of the signal trampoline. The signal trampoline is therefore
executed before the signal handler, it calls the signal handler, and then sigreturn()
.
Another thing to note is that the signal trampoline is not on the user stack. In fact, on IRIX,
gdb
is kind enough to gives us its symbol name when we dump it using x/32i $ra-100: _sigtramp
.
We can check that this symbol is defined in libc
itself:
$ nm -D libc.so.1 | grep sigtramp
0faee444 A _sigtramp
On IRIX, the signal trampoline is therefore provided by the libc
. This is another big difference from NetBSD.
The IRIX signal trampoline is also quite big compared to NetBSD's native one. Copyrights forbid
us to reproduce it, but at least we can tell what it does (and this will probably say more to
most readers). The signal frame is called sf
here, it is as defined earlier.
- The signal trampoline holds extra bytes on the stack (48 on IRIX 6, 24 on IRIX 5) for the signal frame.
- It checks if the higher bit of
A0
is set. By trying with and withoutSA_SIGINFO
here, we discover that this bit is set whenSA_SIGINFO
is set. - If so, it stores
A2
insf.isf_ucp
, andNULL
insf.isf_scp
- If not, stores
A2
insf.isf_scp
andsf.isf_ucp
. - Finds the address of
errno
, and stores it insf.isf_uep
(IRIX 6 only). This is done by looking up the Global Offset Table and assuming that theerrnoaddr
symbol is at a fixed offset from the signal trampoline. - Invokes the signal handler
- Sets
errno
usingsf.isf_uep
andsf.isf_errno
(IRIX 6 only) - Calls
sigreturn(sf.isf_scp, sf.isf_ucp, sf.isf_signo)
on IRIX 6 andsigreturn(sf.isf_scp, sf.isf_ucp)
on IRIX 5.
This is where we discover that sf.isf_errno
holds the errno
value. We
also discover that the kernel transmits the information that we use a struct
irix_ucontext
instead of a struct irix_sigcontext
by setting the higher
bit of A0
on signal handler invocation.
The function irix_sigreturn
can discover if the context is to be restored from a
struct irix_ucontext
instead of a struct irix_sigcontext
when sf.isf_scp
is NULL. sf.isf_ucp
then points to the struct irix_ucontext
.
Now that we know everything about the signal trampoline. What should we do with it? Well, just use it. In sys/compat/irix/irix_signal.c:irix_sendsig() instead of returning to the signal handler, with the return address set to a signal trampoline on the stack (which is a NetBSD flavor signal delivery), we can return to the signal trampoline and let it do all the work for us. This behavior is much closer to the way signals are handled on IRIX.
The only problem is that the kernel does not have the signal trampoline location. The signal
trampoline is in a userland program, and we do not have any simple way of digging it out. It is
not possible to assume that we can find it at a hard coded place in user memory, since it will
change with libc
versions.
To learn the signal trampoline address, we can run a shell command such as:
$ nm -D libc.so.1 |grep _sigtramp
0faee444 A _sigtramp
If a shell command can do it, the kernel can do it. A first idea could be to find the address
the same way we do it from the shell: by looking up the symbol table. This means loading the
ELF
section containing the symbol table, then walking through the table looking for our symbol,
et voila.
This method could work, but it is still extremely weak. First, looking up the symbol is time-consuming. Second, it means importing into the kernel a lot of user level code, thus increasing kernel size and increasing the odds of creating kernel bugs. Third, it is not trivial to find The Right Place for this symbol check in the kernel sources. Doing it that way would be a nasty hack.
