aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/signal.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-06-14 05:20:17 -0400
committerOleg Nesterov <oleg@redhat.com>2011-06-16 15:41:53 -0400
commitfb1d910c178ba0c5bc32d3e5a9e82e05b7aad3cd (patch)
treedd889f33758a914cb538583073000bc21aeb1759 /kernel/signal.c
parentfca26f260c528ee51a2e451b5b200aeb528f3e09 (diff)
ptrace: implement TRAP_NOTIFY and use it for group stop events
Currently there's no way for ptracer to find out whether group stop finished other than polling with INTERRUPT - GETSIGINFO - CONT sequence. This patch implements group stop notification for ptracer using STOP traps. When group stop state of a seized tracee changes, JOBCTL_TRAP_NOTIFY is set, which schedules a STOP trap which is sticky - it isn't cleared by other traps and at least one STOP trap will happen eventually. STOP trap is synchronization point for event notification and the tracer can determine the current group stop state by looking at the signal number portion of exit code (si_status from waitid(2) or si_code from PTRACE_GETSIGINFO). Notifications are generated both on start and end of group stops but, because group stop participation always happens before STOP trap, this doesn't cause an extra trap while tracee is participating in group stop. The symmetry will be useful later. Note that this notification works iff tracee is not trapped. Currently there is no way to be notified of group stop state changes while tracee is trapped. This will be addressed by a later patch. An example program follows. #define PTRACE_SEIZE 0x4206 #define PTRACE_INTERRUPT 0x4207 #define PTRACE_SEIZE_DEVEL 0x80000000 static const struct timespec ts1s = { .tv_sec = 1 }; int main(int argc, char **argv) { pid_t tracee, tracer; int i; tracee = fork(); if (!tracee) while (1) pause(); tracer = fork(); if (!tracer) { siginfo_t si; ptrace(PTRACE_SEIZE, tracee, NULL, (void *)(unsigned long)PTRACE_SEIZE_DEVEL); ptrace(PTRACE_INTERRUPT, tracee, NULL, NULL); repeat: waitid(P_PID, tracee, NULL, WSTOPPED); ptrace(PTRACE_GETSIGINFO, tracee, NULL, &si); if (!si.si_code) { printf("tracer: SIG %d\n", si.si_signo); ptrace(PTRACE_CONT, tracee, NULL, (void *)(unsigned long)si.si_signo); goto repeat; } printf("tracer: stopped=%d signo=%d\n", si.si_signo != SIGTRAP, si.si_signo); ptrace(PTRACE_CONT, tracee, NULL, NULL); goto repeat; } for (i = 0; i < 3; i++) { nanosleep(&ts1s, NULL); printf("mother: SIGSTOP\n"); kill(tracee, SIGSTOP); nanosleep(&ts1s, NULL); printf("mother: SIGCONT\n"); kill(tracee, SIGCONT); } nanosleep(&ts1s, NULL); kill(tracer, SIGKILL); kill(tracee, SIGKILL); return 0; } In the above program, tracer keeps tracee running and gets notification of each group stop state changes. # ./test-notify tracer: stopped=0 signo=5 mother: SIGSTOP tracer: SIG 19 tracer: stopped=1 signo=19 mother: SIGCONT tracer: stopped=0 signo=5 tracer: SIG 18 mother: SIGSTOP tracer: SIG 19 tracer: stopped=1 signo=19 mother: SIGCONT tracer: stopped=0 signo=5 tracer: SIG 18 mother: SIGSTOP tracer: SIG 19 tracer: stopped=1 signo=19 mother: SIGCONT tracer: stopped=0 signo=5 tracer: SIG 18 Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Oleg Nesterov <oleg@redhat.com>
Diffstat (limited to 'kernel/signal.c')
-rw-r--r--kernel/signal.c38
1 files changed, 35 insertions, 3 deletions
diff --git a/kernel/signal.c b/kernel/signal.c
index 589292f38530..06177e2b3917 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -817,6 +817,30 @@ static int check_kill_permission(int sig, struct siginfo *info,
817 return security_task_kill(t, info, sig, 0); 817 return security_task_kill(t, info, sig, 0);
818} 818}
819 819
820/**
821 * ptrace_trap_notify - schedule trap to notify ptracer
822 * @t: tracee wanting to notify tracer
823 *
824 * This function schedules sticky ptrace trap which is cleared on the next
825 * TRAP_STOP to notify ptracer of an event. @t must have been seized by
826 * ptracer.
827 *
828 * If @t is running, STOP trap will be taken. If already trapped, STOP
829 * trap will be eventually taken without returning to userland after the
830 * existing traps are finished by PTRACE_CONT.
831 *
832 * CONTEXT:
833 * Must be called with @task->sighand->siglock held.
834 */
835static void ptrace_trap_notify(struct task_struct *t)
836{
837 WARN_ON_ONCE(!(t->ptrace & PT_SEIZED));
838 assert_spin_locked(&t->sighand->siglock);
839
840 task_set_jobctl_pending(t, JOBCTL_TRAP_NOTIFY);
841 signal_wake_up(t, 0);
842}
843
820/* 844/*
821 * Handle magic process-wide effects of stop/continue signals. Unlike 845 * Handle magic process-wide effects of stop/continue signals. Unlike
822 * the signal actions, these happen immediately at signal-generation 846 * the signal actions, these happen immediately at signal-generation
@@ -855,7 +879,10 @@ static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
855 do { 879 do {
856 task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING); 880 task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
857 rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending); 881 rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
858 wake_up_state(t, __TASK_STOPPED); 882 if (likely(!(t->ptrace & PT_SEIZED)))
883 wake_up_state(t, __TASK_STOPPED);
884 else
885 ptrace_trap_notify(t);
859 } while_each_thread(p, t); 886 } while_each_thread(p, t);
860 887
861 /* 888 /*
@@ -1797,8 +1824,10 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
1797 if (why == CLD_STOPPED && (current->jobctl & JOBCTL_STOP_PENDING)) 1824 if (why == CLD_STOPPED && (current->jobctl & JOBCTL_STOP_PENDING))
1798 gstop_done = task_participate_group_stop(current); 1825 gstop_done = task_participate_group_stop(current);
1799 1826
1800 /* any trap clears pending STOP trap */ 1827 /* any trap clears pending STOP trap, STOP trap clears NOTIFY */
1801 task_clear_jobctl_pending(current, JOBCTL_TRAP_STOP); 1828 task_clear_jobctl_pending(current, JOBCTL_TRAP_STOP);
1829 if (info && info->si_code >> 8 == PTRACE_EVENT_STOP)
1830 task_clear_jobctl_pending(current, JOBCTL_TRAP_NOTIFY);
1802 1831
1803 /* entering a trap, clear TRAPPING */ 1832 /* entering a trap, clear TRAPPING */
1804 task_clear_jobctl_trapping(current); 1833 task_clear_jobctl_trapping(current);
@@ -1972,7 +2001,10 @@ static bool do_signal_stop(int signr)
1972 if (!task_is_stopped(t) && 2001 if (!task_is_stopped(t) &&
1973 task_set_jobctl_pending(t, signr | gstop)) { 2002 task_set_jobctl_pending(t, signr | gstop)) {
1974 sig->group_stop_count++; 2003 sig->group_stop_count++;
1975 signal_wake_up(t, 0); 2004 if (likely(!(t->ptrace & PT_SEIZED)))
2005 signal_wake_up(t, 0);
2006 else
2007 ptrace_trap_notify(t);
1976 } 2008 }
1977 } 2009 }
1978 } 2010 }