内核中,信号是进程间通信的一种方式,进程间会kill不同的信号给其他进程,但是如果目标进程标记了trace状态(也就是被跟踪,ptrace
attach到了这个进程的pid上),也就是说目标进程处于被调试器跟踪的状态,那么目标进程在处理信号之前,如果调试进程有信号处理函数,那么会把这个信号再次发送给调试进程。同时被调试器跟踪的进程会暂停状态,阻塞在wait操作的调试器进程会被唤醒。也就是说,被跟踪的进程在收到信号的时候,一定会给调试器一个处理的机会。调试器操作完毕后在调用ptrace函数的continue操作,使得被跟踪进程继续执行。
关于调试的原理,请参考调试断点原理
函数调用关系和逻辑关系:
代码:
1.Kernel/signal.c: get_signal_to_deliver函数
-
signr = dequeue_signal(current, ¤t->blocked, info); //从信号队列中取出一个信号
-
-
if (!signr)
-
break; /* will return 0 */
-
-
if (signr != SIGKILL) {
-
signr = ptrace_signal(signr, info, regs, cookie); //如果不是SIGKILL信号就发送到调试器进程,同时激活调试器,停止当前进程
-
if (!signr)
-
continue;
-
}
2.kernel/signal.c: ptrace_signal函数
-
static int ptrace_signal(int signr, siginfo_t *info,struct pt_regs *regs, void *cookie)
-
{
-
if (!task_ptrace(current)) //如果当前进程没有标志被跟踪,就是说没有进程attach trace到当前进程,就没有后面的通知
-
return signr;
-
-
ptrace_signal_deliver(regs, cookie);
-
-
/* Let the debugger run. */
-
ptrace_stop(signr, 0, info); //向调试进程发信号,同时当前进程暂停,使调试进程唤醒
-
-
/* We're back. Did the debugger cancel the sig? */
-
signr = current->exit_code;
-
if (signr == 0)
-
return signr;
3.kernel/signal.c:ptrace_stop函数
-
if (current->signal->group_stop_count > 0) //这里group_stop_count表示如果需要group stop,那么当前线程一定是会stop的,因为当前线程需要被调试器跟踪,当前状态下一定会停止,所以这里会减一
-
--current->signal->group_stop_count;
-
-
current->last_siginfo = info;
-
current->exit_code = exit_code;
-
-
/* Let the debugger run. */
-
__set_current_state(TASK_TRACED); //标记当前进程状态为trace状态,同时cpu看到这个状态会不去调度当前进程
-
spin_unlock_irq(¤t->sighand->siglock);
-
read_lock(&tasklist_lock);
-
if (may_ptrace_stop()) { //如果标记了被跟踪
-
do_notify_parent_cldstop(current, CLD_TRAPPED); //一定条件下发送信号给调试器进程,同时,被动的唤醒调试器进程
-
/*
-
* Don't want to allow preemption here, because
-
* sys_ptrace() needs this task to be inactive.
-
*
-
* XXX: implement read_unlock_no_resched().
-
*/
-
preempt_disable();
-
read_unlock(&tasklist_lock);
-
preempt_enable_no_resched();
-
schedule(); //放弃调度权
这里关于group_stop_count的说明和在内核中的作用,参考stop信号对线程组的影响
4.kernel/signal.c:
do_notify_parent_cldstop函数
-
static void do_notify_parent_cldstop(struct task_struct *tsk, int why)
-
{
-
struct siginfo info;
-
unsigned long flags;
-
struct task_struct *parent;
-
struct sighand_struct *sighand;
-
-
if (task_ptrace(tsk)) //前面可以肯定的一定是trace状态
-
parent = tsk->parent; //parent代表调试器进程,real_parent是真实的父进程
-
else {
-
tsk = tsk->group_leader;
-
parent = tsk->real_parent;
-
}
-
-
info.si_signo = SIGCHLD;
-
info.si_errno = 0;
-
/*
-
* see comment in do_notify_parent() abot the following 3 lines
-
*/
-
rcu_read_lock();
-
info.si_pid = task_pid_nr_ns(tsk, parent->nsproxy->pid_ns); //按照pid namespace获得pid
-
info.si_uid = __task_cred(tsk)->uid;
-
rcu_read_unlock();
-
-
info.si_utime = cputime_to_clock_t(tsk->utime);
-
info.si_stime = cputime_to_clock_t(tsk->stime);
-
-
info.si_code = why;
-
switch (why) {
-
case CLD_CONTINUED: //如果这里获得的信号是SIGCONT
-
info.si_status = SIGCONT;
-
break;
-
case CLD_STOPPED: //如果是group stop,线程组都停止了
-
info.si_status = tsk->signal->group_exit_code & 0x7f;
-
break;
-
case CLD_TRAPPED: //其他的信号
-
info.si_status = tsk->exit_code & 0x7f;
-
break;
-
default:
-
BUG();
-
}
-
-
sighand = parent->sighand;
-
spin_lock_irqsave(&sighand->siglock, flags);
-
//如果在用户态设置了信号处理函数并且标志 SA_NOCLDSTOP代表,进程停止时,不发送信号到父进程或者trace进程
-
if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN &&!(sighand->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
-
__group_send_sig_info(SIGCHLD, &info, parent);
-
/*
-
* Even if SIGCHLD is not generated, we must wake up wait4 calls.
-
*/
-
__wake_up_parent(tsk, parent); //这里唤醒正在wait操作等待的线程,这里是调试器进程(或者是父进程)
-
spin_unlock_irqrestore(&sighand->siglock, flags);
-
}
这里pid
namespace请参考 pid namespace浅分析,pid
namespace 浅分析
续,对于这里调试器进程的wait函数和被跟踪进程最终调用_wake_up_parent函数,这里原理我并不了解,后面会分析这两个函数的源代码
总结:
如果一个进程被跟踪(进程调用ptrace
attach 一个进程),同时调试进程设置了信号处理函数,那么这个被跟踪的进程所产生的信号,会被发送到调试进程,但是不管调试进程是否设置了信号处理函数,被跟踪的进程肯定会休眠,唤醒调试进程来调试信息。
阅读(5283) | 评论(2) | 转发(1) |