aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-06-14 05:20:18 -0400
committerOleg Nesterov <oleg@redhat.com>2011-06-16 15:41:54 -0400
commit544b2c91a9f14f9565af1972203438b7f49afd48 (patch)
tree38615eeed1e50580a2341b5a9d15c98793d33c2d
parentfb1d910c178ba0c5bc32d3e5a9e82e05b7aad3cd (diff)
ptrace: implement PTRACE_LISTEN
The previous patch implemented async notification for ptrace but it only worked while trace is running. This patch introduces PTRACE_LISTEN which is suggested by Oleg Nestrov. It's allowed iff tracee is in STOP trap and puts tracee into quasi-running state - tracee never really runs but wait(2) and ptrace(2) consider it to be running. While ptracer is listening, tracee is allowed to re-enter STOP to notify an async event. Listening state is cleared on the first notification. Ptracer can also clear it by issuing INTERRUPT - tracee will re-trap into STOP with listening state cleared. This allows ptracer to monitor group stop state without running tracee - use INTERRUPT to put tracee into STOP trap, issue LISTEN and then wait(2) to wait for the next group stop event. When it happens, PTRACE_GETSIGINFO provides information to determine the current state. Test program follows. #define PTRACE_SEIZE 0x4206 #define PTRACE_INTERRUPT 0x4207 #define PTRACE_LISTEN 0x4208 #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); if (si.si_signo != SIGTRAP) ptrace(PTRACE_LISTEN, tracee, NULL, NULL); else 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; } This is identical to the program to test TRAP_NOTIFY except that tracee is PTRACE_LISTEN'd instead of PTRACE_CONT'd when group stopped. This allows ptracer to monitor when group stop ends without running tracee. # ./test-listen 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 -v2: Moved JOBCTL_LISTENING check in wait_task_stopped() into task_stopped_code() as suggested by Oleg. Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Oleg Nesterov <oleg@redhat.com>
-rw-r--r--include/linux/ptrace.h1
-rw-r--r--include/linux/sched.h2
-rw-r--r--kernel/exit.c3
-rw-r--r--kernel/ptrace.c42
-rw-r--r--kernel/signal.c13
5 files changed, 53 insertions, 8 deletions
diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h
index ad754d1e0b13..4f224f169524 100644
--- a/include/linux/ptrace.h
+++ b/include/linux/ptrace.h
@@ -49,6 +49,7 @@
49 49
50#define PTRACE_SEIZE 0x4206 50#define PTRACE_SEIZE 0x4206
51#define PTRACE_INTERRUPT 0x4207 51#define PTRACE_INTERRUPT 0x4207
52#define PTRACE_LISTEN 0x4208
52 53
53/* flags in @data for PTRACE_SEIZE */ 54/* flags in @data for PTRACE_SEIZE */
54#define PTRACE_SEIZE_DEVEL 0x80000000 /* temp flag for development */ 55#define PTRACE_SEIZE_DEVEL 0x80000000 /* temp flag for development */
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1854def284f5..87f7ca7ed6f6 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1813,6 +1813,7 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
1813#define JOBCTL_TRAP_STOP_BIT 19 /* trap for STOP */ 1813#define JOBCTL_TRAP_STOP_BIT 19 /* trap for STOP */
1814#define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */ 1814#define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */
1815#define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */ 1815#define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */
1816#define JOBCTL_LISTENING_BIT 22 /* ptracer is listening for events */
1816 1817
1817#define JOBCTL_STOP_DEQUEUED (1 << JOBCTL_STOP_DEQUEUED_BIT) 1818#define JOBCTL_STOP_DEQUEUED (1 << JOBCTL_STOP_DEQUEUED_BIT)
1818#define JOBCTL_STOP_PENDING (1 << JOBCTL_STOP_PENDING_BIT) 1819#define JOBCTL_STOP_PENDING (1 << JOBCTL_STOP_PENDING_BIT)
@@ -1820,6 +1821,7 @@ extern void thread_group_times(struct task_struct *p, cputime_t *ut, cputime_t *
1820#define JOBCTL_TRAP_STOP (1 << JOBCTL_TRAP_STOP_BIT) 1821#define JOBCTL_TRAP_STOP (1 << JOBCTL_TRAP_STOP_BIT)
1821#define JOBCTL_TRAP_NOTIFY (1 << JOBCTL_TRAP_NOTIFY_BIT) 1822#define JOBCTL_TRAP_NOTIFY (1 << JOBCTL_TRAP_NOTIFY_BIT)
1822#define JOBCTL_TRAPPING (1 << JOBCTL_TRAPPING_BIT) 1823#define JOBCTL_TRAPPING (1 << JOBCTL_TRAPPING_BIT)
1824#define JOBCTL_LISTENING (1 << JOBCTL_LISTENING_BIT)
1823 1825
1824#define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY) 1826#define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)
1825#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK) 1827#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
diff --git a/kernel/exit.c b/kernel/exit.c
index 20a406471525..289f59d686bf 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1368,7 +1368,8 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
1368static int *task_stopped_code(struct task_struct *p, bool ptrace) 1368static int *task_stopped_code(struct task_struct *p, bool ptrace)
1369{ 1369{
1370 if (ptrace) { 1370 if (ptrace) {
1371 if (task_is_stopped_or_traced(p)) 1371 if (task_is_stopped_or_traced(p) &&
1372 !(p->jobctl & JOBCTL_LISTENING))
1372 return &p->exit_code; 1373 return &p->exit_code;
1373 } else { 1374 } else {
1374 if (p->signal->flags & SIGNAL_STOP_STOPPED) 1375 if (p->signal->flags & SIGNAL_STOP_STOPPED)
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 6852c0f4a916..e18966c1c0da 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -146,7 +146,8 @@ int ptrace_check_attach(struct task_struct *child, bool ignore_state)
146 */ 146 */
147 spin_lock_irq(&child->sighand->siglock); 147 spin_lock_irq(&child->sighand->siglock);
148 WARN_ON_ONCE(task_is_stopped(child)); 148 WARN_ON_ONCE(task_is_stopped(child));
149 if (task_is_traced(child) || ignore_state) 149 if (ignore_state || (task_is_traced(child) &&
150 !(child->jobctl & JOBCTL_LISTENING)))
150 ret = 0; 151 ret = 0;
151 spin_unlock_irq(&child->sighand->siglock); 152 spin_unlock_irq(&child->sighand->siglock);
152 } 153 }
@@ -660,7 +661,7 @@ int ptrace_request(struct task_struct *child, long request,
660{ 661{
661 bool seized = child->ptrace & PT_SEIZED; 662 bool seized = child->ptrace & PT_SEIZED;
662 int ret = -EIO; 663 int ret = -EIO;
663 siginfo_t siginfo; 664 siginfo_t siginfo, *si;
664 void __user *datavp = (void __user *) data; 665 void __user *datavp = (void __user *) data;
665 unsigned long __user *datalp = datavp; 666 unsigned long __user *datalp = datavp;
666 unsigned long flags; 667 unsigned long flags;
@@ -710,8 +711,43 @@ int ptrace_request(struct task_struct *child, long request,
710 if (unlikely(!seized || !lock_task_sighand(child, &flags))) 711 if (unlikely(!seized || !lock_task_sighand(child, &flags)))
711 break; 712 break;
712 713
714 /*
715 * INTERRUPT doesn't disturb existing trap sans one
716 * exception. If ptracer issued LISTEN for the current
717 * STOP, this INTERRUPT should clear LISTEN and re-trap
718 * tracee into STOP.
719 */
713 if (likely(task_set_jobctl_pending(child, JOBCTL_TRAP_STOP))) 720 if (likely(task_set_jobctl_pending(child, JOBCTL_TRAP_STOP)))
714 signal_wake_up(child, 0); 721 signal_wake_up(child, child->jobctl & JOBCTL_LISTENING);
722
723 unlock_task_sighand(child, &flags);
724 ret = 0;
725 break;
726
727 case PTRACE_LISTEN:
728 /*
729 * Listen for events. Tracee must be in STOP. It's not
730 * resumed per-se but is not considered to be in TRACED by
731 * wait(2) or ptrace(2). If an async event (e.g. group
732 * stop state change) happens, tracee will enter STOP trap
733 * again. Alternatively, ptracer can issue INTERRUPT to
734 * finish listening and re-trap tracee into STOP.
735 */
736 if (unlikely(!seized || !lock_task_sighand(child, &flags)))
737 break;
738
739 si = child->last_siginfo;
740 if (unlikely(!si || si->si_code >> 8 != PTRACE_EVENT_STOP))
741 break;
742
743 child->jobctl |= JOBCTL_LISTENING;
744
745 /*
746 * If NOTIFY is set, it means event happened between start
747 * of this trap and now. Trigger re-trap immediately.
748 */
749 if (child->jobctl & JOBCTL_TRAP_NOTIFY)
750 signal_wake_up(child, true);
715 751
716 unlock_task_sighand(child, &flags); 752 unlock_task_sighand(child, &flags);
717 ret = 0; 753 ret = 0;
diff --git a/kernel/signal.c b/kernel/signal.c
index 06177e2b3917..97e575a3387e 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -825,9 +825,11 @@ static int check_kill_permission(int sig, struct siginfo *info,
825 * TRAP_STOP to notify ptracer of an event. @t must have been seized by 825 * TRAP_STOP to notify ptracer of an event. @t must have been seized by
826 * ptracer. 826 * ptracer.
827 * 827 *
828 * If @t is running, STOP trap will be taken. If already trapped, STOP 828 * If @t is running, STOP trap will be taken. If trapped for STOP and
829 * trap will be eventually taken without returning to userland after the 829 * ptracer is listening for events, tracee is woken up so that it can
830 * existing traps are finished by PTRACE_CONT. 830 * re-trap for the new event. If trapped otherwise, STOP trap will be
831 * eventually taken without returning to userland after the existing traps
832 * are finished by PTRACE_CONT.
831 * 833 *
832 * CONTEXT: 834 * CONTEXT:
833 * Must be called with @task->sighand->siglock held. 835 * Must be called with @task->sighand->siglock held.
@@ -838,7 +840,7 @@ static void ptrace_trap_notify(struct task_struct *t)
838 assert_spin_locked(&t->sighand->siglock); 840 assert_spin_locked(&t->sighand->siglock);
839 841
840 task_set_jobctl_pending(t, JOBCTL_TRAP_NOTIFY); 842 task_set_jobctl_pending(t, JOBCTL_TRAP_NOTIFY);
841 signal_wake_up(t, 0); 843 signal_wake_up(t, t->jobctl & JOBCTL_LISTENING);
842} 844}
843 845
844/* 846/*
@@ -1894,6 +1896,9 @@ static void ptrace_stop(int exit_code, int why, int clear_code, siginfo_t *info)
1894 spin_lock_irq(&current->sighand->siglock); 1896 spin_lock_irq(&current->sighand->siglock);
1895 current->last_siginfo = NULL; 1897 current->last_siginfo = NULL;
1896 1898
1899 /* LISTENING can be set only during STOP traps, clear it */
1900 current->jobctl &= ~JOBCTL_LISTENING;
1901
1897 /* 1902 /*
1898 * Queued signals ignored us while we were stopped for tracing. 1903 * Queued signals ignored us while we were stopped for tracing.
1899 * So check for any that we should take before resuming user mode. 1904 * So check for any that we should take before resuming user mode.