Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 25 Mar 2017 13:33:24 +0000 (UTC)
From:      Eric Badger <badger@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org
Subject:   svn commit: r315949 - in stable: 10/sys/kern 10/sys/sys 10/tests/sys/kern 11/sys/kern 11/sys/sys 11/tests/sys/kern
Message-ID:  <201703251333.v2PDXOmS004369@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: badger
Date: Sat Mar 25 13:33:23 2017
New Revision: 315949
URL: https://svnweb.freebsd.org/changeset/base/315949

Log:
  MFC r313992, r314075, r314118, r315484:
  
  r315484:
      ptrace_test: eliminate assumption about thread scheduling
  
      A couple of the ptrace tests make assumptions about which thread in a
      multithreaded process will run after a halt. This makes the tests less
      portable across branches, and susceptible to future breakage. Instead,
      twiddle thread scheduling and priorities to match the tests'
      expectation.
  
  r314118:
      Actually fix buildworlds other than i386/amd64/sparc64 after r313992
  
      Disable offending test for platforms without a userspace visible
      breakpoint().
  
  r314075:
      Fix world build for archs where __builtin_debugtrap() does not work.
  
      The offending code was introduced in r313992.
  
  r313992:
      Defer ptracestop() signals that cannot be delivered immediately
  
      When a thread is stopped in ptracestop(), the ptrace(2) user may request
      a signal be delivered upon resumption of the thread. Heretofore, those signals
      were discarded unless ptracestop()'s caller was issignal(). Fix this by
      modifying ptracestop() to queue up signals requested by the ptrace user that
      will be delivered when possible. Take special care when the signal is SIGKILL
      (usually generated from a PT_KILL request); no new stop events should be
      triggered after a PT_KILL.
  
      Add a number of tests for the new functionality. Several tests were authored
      by jhb.
  
  PR:		212607
  Sponsored by:	Dell EMC

Modified:
  stable/11/sys/kern/kern_fork.c
  stable/11/sys/kern/kern_sig.c
  stable/11/sys/kern/kern_thr.c
  stable/11/sys/kern/subr_syscall.c
  stable/11/sys/kern/sys_process.c
  stable/11/sys/sys/signalvar.h
  stable/11/tests/sys/kern/Makefile
  stable/11/tests/sys/kern/ptrace_test.c
Directory Properties:
  stable/11/   (props changed)

Changes in other areas also in this revision:
Modified:
  stable/10/sys/kern/kern_fork.c
  stable/10/sys/kern/kern_sig.c
  stable/10/sys/kern/kern_thr.c
  stable/10/sys/kern/subr_syscall.c
  stable/10/sys/kern/sys_process.c
  stable/10/sys/sys/signalvar.h
  stable/10/tests/sys/kern/Makefile
  stable/10/tests/sys/kern/ptrace_test.c
Directory Properties:
  stable/10/   (props changed)

Modified: stable/11/sys/kern/kern_fork.c
==============================================================================
--- stable/11/sys/kern/kern_fork.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/kern/kern_fork.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -1083,7 +1083,7 @@ fork_return(struct thread *td, struct tr
 			proc_reparent(p, dbg);
 			sx_xunlock(&proctree_lock);
 			td->td_dbgflags |= TDB_CHILD | TDB_SCX | TDB_FSTP;
-			ptracestop(td, SIGSTOP);
+			ptracestop(td, SIGSTOP, NULL);
 			td->td_dbgflags &= ~(TDB_CHILD | TDB_SCX);
 		} else {
 			/*
@@ -1104,7 +1104,7 @@ fork_return(struct thread *td, struct tr
 		_STOPEVENT(p, S_SCX, td->td_dbg_sc_code);
 		if ((p->p_ptevents & PTRACE_SCX) != 0 ||
 		    (td->td_dbgflags & TDB_BORN) != 0)
-			ptracestop(td, SIGTRAP);
+			ptracestop(td, SIGTRAP, NULL);
 		td->td_dbgflags &= ~(TDB_SCX | TDB_BORN);
 		PROC_UNLOCK(p);
 	}

Modified: stable/11/sys/kern/kern_sig.c
==============================================================================
--- stable/11/sys/kern/kern_sig.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/kern/kern_sig.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -278,6 +278,7 @@ sigqueue_init(sigqueue_t *list, struct p
 {
 	SIGEMPTYSET(list->sq_signals);
 	SIGEMPTYSET(list->sq_kill);
+	SIGEMPTYSET(list->sq_ptrace);
 	TAILQ_INIT(&list->sq_list);
 	list->sq_proc = p;
 	list->sq_flags = SQ_INIT;
@@ -301,9 +302,15 @@ sigqueue_get(sigqueue_t *sq, int signo, 
 	if (!SIGISMEMBER(sq->sq_signals, signo))
 		return (0);
 
+	if (SIGISMEMBER(sq->sq_ptrace, signo)) {
+		count++;
+		SIGDELSET(sq->sq_ptrace, signo);
+		si->ksi_flags |= KSI_PTRACE;
+	}
 	if (SIGISMEMBER(sq->sq_kill, signo)) {
 		count++;
-		SIGDELSET(sq->sq_kill, signo);
+		if (count == 1)
+			SIGDELSET(sq->sq_kill, signo);
 	}
 
 	TAILQ_FOREACH_SAFE(ksi, &sq->sq_list, ksi_link, next) {
@@ -347,7 +354,8 @@ sigqueue_take(ksiginfo_t *ksi)
 		if (kp->ksi_signo == ksi->ksi_signo)
 			break;
 	}
-	if (kp == NULL && !SIGISMEMBER(sq->sq_kill, ksi->ksi_signo))
+	if (kp == NULL && !SIGISMEMBER(sq->sq_kill, ksi->ksi_signo) &&
+	    !SIGISMEMBER(sq->sq_ptrace, ksi->ksi_signo))
 		SIGDELSET(sq->sq_signals, ksi->ksi_signo);
 }
 
@@ -360,6 +368,10 @@ sigqueue_add(sigqueue_t *sq, int signo, 
 
 	KASSERT(sq->sq_flags & SQ_INIT, ("sigqueue not inited"));
 
+	/*
+	 * SIGKILL/SIGSTOP cannot be caught or masked, so take the fast path
+	 * for these signals.
+	 */
 	if (signo == SIGKILL || signo == SIGSTOP || si == NULL) {
 		SIGADDSET(sq->sq_kill, signo);
 		goto out_set_bit;
@@ -398,16 +410,19 @@ sigqueue_add(sigqueue_t *sq, int signo, 
 		ksi->ksi_sigq = sq;
 	}
 
-	if ((si->ksi_flags & KSI_TRAP) != 0 ||
-	    (si->ksi_flags & KSI_SIGQ) == 0) {
-		if (ret != 0)
+	if (ret != 0) {
+		if ((si->ksi_flags & KSI_PTRACE) != 0) {
+			SIGADDSET(sq->sq_ptrace, signo);
+			ret = 0;
+			goto out_set_bit;
+		} else if ((si->ksi_flags & KSI_TRAP) != 0 ||
+		    (si->ksi_flags & KSI_SIGQ) == 0) {
 			SIGADDSET(sq->sq_kill, signo);
-		ret = 0;
-		goto out_set_bit;
-	}
-
-	if (ret != 0)
+			ret = 0;
+			goto out_set_bit;
+		}
 		return (ret);
+	}
 
 out_set_bit:
 	SIGADDSET(sq->sq_signals, signo);
@@ -434,6 +449,7 @@ sigqueue_flush(sigqueue_t *sq)
 
 	SIGEMPTYSET(sq->sq_signals);
 	SIGEMPTYSET(sq->sq_kill);
+	SIGEMPTYSET(sq->sq_ptrace);
 }
 
 static void
@@ -466,6 +482,11 @@ sigqueue_move_set(sigqueue_t *src, sigqu
 	SIGSETOR(dst->sq_kill, tmp);
 	SIGSETNAND(src->sq_kill, tmp);
 
+	tmp = src->sq_ptrace;
+	SIGSETAND(tmp, *set);
+	SIGSETOR(dst->sq_ptrace, tmp);
+	SIGSETNAND(src->sq_ptrace, tmp);
+
 	tmp = src->sq_signals;
 	SIGSETAND(tmp, *set);
 	SIGSETOR(dst->sq_signals, tmp);
@@ -502,6 +523,7 @@ sigqueue_delete_set(sigqueue_t *sq, cons
 		}
 	}
 	SIGSETNAND(sq->sq_kill, *set);
+	SIGSETNAND(sq->sq_ptrace, *set);
 	SIGSETNAND(sq->sq_signals, *set);
 }
 
@@ -2501,69 +2523,116 @@ sig_suspend_threads(struct thread *td, s
 	return (wakeup_swapper);
 }
 
+/*
+ * Stop the process for an event deemed interesting to the debugger. If si is
+ * non-NULL, this is a signal exchange; the new signal requested by the
+ * debugger will be returned for handling. If si is NULL, this is some other
+ * type of interesting event. The debugger may request a signal be delivered in
+ * that case as well, however it will be deferred until it can be handled.
+ */
 int
-ptracestop(struct thread *td, int sig)
+ptracestop(struct thread *td, int sig, ksiginfo_t *si)
 {
 	struct proc *p = td->td_proc;
+	struct thread *td2;
+	ksiginfo_t ksi;
+	int prop;
 
 	PROC_LOCK_ASSERT(p, MA_OWNED);
 	KASSERT(!(p->p_flag & P_WEXIT), ("Stopping exiting process"));
 	WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK,
 	    &p->p_mtx.lock_object, "Stopping for traced signal");
 
-	td->td_dbgflags |= TDB_XSIG;
 	td->td_xsig = sig;
-	CTR4(KTR_PTRACE, "ptracestop: tid %d (pid %d) flags %#x sig %d",
-	    td->td_tid, p->p_pid, td->td_dbgflags, sig);
-	PROC_SLOCK(p);
-	while ((p->p_flag & P_TRACED) && (td->td_dbgflags & TDB_XSIG)) {
-		if (p->p_flag & P_SINGLE_EXIT &&
-		    !(td->td_dbgflags & TDB_EXIT)) {
+
+	if (si == NULL || (si->ksi_flags & KSI_PTRACE) == 0) {
+		td->td_dbgflags |= TDB_XSIG;
+		CTR4(KTR_PTRACE, "ptracestop: tid %d (pid %d) flags %#x sig %d",
+		    td->td_tid, p->p_pid, td->td_dbgflags, sig);
+		PROC_SLOCK(p);
+		while ((p->p_flag & P_TRACED) && (td->td_dbgflags & TDB_XSIG)) {
+			if (P_KILLED(p)) {
+				/*
+				 * Ensure that, if we've been PT_KILLed, the
+				 * exit status reflects that. Another thread
+				 * may also be in ptracestop(), having just
+				 * received the SIGKILL, but this thread was
+				 * unsuspended first.
+				 */
+				td->td_dbgflags &= ~TDB_XSIG;
+				td->td_xsig = SIGKILL;
+				p->p_ptevents = 0;
+				break;
+			}
+			if (p->p_flag & P_SINGLE_EXIT &&
+			    !(td->td_dbgflags & TDB_EXIT)) {
+				/*
+				 * Ignore ptrace stops except for thread exit
+				 * events when the process exits.
+				 */
+				td->td_dbgflags &= ~TDB_XSIG;
+				PROC_SUNLOCK(p);
+				return (0);
+			}
+
 			/*
-			 * Ignore ptrace stops except for thread exit
-			 * events when the process exits.
+			 * Make wait(2) work.  Ensure that right after the
+			 * attach, the thread which was decided to become the
+			 * leader of attach gets reported to the waiter.
+			 * Otherwise, just avoid overwriting another thread's
+			 * assignment to p_xthread.  If another thread has
+			 * already set p_xthread, the current thread will get
+			 * a chance to report itself upon the next iteration.
 			 */
-			td->td_dbgflags &= ~TDB_XSIG;
-			PROC_SUNLOCK(p);
-			return (sig);
-		}
-
-		/*
-		 * Make wait(2) work.  Ensure that right after the
-		 * attach, the thread which was decided to become the
-		 * leader of attach gets reported to the waiter.
-		 * Otherwise, just avoid overwriting another thread's
-		 * assignment to p_xthread.  If another thread has
-		 * already set p_xthread, the current thread will get
-		 * a chance to report itself upon the next iteration.
-		 */
-		if ((td->td_dbgflags & TDB_FSTP) != 0 ||
-		    ((p->p_flag2 & P2_PTRACE_FSTP) == 0 &&
-		    p->p_xthread == NULL)) {
-			p->p_xsig = sig;
-			p->p_xthread = td;
-			td->td_dbgflags &= ~TDB_FSTP;
-			p->p_flag2 &= ~P2_PTRACE_FSTP;
-			p->p_flag |= P_STOPPED_SIG | P_STOPPED_TRACE;
-			sig_suspend_threads(td, p, 0);
-		}
-		if ((td->td_dbgflags & TDB_STOPATFORK) != 0) {
-			td->td_dbgflags &= ~TDB_STOPATFORK;
-			cv_broadcast(&p->p_dbgwait);
-		}
+			if ((td->td_dbgflags & TDB_FSTP) != 0 ||
+			    ((p->p_flag2 & P2_PTRACE_FSTP) == 0 &&
+			    p->p_xthread == NULL)) {
+				p->p_xsig = sig;
+				p->p_xthread = td;
+				td->td_dbgflags &= ~TDB_FSTP;
+				p->p_flag2 &= ~P2_PTRACE_FSTP;
+				p->p_flag |= P_STOPPED_SIG | P_STOPPED_TRACE;
+				sig_suspend_threads(td, p, 0);
+			}
+			if ((td->td_dbgflags & TDB_STOPATFORK) != 0) {
+				td->td_dbgflags &= ~TDB_STOPATFORK;
+				cv_broadcast(&p->p_dbgwait);
+			}
 stopme:
-		thread_suspend_switch(td, p);
-		if (p->p_xthread == td)
-			p->p_xthread = NULL;
-		if (!(p->p_flag & P_TRACED))
-			break;
-		if (td->td_dbgflags & TDB_SUSPEND) {
-			if (p->p_flag & P_SINGLE_EXIT)
+			thread_suspend_switch(td, p);
+			if (p->p_xthread == td)
+				p->p_xthread = NULL;
+			if (!(p->p_flag & P_TRACED))
 				break;
-			goto stopme;
+			if (td->td_dbgflags & TDB_SUSPEND) {
+				if (p->p_flag & P_SINGLE_EXIT)
+					break;
+				goto stopme;
+			}
 		}
+		PROC_SUNLOCK(p);
 	}
-	PROC_SUNLOCK(p);
+
+	if (si != NULL && sig == td->td_xsig) {
+		/* Parent wants us to take the original signal unchanged. */
+		si->ksi_flags |= KSI_HEAD;
+		if (sigqueue_add(&td->td_sigqueue, sig, si) != 0)
+			si->ksi_signo = 0;
+	} else if (td->td_xsig != 0) {
+		/*
+		 * If parent wants us to take a new signal, then it will leave
+		 * it in td->td_xsig; otherwise we just look for signals again.
+		 */
+		ksiginfo_init(&ksi);
+		ksi.ksi_signo = td->td_xsig;
+		ksi.ksi_flags |= KSI_PTRACE;
+		prop = sigprop(td->td_xsig);
+		td2 = sigtd(p, td->td_xsig, prop);
+		tdsendsignal(p, td2, td->td_xsig, &ksi);
+		if (td != td2)
+			return (0);
+	}
+
 	return (td->td_xsig);
 }
 
@@ -2721,7 +2790,7 @@ issignal(struct thread *td)
 	struct sigacts *ps;
 	struct sigqueue *queue;
 	sigset_t sigpending;
-	int sig, prop, newsig;
+	int sig, prop;
 
 	p = td->td_proc;
 	ps = p->p_sigacts;
@@ -2784,47 +2853,18 @@ issignal(struct thread *td)
 			}
 
 			mtx_unlock(&ps->ps_mtx);
-			newsig = ptracestop(td, sig);
+			sig = ptracestop(td, sig, &td->td_dbgksi);
 			mtx_lock(&ps->ps_mtx);
 
-			if (sig != newsig) {
-
-				/*
-				 * If parent wants us to take the signal,
-				 * then it will leave it in p->p_xsig;
-				 * otherwise we just look for signals again.
-				*/
-				if (newsig == 0)
-					continue;
-				sig = newsig;
-
-				/*
-				 * Put the new signal into td_sigqueue. If the
-				 * signal is being masked, look for other
-				 * signals.
-				 */
-				sigqueue_add(queue, sig, NULL);
-				if (SIGISMEMBER(td->td_sigmask, sig))
-					continue;
-				signotify(td);
-			} else {
-				if (td->td_dbgksi.ksi_signo != 0) {
-					td->td_dbgksi.ksi_flags |= KSI_HEAD;
-					if (sigqueue_add(&td->td_sigqueue, sig,
-					    &td->td_dbgksi) != 0)
-						td->td_dbgksi.ksi_signo = 0;
-				}
-				if (td->td_dbgksi.ksi_signo == 0)
-					sigqueue_add(&td->td_sigqueue, sig,
-					    NULL);
-			}
-
-			/*
+			/* 
+			 * Keep looking if the debugger discarded the signal
+			 * or replaced it with a masked signal.
+			 *
 			 * If the traced bit got turned off, go back up
 			 * to the top to rescan signals.  This ensures
 			 * that p_sig* and p_sigact are consistent.
 			 */
-			if ((p->p_flag & P_TRACED) == 0)
+			if (sig == 0 || (p->p_flag & P_TRACED) == 0)
 				continue;
 		}
 

Modified: stable/11/sys/kern/kern_thr.c
==============================================================================
--- stable/11/sys/kern/kern_thr.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/kern/kern_thr.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -357,7 +357,7 @@ kern_thr_exit(struct thread *td)
 	p->p_pendingexits++;
 	td->td_dbgflags |= TDB_EXIT;
 	if (p->p_ptevents & PTRACE_LWP)
-		ptracestop(td, SIGTRAP);
+		ptracestop(td, SIGTRAP, NULL);
 	PROC_UNLOCK(p);
 	tidhash_remove(td);
 	PROC_LOCK(p);

Modified: stable/11/sys/kern/subr_syscall.c
==============================================================================
--- stable/11/sys/kern/subr_syscall.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/kern/subr_syscall.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -88,7 +88,7 @@ syscallenter(struct thread *td, struct s
 			td->td_dbg_sc_code = sa->code;
 			td->td_dbg_sc_narg = sa->narg;
 			if (p->p_ptevents & PTRACE_SCE)
-				ptracestop((td), SIGTRAP);
+				ptracestop((td), SIGTRAP, NULL);
 			PROC_UNLOCK(p);
 		}
 		if (td->td_dbgflags & TDB_USERWR) {
@@ -222,7 +222,7 @@ syscallret(struct thread *td, int error,
 		if (traced &&
 		    ((td->td_dbgflags & (TDB_FORK | TDB_EXEC)) != 0 ||
 		    (p->p_ptevents & PTRACE_SCX) != 0))
-			ptracestop(td, SIGTRAP);
+			ptracestop(td, SIGTRAP, NULL);
 		td->td_dbgflags &= ~(TDB_SCX | TDB_EXEC | TDB_FORK);
 		PROC_UNLOCK(p);
 	}
@@ -259,7 +259,7 @@ again:
 		if (td->td_dbgflags & TDB_VFORK) {
 			PROC_LOCK(p);
 			if (p->p_ptevents & PTRACE_VFORK)
-				ptracestop(td, SIGTRAP);
+				ptracestop(td, SIGTRAP, NULL);
 			td->td_dbgflags &= ~TDB_VFORK;
 			PROC_UNLOCK(p);
 		}

Modified: stable/11/sys/kern/sys_process.c
==============================================================================
--- stable/11/sys/kern/sys_process.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/kern/sys_process.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -1125,6 +1125,16 @@ kern_ptrace(struct thread *td, int req, 
 			td2->td_dbgflags &= ~TDB_XSIG;
 			td2->td_xsig = data;
 
+			/*
+			 * P_WKILLED is insurance that a PT_KILL/SIGKILL always
+			 * works immediately, even if another thread is
+			 * unsuspended first and attempts to handle a different
+			 * signal or if the POSIX.1b style signal queue cannot
+			 * accommodate any new signals.
+			 */
+			if (data == SIGKILL)
+				p->p_flag |= P_WKILLED;
+
 			if (req == PT_DETACH) {
 				FOREACH_THREAD_IN_PROC(p, td3)
 					td3->td_dbgflags &= ~TDB_SUSPEND;

Modified: stable/11/sys/sys/signalvar.h
==============================================================================
--- stable/11/sys/sys/signalvar.h	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/sys/sys/signalvar.h	Sat Mar 25 13:33:23 2017	(r315949)
@@ -237,13 +237,15 @@ typedef struct ksiginfo {
 #define	KSI_INS		0x04	/* Directly insert ksi, not the copy */
 #define	KSI_SIGQ	0x08	/* Generated by sigqueue, might ret EAGAIN. */
 #define	KSI_HEAD	0x10	/* Insert into head, not tail. */
-#define	KSI_COPYMASK	(KSI_TRAP|KSI_SIGQ)
+#define	KSI_PTRACE	0x20	/* Generated by ptrace. */
+#define	KSI_COPYMASK	(KSI_TRAP | KSI_SIGQ | KSI_PTRACE)
 
 #define	KSI_ONQ(ksi)	((ksi)->ksi_sigq != NULL)
 
 typedef struct sigqueue {
 	sigset_t	sq_signals;	/* All pending signals. */
 	sigset_t	sq_kill;	/* Legacy depth 1 queue. */
+	sigset_t	sq_ptrace;	/* Depth 1 queue for ptrace(2). */
 	TAILQ_HEAD(, ksiginfo)	sq_list;/* Queued signal info. */
 	struct proc	*sq_proc;
 	int		sq_flags;
@@ -370,7 +372,7 @@ void	pgsigio(struct sigio **sigiop, int 
 void	pgsignal(struct pgrp *pgrp, int sig, int checkctty, ksiginfo_t *ksi);
 int	postsig(int sig);
 void	kern_psignal(struct proc *p, int sig);
-int	ptracestop(struct thread *td, int sig);
+int	ptracestop(struct thread *td, int sig, ksiginfo_t *si);
 void	sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *retmask);
 struct sigacts *sigacts_alloc(void);
 void	sigacts_copy(struct sigacts *dest, struct sigacts *src);

Modified: stable/11/tests/sys/kern/Makefile
==============================================================================
--- stable/11/tests/sys/kern/Makefile	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/tests/sys/kern/Makefile	Sat Mar 25 13:33:23 2017	(r315949)
@@ -8,6 +8,7 @@ TESTSDIR=	${TESTSBASE}/sys/kern
 ATF_TESTS_C+=	kern_copyin
 ATF_TESTS_C+=	kern_descrip_test
 ATF_TESTS_C+=	ptrace_test
+TEST_METADATA.ptrace_test+=		timeout="15"
 ATF_TESTS_C+=	reaper
 PLAIN_TESTS_C+=	subr_unit_test
 ATF_TESTS_C+=	unix_seqpacket_test

Modified: stable/11/tests/sys/kern/ptrace_test.c
==============================================================================
--- stable/11/tests/sys/kern/ptrace_test.c	Sat Mar 25 13:32:28 2017	(r315948)
+++ stable/11/tests/sys/kern/ptrace_test.c	Sat Mar 25 13:33:23 2017	(r315949)
@@ -28,13 +28,21 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/types.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/time.h>
 #include <sys/ptrace.h>
+#include <sys/queue.h>
+#include <sys/runq.h>
 #include <sys/syscall.h>
 #include <sys/sysctl.h>
 #include <sys/user.h>
 #include <sys/wait.h>
 #include <errno.h>
+#include <machine/cpufunc.h>
 #include <pthread.h>
+#include <sched.h>
+#include <semaphore.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -1673,6 +1681,1143 @@ ATF_TC_BODY(ptrace__ptrace_vfork_follow,
 	ATF_REQUIRE(errno == ECHILD);
 }
 
+/*
+ * XXX: There's nothing inherently platform specific about this test, however a
+ * userspace visible breakpoint() is a prerequisite.
+ */
+ #if defined(__amd64__) || defined(__i386__) || defined(__sparc64__)
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when stopped due to a breakpoint trap.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_breakpoint);
+ATF_TC_BODY(ptrace__PT_KILL_breakpoint, tc)
+{
+	pid_t fpid, wpid;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		breakpoint();
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+	/* The second wait() should report hitting the breakpoint. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+	/* Kill the child process. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+#endif /* defined(__amd64__) || defined(__i386__) || defined(__sparc64__) */
+
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when stopped inside of a system call.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_system_call);
+ATF_TC_BODY(ptrace__PT_KILL_system_call, tc)
+{
+	struct ptrace_lwpinfo pl;
+	pid_t fpid, wpid;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		getpid();
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	/* Continue the child ignoring the SIGSTOP and tracing system calls. */
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	/* The second wait() should report a system call entry for getpid(). */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+	ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE);
+
+	/* Kill the child process. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when killing a multithreaded process.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_threads);
+ATF_TC_BODY(ptrace__PT_KILL_threads, tc)
+{
+	struct ptrace_lwpinfo pl;
+	pid_t fpid, wpid;
+	lwpid_t main_lwp;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		simple_thread_main();
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+	    sizeof(pl)) != -1);
+	main_lwp = pl.pl_lwpid;
+
+	ATF_REQUIRE(ptrace(PT_LWP_EVENTS, wpid, NULL, 1) == 0);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+	/* The first event should be for the child thread's birth. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+		
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+	ATF_REQUIRE((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)) ==
+	    (PL_FLAG_BORN | PL_FLAG_SCX));
+	ATF_REQUIRE(pl.pl_lwpid != main_lwp);
+
+	/* Kill the child process. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+static void *
+mask_usr1_thread(void *arg)
+{
+	pthread_barrier_t *pbarrier;
+	sigset_t sigmask;
+
+	pbarrier = (pthread_barrier_t*)arg;
+
+	sigemptyset(&sigmask);
+	sigaddset(&sigmask, SIGUSR1);
+	CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+	/* Sync up with other thread after sigmask updated. */
+	pthread_barrier_wait(pbarrier);
+
+	for (;;)
+		sleep(60);
+
+	return (NULL);
+}
+
+/*
+ * Verify that the SIGKILL from PT_KILL takes priority over other signals
+ * and prevents spurious stops due to those other signals.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_competing_signal);
+ATF_TC_BODY(ptrace__PT_KILL_competing_signal, tc)
+{
+	pid_t fpid, wpid;
+	int status;
+	cpuset_t setmask;
+	pthread_t t;
+	pthread_barrier_t barrier;
+	struct sched_param sched_param;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		/* Bind to one CPU so only one thread at a time will run. */
+		CPU_ZERO(&setmask);
+		CPU_SET(0, &setmask);
+		cpusetid_t setid;
+		CHILD_REQUIRE(cpuset(&setid) == 0);
+		CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET,
+		    CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0);
+
+		CHILD_REQUIRE(pthread_barrier_init(&barrier, NULL, 2) == 0);
+
+		CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread,
+		    (void*)&barrier) == 0);
+
+		/*
+		 * Give the main thread higher priority. The test always
+		 * assumes that, if both threads are able to run, the main
+		 * thread runs first.
+		 */
+		sched_param.sched_priority =
+		    (sched_get_priority_max(SCHED_FIFO) +
+		    sched_get_priority_min(SCHED_FIFO)) / 2;
+		CHILD_REQUIRE(pthread_setschedparam(pthread_self(),
+		    SCHED_FIFO, &sched_param) == 0);
+		sched_param.sched_priority -= RQ_PPQ;
+		CHILD_REQUIRE(pthread_setschedparam(t, SCHED_FIFO,
+		    &sched_param) == 0);
+
+		sigset_t sigmask;
+		sigemptyset(&sigmask);
+		sigaddset(&sigmask, SIGUSR2);
+		CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+		/* Sync up with other thread after sigmask updated. */
+		pthread_barrier_wait(&barrier);
+
+		trace_me();
+
+		for (;;)
+			sleep(60);
+
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+	/* Send a signal that only the second thread can handle. */
+	ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+	/* The second wait() should report the SIGUSR2. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+	/* Send a signal that only the first thread can handle. */
+	ATF_REQUIRE(kill(fpid, SIGUSR1) == 0);
+
+	/* Replace the SIGUSR2 with a kill. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL (not the SIGUSR signal). */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that the SIGKILL from PT_KILL takes priority over other stop events
+ * and prevents spurious stops caused by those events.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_competing_stop);
+ATF_TC_BODY(ptrace__PT_KILL_competing_stop, tc)
+{
+	pid_t fpid, wpid;
+	int status;
+	cpuset_t setmask;
+	pthread_t t;
+	pthread_barrier_t barrier;
+	lwpid_t main_lwp;
+	struct ptrace_lwpinfo pl;
+	struct sched_param sched_param;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+
+		/* Bind to one CPU so only one thread at a time will run. */
+		CPU_ZERO(&setmask);
+		CPU_SET(0, &setmask);
+		cpusetid_t setid;
+		CHILD_REQUIRE(cpuset(&setid) == 0);
+		CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET,
+		    CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0);
+
+		CHILD_REQUIRE(pthread_barrier_init(&barrier, NULL, 2) == 0);
+
+		CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread,
+		    (void*)&barrier) == 0);
+
+		/*
+		 * Give the main thread higher priority. The test always
+		 * assumes that, if both threads are able to run, the main
+		 * thread runs first.
+		 */
+		sched_param.sched_priority =
+		    (sched_get_priority_max(SCHED_FIFO) +
+		    sched_get_priority_min(SCHED_FIFO)) / 2;
+		CHILD_REQUIRE(pthread_setschedparam(pthread_self(),
+		    SCHED_FIFO, &sched_param) == 0);
+		sched_param.sched_priority -= RQ_PPQ;
+		CHILD_REQUIRE(pthread_setschedparam(t, SCHED_FIFO,
+		    &sched_param) == 0);
+
+		sigset_t sigmask;
+		sigemptyset(&sigmask);
+		sigaddset(&sigmask, SIGUSR2);
+		CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+		/* Sync up with other thread after sigmask updated. */
+		pthread_barrier_wait(&barrier);
+
+		/* Sync up with the test before doing the getpid(). */
+		raise(SIGSTOP);
+
+		getpid();
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+	main_lwp = pl.pl_lwpid;
+
+	/* Continue the child ignoring the SIGSTOP and tracing system calls. */
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	/*
+	 * Continue until child is done with setup, which is indicated with
+	 * SIGSTOP. Ignore system calls in the meantime.
+	 */
+	for (;;) {
+		wpid = waitpid(fpid, &status, 0);
+		ATF_REQUIRE(wpid == fpid);
+		ATF_REQUIRE(WIFSTOPPED(status));
+		if (WSTOPSIG(status) == SIGTRAP) {
+			ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+			    sizeof(pl)) != -1);
+			ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+		} else {
+			ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+			break;
+		}
+		ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+	}
+
+	/* Proceed, allowing main thread to hit syscall entry for getpid(). */
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+	    sizeof(pl)) != -1);
+	ATF_REQUIRE(pl.pl_lwpid == main_lwp);
+	ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE);
+	/* Prevent the main thread from hitting its syscall exit for now. */
+	ATF_REQUIRE(ptrace(PT_SUSPEND, main_lwp, 0, 0) == 0);
+
+	/*
+	 * Proceed, allowing second thread to hit syscall exit for
+	 * pthread_barrier_wait().
+	 */
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+	ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+	    sizeof(pl)) != -1);
+	ATF_REQUIRE(pl.pl_lwpid != main_lwp);
+	ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX);
+
+	/* Send a signal that only the second thread can handle. */
+	ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	/* The next wait() should report the SIGUSR2. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+	/* Allow the main thread to try to finish its system call. */
+	ATF_REQUIRE(ptrace(PT_RESUME, main_lwp, 0, 0) == 0);
+
+	/*
+	 * At this point, the main thread is in the middle of a system call and
+	 * has been resumed. The second thread has taken a SIGUSR2 which will
+	 * be replaced with a SIGKILL below. The main thread will get to run
+	 * first. It should notice the kill request (even though the signal
+	 * replacement occurred in the other thread) and exit accordingly.  It
+	 * should not stop for the system call exit event.
+	 */
+
+	/* Replace the SIGUSR2 with a kill. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL (not a syscall exit). */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+sigusr1_handler(int sig)
+{
+
+	CHILD_REQUIRE(sig == SIGUSR1);
+	_exit(2);
+}
+
+/*
+ * Verify that even if the signal queue is full for a child process,
+ * a PT_KILL will kill the process.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_with_signal_full_sigqueue);
+ATF_TC_BODY(ptrace__PT_KILL_with_signal_full_sigqueue, tc)
+{
+	pid_t fpid, wpid;
+	int status;
+	int max_pending_per_proc;
+	size_t len;
+	int i;
+
+	ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR);
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	len = sizeof(max_pending_per_proc);
+	ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc",
+	    &max_pending_per_proc, &len, NULL, 0) == 0);
+
+	/* Fill the signal queue. */
+	for (i = 0; i < max_pending_per_proc; ++i)
+		ATF_REQUIRE(kill(fpid, SIGUSR1) == 0);
+
+	/* Kill the child process. */
+	ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+	/* The last wait() should report the SIGKILL. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSIGNALED(status));
+	ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that when stopped at a system call entry, a signal can be
+ * requested with PT_CONTINUE which will be delivered once the system
+ * call is complete.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_system_call_entry);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_system_call_entry, tc)
+{
+	struct ptrace_lwpinfo pl;
+	pid_t fpid, wpid;
+	int status;
+
+	ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR);
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		getpid();
+		exit(1);
+	}
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	/* Continue the child ignoring the SIGSTOP and tracing system calls. */
+	ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+	/* The second wait() should report a system call entry for getpid(). */

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201703251333.v2PDXOmS004369>