]> granicus.if.org Git - strace/commitdiff
2003-01-08 Roland McGrath <roland@redhat.com>
authorRoland McGrath <roland@redhat.com>
Thu, 9 Jan 2003 06:53:31 +0000 (06:53 +0000)
committerRoland McGrath <roland@redhat.com>
Thu, 9 Jan 2003 06:53:31 +0000 (06:53 +0000)
Support for new Linux 2.5 thread features.
* defs.h [LINUX]: Define __NR_exit_group if not defined.
(struct tcb): New members nclone_threads, nclone_detached,
and nclone_waiting.
(TCB_CLONE_DETACHED, TCB_CLONE_THREAD, TCB_GROUP_EXITING): New macros.
(waiting_parent): Macro removed.
(pid2tcb): Declare it.
* process.c (internal_clone) [TCB_CLONE_THREAD]: Reparent the new
child to our parent if we are a CLONE_THREAD child ourselves.
Maintain TCB_CLONE_THREAD and TCB_CLONE_DETACHED flags and counts.
(internal_wait) [TCB_CLONE_THREAD]: Factor out detached children when
determining if we have any.  If TCB_CLONE_THREAD is set, check
parent's children instead of our own, and bump nclone_waiting count.
(internal_exit) [__NR_exit_group]: Set the TCB_GROUP_EXITING flag if
the syscall was exit_group.
* syscall.c (internal_syscall): Use internal_exit for exit_group.
* strace.c (pid2tcb): No longer static.
(alloctcb) [TCB_CLONE_THREAD]: Initialize new fields.
(droptcb) [TCB_CLONE_THREAD]: Maintain new fields.
If we have thread children, set TCB_EXITING and don't clear the TCB.
(resume) [TCB_CLONE_THREAD]: Decrement parent's nclone_waiting.
(detach) [TCB_CLONE_THREAD]: When calling resume, check all thread
children of our parent that might be waiting for us too.
[TCB_GROUP_EXITING] (handle_group_exit): New function.
(trace) [TCB_GROUP_EXITING]: Use that in place of detach or droptcb.
Revamp -f support for Linux.
* util.c [LINUX] (setbpt, clearbpt): New implementations that tweak
the system call to be clone with CLONE_PTRACE set.  Various new static
helper functions.
* process.c (internal_clone): Define also #ifdef SYS_clone2.
Initialize TCPCHILD->parent field.
[CLONE_PTRACE]: Don't do PTRACE_ATTACH here, because it's preattached.
Check in case the new child is in the tcb already.
(internal_fork) [LINUX]: Just call internal_clone.
* strace.c (trace) [LINUX]: Under -f/-F, grok an unknown pid
reporting to wait, put it in the TCB with TCB_ATTACHED|TCB_SUSPENDED.

process.c
strace.c

index 14f3273b68e27eec305ee680f3d2265f834841a5..1212ff7b8e8b24c425bbb348621e7d615fac2e23 100644 (file)
--- a/process.c
+++ b/process.c
@@ -363,8 +363,13 @@ int
 internal_exit(tcp)
 struct tcb *tcp;
 {
-       if (entering(tcp))
+       if (entering(tcp)) {
                tcp->flags |= TCB_EXITING;
+#ifdef __NR_exit_group
+               if (tcp->scno == __NR_exit_group)
+                       tcp->flags |= TCB_GROUP_EXITING;
+#endif
+       }
        return 0;
 }
 
@@ -726,7 +731,7 @@ setarg(tcp, argnum)
        return 0;
 }
 
-#ifdef SYS_clone
+#if defined SYS_clone || defined SYS_clone2
 int
 internal_clone(tcp)
 struct tcb *tcp;
@@ -753,6 +758,21 @@ 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
                if ((tcpchild = alloctcb(pid)) == NULL) {
                        if (bpt)
                                clearbpt(tcp);
@@ -761,6 +781,7 @@ struct tcb *tcp;
                        return 0;
                }
 
+#ifndef CLONE_PTRACE
                /* Attach to the new child */
                if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) {
                        if (bpt)
@@ -770,23 +791,72 @@ struct tcb *tcp;
                        droptcb(tcpchild);
                        return 0;
                }
+#endif
 
                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);
                }
-               newoutf(tcpchild);
                tcpchild->parent = tcp;
-               tcp->nchildren++;
-               if (!qflag)
-                       fprintf(stderr, "Process %d attached\n", pid);
-       }
+               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(PTRACE_SYSCALL, pid, (char *) 1, 0) < 0) {
+                               perror("resume: ptrace(PTRACE_SYSCALL, ...)");
+                               return -1;
+                       }
+
+                       if (!qflag)
+                               fprintf(stderr, "\
+Process %u resumed (parent %d ready)\n",
+                                       pid, tcp->pid);
+               }
+               else {
+                       newoutf(tcpchild);
+                       if (!qflag)
+                               fprintf(stderr, "Process %d attached\n", pid);
+               }
+
+#ifdef TCB_CLONE_THREAD
+               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->u_arg[0] = tcp->parent->u_arg[0];
+                       tcp = tcp->parent;
+                       tcpchild->parent = tcp;
+                       ++tcp->nchildren;
+               }
+
+               if (tcp->u_arg[0] & CLONE_THREAD) {
+                       tcpchild->flags |= TCB_CLONE_THREAD;
+                       ++tcp->nclone_threads;
+               }
+               if (tcp->u_arg[0] & CLONE_DETACHED) {
+                       tcpchild->flags |= TCB_CLONE_DETACHED;
+                       ++tcp->nclone_detached;
+               }
+#endif
+
+       }
        return 0;
 }
 #endif
@@ -795,6 +865,11 @@ int
 internal_fork(tcp)
 struct tcb *tcp;
 {
+#ifdef LINUX
+       /* We do special magic with clone for any clone or fork.  */
+       return internal_clone(tcp);
+#else
+
        struct tcb *tcpchild;
        int pid;
        int dont_follow = 0;
@@ -904,6 +979,7 @@ struct tcb *tcp;
                        fprintf(stderr, "Process %d attached\n", pid);
        }
        return 0;
+#endif
 }
 
 #endif /* !USE_PROCFS */
@@ -1650,7 +1726,20 @@ int
 internal_wait(tcp)
 struct tcb *tcp;
 {
-       if (entering(tcp) && tcp->nchildren > 0) {
+       int got_kids;
+
+#ifdef TCB_CLONE_THREAD
+       if (tcp->flags & TCB_CLONE_THREAD)
+               /* The children we wait for are our parent's children.  */
+               got_kids = (tcp->parent->nchildren
+                           > tcp->parent->nclone_detached);
+       else
+               got_kids = (tcp->nchildren > tcp->nclone_detached);
+#else
+       got_kids = tcp->nchildren > 0;
+#endif
+
+       if (entering(tcp) && got_kids) {
                /* There are children that this parent should block for.
                   But ptrace made us the parent of the traced children
                   and the real parent will get ECHILD from the wait call.
@@ -1665,9 +1754,13 @@ struct tcb *tcp;
                        /* There are traced children */
                        tcp->flags |= TCB_SUSPENDED;
                        tcp->waitpid = tcp->u_arg[0];
+#ifdef TCB_CLONE_THREAD
+                       if (tcp->flags & TCB_CLONE_THREAD)
+                               tcp->parent->nclone_waiting++;
+#endif
                }
        }
-       if (exiting(tcp) && tcp->u_error == ECHILD && tcp->nchildren > 0) {
+       if (exiting(tcp) && tcp->u_error == ECHILD && got_kids) {
                if (tcp->u_arg[2] & WNOHANG) {
                        /* We must force a fake result of 0 instead of
                           the ECHILD error.  */
index faf952fcb924d61f444162328bf1bc2b9357c9f3..8f0f7dcf4f23153be53e30a04045c8ed8c782117 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -84,7 +84,6 @@ char *progname;
 extern const char version[];
 extern char **environ;
 
-static struct tcb *pid2tcb P((int pid));
 static int trace P((void));
 static void cleanup P((void));
 static void interrupt P((int sig));
@@ -610,6 +609,10 @@ int pid;
                        tcp->pid = pid;
                        tcp->parent = NULL;
                        tcp->nchildren = 0;
+#ifdef TCB_CLONE_THREAD
+                       tcp->nclone_threads = tcp->nclone_detached = 0;
+                       tcp->nclone_waiting = 0;
+#endif
                        tcp->flags = TCB_INUSE | TCB_STARTUP;
                        tcp->outf = outf; /* Initialise to current out file */
                        tcp->stime.tv_sec = 0;
@@ -932,7 +935,7 @@ int attaching;
 
 #endif /* USE_PROCFS */
 
-static struct tcb *
+struct tcb *
 pid2tcb(pid)
 int pid;
 {
@@ -975,9 +978,18 @@ struct tcb *tcp;
 {
        if (tcp->pid == 0)
                return;
+#ifdef TCB_CLONE_THREAD
+       if (tcp->nclone_threads > 0) {
+               /* There are other threads left in this process, but this
+                  is the one whose PID represents the whole process.
+                  We need to keep this record around as a zombie until
+                  all the threads die.  */
+               tcp->flags |= TCB_EXITING;
+               return;
+       }
+#endif
        nprocs--;
        tcp->pid = 0;
-       tcp->flags = 0;
 
        if (tcp->pfd != -1) {
                close(tcp->pfd);
@@ -998,6 +1010,12 @@ struct tcb *tcp;
        }
        if (tcp->parent != NULL) {
                tcp->parent->nchildren--;
+#ifdef TCB_CLONE_THREAD
+               if (tcp->flags & TCB_CLONE_DETACHED)
+                       tcp->parent->nclone_detached--;
+               if (tcp->flags & TCB_CLONE_THREAD)
+                       tcp->parent->nclone_threads--;
+#endif
                tcp->parent = NULL;
        }
 
@@ -1005,6 +1023,7 @@ struct tcb *tcp;
                fclose(tcp->outf);
 
        tcp->outf = 0;
+       tcp->flags = 0;
 }
 
 #ifndef USE_PROCFS
@@ -1021,6 +1040,10 @@ struct tcb *tcp;
                return -1;
        }
        tcp->flags &= ~TCB_SUSPENDED;
+#ifdef TCB_CLONE_THREAD
+       if (tcp->flags & TCB_CLONE_THREAD)
+               tcp->parent->nclone_waiting--;
+#endif
 
        if (ptrace(PTRACE_SYSCALL, tcp->pid, (char *) 1, 0) < 0) {
                perror("resume: ptrace(PTRACE_SYSCALL, ...)");
@@ -1042,9 +1065,7 @@ struct tcb *tcp;
 int sig;
 {
        int error = 0;
-#ifdef LINUX
-       int status;
-#endif
+       int status, resumed;
 
        if (tcp->flags & TCB_BPTSET)
                sig = SIGKILL;
@@ -1138,8 +1159,64 @@ int sig;
 #endif /* SUNOS4 */
 
 #ifndef USE_PROCFS
-       if (waiting_parent(tcp))
-               error = resume(tcp->parent);
+       resumed = 0;
+
+       /* XXX This won't always be quite right (but it never was).
+          A waiter with argument 0 or < -1 is waiting for any pid in
+          a particular pgrp, which this child might or might not be
+          in.  The waiter will only wake up if it's argument is -1
+          or if it's waiting for tcp->pid's pgrp.  It makes a
+          difference to wake up a waiter when there might be more
+          traced children, because it could get a false ECHILD
+          error.  OTOH, if this was the last child in the pgrp, then
+          it ought to wake up and get ECHILD.  We would have to
+          search the system for all pid's in the pgrp to be sure.
+
+            && (t->waitpid == -1 ||
+                (t->waitpid == 0 && getpgid (tcp->pid) == getpgid (t->pid))
+                || (t->waitpid < 0 && t->waitpid == -getpid (t->pid)))
+       */
+
+       if (tcp->parent &&
+           (tcp->parent->flags & TCB_SUSPENDED) &&
+           (tcp->parent->waitpid <= 0 || tcp->parent->waitpid == tcp->pid)) {
+               error = resume(tcp->parent);
+               ++resumed;
+       }
+#ifdef TCB_CLONE_THREAD
+       if (tcp->parent && tcp->parent->nclone_waiting > 0) {
+               /* Some other threads of our parent are waiting too.  */
+               unsigned int i;
+
+               /* Resume all the threads that were waiting for this PID.  */
+               for (i = 0; i < tcbtabsize; i++) {
+                       struct tcb *t = tcbtab[i];
+                       if (t->parent == tcp->parent && t != tcp
+                           && ((t->flags & (TCB_CLONE_THREAD|TCB_SUSPENDED))
+                               == (TCB_CLONE_THREAD|TCB_SUSPENDED))
+                           && t->waitpid == tcp->pid) {
+                               error |= resume (t);
+                               ++resumed;
+                       }
+               }
+               if (resumed == 0)
+                       /* Noone was waiting for this PID in particular,
+                          so now we might need to resume some wildcarders.  */
+                       for (i = 0; i < tcbtabsize; i++) {
+                               struct tcb *t = tcbtab[i];
+                               if (t->parent == tcp->parent && t != tcp
+                                   && ((t->flags
+                                        & (TCB_CLONE_THREAD|TCB_SUSPENDED))
+                                       == (TCB_CLONE_THREAD|TCB_SUSPENDED))
+                                   && t->waitpid <= 0
+                                       ) {
+                                       error |= resume (t);
+                                       break;
+                               }
+                       }
+       }
+#endif
+
 #endif /* !USE_PROCFS */
 
        if (!qflag)
@@ -1701,6 +1778,81 @@ trace()
 
 #else /* !USE_PROCFS */
 
+#ifdef TCB_GROUP_EXITING
+/* Handle an exit detach or death signal that is taking all the
+   related clone threads with it.  This is called in three circumstances:
+   SIG == -1   TCP has already died (TCB_ATTACHED is clear, strace is parent).
+   SIG == 0    Continuing TCP will perform an exit_group syscall.
+   SIG == other        Continuing TCP with SIG will kill the process.
+*/
+static int
+handle_group_exit(struct tcb *tcp, int sig)
+{
+       /* We need to locate our records of all the clone threads
+          related to TCP, either its children or siblings.  */
+       struct tcb *leader = ((tcp->flags & TCB_CLONE_THREAD)
+                             ? tcp->parent
+                             : tcp->nclone_detached > 0
+                             ? tcp : NULL);
+
+       if (sig < 0) {
+               if (leader != NULL && leader != tcp)
+                       fprintf(stderr,
+                               "PANIC: handle_group_exit: %d leader %d\n",
+                               tcp->pid, leader ? leader->pid : -1);
+               droptcb(tcp);   /* Already died.  */
+       }
+       else {
+               if (tcp->flags & TCB_ATTACHED) {
+                       if (leader != NULL && leader != tcp) {
+                               /* We need to detach the leader so that the
+                                  process death will be reported to its real
+                                  parent.  But we kill it first to prevent
+                                  it doing anything before we kill the whole
+                                  process in a moment.  We can use
+                                  PTRACE_KILL on a thread that's not already
+                                  stopped.  Then the value we pass in
+                                  PTRACE_DETACH just sets the death
+                                  signal reported to the real parent.  */
+                               ptrace(PTRACE_KILL, leader->pid, 0, 0);
+                               if (debug)
+                                       fprintf(stderr,
+                                               " [%d exit %d kills %d]\n",
+                                               tcp->pid, sig, leader->pid);
+                               detach(leader, sig);
+                       }
+                       detach(tcp, sig);
+               }
+               else if (ptrace(PTRACE_CONT, tcp->pid, (char *) 1, sig) < 0) {
+                       perror("strace: ptrace(PTRACE_CONT, ...)");
+                       cleanup();
+                       return -1;
+               }
+               else {
+                       if (leader != NULL && leader != tcp)
+                               droptcb(tcp);
+                       /* The leader will report to us as parent now,
+                          and then we'll get to the SIG==-1 case.  */
+                       return 0;
+               }
+       }
+
+       /* Note that TCP and LEADER are no longer valid,
+          but we can still compare against them.  */
+       if (leader != NULL) {
+               unsigned int i;
+               for (i = 0; i < tcbtabsize; i++) {
+                       struct tcb *t = tcbtab[i];
+                       if (t != tcp && (t->flags & TCB_CLONE_DETACHED)
+                           && t->parent == leader)
+                               droptcb(t);
+               }
+       }
+
+       return 0;
+}
+#endif
+
 static int
 trace()
 {
@@ -1780,25 +1932,39 @@ trace()
 
                /* Look up `pid' in our table. */
                if ((tcp = pid2tcb(pid)) == NULL) {
-#if 0 /* XXX davidm */ /* WTA: disabled again */
-                       struct tcb *tcpchild;
-
-                       if ((tcpchild = alloctcb(pid)) == NULL) {
-                               fprintf(stderr, " [tcb table full]\n");
-                               kill(pid, SIGKILL); /* XXX */
-                               return 0;
+#ifdef LINUX
+                       if (followfork || followvfork) {
+                               /* This is needed to go with the CLONE_PTRACE
+                                  changes in process.c/util.c: we might see
+                                  the child's initial trap before we see the
+                                  parent return from the clone syscall.
+                                  Leave the child suspended until the parent
+                                  returns from its system call.  Only then
+                                  will we have the association of parent and
+                                  child so that we know how to do clearbpt
+                                  in the child.  */
+                               if ((tcp = alloctcb(pid)) == NULL) {
+                                       fprintf(stderr, " [tcb table full]\n");
+                                       kill(pid, SIGKILL); /* XXX */
+                                       return 0;
+                               }
+                               tcp->flags |= TCB_ATTACHED | TCB_SUSPENDED;
+                               newoutf(tcp);
+                               if (!qflag)
+                                       fprintf(stderr, "\
+Process %d attached (waiting for parent)\n",
+                                               pid);
                        }
-                       tcpchild->flags |= TCB_ATTACHED;
-                       newoutf(tcpchild);
-                       tcp->nchildren++;
-                       if (!qflag)
-                               fprintf(stderr, "Process %d attached\n", pid);
-#else
-                       fprintf(stderr, "unknown pid: %u\n", pid);
-                       if (WIFSTOPPED(status))
-                               ptrace(PTRACE_CONT, pid, (char *) 1, 0);
-                       exit(1);
+                       else
+                               /* This can happen if a clone call used
+                                  CLONE_PTRACE itself.  */
 #endif
+                       {
+                               fprintf(stderr, "unknown pid: %u\n", pid);
+                               if (WIFSTOPPED(status))
+                                       ptrace(PTRACE_CONT, pid, (char *) 1, 0);
+                               exit(1);
+                       }
                }
                /* set current output file */
                outf = tcp->outf;
@@ -1828,7 +1994,11 @@ trace()
                                        signame(WTERMSIG(status)));
                                printtrailer(tcp);
                        }
+#ifdef TCB_GROUP_EXITING
+                       handle_group_exit(tcp, -1);
+#else
                        droptcb(tcp);
+#endif
                        continue;
                }
                if (WIFEXITED(status)) {
@@ -1838,7 +2008,11 @@ trace()
                                fprintf(stderr,
                                        "PANIC: attached pid %u exited\n",
                                        pid);
+#ifdef TCB_GROUP_EXITING
+                       handle_group_exit(tcp, -1);
+#else
                        droptcb(tcp);
+#endif
                        continue;
                }
                if (!WIFSTOPPED(status)) {
@@ -1927,7 +2101,11 @@ trace()
                        }
                        if ((tcp->flags & TCB_ATTACHED) &&
                                !sigishandled(tcp, WSTOPSIG(status))) {
+#ifdef TCB_GROUP_EXITING
+                               handle_group_exit(tcp, WSTOPSIG(status));
+#else
                                detach(tcp, WSTOPSIG(status));
+#endif
                                continue;
                        }
                        if (ptrace(PTRACE_SYSCALL, pid, (char *) 1,
@@ -1950,6 +2128,13 @@ trace()
                        continue;
                }
                if (tcp->flags & TCB_EXITING) {
+#ifdef TCB_GROUP_EXITING
+                       if (tcp->flags & TCB_GROUP_EXITING) {
+                               if (handle_group_exit(tcp, 0) < 0)
+                                       return -1;
+                               continue;
+                       }
+#endif
                        if (tcp->flags & TCB_ATTACHED)
                                detach(tcp, 0);
                        else if (ptrace(PTRACE_CONT, pid, (char *) 1, 0) < 0) {