Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

signal trampoline code

560 views
Skip to first unread message

Michael Schmitz

unread,
Oct 9, 1991, 12:45:04 PM10/9/91
to
I have noticed that in the OSF MIPS and Multimax signal delivery
code that the signal trapoline code is not in the u area, but in
the C library. When signal (or sigaction) is called, the address
of the library's trampoline code is passed as another system call
argument which is saved by the kernel. Things are pretty much as
in BSD -- the user's PC is set to this address to catch the signal.

The Question: Why does BSD (for the VAX) put the trampoline code
in the u area? That is machine dependent and obviously unnecessary.

--Michael--

Chris Torek

unread,
Oct 30, 1991, 10:28:09 PM10/30/91
to
In article <18...@rust.zso.dec.com> sch...@rust.zso.dec.com

It is true that it is machine dependent and unnecessary. There is
still a good reason for it, though.

There are two ways to save the user's process context during signal
delivery, and restore it afterward: a) have the kernel do it; b) have
the user process do it. The advantage of approach (a) is that there
need be no special user code. The disadvantage is that, since the
kernel is privileged, it tends to be difficult to do (a) correctly.
The difficulty level is machine-dependent, but has always been high
enough to favor approach (b).

The code a user process must use to save and restore its own context
generally looks like this:

- save some machine registers on the correct stack;
- call the signal handler with several parameters;
- reload the registers from the stack;
- call the `sigreturn' system call.

The sigreturn system call also takes some parameters, including the
address(es) to which to return and any registers that could not be
saved and restored in user code because they are needed for the
sigreturn call itself. On the VAX, for instance, the sigreturn system
call uses the current stack pointer, so the original stack pointer must
be in the arguments to sigreturn. These parameters must clearly be set
up by the kernel. If they are stored in memory (and they are), the
kernel thus needs to know where to store them, i.e., the address of the
`signal stack' and a flag to tell whether the process is already on
that stack. (Normally, signals are handled on the single `normal'
stack, but this is a burden to some runtime systems, so 4BSD provides
an alternative signal stack, rather like the VAX's hardware interrupt
stack. Since this is done in software, it is not machine-dependent,
beyond the assumption that one or more stacks exist.) The correct
stack is thus normally set up by the kernel. Thus, the approach
actually used is a mix between (a) and (b): the kernel sets up a
minimal amount of state, then `bounces off' the user `trampoline' code,
so that the signal handlers themselves can be ordinary C functions.

In order to reach the user's signal trampoline code, the kernel must
know where it resides. If the address is fixed by the kernel, this is
easy. This is the approach taken on the VAX: the trampoline code is in
the u. area, which is readable and executable by user code, and which
has a fixed address. Thus, the kernel holds the address of the actual
user signal handler for each signal, and when delivering a signal,
calls this `sigtramp' code, passing the address of the user function
and the arguments to hand to it. In effect, we have:

/* in u. area: */
dead void sigtramp(void (*f)(int, int, struct sigcontext *),
int sig, int code, struct sigcontext *scp) {
(*f)(sig, code, scp);
sigreturn(scp);
/* if we get here, the user broke *scp; kill the process */
asm("halt");
/* NOTREACHED */
}

(the BSD VAX kernel actually uses a `callg' instruction to call the
function, saving a few stack operations).

Now suppose that we do not have a readable u. area, as is the case on
the BSD SPARC kernel. Here we can use the approach described for the
OSF MIPS and Multimax kernels. Calls to signal() (actually sigaction)
pass to the kernel the address of the sigtramp() code, rather than the
address of the user's handler. That is, sigaction is implemented
something like:

/* libc sigaction(): massage the parameters and call the
real kernel sigaction(). */
int sigaction(int sig, struct sigaction *act, struct sigaction *oact) {
int ret;
struct sigaction realaction, oldrealaction;
extern void __sigtramp(<some arguments>);

realaction.sa_handler = __sigtramp;
<set up the rest of realaction based on *act>;
ret = __kernel_sigaction(sig, &realaction, &oldrealaction);
<check for errors>;
<translate __sigtramp to user functions for returns>;
return (ret);
}

where __sigtramp() is similar to the one found in the u. area on the VAX.
Now we have a problem: somehow, we have to go from the call to sigtramp()
to the actual user's function. The kernel no longer has this information;
the kernel has only the address of __sigtramp().

There are two ways to handle this. We can either change the kernel
interface to provide the `sigtramp address' and the `handler address',
or we can do what SunOS does: keep a table in the user process. That
is, in sigaction() above, before calling __kernel_sigaction, we have
to record the user's handler in a table per signal, something like
(as in SunOS):

typedef void (*sig_handler)(int sig, int code,
struct sigcontext *scp, char *addr);

(Note that SunOS has here added an additional parameter beyond the
three found in 4BSD: a mistake; it should be in the sigcontext, as
should the code have been originally. The sigcontext structure should
have been defined as the interface in both directions, rather than
just in the return direction, as both parts are inherently machine
dependent and it is wise to encapsulate machine dependencies.)

Unfortunately, if we do what SunOS does, we have a new problem. The
signal could be delivered between the time we change the table and the
time we call __kernel_sigaction. If, for instance, the old handler ran
on the regular stack, but the new one runs on the signal stack, we will
then call the new handler on the regular stack. The SunOS solution to
this problem is to use sigblock and sigsetmask to block delivery while
the status is changing. This, unfortunately, requires three system
calls per change. (SunOS actually makes four system calls, for, as far
as I know, no good reason.) That is, we must write:

#ifdef __GNUC__
#define dead volatile
#else
#define dead /*empty*/
#endif
extern dead void sigtramp(int, int, struct sigcontext *, char *);
typedef void (*sig_handler)(int, int, struct sigcontext *, char *);
sig_handler __sigtable[NSIG];

/* libc sigaction(), probably correct, but untested */
int sigaction(int sig, struct sigaction *act, struct sigaction *oact) {
int ret, saverr;
sigmask_t omask;
sig_handler ofun;
struct sigaction ra;

/* verify arguments, as much as possible */
if (sig < 1 || sig >= NSIG) {
errno = EINVAL;
return (-1);
}
/*
* If we are going to ignore or default the signal, we
* do not need to change the table. A series of calls
* that set SIG_IGN, followed by nothing further ever,
* is a frequent special case.
*
* If the previous catcher is __sigtramp, the table contents
* must be valid (by definition).
*/
if (act->sa_handler == SIG_DFL || act->sa_handler == SIG_IGN) {
ret = __kernel_sigaction(sig, act, oact);
if (ret >= 0 && oact->sa_handler == __sigtramp)
oact->sa_handler = __sigtable[sig];
return (ret);
}
/*
* We are going to set a real catcher, so we must block
* occurrences of this signal while we change the table.
* Set ra.ra_mask and ra.ra_flags before blocking, in case
* either access faults.
*/
ra.ra_mask = act->sa_mask;
ra.ra_flags = act->sa_flags;
omask = sigblock(sigmask(sig));
ofun = __sigtable[sig];
__sigtable[sig] = act->sa_handler;
ra.ra_handler = __sigtramp;
ret = __kernel_sigaction(sig, &ra, oact);
if (ret >= 0) {
(void) sigsetmask(omask);
if (oact->sa_handler == __sigtramp)
oact->sa_handler = ofun;
return (ret);
}
/*
* The change failed: restore the table, unblock, and return.
*/
saverr = errno;
__sigtable[sig] = ofun;
(void) sigsetmask(omask);
errno = saverr;
return (ret);
}

This argues for one of three things:

a) changing the real kernel interface to include the function;
b) adding a call to tell the kernel where __sigtramp is, and otherwise
leaving the kernel interface alone (the C startup would make the new
system call before calling main());
c) `hiding' sigtramp at a known address, in some way.

In my BSD SPARC kernel, I took approach (c) from the HP-BSD kernel: in
exec(), we build the trampoline code on the user's stack, above the
argv and evironment strings. Unfortunately, for SunOS compatibility I
had to add kludges to otherwise machine-independent parts of the kernel.

Which, if any, of these various solutions have the OSF people chosen?
--
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
Berkeley, CA Domain: to...@ee.lbl.gov

Bakul Shah

unread,
Nov 1, 1991, 2:45:52 AM11/1/91
to
>In article <18...@rust.zso.dec.com> sch...@rust.zso.dec.com
>(Michael Schmitz) writes:
> ...

>>The Question: Why does BSD (for the VAX) put the trampoline code
>>in the u area? That is machine dependent and obviously unnecessary.

Chris Torek <to...@horse.ee.lbl.gov> responds:

>It is true that it is machine dependent and unnecessary. There is
>still a good reason for it, though.

[following it up with a very nice explanation]

>This argues for one of three things:

> a) changing the real kernel interface to include the function;
> b) adding a call to tell the kernel where __sigtramp is, and otherwise
> leaving the kernel interface alone (the C startup would make the new
> system call before calling main());
> c) `hiding' sigtramp at a known address, in some way.

>In my BSD SPARC kernel, I took approach (c) from the HP-BSD kernel: in
>exec(), we build the trampoline code on the user's stack, above the
>argv and evironment strings. Unfortunately, for SunOS compatibility I
>had to add kludges to otherwise machine-independent parts of the kernel.

We used approach b) in the BSD 29K kernel (that was also how we
supplied the kernel with register spill/fill handler addresses).
The hardest part was dealing with a potentially inconsistent
register stack in _sigtramp() (actually, sigcode). Once we dealt
with that the rest was just like _sigtramp() on the VAX. The
kernel sigaction() (actually, sendsig -- this was 4.3 BSD) was changed
to block out all catchable signals before ``returning'' to the user
mode sigcode and the first thing sigcode did after setting up C
language context was to unblock signals specifically blocked for
this purpose. This was done to avoid having to handle another
signal delivery before C context was setup.

If I were to do this all over again, I would consider using a
separate thread for handling signals. The only mess is then how
to deal with a longjmp out of a signal handler. Ugh!

-- Bakul Shah
b...@BitBlocks.COM or ..!uunet!amdcad!light!bvs

Neil Webber

unread,
Nov 1, 1991, 5:00:22 PM11/1/91
to
In article <18...@dog.ee.lbl.gov> to...@horse.ee.lbl.gov (Chris Torek) writes:
> c) `hiding' sigtramp at a known address, in some way.
>
>In my BSD SPARC kernel, I took approach (c) from the HP-BSD kernel: in
>exec(), we build the trampoline code on the user's stack, above the
>argv and evironment strings.

An easier version of (c) is to have one page of the kernel text
segment be executable by user code, and put the signal trampoline
code in there. Of course, this only works if your MMU, cache,
and VM architecture allow it.

I did this on the Epoch-1, which is a 68020/68851 box. The
first page of the kernel address space contains the boot-time and
early startup code. I put the trampoline code at the beginning
of this page and set the page to have both user execute (read-only)
and kernel execute (read-only) permission. That gave me trampoline
code at an address known to the kernel.

--
Neil Webber / Epoch Systems Inc., Westboro MA / (508) 836-4711, x309
n...@epoch.com, uunet!epochsys!nw
--
Neil Webber / Epoch Systems Inc., Westboro MA / (508) 836-4711, x309
n...@epoch.com, uunet!epochsys!nw

0 new messages