Chinaunix首页 | 论坛 | 博客
  • 博客访问: 710418
  • 博文数量: 31
  • 博客积分: 330
  • 博客等级: 一等列兵
  • 技术积分: 3004
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-05 22:38
个人简介

java开发工程师,专注于内核源码,算法,数据结构。 qq:630501400

文章分类
文章存档

2014年(2)

2013年(22)

2012年(7)

分类: C/C++

2013-02-26 10:17:24

       内核中,信号是进程间通信的一种方式,进程间会kill不同的信号给其他进程,但是如果目标进程标记了trace状态(也就是被跟踪,ptrace attach到了这个进程的pid上),也就是说目标进程处于被调试器跟踪的状态,那么目标进程在处理信号之前,如果调试进程有信号处理函数,那么会把这个信号再次发送给调试进程。同时被调试器跟踪的进程会暂停状态,阻塞在wait操作的调试器进程会被唤醒。也就是说,被跟踪的进程在收到信号的时候,一定会给调试器一个处理的机会。调试器操作完毕后在调用ptrace函数的continue操作,使得被跟踪进程继续执行。

    关于调试的原理,请参考调试断点原理

函数调用关系和逻辑关系:



代码:

1.Kernel/signal.c: get_signal_to_deliver函数


  

  1. signr = dequeue_signal(current, &current->blocked, info); //从信号队列中取出一个信号

  2.     if (!signr)
  3.         break; /* will return 0 */

  4.     if (signr != SIGKILL) {
  5.         signr = ptrace_signal(signr, info, regs, cookie); //如果不是SIGKILL信号就发送到调试器进程,同时激活调试器,停止当前进程
  6.     if (!signr)
  7.         continue;
  8.     }


2.kernel/signal.c: ptrace_signal函数


  1. static int ptrace_signal(int signr, siginfo_t *info,struct pt_regs *regs, void *cookie)
  2. {
  3.     if (!task_ptrace(current)) //如果当前进程没有标志被跟踪,就是说没有进程attach trace到当前进程,就没有后面的通知
  4.         return signr;

  5.     ptrace_signal_deliver(regs, cookie);

  6.     /* Let the debugger run. */
  7.     ptrace_stop(signr, 0, info); //向调试进程发信号,同时当前进程暂停,使调试进程唤醒

  8.     /* We're back. Did the debugger cancel the sig? */
  9.     signr = current->exit_code;
  10.     if (signr == 0)
  11.         return signr;


3.kernel/signal.c:ptrace_stop函数

   

  1. if (current->signal->group_stop_count > 0)      //这里group_stop_count表示如果需要group stop,那么当前线程一定是会stop的,因为当前线程需要被调试器跟踪,当前状态下一定会停止,所以这里会减一
  2.         --current->signal->group_stop_count;

  3.     current->last_siginfo = info;
  4.     current->exit_code = exit_code;

  5.     /* Let the debugger run. */
  6.     __set_current_state(TASK_TRACED);          //标记当前进程状态为trace状态,同时cpu看到这个状态会不去调度当前进程
  7.     spin_unlock_irq(&current->sighand->siglock);
  8.     read_lock(&tasklist_lock);
  9.     if (may_ptrace_stop()) {                    //如果标记了被跟踪
  10.         do_notify_parent_cldstop(current, CLD_TRAPPED); //一定条件下发送信号给调试器进程,同时,被动的唤醒调试器进程
  11.     /*
  12.     * Don't want to allow preemption here, because
  13.     * sys_ptrace() needs this task to be inactive.
  14.     *
  15.     * XXX: implement read_unlock_no_resched().
  16.     */
  17.     preempt_disable();
  18.     read_unlock(&tasklist_lock);
  19.     preempt_enable_no_resched();
  20.     schedule();                                 //放弃调度权

    这里关于group_stop_count的说明和在内核中的作用,参考stop信号对线程组的影响


4.kernel/signal.c: do_notify_parent_cldstop函数

  

  1. static void do_notify_parent_cldstop(struct task_struct *tsk, int why)
  2.     {
  3.     struct siginfo info;
  4.     unsigned long flags;
  5.     struct task_struct *parent;
  6.     struct sighand_struct *sighand;

  7.     if (task_ptrace(tsk)) //前面可以肯定的一定是trace状态
  8.         parent = tsk->parent; //parent代表调试器进程,real_parent是真实的父进程
  9.     else {
  10.         tsk = tsk->group_leader;
  11.         parent = tsk->real_parent;
  12.     }

  13.     info.si_signo = SIGCHLD;
  14.     info.si_errno = 0;
  15.     /*
  16.     * see comment in do_notify_parent() abot the following 3 lines
  17.     */
  18.     rcu_read_lock();
  19.     info.si_pid = task_pid_nr_ns(tsk, parent->nsproxy->pid_ns); //按照pid namespace获得pid
  20.     info.si_uid = __task_cred(tsk)->uid;
  21.     rcu_read_unlock();

  22.     info.si_utime = cputime_to_clock_t(tsk->utime);
  23.     info.si_stime = cputime_to_clock_t(tsk->stime);

  24.     info.si_code = why;
  25.     switch (why) {
  26.         case CLD_CONTINUED: //如果这里获得的信号是SIGCONT
  27.             info.si_status = SIGCONT;
  28.             break;
  29.         case CLD_STOPPED: //如果是group stop,线程组都停止了
  30.             info.si_status = tsk->signal->group_exit_code & 0x7f;
  31.             break;
  32.         case CLD_TRAPPED: //其他的信号
  33.             info.si_status = tsk->exit_code & 0x7f;
  34.             break;
  35.         default:
  36.             BUG();
  37.     }

  38.     sighand = parent->sighand;
  39.     spin_lock_irqsave(&sighand->siglock, flags);
  40.     //如果在用户态设置了信号处理函数并且标志 SA_NOCLDSTOP代表,进程停止时,不发送信号到父进程或者trace进程
  41.     if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN &&!(sighand->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
  42.         __group_send_sig_info(SIGCHLD, &info, parent);
  43.     /*
  44.     * Even if SIGCHLD is not generated, we must wake up wait4 calls.
  45.     */
  46.     __wake_up_parent(tsk, parent); //这里唤醒正在wait操作等待的线程,这里是调试器进程(或者是父进程)
  47.     spin_unlock_irqrestore(&sighand->siglock, flags);
  48. }

     这里pid namespace请参考 pid namespace浅分析pid namespace 浅分析 续,对于这里调试器进程的wait函数和被跟踪进程最终调用_wake_up_parent函数,这里原理我并不了解,后面会分析这两个函数的源代码

总结:

     如果一个进程被跟踪(进程调用ptrace attach 一个进程),同时调试进程设置了信号处理函数,那么这个被跟踪的进程所产生的信号,会被发送到调试进程,但是不管调试进程是否设置了信号处理函数,被跟踪的进程肯定会休眠,唤醒调试进程来调试信息。




阅读(5283) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

djjsindy2013-02-27 16:17:43

Bean_lee:功力很深啊。

多谢彬哥赞美,signal_struct里的group_stop_count,notify_count等,还有那个flag标志位纠结了很久才理解,还有一些完全没搞懂的变量,看懂了,分享给大家

回复 | 举报

Bean_lee2013-02-27 12:48:55

功力很深啊。