diff options
author | Tejun Heo <tj@kernel.org> | 2011-06-14 05:20:18 -0400 |
---|---|---|
committer | Oleg Nesterov <oleg@redhat.com> | 2011-06-16 15:41:54 -0400 |
commit | 544b2c91a9f14f9565af1972203438b7f49afd48 (patch) | |
tree | 38615eeed1e50580a2341b5a9d15c98793d33c2d | |
parent | fb1d910c178ba0c5bc32d3e5a9e82e05b7aad3cd (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.h | 1 | ||||
-rw-r--r-- | include/linux/sched.h | 2 | ||||
-rw-r--r-- | kernel/exit.c | 3 | ||||
-rw-r--r-- | kernel/ptrace.c | 42 | ||||
-rw-r--r-- | kernel/signal.c | 13 |
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) | |||
1368 | static int *task_stopped_code(struct task_struct *p, bool ptrace) | 1368 | static 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(¤t->sighand->siglock); | 1896 | spin_lock_irq(¤t->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. |