• T
    job control: Notify the real parent of job control events regardless of ptrace · ceb6bd67
    Tejun Heo 提交于
    With recent changes, job control and ptrace stopped states are
    properly separated and accessible to the real parent and the ptracer
    respectively; however, notifications of job control stopped/continued
    events to the real parent while ptraced are still missing.
    
    A ptracee participates in group stop in ptrace_stop() but the
    completion isn't notified.  If participation results in completion of
    group stop, notify the real parent of the event.  The ptrace and group
    stops are separate and can be handled as such.
    
    However, when the real parent and the ptracer are in the same thread
    group, only the ptrace stop event is visible through wait(2) and the
    duplicate notifications are different from the current behavior and
    are confusing.  Suppress group stop notification in such cases.
    
    The continued state is shared between the real parent and the ptracer
    but is only meaningful to the real parent.  Always notify the real
    parent and notify the ptracer too for backward compatibility.  Similar
    to stop notification, if the real parent is the ptracer, suppress a
    duplicate notification.
    
    Test case follows.
    
      #include <stdio.h>
      #include <unistd.h>
      #include <time.h>
      #include <errno.h>
      #include <sys/types.h>
      #include <sys/ptrace.h>
      #include <sys/wait.h>
    
      int main(void)
      {
    	  const struct timespec ts100ms = { .tv_nsec = 100000000 };
    	  pid_t tracee, tracer;
    	  siginfo_t si;
    	  int i;
    
    	  tracee = fork();
    	  if (tracee == 0) {
    		  while (1) {
    			  printf("tracee: SIGSTOP\n");
    			  raise(SIGSTOP);
    			  nanosleep(&ts100ms, NULL);
    			  printf("tracee: SIGCONT\n");
    			  raise(SIGCONT);
    			  nanosleep(&ts100ms, NULL);
    		  }
    	  }
    
    	  waitid(P_PID, tracee, &si, WSTOPPED | WNOHANG | WNOWAIT);
    
    	  tracer = fork();
    	  if (tracer == 0) {
    		  nanosleep(&ts100ms, NULL);
    		  ptrace(PTRACE_ATTACH, tracee, NULL, NULL);
    
    		  for (i = 0; i < 11; i++) {
    			  si.si_pid = 0;
    			  waitid(P_PID, tracee, &si, WSTOPPED);
    			  if (si.si_pid && si.si_code == CLD_TRAPPED)
    				  ptrace(PTRACE_CONT, tracee, NULL,
    					 (void *)(long)si.si_status);
    		  }
    		  printf("tracer: EXITING\n");
    		  return 0;
    	  }
    
    	  while (1) {
    		  si.si_pid = 0;
    		  waitid(P_PID, tracee, &si, WSTOPPED | WCONTINUED | WEXITED);
    		  if (si.si_pid)
    			  printf("mommy : WAIT status=%02d code=%02d\n",
    				 si.si_status, si.si_code);
    	  }
    	  return 0;
      }
    
    Before this patch, while ptraced, the real parent doesn't get
    notifications for job control events, so although it can access those
    events, the later waitid(2) call never wakes up.
    
      tracee: SIGSTOP
      mommy : WAIT status=19 code=05
      tracee: SIGCONT
      tracee: SIGSTOP
      tracee: SIGCONT
      tracee: SIGSTOP
      tracee: SIGCONT
      tracee: SIGSTOP
      tracer: EXITING
      mommy : WAIT status=19 code=05
      ^C
    
    After this patch, it works as expected.
    
      tracee: SIGSTOP
      mommy : WAIT status=19 code=05
      tracee: SIGCONT
      mommy : WAIT status=18 code=06
      tracee: SIGSTOP
      mommy : WAIT status=19 code=05
      tracee: SIGCONT
      mommy : WAIT status=18 code=06
      tracee: SIGSTOP
      mommy : WAIT status=19 code=05
      tracee: SIGCONT
      mommy : WAIT status=18 code=06
      tracee: SIGSTOP
      tracer: EXITING
      mommy : WAIT status=19 code=05
      ^C
    
    -v2: Oleg pointed out that
    
         * Group stop notification to the real parent should also happen
           when ptracer detach races with ptrace_stop().
    
         * real_parent_is_ptracer() should be testing thread group
           equality not the task itself as wait(2) and stop/cont
           notifications are normally thread-group wide.
    
         Both issues are fixed accordingly.
    
    -v3: real_parent_is_ptracer() updated to test child->real_parent
         instead of child->group_leader->real_parent per Oleg's
         suggestion.
    Signed-off-by: NTejun Heo <tj@kernel.org>
    Acked-by: NOleg Nesterov <oleg@redhat.com>
    ceb6bd67
signal.c 76.3 KB