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;
}
return 0;
}
-#ifdef SYS_clone
+#if defined SYS_clone || defined SYS_clone2
int
internal_clone(tcp)
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);
return 0;
}
+#ifndef CLONE_PTRACE
/* Attach to the new child */
if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) {
if (bpt)
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
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;
fprintf(stderr, "Process %d attached\n", pid);
}
return 0;
+#endif
}
#endif /* !USE_PROCFS */
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.
/* 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. */
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));
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;
#endif /* USE_PROCFS */
-static struct tcb *
+struct tcb *
pid2tcb(pid)
int pid;
{
{
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);
}
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;
}
fclose(tcp->outf);
tcp->outf = 0;
+ tcp->flags = 0;
}
#ifndef USE_PROCFS
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, ...)");
int sig;
{
int error = 0;
-#ifdef LINUX
- int status;
-#endif
+ int status, resumed;
if (tcp->flags & TCB_BPTSET)
sig = SIGKILL;
#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)
#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()
{
/* 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;
signame(WTERMSIG(status)));
printtrailer(tcp);
}
+#ifdef TCB_GROUP_EXITING
+ handle_group_exit(tcp, -1);
+#else
droptcb(tcp);
+#endif
continue;
}
if (WIFEXITED(status)) {
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)) {
}
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,
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) {