ptrace: Make do_signal_stop() use ptrace_stop() if the task is being ptraced
[linux-2.6-block.git] / kernel / signal.c
index 31751868de8856cbe4ee909ef09915e6a9a9c679..418776c41d24a0d061b8545bd7b54896edba1e1f 100644 (file)
@@ -124,7 +124,7 @@ static inline int has_pending_signals(sigset_t *signal, sigset_t *blocked)
 
 static int recalc_sigpending_tsk(struct task_struct *t)
 {
-       if (t->signal->group_stop_count > 0 ||
+       if ((t->group_stop & GROUP_STOP_PENDING) ||
            PENDING(&t->pending, &t->blocked) ||
            PENDING(&t->signal->shared_pending, &t->blocked)) {
                set_tsk_thread_flag(t, TIF_SIGPENDING);
@@ -223,6 +223,54 @@ static inline void print_dropped_signal(int sig)
                                current->comm, current->pid, sig);
 }
 
+/**
+ * task_clear_group_stop_pending - clear pending group stop
+ * @task: target task
+ *
+ * Clear group stop states for @task.
+ *
+ * CONTEXT:
+ * Must be called with @task->sighand->siglock held.
+ */
+void task_clear_group_stop_pending(struct task_struct *task)
+{
+       task->group_stop &= ~(GROUP_STOP_PENDING | GROUP_STOP_CONSUME);
+}
+
+/**
+ * task_participate_group_stop - participate in a group stop
+ * @task: task participating in a group stop
+ *
+ * @task has GROUP_STOP_PENDING set and is participating in a group stop.
+ * Group stop states are cleared and the group stop count is consumed if
+ * %GROUP_STOP_CONSUME was set.  If the consumption completes the group
+ * stop, the appropriate %SIGNAL_* flags are set.
+ *
+ * CONTEXT:
+ * Must be called with @task->sighand->siglock held.
+ */
+static bool task_participate_group_stop(struct task_struct *task)
+{
+       struct signal_struct *sig = task->signal;
+       bool consume = task->group_stop & GROUP_STOP_CONSUME;
+
+       WARN_ON_ONCE(!(task->group_stop & GROUP_STOP_PENDING));
+
+       task_clear_group_stop_pending(task);
+
+       if (!consume)
+               return false;
+
+       if (!WARN_ON_ONCE(sig->group_stop_count == 0))
+               sig->group_stop_count--;
+
+       if (!sig->group_stop_count) {
+               sig->flags = SIGNAL_STOP_STOPPED;
+               return true;
+       }
+       return false;
+}
+
 /*
  * allocate a new signal queue record
  * - this may be called without locks if and only if t == current, otherwise an
@@ -719,6 +767,9 @@ static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
                t = p;
                do {
                        unsigned int state;
+
+                       task_clear_group_stop_pending(t);
+
                        rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
                        /*
                         * If there is a handler for SIGCONT, we must make
@@ -860,6 +911,7 @@ static void complete_signal(int sig, struct task_struct *p, int group)
                        signal->group_stop_count = 0;
                        t = p;
                        do {
+                               task_clear_group_stop_pending(t);
                                sigaddset(&t->pending.signal, SIGKILL);
                                signal_wake_up(t, 1);
                        } while_each_thread(p, t);
@@ -1093,6 +1145,7 @@ int zap_other_threads(struct task_struct *p)
        p->signal->group_stop_count = 0;
 
        while_each_thread(p, t) {
+               task_clear_group_stop_pending(t);
                count++;
 
                /* Don't bother with already dead threads */
@@ -1617,7 +1670,7 @@ static int sigkill_pending(struct task_struct *tsk)
  * If we actually decide not to stop at all because the tracer
  * is gone, we keep current->exit_code unless clear_code.
  */
-static void ptrace_stop(int exit_code, int clear_code, siginfo_t *info)
+static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
        __releases(&current->sighand->siglock)
        __acquires(&current->sighand->siglock)
 {
@@ -1641,11 +1694,14 @@ static void ptrace_stop(int exit_code, int clear_code, siginfo_t *info)
        }
 
        /*
-        * If there is a group stop in progress,
-        * we must participate in the bookkeeping.
+        * If @why is CLD_STOPPED, we're trapping to participate in a group
+        * stop.  Do the bookkeeping.  Note that if SIGCONT was delievered
+        * while siglock was released for the arch hook, PENDING could be
+        * clear now.  We act as if SIGCONT is received after TASK_TRACED
+        * is entered - ignore it.
         */
-       if (current->signal->group_stop_count > 0)
-               --current->signal->group_stop_count;
+       if (why == CLD_STOPPED && (current->group_stop & GROUP_STOP_PENDING))
+               task_participate_group_stop(current);
 
        current->last_siginfo = info;
        current->exit_code = exit_code;
@@ -1655,7 +1711,7 @@ static void ptrace_stop(int exit_code, int clear_code, siginfo_t *info)
        spin_unlock_irq(&current->sighand->siglock);
        read_lock(&tasklist_lock);
        if (may_ptrace_stop()) {
-               do_notify_parent_cldstop(current, CLD_TRAPPED);
+               do_notify_parent_cldstop(current, why);
                /*
                 * Don't want to allow preemption here, because
                 * sys_ptrace() needs this task to be inactive.
@@ -1714,7 +1770,7 @@ void ptrace_notify(int exit_code)
 
        /* Let the debugger run.  */
        spin_lock_irq(&current->sighand->siglock);
-       ptrace_stop(exit_code, 1, &info);
+       ptrace_stop(exit_code, CLD_TRAPPED, 1, &info);
        spin_unlock_irq(&current->sighand->siglock);
 }
 
@@ -1727,9 +1783,9 @@ void ptrace_notify(int exit_code)
 static int do_signal_stop(int signr)
 {
        struct signal_struct *sig = current->signal;
-       int notify;
 
-       if (!sig->group_stop_count) {
+       if (!(current->group_stop & GROUP_STOP_PENDING)) {
+               unsigned int gstop = GROUP_STOP_PENDING | GROUP_STOP_CONSUME;
                struct task_struct *t;
 
                if (!likely(sig->flags & SIGNAL_STOP_DEQUEUED) ||
@@ -1741,6 +1797,7 @@ static int do_signal_stop(int signr)
                 */
                sig->group_exit_code = signr;
 
+               current->group_stop = gstop;
                sig->group_stop_count = 1;
                for (t = next_thread(current); t != current; t = next_thread(t))
                        /*
@@ -1748,42 +1805,44 @@ static int do_signal_stop(int signr)
                         * stop is always done with the siglock held,
                         * so this check has no races.
                         */
-                       if (!(t->flags & PF_EXITING) &&
-                           !task_is_stopped_or_traced(t)) {
+                       if (!(t->flags & PF_EXITING) && !task_is_stopped(t)) {
+                               t->group_stop = gstop;
                                sig->group_stop_count++;
                                signal_wake_up(t, 0);
-                       }
+                       } else
+                               task_clear_group_stop_pending(t);
        }
-       /*
-        * If there are no other threads in the group, or if there is
-        * a group stop in progress and we are the last to stop, report
-        * to the parent.  When ptraced, every thread reports itself.
-        */
-       notify = sig->group_stop_count == 1 ? CLD_STOPPED : 0;
-       notify = tracehook_notify_jctl(notify, CLD_STOPPED);
-       /*
-        * tracehook_notify_jctl() can drop and reacquire siglock, so
-        * we keep ->group_stop_count != 0 before the call. If SIGCONT
-        * or SIGKILL comes in between ->group_stop_count == 0.
-        */
-       if (sig->group_stop_count) {
-               if (!--sig->group_stop_count)
-                       sig->flags = SIGNAL_STOP_STOPPED;
-               current->exit_code = sig->group_exit_code;
-               __set_current_state(TASK_STOPPED);
-       }
-       spin_unlock_irq(&current->sighand->siglock);
 
-       if (notify) {
-               read_lock(&tasklist_lock);
-               do_notify_parent_cldstop(current, notify);
-               read_unlock(&tasklist_lock);
-       }
+       current->exit_code = sig->group_exit_code;
+       __set_current_state(TASK_STOPPED);
+
+       if (likely(!task_ptrace(current))) {
+               int notify = 0;
+
+               /*
+                * If there are no other threads in the group, or if there
+                * is a group stop in progress and we are the last to stop,
+                * report to the parent.
+                */
+               if (task_participate_group_stop(current))
+                       notify = CLD_STOPPED;
+
+               spin_unlock_irq(&current->sighand->siglock);
+
+               if (notify) {
+                       read_lock(&tasklist_lock);
+                       do_notify_parent_cldstop(current, notify);
+                       read_unlock(&tasklist_lock);
+               }
 
-       /* Now we don't run again until woken by SIGCONT or SIGKILL */
-       do {
+               /* Now we don't run again until woken by SIGCONT or SIGKILL */
                schedule();
-       } while (try_to_freeze());
+
+               spin_lock_irq(&current->sighand->siglock);
+       } else
+               ptrace_stop(current->exit_code, CLD_STOPPED, 0, NULL);
+
+       spin_unlock_irq(&current->sighand->siglock);
 
        tracehook_finish_jctl();
        current->exit_code = 0;
@@ -1800,7 +1859,7 @@ static int ptrace_signal(int signr, siginfo_t *info,
        ptrace_signal_deliver(regs, cookie);
 
        /* Let the debugger run.  */
-       ptrace_stop(signr, 0, info);
+       ptrace_stop(signr, CLD_TRAPPED, 0, info);
 
        /* We're back.  Did the debugger cancel the sig?  */
        signr = current->exit_code;
@@ -1853,18 +1912,20 @@ relock:
         * the CLD_ si_code into SIGNAL_CLD_MASK bits.
         */
        if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
-               int why = (signal->flags & SIGNAL_STOP_CONTINUED)
-                               ? CLD_CONTINUED : CLD_STOPPED;
+               int why;
+
+               if (signal->flags & SIGNAL_CLD_CONTINUED)
+                       why = CLD_CONTINUED;
+               else
+                       why = CLD_STOPPED;
+
                signal->flags &= ~SIGNAL_CLD_MASK;
 
-               why = tracehook_notify_jctl(why, CLD_CONTINUED);
                spin_unlock_irq(&sighand->siglock);
 
-               if (why) {
-                       read_lock(&tasklist_lock);
-                       do_notify_parent_cldstop(current->group_leader, why);
-                       read_unlock(&tasklist_lock);
-               }
+               read_lock(&tasklist_lock);
+               do_notify_parent_cldstop(current->group_leader, why);
+               read_unlock(&tasklist_lock);
                goto relock;
        }
 
@@ -1881,8 +1942,8 @@ relock:
                if (unlikely(signr != 0))
                        ka = return_ka;
                else {
-                       if (unlikely(signal->group_stop_count > 0) &&
-                           do_signal_stop(0))
+                       if (unlikely(current->group_stop &
+                                    GROUP_STOP_PENDING) && do_signal_stop(0))
                                goto relock;
 
                        signr = dequeue_signal(current, &current->blocked,
@@ -2028,11 +2089,9 @@ void exit_signals(struct task_struct *tsk)
                if (!signal_pending(t) && !(t->flags & PF_EXITING))
                        recalc_sigpending_and_wake(t);
 
-       if (unlikely(tsk->signal->group_stop_count) &&
-                       !--tsk->signal->group_stop_count) {
-               tsk->signal->flags = SIGNAL_STOP_STOPPED;
-               group_stop = tracehook_notify_jctl(CLD_STOPPED, CLD_STOPPED);
-       }
+       if (unlikely(tsk->group_stop & GROUP_STOP_PENDING) &&
+           task_participate_group_stop(tsk))
+               group_stop = CLD_STOPPED;
 out:
        spin_unlock_irq(&tsk->sighand->siglock);