内核处理信号对应用层堆栈的影响

by alert7 < alert7@xfocus.org >
主页:   http://www.xfocus.org/  http://www.whitecell.org/  
时间:2003年8月1日
好久没有为组织做点贡献了,真有点过意不去:(
本文着重点在内核信号处理对应用层堆栈的影响上,其他的一些在处理信号细节上被忽略。
至于本文是否跟安全相关,那就是仁者见仁智者见智了。
1 发送信号过程:
发送信号的过程比接收信号的过程简单的多。当应用层用KILL命令向某个进程发送进程的时候,
内核只在进程task_struct的sigpending结构中安排一个信号位。
2 接收信号过程
信号处理的时机。
当某个进程有悬而未决的信号的时候,内核就会调用do_signal函数
do_signal做一些其他功能上的事情,真正递送一个信号是在handle_signal函数。于是在最后
do_signal函数调用了handle_signal真正递送一个信号。当然要想到达这一步需要一些条件。比如说
应用层已经声明要处理该信号,信号不是些不可捕获的信号等等...
重点中的重点,我们来看看handle_signal函数
/*
* OK, we're invoking a handler
*/    
static void
handle_signal(unsigned long sig, struct k_sigaction *ka,
          siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)
{
....
    /* Set up the stack frame */
    if (ka->sa.sa_flags & SA_SIGINFO)
        setup_rt_frame(sig, ka, info, oldset, regs);
    else
        setup_frame(sig, ka, oldset, regs);
.....
}
去掉一些我们不想关心的东西,代码就剩下上面这些。
以上函数setup_rt_frame和setup_frame就是内核在应用层的堆栈上安排信号堆栈帧的过程,就是我们所
要关注的。setup_rt_frame和setup_frame雷同,我们就来分析下setup_frame函数。
static void setup_frame(int sig, struct k_sigaction *ka,
            sigset_t *set, struct pt_regs * regs)
{
    struct sigframe *frame;
    int err = 0;
    frame = get_sigframe(ka, regs, sizeof(*frame)); //决定要使用应用层堆栈的地址
    if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) //判断是否可写
        goto give_sigsegv;
    err |= __put_user((current->exec_domain
                   && current->exec_domain->signal_invmap
                   && sig < 32
                   ? current->exec_domain->signal_invmap[sig]
                   : sig),
                  &frame->sig);
    if (err)
        goto give_sigsegv;
    
    /*保存寄存器信号到&frame->sc和&frame->fpstate中*/
    err |= setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
    if (err)
        goto give_sigsegv;
    if (_NSIG_WORDS > 1) {
        err |= __copy_to_user(frame->extramask, &set->sig[1],
                      sizeof(frame->extramask));
    }
    if (err)
        goto give_sigsegv;
    /* Set up to return from userspace.  If provided, use a stub
       already in userspace.  */
    if (ka->sa.sa_flags & SA_RESTORER) {
        err |= __put_user(ka->sa.sa_restorer, &frame->pretcode);
    } else {
        /*把frame->retcod的地址放到&frame->pretcode中,这样当信号处理函数返回时候就会*/
        /*跳到frame->retcode地址去执行代码了*/
        err |= __put_user(frame->retcode, &frame->pretcode);
        /* This is popl %eax ; movl $,%eax ; int $0x80 */
        err |= __put_user(0xb858, (short *)(frame->retcode+0));
        err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
        err |= __put_user(0x80cd, (short *)(frame->retcode+6));
        /*以上在frame->retcode上安排了popl %eax ; movl $,%eax ; int $0x80指令*/
    }
    if (err)
        goto give_sigsegv;
    /* Set up registers for signal handler */
    regs->esp = (unsigned long) frame; //让应用层的esp指向frame;
    regs->eip = (unsigned long) ka->sa.sa_handler;//EIP为信号处理函数
    set_fs(USER_DS);
    regs->xds = __USER_DS;
    regs->xes = __USER_DS;
    regs->xss = __USER_DS;
    regs->xcs = __USER_CS;
    regs->eflags &= ~TF_MASK;
#if DEBUG_SIG
    printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",
        current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif
    return;
give_sigsegv:
    if (sig == SIGSEGV)
        ka->sa.sa_handler = SIG_DFL;
    force_sig(SIGSEGV, current);
}
到此,内核在应用层的堆栈上就安排了一个帧,我们来看一下一个实际的例子。
[alert7@redhat73 sigal]$ cat test.c
test ()
        {
        printf("test");
return;
        }
int main(int argv,char **argc) {
         char buf[256];
        signal(10,test);
        while(1);
}
[alert7@redhat73]
(gdb) b main
Breakpoint 1 at 0x8048501
(gdb) r dd dd
Starting program: /home/alert7/sigal/test dd dd
Breakpoint 1, 0x08048501 in main ()
(gdb)  
Breakpoint 2 at 0x42029098
(gdb) c
Continuing.
(gdb) x/5i 0x42029098
0x42029098 <__restore>: pop    %eax
0x42029099 <__restore+1>:       mov    $0x77,%eax
0x4202909e <__restore+6>:       int    $0x80
0x420290a0 <__restore+8>:       mov    (%esp,1),%ebx
0x420290a3 <__restore+11>:      ret
(gdb) i reg esp ebp eip
esp            0xbffff748       0xbffff748
ebp            0xbffffb38       0xbffffb38
eip            0x4202909e       0x4202909e
(gdb) x/50x $esp-8 //$esp-8就是内核构造的一个信号帧
0xbffff740:     0x42029098      0x0000000a      0x00000000      0x00000000
0xbffff750:     0x0000002b      0x0000002b      0xbffffba4      0x40013020
0xbffff760:     0xbffffb38      0xbffffa20      0x4213030c      0xbffffc00
0xbffff770:     0x08049752      0xbffffb2c      0x00000001      0x00000000
0xbffff780:     0x08048570      0x00000023      0x00000346      0xbffffa20
0xbffff790:     0x0000002b      0x00000000      0x00000000      0x00000000
0xbffff7a0:     0x4000083e      0x400005b8      0x40000218      0x400131e8
0xbffff7b0:     0x00000003      0x40013e48      0x00000003      0x42009e38
0xbffff7c0:     0x40013d68      0x0d1fc7ae      0x0d1fc7ae      0xbffff890
0xbffff7d0:     0x40013bc8      0x4200f624      0x00000000      0x00000000
0xbffff7e0:     0x42009e38      0x40013bc8      0x00000000      0x00000000
0xbffff7f0:     0x00000000      0x00000000      0x00000000      0x00000000
0xbffff800:     0x00000000      0x00000000
struct sigframe
{
    char *pretcode;            //这里为0x42029098,在该程序中,ka->sa.sa_flags 有 SA_RESTORER标志,
                    //所以没有在堆栈中安排指令,而是使用了一个现成的地址
    int sig;            //信号为10
    struct sigcontext sc;
    struct _fpstate fpstate;
    unsigned long extramask[_NSIG_WORDS-1];
    char retcode[8];
};
struct sigcontext {
    unsigned short gs, __gsh;//0,0
    unsigned short fs, __fsh;//0,0
    unsigned short es, __esh;//0x2b,0
    unsigned short ds, __dsh;//0x2b,0
    unsigned long edi;        //0xbffffba4
    unsigned long esi;        //0x40013020
    unsigned long ebp;        //0xbffffb38
    unsigned long esp;        //0xbffffa20
    unsigned long ebx;        //0x4213030c
    unsigned long edx;        //0xbffffc00
    unsigned long ecx;        //0x08049752
    unsigned long eax;        //0xbffffb2c
    unsigned long trapno;        //0x00000001
    unsigned long err;        //0x00000000
    unsigned long eip;        //0x08048570
    unsigned short cs, __csh;    //0x23,0
    unsigned long eflags;        //0x00000346
    unsigned long esp_at_signal;    //0xbffffa20
    unsigned short ss, __ssh;    //0x2b,0
    struct _fpstate * fpstate;    //0x00000000
    unsigned long oldmask;        //0x00000000
    unsigned long cr2;        //0x00000000
};
内核在应用层的堆栈上安了一个帧后,当一返回到应用态的时候就跳到信号处理函数test去执行了。
此时图一 ①,应用层的堆栈多了一个帧,如下:
**********************************************************************************
图一
        (内存高址)
        +--------------------------------------+
        | ...                                  |  
        +--------------------------------------+
        | char retcode[8]                      | 8个字节  
        +--------------------------------------+
        | long extramask[_NSIG_WORDS-1];       |  
        +--------------------------------------+
        | struct _fpstate fpstate;           |  
        +--------------------------------------+
        |  struct sigcontext sc;           |  
        +--------------------------------------+  <---------esp指向这里 ③
        | int sig;                   |
        +--------------------------------------+  <---------esp指向这里 ②
        | char *pretcode;               |
        +--------------------------------------+  <---------esp指向这里 ①
        |  ...                       |    
        +--------------------------------------+
        (内存低址)
**********************************************************************************
由于内核是让应用程序跳到信号处理函数的,所以不象一般的调用会把当前的EIP压入堆栈,所以现在
esp指向的pretcode的值将来信号处理完就返回到那里去了。此时ESP情况如图一 ② 的情况
当test信号处理函数完成时候,将返回到frame->pretcode也就是0x42029098的地址去执行,在这里0x42029098地址代码如下:
0x42029098 <__restore>:        pop    %eax    //弹出frame->sig,这里为10
0x42029099 <__restore+1>:       mov    $0x77,%eax
0x4202909e <__restore+6>:       int    $0x80    //请求sys_sigreturn系统调用
当执行完以上三条指令的时候,应用层的堆栈就变成了 ③ 的情况了。
忽略切入内核的细节,sys_sigreturn系统调用被调用。下面是该函数的实现细节。
asmlinkage int sys_sigreturn(unsigned long __unused)
{
    struct pt_regs *regs = (struct pt_regs *) &__unused;
    struct sigframe *frame = (struct sigframe *)(regs->esp - 8);//取得frame地址,-8是为了补上ret和pop
                                                            //这两个指令分别弹出的pretcode和sig
                                    //看看上面的图会更清楚
    sigset_t set;
    int eax;
    if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
        goto badframe;
    if (__get_user(set.sig[0], &frame->sc.oldmask)
        || (_NSIG_WORDS > 1
        && __copy_from_user(&set.sig[1], &frame->extramask,
                    sizeof(frame->extramask))))
        goto badframe;
    sigdelsetmask(&set, ~_BLOCKABLE);
    spin_lock_irq(&current->sigmask_lock);
    current->blocked = set;
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);
    
    /*把frame保存的一些信息恢复出来,修改regs一些寄存器*/    
    if (restore_sigcontext(regs, &frame->sc, &eax))
        goto badframe;
    return eax;
badframe:
    force_sig(SIGSEGV, current);
    return 0;
}    
restore_sigcontext函数好象也没有什么好说的,等到sys_sigreturn函数返回,regs的一些寄存器又恢复到信号来之前的值了。
所以等到内核态在返回到应用态的时候,又恢复到原来的地址去执行了。
参考资料:
    linux 2.4.18 kernel src  
-------the end--------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值