]> granicus.if.org Git - strace/commitdiff
Handle followfork using ptrace_setoptions if available
authorWang Chao <wang.chao@cn.fujitsu.com>
Fri, 12 Nov 2010 09:26:08 +0000 (17:26 +0800)
committerDmitry V. Levin <ldv@altlinux.org>
Tue, 30 Nov 2010 17:19:09 +0000 (17:19 +0000)
If PTRACE_O_TRACECLONE et al options are supported by kernel,
use them to do followfork rather than the original setbpt
method that changes registers ourselves.

* defs.h [LINUX] (handle_new_child): New function prototype.
* process.c [LINUX] (handle_new_child): New function based on the
code from internal_fork(), with a trivial change: do reparent only
for sys_clone.
[LINUX] (internal_fork): Use handle_new_child().  Do nothing if
ptrace_setoptions is in effect.
* strace.c [LINUX] (handle_ptrace_event): New function.
[LINUX] (trace): If ptrace_setoptions is in effect, then
call the new function to handle PTRACE_EVENT_* status, and
set PTRACE_SETOPTIONS when we see the initial stop of tracee.

Signed-off-by: Wang Chao <wang.chao@cn.fujitsu.com>
defs.h
process.c
strace.c

diff --git a/defs.h b/defs.h
index eee4710963b3941d193c46e0f95bbedc530d13a6..7a7a071bb0327fe92c0715490322f6ab4b413764 100644 (file)
--- 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 *);
index 2366d1f3ada1c05e4d128545e194d1bf157951ef..7b3eda1dc600804c24cb4f0e0a0d1113195db745 100644 (file)
--- a/process.c
+++ b/process.c
@@ -792,9 +792,127 @@ 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;
 }
index 09aedacd3abb2122f9bb19f6378b2841be613516..6f63ee9a7fa2bcc9feb3e4068fac097330f07f14 100644 (file)
--- 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;
                }