How does my signal handler get to run

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:
Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

Chris W Beal

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today