Commit 544b2c91a9f14f9565af1972203438b7f49afd48
Committed by
Oleg Nesterov
1 parent
fb1d910c17
Exists in
master
and in
4 other branches
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>
Showing 5 changed files with 53 additions and 8 deletions Side-by-side Diff
include/linux/ptrace.h
include/linux/sched.h
... | ... | @@ -1813,6 +1813,7 @@ |
1813 | 1813 | #define JOBCTL_TRAP_STOP_BIT 19 /* trap for STOP */ |
1814 | 1814 | #define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */ |
1815 | 1815 | #define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */ |
1816 | +#define JOBCTL_LISTENING_BIT 22 /* ptracer is listening for events */ | |
1816 | 1817 | |
1817 | 1818 | #define JOBCTL_STOP_DEQUEUED (1 << JOBCTL_STOP_DEQUEUED_BIT) |
1818 | 1819 | #define JOBCTL_STOP_PENDING (1 << JOBCTL_STOP_PENDING_BIT) |
... | ... | @@ -1820,6 +1821,7 @@ |
1820 | 1821 | #define JOBCTL_TRAP_STOP (1 << JOBCTL_TRAP_STOP_BIT) |
1821 | 1822 | #define JOBCTL_TRAP_NOTIFY (1 << JOBCTL_TRAP_NOTIFY_BIT) |
1822 | 1823 | #define JOBCTL_TRAPPING (1 << JOBCTL_TRAPPING_BIT) |
1824 | +#define JOBCTL_LISTENING (1 << JOBCTL_LISTENING_BIT) | |
1823 | 1825 | |
1824 | 1826 | #define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY) |
1825 | 1827 | #define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK) |
kernel/exit.c
... | ... | @@ -1368,7 +1368,8 @@ |
1368 | 1368 | static int *task_stopped_code(struct task_struct *p, bool ptrace) |
1369 | 1369 | { |
1370 | 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 | 1373 | return &p->exit_code; |
1373 | 1374 | } else { |
1374 | 1375 | if (p->signal->flags & SIGNAL_STOP_STOPPED) |
kernel/ptrace.c
... | ... | @@ -146,7 +146,8 @@ |
146 | 146 | */ |
147 | 147 | spin_lock_irq(&child->sighand->siglock); |
148 | 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 | 151 | ret = 0; |
151 | 152 | spin_unlock_irq(&child->sighand->siglock); |
152 | 153 | } |
... | ... | @@ -660,7 +661,7 @@ |
660 | 661 | { |
661 | 662 | bool seized = child->ptrace & PT_SEIZED; |
662 | 663 | int ret = -EIO; |
663 | - siginfo_t siginfo; | |
664 | + siginfo_t siginfo, *si; | |
664 | 665 | void __user *datavp = (void __user *) data; |
665 | 666 | unsigned long __user *datalp = datavp; |
666 | 667 | unsigned long flags; |
667 | 668 | |
... | ... | @@ -710,8 +711,43 @@ |
710 | 711 | if (unlikely(!seized || !lock_task_sighand(child, &flags))) |
711 | 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 | 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 | 752 | unlock_task_sighand(child, &flags); |
717 | 753 | ret = 0; |
kernel/signal.c
... | ... | @@ -825,9 +825,11 @@ |
825 | 825 | * TRAP_STOP to notify ptracer of an event. @t must have been seized by |
826 | 826 | * ptracer. |
827 | 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. | |
828 | + * If @t is running, STOP trap will be taken. If trapped for STOP and | |
829 | + * ptracer is listening for events, tracee is woken up so that it can | |
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 | 834 | * CONTEXT: |
833 | 835 | * Must be called with @task->sighand->siglock held. |
... | ... | @@ -838,7 +840,7 @@ |
838 | 840 | assert_spin_locked(&t->sighand->siglock); |
839 | 841 | |
840 | 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 | /* |
... | ... | @@ -1893,6 +1895,9 @@ |
1893 | 1895 | */ |
1894 | 1896 | spin_lock_irq(¤t->sighand->siglock); |
1895 | 1897 | current->last_siginfo = NULL; |
1898 | + | |
1899 | + /* LISTENING can be set only during STOP traps, clear it */ | |
1900 | + current->jobctl &= ~JOBCTL_LISTENING; | |
1896 | 1901 | |
1897 | 1902 | /* |
1898 | 1903 | * Queued signals ignored us while we were stopped for tracing. |