[PATCH/v3 2/2] Handle followfork using ptrace_setoptions if available

Wang Chao wang.chao at cn.fujitsu.com
Fri Nov 12 09:26:08 UTC 2010


If PTRACE_O_TRACECLONE et al options are supported by kernel,
we use them to do followfork rather than the original setbpt
method which change registers ourselves.

* process.c [LINUX](internal_fork): Do nothing if these options
  are supported by kernel.
  [LINUX](handle_new_child): New common function which move most
  work at exit point of internal_fork here, except a trivial
  change: only do reparent work for syscall clone.
* strace.c [LINUX](handle_ptrace_event): New function.
  [LINUX](trace): If ptrace_setoptions is in effect:
  1) Call the new function to handle PTRACE_EVENT_* status.
  2) Set PTRACE_SETOPTIONS when we see the initial stop of tracee.

Signed-off-by: Wang Chao <wang.chao at cn.fujitsu.com>
---
 defs.h    |    3 +
 process.c |  224 ++++++++++++++++++++++++++++++++----------------------------
 strace.c  |   37 ++++++++++
 3 files changed, 159 insertions(+), 105 deletions(-)

diff --git a/defs.h b/defs.h
index eee4710..7a7a071 100644
--- a/defs.h
+++ b/defs.h
@@ -571,6 +571,9 @@ extern int internal_fork(struct tcb *);
 extern int internal_exec(struct tcb *);
 extern int internal_wait(struct tcb *, int);
 extern int internal_exit(struct tcb *);
+#ifdef LINUX
+extern int handle_new_child(struct tcb *, int, int);
+#endif
 
 extern const struct ioctlent *ioctl_lookup(long);
 extern const struct ioctlent *ioctl_next_match(const struct ioctlent *);
diff --git a/process.c b/process.c
index 2366d1f..7b3eda1 100644
--- a/process.c
+++ b/process.c
@@ -793,8 +793,126 @@ change_syscall(struct tcb *tcp, int new)
 
 #ifdef LINUX
 int
+handle_new_child(struct tcb *tcp, int pid, int bpt)
+{
+	struct tcb *tcpchild;
+
+#ifdef CLONE_PTRACE		/* See new setbpt code.  */
+	tcpchild = pid2tcb(pid);
+	if (tcpchild != NULL) {
+		/* The child already reported its startup trap
+		   before the parent reported its syscall return.  */
+		if ((tcpchild->flags
+		     & (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED))
+		    != (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED))
+			fprintf(stderr, "\
+[preattached child %d of %d in weird state!]\n",
+				pid, tcp->pid);
+	}
+	else
+#endif /* CLONE_PTRACE */
+	{
+		fork_tcb(tcp);
+		tcpchild = alloctcb(pid);
+	}
+
+#ifndef CLONE_PTRACE
+	/* Attach to the new child */
+	if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) {
+		if (bpt)
+			clearbpt(tcp);
+		perror("PTRACE_ATTACH");
+		fprintf(stderr, "Too late?\n");
+		droptcb(tcpchild);
+		return 0;
+	}
+#endif /* !CLONE_PTRACE */
+
+	if (bpt)
+		clearbpt(tcp);
+
+	tcpchild->flags |= TCB_ATTACHED;
+	/* Child has BPT too, must be removed on first occasion.  */
+	if (bpt) {
+		tcpchild->flags |= TCB_BPTSET;
+		tcpchild->baddr = tcp->baddr;
+		memcpy(tcpchild->inst, tcp->inst,
+			sizeof tcpchild->inst);
+	}
+	tcpchild->parent = tcp;
+	tcp->nchildren++;
+	if (tcpchild->flags & TCB_SUSPENDED) {
+		/* The child was born suspended, due to our having
+		   forced CLONE_PTRACE.  */
+		if (bpt)
+			clearbpt(tcpchild);
+
+		tcpchild->flags &= ~(TCB_SUSPENDED|TCB_STARTUP);
+		if (ptrace_restart(PTRACE_SYSCALL, tcpchild, 0) < 0)
+			return -1;
+
+		if (!qflag)
+			fprintf(stderr, "\
+Process %u resumed (parent %d ready)\n",
+				pid, tcp->pid);
+	}
+	else {
+		if (!qflag)
+			fprintf(stderr, "Process %d attached\n", pid);
+	}
+
+#ifdef TCB_CLONE_THREAD
+	if (sysent[tcp->scno].sys_func == sys_clone)
+	{
+		/*
+		 * Save the flags used in this call,
+		 * in case we point TCP to our parent below.
+		 */
+		int call_flags = tcp->u_arg[ARG_FLAGS];
+		if ((tcp->flags & TCB_CLONE_THREAD) &&
+		    tcp->parent != NULL) {
+			/* The parent in this clone is itself a
+			   thread belonging to another process.
+			   There is no meaning to the parentage
+			   relationship of the new child with the
+			   thread, only with the process.  We
+			   associate the new thread with our
+			   parent.  Since this is done for every
+			   new thread, there will never be a
+			   TCB_CLONE_THREAD process that has
+			   children.  */
+			--tcp->nchildren;
+			tcp = tcp->parent;
+			tcpchild->parent = tcp;
+			++tcp->nchildren;
+		}
+		if (call_flags & CLONE_THREAD) {
+			tcpchild->flags |= TCB_CLONE_THREAD;
+			++tcp->nclone_threads;
+		}
+		if ((call_flags & CLONE_PARENT) &&
+		    !(call_flags & CLONE_THREAD)) {
+			--tcp->nchildren;
+			tcpchild->parent = NULL;
+			if (tcp->parent != NULL) {
+				tcp = tcp->parent;
+				tcpchild->parent = tcp;
+				++tcp->nchildren;
+			}
+		}
+	}
+#endif /* TCB_CLONE_THREAD */
+	return 0;
+}
+
+int
 internal_fork(struct tcb *tcp)
 {
+	if ((ptrace_setoptions
+	    & (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK))
+	   == (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK))
+		return 0;
+
 	if (entering(tcp)) {
 		tcp->flags &= ~TCB_FOLLOWFORK;
 		if (!followfork)
@@ -811,7 +929,6 @@ internal_fork(struct tcb *tcp)
 		if (setbpt(tcp) < 0)
 			return 0;
 	} else {
-		struct tcb *tcpchild;
 		int pid;
 		int bpt;
 
@@ -828,110 +945,7 @@ internal_fork(struct tcb *tcp)
 
 		pid = tcp->u_rval;
 
-#ifdef CLONE_PTRACE		/* See new setbpt code.  */
-		tcpchild = pid2tcb(pid);
-		if (tcpchild != NULL) {
-			/* The child already reported its startup trap
-			   before the parent reported its syscall return.  */
-			if ((tcpchild->flags
-			     & (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED))
-			    != (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED))
-				fprintf(stderr, "\
-[preattached child %d of %d in weird state!]\n",
-					pid, tcp->pid);
-		}
-		else
-#endif /* CLONE_PTRACE */
-		{
-			fork_tcb(tcp);
-			tcpchild = alloctcb(pid);
-		}
-
-#ifndef CLONE_PTRACE
-		/* Attach to the new child */
-		if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) {
-			if (bpt)
-				clearbpt(tcp);
-			perror("PTRACE_ATTACH");
-			fprintf(stderr, "Too late?\n");
-			droptcb(tcpchild);
-			return 0;
-		}
-#endif /* !CLONE_PTRACE */
-
-		if (bpt)
-			clearbpt(tcp);
-
-		tcpchild->flags |= TCB_ATTACHED;
-		/* Child has BPT too, must be removed on first occasion.  */
-		if (bpt) {
-			tcpchild->flags |= TCB_BPTSET;
-			tcpchild->baddr = tcp->baddr;
-			memcpy(tcpchild->inst, tcp->inst,
-				sizeof tcpchild->inst);
-		}
-		tcpchild->parent = tcp;
-		tcp->nchildren++;
-		if (tcpchild->flags & TCB_SUSPENDED) {
-			/* The child was born suspended, due to our having
-			   forced CLONE_PTRACE.  */
-			if (bpt)
-				clearbpt(tcpchild);
-
-			tcpchild->flags &= ~(TCB_SUSPENDED|TCB_STARTUP);
-			if (ptrace_restart(PTRACE_SYSCALL, tcpchild, 0) < 0)
-				return -1;
-
-			if (!qflag)
-				fprintf(stderr, "\
-Process %u resumed (parent %d ready)\n",
-					pid, tcp->pid);
-		}
-		else {
-			if (!qflag)
-				fprintf(stderr, "Process %d attached\n", pid);
-		}
-
-#ifdef TCB_CLONE_THREAD
-		{
-			/*
-			 * Save the flags used in this call,
-			 * in case we point TCP to our parent below.
-			 */
-			int call_flags = tcp->u_arg[ARG_FLAGS];
-			if ((tcp->flags & TCB_CLONE_THREAD) &&
-			    tcp->parent != NULL) {
-				/* The parent in this clone is itself a
-				   thread belonging to another process.
-				   There is no meaning to the parentage
-				   relationship of the new child with the
-				   thread, only with the process.  We
-				   associate the new thread with our
-				   parent.  Since this is done for every
-				   new thread, there will never be a
-				   TCB_CLONE_THREAD process that has
-				   children.  */
-				--tcp->nchildren;
-				tcp = tcp->parent;
-				tcpchild->parent = tcp;
-				++tcp->nchildren;
-			}
-			if (call_flags & CLONE_THREAD) {
-				tcpchild->flags |= TCB_CLONE_THREAD;
-				++tcp->nclone_threads;
-			}
-			if ((call_flags & CLONE_PARENT) &&
-			    !(call_flags & CLONE_THREAD)) {
-				--tcp->nchildren;
-				tcpchild->parent = NULL;
-				if (tcp->parent != NULL) {
-					tcp = tcp->parent;
-					tcpchild->parent = tcp;
-					++tcp->nchildren;
-				}
-			}
-		}
-#endif /* TCB_CLONE_THREAD */
+		return handle_new_child(tcp, pid, bpt);
 	}
 	return 0;
 }
diff --git a/strace.c b/strace.c
index 09aedac..6f63ee9 100644
--- a/strace.c
+++ b/strace.c
@@ -2363,6 +2363,31 @@ handle_group_exit(struct tcb *tcp, int sig)
 }
 #endif
 
+#ifdef LINUX
+static int
+handle_ptrace_event(int status, struct tcb *tcp)
+{
+	if (status >> 16 == PTRACE_EVENT_VFORK ||
+	    status >> 16 == PTRACE_EVENT_CLONE ||
+	    status >> 16 == PTRACE_EVENT_FORK) {
+		int childpid;
+
+		if (do_ptrace(PTRACE_GETEVENTMSG, tcp, NULL, &childpid) < 0) {
+			if (errno != ESRCH) {
+				fprintf(stderr, "\
+%s: handle_ptrace_event: ptrace cannot get new child's pid\n",
+					progname);
+				cleanup();
+				exit(1);
+			}
+			return -1;
+		}
+		return handle_new_child(tcp, childpid, 0);
+	}
+	return 1;
+}
+#endif
+
 static int
 trace()
 {
@@ -2548,6 +2573,11 @@ Process %d attached (waiting for parent)\n",
 			fprintf(stderr, "pid %u stopped, [%s]\n",
 				pid, signame(WSTOPSIG(status)));
 
+		if (ptrace_setoptions && (status >> 16)) {
+			if (handle_ptrace_event(status, tcp) != 1)
+				goto tracing;
+		}
+
 		/*
 		 * Interestingly, the process may stop
 		 * with STOPSIG equal to some other signal
@@ -2575,6 +2605,13 @@ Process %d attached (waiting for parent)\n",
 					return -1;
 				}
 			}
+#ifdef LINUX
+			if (followfork && (tcp->parent == NULL) && ptrace_setoptions)
+				if (ptrace(PTRACE_SETOPTIONS, tcp->pid,
+					   NULL, ptrace_setoptions) < 0 &&
+				    errno != ESRCH)
+					ptrace_setoptions = 0;
+#endif
 			goto tracing;
 		}
 
-- 
1.6.5.2





More information about the Strace-devel mailing list