Commit fb1d910c178ba0c5bc32d3e5a9e82e05b7aad3cd

Authored by Tejun Heo
Committed by Oleg Nesterov
1 parent fca26f260c

ptrace: implement TRAP_NOTIFY and use it for group stop events

Currently there's no way for ptracer to find out whether group stop
finished other than polling with INTERRUPT - GETSIGINFO - CONT
sequence.  This patch implements group stop notification for ptracer
using STOP traps.

When group stop state of a seized tracee changes, JOBCTL_TRAP_NOTIFY
is set, which schedules a STOP trap which is sticky - it isn't cleared
by other traps and at least one STOP trap will happen eventually.
STOP trap is synchronization point for event notification and the
tracer can determine the current group stop state by looking at the
signal number portion of exit code (si_status from waitid(2) or
si_code from PTRACE_GETSIGINFO).

Notifications are generated both on start and end of group stops but,
because group stop participation always happens before STOP trap, this
doesn't cause an extra trap while tracee is participating in group
stop.  The symmetry will be useful later.

Note that this notification works iff tracee is not trapped.
Currently there is no way to be notified of group stop state changes
while tracee is trapped.  This will be addressed by a later patch.

An example program follows.

  #define PTRACE_SEIZE		0x4206
  #define PTRACE_INTERRUPT	0x4207

  #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);
		  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;
  }

In the above program, tracer keeps tracee running and gets
notification of each group stop state changes.

  # ./test-notify
  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

Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>

Showing 2 changed files with 38 additions and 4 deletions Side-by-side Diff

include/linux/sched.h
... ... @@ -1811,15 +1811,17 @@
1811 1811 #define JOBCTL_STOP_PENDING_BIT 17 /* task should stop for group stop */
1812 1812 #define JOBCTL_STOP_CONSUME_BIT 18 /* consume group stop count */
1813 1813 #define JOBCTL_TRAP_STOP_BIT 19 /* trap for STOP */
  1814 +#define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */
1814 1815 #define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */
1815 1816  
1816 1817 #define JOBCTL_STOP_DEQUEUED (1 << JOBCTL_STOP_DEQUEUED_BIT)
1817 1818 #define JOBCTL_STOP_PENDING (1 << JOBCTL_STOP_PENDING_BIT)
1818 1819 #define JOBCTL_STOP_CONSUME (1 << JOBCTL_STOP_CONSUME_BIT)
1819 1820 #define JOBCTL_TRAP_STOP (1 << JOBCTL_TRAP_STOP_BIT)
  1821 +#define JOBCTL_TRAP_NOTIFY (1 << JOBCTL_TRAP_NOTIFY_BIT)
1820 1822 #define JOBCTL_TRAPPING (1 << JOBCTL_TRAPPING_BIT)
1821 1823  
1822   -#define JOBCTL_TRAP_MASK JOBCTL_TRAP_STOP
  1824 +#define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)
1823 1825 #define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
1824 1826  
1825 1827 extern bool task_set_jobctl_pending(struct task_struct *task,
... ... @@ -817,6 +817,30 @@
817 817 return security_task_kill(t, info, sig, 0);
818 818 }
819 819  
  820 +/**
  821 + * ptrace_trap_notify - schedule trap to notify ptracer
  822 + * @t: tracee wanting to notify tracer
  823 + *
  824 + * This function schedules sticky ptrace trap which is cleared on the next
  825 + * TRAP_STOP to notify ptracer of an event. @t must have been seized by
  826 + * ptracer.
  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.
  831 + *
  832 + * CONTEXT:
  833 + * Must be called with @task->sighand->siglock held.
  834 + */
  835 +static void ptrace_trap_notify(struct task_struct *t)
  836 +{
  837 + WARN_ON_ONCE(!(t->ptrace & PT_SEIZED));
  838 + assert_spin_locked(&t->sighand->siglock);
  839 +
  840 + task_set_jobctl_pending(t, JOBCTL_TRAP_NOTIFY);
  841 + signal_wake_up(t, 0);
  842 +}
  843 +
820 844 /*
821 845 * Handle magic process-wide effects of stop/continue signals. Unlike
822 846 * the signal actions, these happen immediately at signal-generation
... ... @@ -855,7 +879,10 @@
855 879 do {
856 880 task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
857 881 rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
858   - wake_up_state(t, __TASK_STOPPED);
  882 + if (likely(!(t->ptrace & PT_SEIZED)))
  883 + wake_up_state(t, __TASK_STOPPED);
  884 + else
  885 + ptrace_trap_notify(t);
859 886 } while_each_thread(p, t);
860 887  
861 888 /*
862 889  
... ... @@ -1797,8 +1824,10 @@
1797 1824 if (why == CLD_STOPPED && (current->jobctl & JOBCTL_STOP_PENDING))
1798 1825 gstop_done = task_participate_group_stop(current);
1799 1826  
1800   - /* any trap clears pending STOP trap */
  1827 + /* any trap clears pending STOP trap, STOP trap clears NOTIFY */
1801 1828 task_clear_jobctl_pending(current, JOBCTL_TRAP_STOP);
  1829 + if (info && info->si_code >> 8 == PTRACE_EVENT_STOP)
  1830 + task_clear_jobctl_pending(current, JOBCTL_TRAP_NOTIFY);
1802 1831  
1803 1832 /* entering a trap, clear TRAPPING */
1804 1833 task_clear_jobctl_trapping(current);
... ... @@ -1972,7 +2001,10 @@
1972 2001 if (!task_is_stopped(t) &&
1973 2002 task_set_jobctl_pending(t, signr | gstop)) {
1974 2003 sig->group_stop_count++;
1975   - signal_wake_up(t, 0);
  2004 + if (likely(!(t->ptrace & PT_SEIZED)))
  2005 + signal_wake_up(t, 0);
  2006 + else
  2007 + ptrace_trap_notify(t);
1976 2008 }
1977 2009 }
1978 2010 }