Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 1 Aug 2015 16:27:53 +0000 (UTC)
From:      John Baldwin <jhb@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r286158 - in head: sys/kern tests/sys/kern
Message-ID:  <201508011627.t71GRr9D025781@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jhb
Date: Sat Aug  1 16:27:52 2015
New Revision: 286158
URL: https://svnweb.freebsd.org/changeset/base/286158

Log:
  Clear P_TRACED before reparenting a detached process back to its
  original parent. Otherwise the debugee will be set as an orphan of
  the debugger.
  
  Add tests for tracing forks via PT_FOLLOW_FORK.
  
  Reviewed by:	kib
  MFC after:	2 weeks
  Differential Revision:	https://reviews.freebsd.org/D2809

Modified:
  head/sys/kern/sys_process.c
  head/tests/sys/kern/ptrace_test.c

Modified: head/sys/kern/sys_process.c
==============================================================================
--- head/sys/kern/sys_process.c	Sat Aug  1 12:20:55 2015	(r286157)
+++ head/sys/kern/sys_process.c	Sat Aug  1 16:27:52 2015	(r286158)
@@ -947,7 +947,15 @@ kern_ptrace(struct thread *td, int req, 
 			}
 			break;
 		case PT_DETACH:
-			/* reset process parent */
+			/*
+			 * Reset the process parent.
+			 *
+			 * NB: This clears P_TRACED before reparenting
+			 * a detached process back to its original
+			 * parent.  Otherwise the debugee will be set
+			 * as an orphan of the debugger.
+			 */
+			p->p_flag &= ~(P_TRACED | P_WAITED | P_FOLLOWFORK);
 			if (p->p_oppid != p->p_pptr->p_pid) {
 				PROC_LOCK(p->p_pptr);
 				sigqueue_take(p->p_ksi);
@@ -963,7 +971,6 @@ kern_ptrace(struct thread *td, int req, 
 			} else
 				CTR1(KTR_PTRACE, "PT_DETACH: pid %d", p->p_pid);
 			p->p_oppid = 0;
-			p->p_flag &= ~(P_TRACED | P_WAITED | P_FOLLOWFORK);
 			p->p_stops = 0;
 
 			/* should we send SIGCHLD? */

Modified: head/tests/sys/kern/ptrace_test.c
==============================================================================
--- head/tests/sys/kern/ptrace_test.c	Sat Aug  1 12:20:55 2015	(r286157)
+++ head/tests/sys/kern/ptrace_test.c	Sat Aug  1 16:27:52 2015	(r286158)
@@ -50,7 +50,7 @@ __FBSDID("$FreeBSD$");
 			    #exp " not met");				\
 	} while (0)
 
-static void __dead2
+static __dead2 void
 child_fail_require(const char *file, int line, const char *str)
 {
 	char buf[128];
@@ -60,6 +60,58 @@ child_fail_require(const char *file, int
 	_exit(32);
 }
 
+static void
+trace_me(void)
+{
+
+	/* Attach the parent process as a tracer of this process. */
+	CHILD_REQUIRE(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
+
+	/* Trigger a stop. */
+	raise(SIGSTOP);
+}
+
+static void
+attach_child(pid_t pid)
+{
+	pid_t wpid;
+	int status;
+
+	ATF_REQUIRE(ptrace(PT_ATTACH, pid, NULL, 0) == 0);
+
+	wpid = waitpid(pid, &status, 0);
+	ATF_REQUIRE(wpid == pid);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+}
+
+static void
+wait_for_zombie(pid_t pid)
+{
+
+	/*
+	 * Wait for a process to exit.  This is kind of gross, but
+	 * there is not a better way.
+	 */
+	for (;;) {
+		struct kinfo_proc kp;
+		size_t len;
+		int mib[4];
+
+		mib[0] = CTL_KERN;
+		mib[1] = KERN_PROC;
+		mib[2] = KERN_PROC_PID;
+		mib[3] = pid;
+		len = sizeof(kp);
+		if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
+			/* The KERN_PROC_PID sysctl fails for zombies. */
+			ATF_REQUIRE(errno == ESRCH);
+			break;
+		}
+		usleep(5000);
+	}
+}
+
 /*
  * Verify that a parent debugger process "sees" the exit of a debugged
  * process exactly once when attached via PT_TRACE_ME.
@@ -73,10 +125,7 @@ ATF_TC_BODY(ptrace__parent_wait_after_tr
 	ATF_REQUIRE((child = fork()) != -1);
 	if (child == 0) {
 		/* Child process. */
-		CHILD_REQUIRE(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
-
-		/* Trigger a stop. */
-		raise(SIGSTOP);
+		trace_me();
 
 		exit(1);
 	}
@@ -131,13 +180,7 @@ ATF_TC_BODY(ptrace__parent_wait_after_at
 	/* Parent process. */
 
 	/* Attach to the child process. */
-	ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) == 0);
-
-	/* The first wait() should report the SIGSTOP from PT_ATTACH. */
-	wpid = waitpid(child, &status, 0);
-	ATF_REQUIRE(wpid == child);
-	ATF_REQUIRE(WIFSTOPPED(status));
-	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+	attach_child(child);
 
 	/* Continue the child ignoring the SIGSTOP. */
 	ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
@@ -223,27 +266,7 @@ ATF_TC_BODY(ptrace__parent_sees_exit_aft
 	ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
 	close(cpipe[0]);
 
-	/*
-	 * Wait for the child to exit.  This is kind of gross, but
-	 * there is not a better way.
-	 */
-	for (;;) {
-		struct kinfo_proc kp;
-		size_t len;
-		int mib[4];
-
-		mib[0] = CTL_KERN;
-		mib[1] = KERN_PROC;
-		mib[2] = KERN_PROC_PID;
-		mib[3] = child;
-		len = sizeof(kp);
-		if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
-			/* The KERN_PROC_PID sysctl fails for zombies. */
-			ATF_REQUIRE(errno == ESRCH);
-			break;
-		}
-		usleep(5000);
-	}
+	wait_for_zombie(child);
 
 	/*
 	 * This wait should return a pid of 0 to indicate no status to
@@ -357,27 +380,7 @@ ATF_TC_BODY(ptrace__parent_sees_exit_aft
 	ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
 	close(cpipe[0]);
 
-	/*
-	 * Wait for the child to exit.  This is kind of gross, but
-	 * there is not a better way.
-	 */
-	for (;;) {
-		struct kinfo_proc kp;
-		size_t len;
-		int mib[4];
-
-		mib[0] = CTL_KERN;
-		mib[1] = KERN_PROC;
-		mib[2] = KERN_PROC_PID;
-		mib[3] = child;
-		len = sizeof(kp);
-		if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
-			/* The KERN_PROC_PID sysctl fails for zombies. */
-			ATF_REQUIRE(errno == ESRCH);
-			break;
-		}
-		usleep(5000);
-	}
+	wait_for_zombie(child);
 
 	/*
 	 * This wait should return a pid of 0 to indicate no status to
@@ -401,6 +404,468 @@ ATF_TC_BODY(ptrace__parent_sees_exit_aft
 	ATF_REQUIRE(WEXITSTATUS(status) == 1);
 }
 
+/*
+ * The parent process should always act the same regardless of how the
+ * debugger is attached to it.
+ */
+static __dead2 void
+follow_fork_parent(void)
+{
+	pid_t fpid, wpid;
+	int status;
+
+	CHILD_REQUIRE((fpid = fork()) != -1);
+
+	if (fpid == 0)
+		/* Child */
+		exit(2);
+
+	wpid = waitpid(fpid, &status, 0);
+	CHILD_REQUIRE(wpid == fpid);
+	CHILD_REQUIRE(WIFEXITED(status));
+	CHILD_REQUIRE(WEXITSTATUS(status) == 2);
+
+	exit(1);
+}
+
+/*
+ * Helper routine for follow fork tests.  This waits for two stops
+ * that report both "sides" of a fork.  It returns the pid of the new
+ * child process.
+ */
+static pid_t
+handle_fork_events(pid_t parent)
+{
+	struct ptrace_lwpinfo pl;
+	bool fork_reported[2];
+	pid_t child, wpid;
+	int i, status;
+
+	fork_reported[0] = false;
+	fork_reported[1] = false;
+	child = -1;
+	
+	/*
+	 * Each process should report a fork event.  The parent should
+	 * report a PL_FLAG_FORKED event, and the child should report
+	 * a PL_FLAG_CHILD event.
+	 */
+	for (i = 0; i < 2; i++) {
+		wpid = wait(&status);
+		ATF_REQUIRE(wpid > 0);
+		ATF_REQUIRE(WIFSTOPPED(status));
+
+		ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+		    sizeof(pl)) != -1);
+		ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) !=
+		    0);
+		ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) !=
+		    (PL_FLAG_FORKED | PL_FLAG_CHILD));
+		if (pl.pl_flags & PL_FLAG_CHILD) {
+			ATF_REQUIRE(wpid != parent);
+			ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+			ATF_REQUIRE(!fork_reported[1]);
+			if (child == -1)
+				child = wpid;
+			else
+				ATF_REQUIRE(child == wpid);
+			fork_reported[1] = true;
+		} else {
+			ATF_REQUIRE(wpid == parent);
+			ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+			ATF_REQUIRE(!fork_reported[0]);
+			if (child == -1)
+				child = pl.pl_child_pid;
+			else
+				ATF_REQUIRE(child == pl.pl_child_pid);
+			fork_reported[0] = true;
+		}
+	}
+
+	return (child);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork and
+ * that the traced parent sees the exit of the child after the debugger
+ * when both processes remain attached to the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached);
+ATF_TC_BODY(ptrace__follow_fork_both_attached, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	children[0] = fpid;
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(children[0], &status, 0);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * The child can't exit until the grandchild reports status, so the
+	 * grandchild should report its exit first to the debugger.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[1]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the new
+ * child process is detached after it reports its fork.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached);
+ATF_TC_BODY(ptrace__follow_fork_child_detached, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	children[0] = fpid;
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(children[0], &status, 0);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * Should not see any status from the grandchild now, only the
+	 * child.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the
+ * traced parent is detached after the fork.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached);
+ATF_TC_BODY(ptrace__follow_fork_parent_detached, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int status;
+
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		trace_me();
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	children[0] = fpid;
+
+	/* The first wait() should report the stop from SIGSTOP. */
+	wpid = waitpid(children[0], &status, 0);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFSTOPPED(status));
+	ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the child ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * The child can't exit until the grandchild reports status, so the
+	 * grandchild should report its exit first to the debugger.
+	 *
+	 * Even though the child process is detached, it is still a
+	 * child of the debugger, so it will still report it's exit
+	 * after the grandchild.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[1]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+attach_fork_parent(int cpipe[2])
+{
+	pid_t fpid;
+
+	close(cpipe[0]);
+
+	/* Double-fork to disassociate from the debugger. */
+	CHILD_REQUIRE((fpid = fork()) != -1);
+	if (fpid != 0)
+		exit(3);
+	
+	/* Send the pid of the disassociated child to the debugger. */
+	fpid = getpid();
+	CHILD_REQUIRE(write(cpipe[1], &fpid, sizeof(fpid)) == sizeof(fpid));
+
+	/* Wait for the debugger to attach. */
+	CHILD_REQUIRE(read(cpipe[1], &fpid, sizeof(fpid)) == 0);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork and
+ * that the traced parent sees the exit of the child after the debugger
+ * when both processes remain attached to the debugger.  In this test
+ * the parent that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_both_attached_unrelated_debugger, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int cpipe[2], status;
+
+	ATF_REQUIRE(pipe(cpipe) == 0);
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		attach_fork_parent(cpipe);
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	close(cpipe[1]);
+
+	/* Wait for the direct child to exit. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+	/* Read the pid of the fork parent. */
+	ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+	    sizeof(children[0]));
+
+	/* Attach to the fork parent. */
+	attach_child(children[0]);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the fork parent ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	/* Signal the fork parent to continue. */
+	close(cpipe[0]);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * The fork parent can't exit until the child reports status,
+	 * so the child should report its exit first to the debugger.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[1]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the new
+ * child process is detached after it reports its fork.  In this test
+ * the parent that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_child_detached_unrelated_debugger, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int cpipe[2], status;
+
+	ATF_REQUIRE(pipe(cpipe) == 0);
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		attach_fork_parent(cpipe);
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	close(cpipe[1]);
+
+	/* Wait for the direct child to exit. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+	/* Read the pid of the fork parent. */
+	ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+	    sizeof(children[0]));
+
+	/* Attach to the fork parent. */
+	attach_child(children[0]);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the fork parent ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	/* Signal the fork parent to continue. */
+	close(cpipe[0]);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * Should not see any status from the child now, only the fork
+	 * parent.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[0]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the
+ * traced parent is detached after the fork.  In this test the parent
+ * that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_parent_detached_unrelated_debugger, tc)
+{
+	pid_t children[0], fpid, wpid;
+	int cpipe[2], status;
+
+	ATF_REQUIRE(pipe(cpipe) == 0);
+	ATF_REQUIRE((fpid = fork()) != -1);
+	if (fpid == 0) {
+		attach_fork_parent(cpipe);
+		follow_fork_parent();
+	}
+
+	/* Parent process. */
+	close(cpipe[1]);
+
+	/* Wait for the direct child to exit. */
+	wpid = waitpid(fpid, &status, 0);
+	ATF_REQUIRE(wpid == fpid);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+	/* Read the pid of the fork parent. */
+	ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+	    sizeof(children[0]));
+
+	/* Attach to the fork parent. */
+	attach_child(children[0]);
+
+	ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+	/* Continue the fork parent ignoring the SIGSTOP. */
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+	/* Signal the fork parent to continue. */
+	close(cpipe[0]);
+
+	children[1] = handle_fork_events(children[0]);
+	ATF_REQUIRE(children[1] > 0);
+
+	ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1);
+	ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+	/*
+	 * Should not see any status from the fork parent now, only
+	 * the child.
+	 */
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == children[1]);
+	ATF_REQUIRE(WIFEXITED(status));
+	ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+	wpid = wait(&status);
+	ATF_REQUIRE(wpid == -1);
+	ATF_REQUIRE(errno == ECHILD);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 
@@ -408,6 +873,14 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_attach);
 	ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_child_debugger);
 	ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_unrelated_debugger);
+	ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached);
+	ATF_TP_ADD_TC(tp, ptrace__follow_fork_child_detached);
+	ATF_TP_ADD_TC(tp, ptrace__follow_fork_parent_detached);
+	ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached_unrelated_debugger);
+	ATF_TP_ADD_TC(tp,
+	    ptrace__follow_fork_child_detached_unrelated_debugger);
+	ATF_TP_ADD_TC(tp,
+	    ptrace__follow_fork_parent_detached_unrelated_debugger);
 
 	return (atf_no_error());
 }



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