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 /kernel/ptrace.c | |
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>
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r-- | kernel/ptrace.c | 42 |
1 files changed, 39 insertions, 3 deletions
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; |