setup_sigcontext()代码如下:
static int
setup_sigcontext(struct sigcontext __user *sc, struct _fpstate __user *fpstate,
struct pt_regs *regs, unsigned long mask)
{
int tmp, err = 0;
//保存regs
err |= __put_user(regs->xfs, (unsigned int __user *)&sc->fs);
savesegment(gs, tmp);
err |= __put_user(tmp, (unsigned int __user *)&sc->gs);
err |= __put_user(regs->xes, (unsigned int __user *)&sc->es);
err |= __put_user(regs->xds, (unsigned int __user *)&sc->ds);
err |= __put_user(regs->edi, &sc->edi);
err |= __put_user(regs->esi, &sc->esi);
err |= __put_user(regs->ebp, &sc->ebp);
err |= __put_user(regs->esp, &sc->esp);
err |= __put_user(regs->ebx, &sc->ebx);
err |= __put_user(regs->edx, &sc->edx);
err |= __put_user(regs->ecx, &sc->ecx);
err |= __put_user(regs->eax, &sc->eax);
err |= __put_user(current->thread.trap_no, &sc->trapno);
err |= __put_user(current->thread.error_code, &sc->err);
err |= __put_user(regs->eip, &sc->eip);
err |= __put_user(regs->xcs, (unsigned int __user *)&sc->cs);
err |= __put_user(regs->eflags, &sc->eflags);
err |= __put_user(regs->esp, &sc->esp_at_signal);
err |= __put_user(regs->xss, (unsigned int __user *)&sc->ss);
//保存FPU,XMM.MXX等信息
tmp = save_i387(fpstate);
if (tmp < 0)
err = 1;
else
err |= __put_user(tmp ? fpstate : NULL, &sc->fpstate);
/* non-iBCS2 extensions.. */
//mask:即为set->sig 的低32位
err |= __put_user(mask, &sc->oldmask);
err |= __put_user(current->thread.cr2, &sc->cr2);
return err;
}
用下图表示上述的操作:
注意到代码中有以下两条指令:
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;
第一条把用户的栈指令指向了frame
第二条把返回用户空间的eip设为了信号的处理函数.
这样返回到用户空间后就会执行ka->sa.sa_handler这个函数.注意到上面的堆栈结构,其实它模拟了一次函数调用.函数调用时,先把参数压栈,再把返回地址压栈.在上面的栈中,函数的参数为sig.返回地址为pretcode.这样,在信号处理函数返回之后.就会把pretcode装入eip.而pretcode又是指向retcode.也就是说函数返回之后,会运行retcode对应的指令.
Retcode在上面的代码中是这样被设置的:
err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));
代码中的0xb858 0x80cd可能对应的就是指令的机器码.它相应于如下指令:
popl %eax ;
movl $,%eax ;
int $0x80
即会产生一个系统调用号为__NR_sigreturn的系统调用.它对应的入口是:
asmlinkage int sys_sigreturn(unsigned long __unused)
{
//第一个参数地址就是栈指针位置
struct pt_regs *regs = (struct pt_regs *) &__unused;
//esp-8是因为在用户空间运行的时候,栈出了两个单元
//即上图中的pretcode出栈.sig出栈
struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
sigset_t set;
int eax;
//检查对应区域是否可读
if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
goto badframe;
//从frame->sc.oldmask 恢复set.sig的低32 位
if (__get_user(set.sig[0], &frame->sc.oldmask)
|| (_NSIG_WORDS > 1
//从frame->extramask 中恢复set.sig的高32位
&& __copy_from_user(&set.sig[1], &frame->extramask,
sizeof(frame->extramask))))
goto badframe;
sigdelsetmask(&set, ~_BLOCKABLE);
spin_lock_irq(¤t->sighand->siglock);
current->blocked = set;
//重新判断是否还有末处理的信号
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
//从frame->sc中恢复系统调用前的硬件环境
if (restore_sigcontext(regs, &frame->sc, &eax))
goto badframe;
return eax;
badframe:
if (show_unhandled_signals && printk_ratelimit())
printk("%s%s[%d] bad frame in sigreturn frame:%p eip:%lx"
" esp:%lx oeax:%lx\n",
task_pid_nr(current) > 1 ? KERN_INFO : KERN_EMERG,
current->comm, task_pid_nr(current), frame, regs->eip,
regs->esp, regs->orig_eax);
force_sig(SIGSEGV, current);
return 0;
}
至此,内核栈又回复到以前的样子了.
五:小结
本节中,在Linux内核中跟踪了信号处理函数的设置,信号的发送.信号的处理.涉及到的代码都不是很难理解.在理解了用户自定义的信号函数的运行机制之后,我们也很容易调用用户空间的一个特定操作.另外,虽然内核涉及到的信号处理比较简单,但要在用户空间使用好信号就要看一个人的程序设计功底了. |