X

An Oracle blog about Solaris

How does my signal handler get to run

Chris Beal
Senior Principal Software Engineer

With
OpenSolaris a reality, I can talk about some of the areas I've had to trouble shoot in the past. I started trying to write blogs and soon found I needed to reference the source code to illustrate the point. This is one area I've had to look at many times when tracking down problems. I've pieced together my understanding from reading the source code, however while writing this blog entry I've used a simple program and some "D" to
illustrate what's going on.





Here's the simple program to copy some memory around, receive an alarm signal
and run a signal handler.

$  cat alarm.c
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void alarm_handler();
main()
{
char \*src, \*dest;
sigset_t set;
struct sigaction sigact;
sigact.sa_handler = alarm_handler;
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask, SIGALRM);
sigact.sa_flags = SA_SIGINFO;
sigaction(SIGALRM,&sigact,NULL);
src = malloc (4096);
dest = malloc (4096);
memset(src, '@', 4096);
alarm(4);
while(1) {
memcpy (src, dest, 4096);
}
}
void alarm_handler()
{
char txt[]="In alarm handler";
write (1, txt, sizeof(txt));
exit(0);
}

memset and memcpy are implemented by using the block load and store functions which use the floating point registers. This was used to emulate a customer's problem.

We know that the signal is actually delivered by the kernel when it is safe to do so, which is usually when you transition from kernel to userland or back (ie entering or leaving a system call). The act of sending a signal simply marks the t_astflag in the thread structure for trap to process later, so we can write a little D to trace anything from trap_cleanup (where the astflag is checked). We also need to check whether we should be checking for a signal at this time (this was new to me as well)

$ cat trap.d
#!/usr/sbin/dtrace -Fs
fbt::trap_cleanup:entry
/
curthread->_tu._ts._t_astflag == 1 &&
curthread->_tu._ts._t_sig_check == 1 &&
execname == "alarm"
/
{
trace (timestamp);
self->trace = 1;
}
fbt::trap_cleanup:return
/
self->trace == 1
/
{
trace (timestamp);
self->trace = 0;
}
fbt:::entry
/
self->trace == 1
/
{
trace (timestamp);
}
fbt:::return
/
self->trace == 1
/
{
trace (timestamp);
}

So we run these two together so we can see what happens. I'll annotate this in-line

$ trap.d
dtrace: script 'trap.d' matched 46375 probes
CPU FUNCTION
1 -> trap_cleanup 94182830223096
1 | trap_cleanup:entry 94182830229763
1 -> sigcheck 94182830231096
1 <- sigcheck 94182830232763

sigcheck is actually part of the ISSIG_PENDING macro which is used to establish if there is a signal there and if we are allowed to do anything with it.

1 -> issig 94182830234346
1 <- issig 94182830235763
1 -> issig_forreal 94182830237763
1 -> schedctl_finish_sigblock 94182830239763
1 <- schedctl_finish_sigblock 94182830241263
1 -> fsig 94182830244096
1 -> sigdiffset 94182830246013
1 <- sigdiffset 94182830247346
1 <- fsig 94182830249263
1 -> fsig 94182830250263
1 -> sigdiffset 94182830251096
1 <- sigdiffset 94182830251930
1 -> lowbit 94182830253430
1 <- lowbit 94182830254513
1 <- fsig 94182830255263
1 -> sigdeq 94182830257513
1 <- sigdeq 94182830259430
1 -> isjobstop 94182830261013
1 <- isjobstop 94182830262430
1 -> undo_watch_step 94182830264346
1 <- undo_watch_step 94182830266096
1 <- issig_forreal 94182830267180

All of this is issig_forreal() a function which extracts the next pending signal and puts it in to p_cursig. We then call psig ()

1 -> psig 94182830268763
1 -> schedctl_finish_sigblock 94182830269680
1 <- schedctl_finish_sigblock 94182830270596

Blocks all signals if requested by the userlevel code setting tdp->sc_shared->sc_sigblock

1 -> sigorset 94182830273596
1 <- sigorset 94182830275013

sendsig() is platform dependant. This is for the sparc version so we go in to sendsig32()

1 -> sendsig32 94182830277430
1 -> flush_user_windows_to_stack 94182830279263
1 <- flush_user_windows_to_stack 94182830280346

Make sure the stack has all been flushed to the stack so we can safely change the stack pointer.
We're going to have to contruct a stack frame for our signal handler so the current registers have to have somewhere to go on the stack. So we need to know how big a stack frame is, we do this by incrementing minstacksz. First of all the floating point registers if the fpu is enabled then the general registers, then the extra register set.

1 -> xregs_getsize 94182830282596
1 <- xregs_getsize 94182830283930

Watch point handling

1 -> watch_disable_addr 94182830285846
1 -> avl_numnodes 94182830287096
1 <- avl_numnodes 94182830288263
1 <- watch_disable_addr 94182830289346

From here we're copying the context (all the register information) in to the area on the stack we've just set up.

1 -> kmem_alloc 94182830290513
1 -> kmem_cache_alloc 94182830291846
1 <- kmem_cache_alloc 94182830293430
1 <- kmem_alloc 94182830294680
1 -> savecontext32 94182830296430
1 -> flush_user_windows_to_stack 94182830298680
1 <- flush_user_windows_to_stack 94182830299513
1 -> getgregs32 94182830301763
1 -> mkpsr 94182830304680
1 <- mkpsr 94182830306096
1 <- getgregs32 94182830307596
1 -> getfpregs 94182830309430
1 -> lwp_getdatamodel 94182830310513
1 <- lwp_getdatamodel 94182830311430
1 <- getfpregs 94182830315096
1 -> fpuregset_nto32 94182830316513
1 <- fpuregset_nto32 94182830318680
1 <- savecontext32 94182830320013
1 -> xregs_setptr32 94182830321846
1 <- xregs_setptr32 94182830323013
1 -> kmem_alloc 94182830324013
1 -> kmem_cache_alloc 94182830325096
1 <- kmem_cache_alloc 94182830326263
1 <- kmem_alloc 94182830327180
1 -> xregs_get 94182830328846
1 -> xregs_getgregs 94182830331263
1 <- xregs_getgregs 94182830333013
1 -> xregs_getgfiller 94182830334763
1 <- xregs_getgfiller 94182830336180
1 <- xregs_get 94182830337346
1 -> xregs_getfpregs 94182830339346
1 -> xregs_getfpfiller 94182830341513
1 <- xregs_getfpfiller 94182830343346
1 <- xregs_getfpregs 94182830344846
1 -> kmem_free 94182830347013
1 <- kmem_free 94182830347930
1 -> kmem_cache_free 94182830348930
1 <- kmem_cache_free 94182830349846
1 -> kmem_free 94182830352263
1 <- kmem_free 94182830353013
1 -> kmem_cache_free 94182830353930
1 <- kmem_cache_free 94182830354846
1 -> watch_disable_addr 94182830356513
1 -> avl_numnodes 94182830357430
1 <- avl_numnodes 94182830358430
1 <- watch_disable_addr 94182830359263
1 -> ucopy 94182830361013
1 <- ucopy 94182830362680
1 <- sendsig32 94182830364680

Interestingly we see nothing in the dtrace output for the crucial bit of the code that sets the registers up to run the signal handler. This is because it is simply done by manipulating the register within sendsig32. Here is a snippit of the code that does this

1451 /\*
1452 \* Set up user registers for execution of signal handler.
1453 \*/
1454 rp->r_sp = (uintptr_t)fp;
1455 rp->r_pc = (uintptr_t)hdlr;
1456 rp->r_npc = (uintptr_t)hdlr + 4;
1457 /\* make sure %asi is ASI_PNF \*/
1458 rp->r_tstate &= ~((uint64_t)TSTATE_ASI_MASK << TSTATE_ASI_SHIFT);
1459 rp->r_tstate |= ((uint64_t)ASI_PNF << TSTATE_ASI_SHIFT);
1460 rp->r_o0 = sig;
1461 rp->r_o1 = (uintptr_t)sip_addr;
1462 rp->r_o2 = (uintptr_t)&fp->uc;
1 <- psig 94182830366263
1 <- trap_cleanup 94182830368013

So once we return to userland the registers we're using are the ones set at the end of sendsig32. As a result we start running in the signal handler.
Simple really. With dtrace it is very simple to see the flow of code through a function. Combined with the opensolaris source browser you can quickly learn about new areas. Essential when tracking down bugs and customer problems.


Technorati Tag:


Technorati Tag:


Technorati Tag:

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.