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/signal.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/signal.c')
-rw-r--r-- | kernel/signal.c | 13 |
1 files changed, 9 insertions, 4 deletions
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. |