| 关键词: frame unsigned 应用层 信号 堆栈 regs 函数 long struct 信号处理 |
好久没有为组织做点贡献了,真有点过意不去:( 本文着重点在内核信号处理对应用层堆栈的影响上,其他的一些在处理信号细节上被忽略。至于本文是否跟安全相关,那就是仁者见仁智者见智了。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 voidhandle_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);elsesetup_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 ? 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 stubalready 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_SIGprintk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",current->comm, current->pid, frame, regs->eip, frame->pretcode);#endifreturn;give_sigsegv:if (sig == SIGSEGV)ka->sa.sa_handler = SIG_DFL;force_sig(SIGSEGV, current);}到此,内核在应用层的堆栈上就安排了一个帧,我们来看一下一个实际的例子。[alert7@redhat73 sigal]$ cat test.ctest (){printf("test");return;}int main(int argv,char **argc) {char buf[256];signal(10,test);while(1);}[alert7@redhat73](gdb) b mainBreakpoint 1 at 0x8048501(gdb) r dd ddStarting program: /home/alert7/sigal/test dd ddBreakpoint 1, 0x08048501 in main ()(gdb) Breakpoint 2 at 0x42029098(gdb) cContinuing.(gdb) x/5i 0x420290980x42029098 : pop %eax0x42029099 : mov $0x77,%eax0x4202909e : int $0x800x420290a0 : mov (%esp,1),%ebx0x420290a3 : ret(gdb) i reg esp ebp eipesp 0xbffff748 0xbffff748ebp 0xbffffb38 0xbffffb38eip 0x4202909e 0x4202909e(gdb) x/50x $esp-8 //$esp-8就是内核构造的一个信号帧0xbffff740: 0x42029098 0x0000000a 0x00000000 0x000000000xbffff750: 0x0000002b 0x0000002b 0xbffffba4 0x400130200xbffff760: 0xbffffb38 0xbffffa20 0x4213030c 0xbffffc000xbffff770: 0x08049752 0xbffffb2c 0x00000001 0x000000000xbffff780: 0x08048570 0x00000023 0x00000346 0xbffffa200xbffff790: 0x0000002b 0x00000000 0x00000000 0x000000000xbffff7a0: 0x4000083e 0x400005b8 0x40000218 0x400131e80xbffff7b0: 0x00000003 0x40013e48 0x00000003 0x42009e380xbffff7c0: 0x40013d68 0x0d1fc7ae 0x0d1fc7ae 0xbffff8900xbffff7d0: 0x40013bc8 0x4200f624 0x00000000 0x000000000xbffff7e0: 0x42009e38 0x40013bc8 0x00000000 0x000000000xbffff7f0: 0x00000000 0x00000000 0x00000000 0x000000000xbffff800: 0x00000000 0x00000000struct sigframe{char *pretcode; //这里为0x42029098,在该程序中,ka->sa.sa_flags 有 SA_RESTORER标志,//所以没有在堆栈中安排指令,而是使用了一个现成的地址int sig; //信号为10struct sigcontext sc;struct _fpstate fpstate;unsigned long extramask[_NSIG_WORDS-1];char retcode[8];};struct sigcontext {unsigned short gs, __gsh;//0,0unsigned short fs, __fsh;//0,0unsigned short es, __esh;//0x2b,0unsigned short ds, __dsh;//0x2b,0unsigned long edi; //0xbffffba4unsigned long esi; //0x40013020unsigned long ebp; //0xbffffb38unsigned long esp; //0xbffffa20unsigned long ebx; //0x4213030cunsigned long edx; //0xbffffc00unsigned long ecx; //0x08049752unsigned long eax; //0xbffffb2cunsigned long trapno; //0x00000001unsigned long err; //0x00000000unsigned long eip; //0x08048570unsigned short cs, __csh; //0x23,0unsigned long eflags; //0x00000346unsigned long esp_at_signal; //0xbffffa20unsigned short ss, __ssh; //0x2b,0struct _fpstate * fpstate; //0x00000000unsigned long oldmask; //0x00000000unsigned long cr2; //0x00000000};内核在应用层的堆栈上安了一个帧后,当一返回到应用态的时候就跳到信号处理函数test去执行了。此时图一 ①,应用层的堆栈多了一个帧,如下:**********************************************************************************图一(内存高址)+--------------------------------------+| ... | +--------------------------------------+| char retcode[8] | 8个字节 +--------------------------------------+| long extramask[_NSIG_WORDS-1]; | +--------------------------------------+| struct _fpstate fpstate; | +--------------------------------------+| struct sigcontext sc; | +--------------------------------------+ | int sig; |+--------------------------------------+ | char *pretcode; |+--------------------------------------+ | ... | +--------------------------------------+(内存低址)**********************************************************************************由于内核是让应用程序跳到信号处理函数的,所以不象一般的调用会把当前的EIP压入堆栈,所以现在esp指向的pretcode的值将来信号处理完就返回到那里去了。此时ESP情况如图一 ② 的情况当test信号处理函数完成时候,将返回到frame->pretcode也就是0x42029098的地址去执行,在这里0x42029098地址代码如下:0x42029098 : pop %eax //弹出frame->sig,这里为100x42029099 : mov $0x77,%eax0x4202909e : 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(¤t->sigmask_lock);current->blocked = set;recalc_sigpending(current);spin_unlock_irq(¤t->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 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|