Commit 544b2c91a9f14f9565af1972203438b7f49afd48

Authored by Tejun Heo
Committed by Oleg Nesterov
1 parent fb1d910c17

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
... ... @@ -49,6 +49,7 @@
49 49  
50 50 #define PTRACE_SEIZE 0x4206
51 51 #define PTRACE_INTERRUPT 0x4207
  52 +#define PTRACE_LISTEN 0x4208
52 53  
53 54 /* flags in @data for PTRACE_SEIZE */
54 55 #define PTRACE_SEIZE_DEVEL 0x80000000 /* temp flag for development */
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)
... ... @@ -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)
... ... @@ -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;
... ... @@ -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(&current->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.