]> granicus.if.org Git - strace/blobdiff - strace.c
Fix "format not a string literal" warning caused by tprintf(str)
[strace] / strace.c
index 6765ba3cce390cc334386dcd6fa7c8fd11c4e871..9c633887265228a6c6c0f00783dba7b5c99e2367 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -33,6 +33,7 @@
 #include "defs.h"
 
 #include <sys/types.h>
+#include <stdarg.h>
 #include <signal.h>
 #include <errno.h>
 #include <sys/param.h>
 #include <pwd.h>
 #include <grp.h>
 #include <string.h>
-#include <limits.h>
 #include <dirent.h>
 
 #ifdef LINUX
 # include <asm/unistd.h>
-# if defined __NR_tgkill
-#  define my_tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig))
-# elif defined __NR_tkill
-#  define my_tgkill(pid, tid, sig) syscall (__NR_tkill, (tid), (sig))
+# if defined __NR_tkill
+#  define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
 # else
    /* kill() may choose arbitrarily the target task of the process group
       while we later wait on a that specific TID.  PID process waits become
       TID task specific waits for a process under ptrace(2).  */
 #  warning "Neither tkill(2) nor tgkill(2) available, risk of strace hangs!"
-#  define my_tgkill(pid, tid, sig) kill ((tid), (sig))
+#  define my_tkill(tid, sig) kill((tid), (sig))
 # endif
 #endif
 
@@ -83,7 +81,11 @@ extern char *optarg;
 
 
 int debug = 0, followfork = 0;
-int dtime = 0, cflag = 0, xflag = 0, qflag = 0;
+unsigned int ptrace_setoptions = 0;
+/* Which WSTOPSIG(status) value marks syscall traps? */
+static unsigned int syscall_trap_sig = SIGTRAP;
+int dtime = 0, xflag = 0, qflag = 0;
+cflag_t cflag = CFLAG_NONE;
 static int iflag = 0, interactive = 0, pflag_seen = 0, rflag = 0, tflag = 0;
 /*
  * daemonized_tracer supports -D option.
@@ -102,49 +104,53 @@ static bool daemonized_tracer = 0;
 /* Sometimes we want to print only succeeding syscalls. */
 int not_failing_only = 0;
 
+/* Show path associated with fd arguments */
+int show_fd_path = 0;
+
+/* are we filtering traces based on paths? */
+int tracing_paths = 0;
+
 static int exit_code = 0;
 static int strace_child = 0;
+static int strace_tracer_pid = 0;
 
 static char *username = NULL;
-uid_t run_uid;
-gid_t run_gid;
+static uid_t run_uid;
+static gid_t run_gid;
 
-int acolumn = DEFAULT_ACOLUMN;
 int max_strlen = DEFAULT_STRLEN;
+static int acolumn = DEFAULT_ACOLUMN;
+static char *acolumn_spaces;
 static char *outfname = NULL;
-FILE *outf;
-struct tcb **tcbtab;
-unsigned int nprocs, tcbtabsize;
-char *progname;
-extern char **environ;
-
-static int detach P((struct tcb *tcp, int sig));
-static int trace P((void));
-static void cleanup P((void));
-static void interrupt P((int sig));
+static FILE *outf;
+static int curcol;
+static struct tcb **tcbtab;
+static unsigned int nprocs, tcbtabsize;
+static const char *progname;
+
+static int detach(struct tcb *tcp, int sig);
+static int trace(void);
+static void cleanup(void);
+static void interrupt(int sig);
 static sigset_t empty_set, blocked_set;
 
 #ifdef HAVE_SIG_ATOMIC_T
 static volatile sig_atomic_t interrupted;
 #else /* !HAVE_SIG_ATOMIC_T */
-#ifdef __STDC__
 static volatile int interrupted;
-#else /* !__STDC__ */
-static int interrupted;
-#endif /* !__STDC__ */
 #endif /* !HAVE_SIG_ATOMIC_T */
 
 #ifdef USE_PROCFS
 
-static struct tcb *pfd2tcb P((int pfd));
-static void reaper P((int sig));
-static void rebuild_pollv P((void));
+static struct tcb *pfd2tcb(int pfd);
+static void reaper(int sig);
+static void rebuild_pollv(void);
 static struct pollfd *pollv;
 
 #ifndef HAVE_POLLABLE_PROCFS
 
-static void proc_poll_open P((void));
-static void proc_poller P((int pfd));
+static void proc_poll_open(void);
+static void proc_poller(int pfd);
 
 struct proc_pollfd {
        int fd;
@@ -165,17 +171,16 @@ static int proc_poll_pipe[2] = { -1, -1 };
 #endif /* USE_PROCFS */
 
 static void
-usage(ofp, exitval)
-FILE *ofp;
-int exitval;
+usage(FILE *ofp, int exitval)
 {
        fprintf(ofp, "\
-usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
+usage: strace [-CdDffhiqrtttTvVxxy] [-a column] [-e expr] ... [-o file]\n\
               [-p pid] ... [-s strsize] [-u username] [-E var=val] ...\n\
-              [command [arg ...]]\n\
-   or: strace -c -D [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\n\
+              [-P path] [command [arg ...]]\n\
+   or: strace -c [-D] [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\n\
               [command [arg ...]]\n\
 -c -- count time, calls, and errors for each syscall and report summary\n\
+-C -- like -c but also print regular output while processes are running\n\
 -f -- follow forks, -ff -- with output into separate files\n\
 -F -- attempt to follow vforks, -h -- print help message\n\
 -i -- print instruction pointer at time of syscall\n\
@@ -184,6 +189,7 @@ usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
 -T -- print time spent in each syscall, -V -- print version\n\
 -v -- verbose mode: print unabbreviated argv, stat, termio[s], etc. args\n\
 -x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\
+-y -- print paths associated with file descriptor arguments\n\
 -a column -- alignment COLUMN for printing syscall results (default %d)\n\
 -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
    options: trace, abbrev, verbose, raw, signal, read, or write\n\
@@ -196,6 +202,7 @@ usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
 -u username -- run command as username handling setuid and/or setgid\n\
 -E var=val -- put var=val in the environment for command\n\
 -E var -- remove var from the environment for command\n\
+-P path -- trace accesses to path\n\
 " /* this is broken, so don't document it
 -z -- print only succeeding syscalls\n\
   */
@@ -203,6 +210,69 @@ usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
        exit(exitval);
 }
 
+static void die(void) __attribute__ ((noreturn));
+static void die(void)
+{
+       if (strace_tracer_pid == getpid()) {
+               cflag = 0;
+               cleanup();
+       }
+       exit(1);
+}
+
+static void verror_msg(int err_no, const char *fmt, va_list p)
+{
+       fflush(NULL);
+       fprintf(stderr, "%s: ", progname);
+       vfprintf(stderr, fmt, p);
+       if (err_no)
+               fprintf(stderr, ": %s\n", strerror(err_no));
+       else
+               putc('\n', stderr);
+       fflush(stderr);
+}
+
+void error_msg(const char *fmt, ...)
+{
+       va_list p;
+       va_start(p, fmt);
+       verror_msg(0, fmt, p);
+       va_end(p);
+}
+
+void error_msg_and_die(const char *fmt, ...)
+{
+       va_list p;
+       va_start(p, fmt);
+       verror_msg(0, fmt, p);
+       die();
+}
+
+void perror_msg(const char *fmt, ...)
+{
+       va_list p;
+       va_start(p, fmt);
+       verror_msg(errno, fmt, p);
+       va_end(p);
+}
+
+void perror_msg_and_die(const char *fmt, ...)
+{
+       va_list p;
+       va_start(p, fmt);
+       verror_msg(errno, fmt, p);
+       die();
+}
+
+void die_out_of_memory(void)
+{
+       static bool recursed = 0;
+       if (recursed)
+               exit(1);
+       recursed = 1;
+       error_msg_and_die("Out of memory");
+}
+
 #ifdef SVR4
 #ifdef MIPS
 void
@@ -220,30 +290,26 @@ foobar()
 # define fork()         vfork()
 #endif
 
-static int
+static void
 set_cloexec_flag(int fd)
 {
-       int     flags, newflags;
-
-       if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
-       {
-               fprintf(stderr, "%s: fcntl F_GETFD: %s\n",
-                       progname, strerror(errno));
-               return -1;
+       int flags, newflags;
+
+       flags = fcntl(fd, F_GETFD);
+       if (flags < 0) {
+               /* Can happen only if fd is bad.
+                * Should never happen: if it does, we have a bug
+                * in the caller. Therefore we just abort
+                * instead of propagating the error.
+                */
+               perror_msg_and_die("fcntl(%d, F_GETFD)", fd);
        }
 
        newflags = flags | FD_CLOEXEC;
        if (flags == newflags)
-               return 0;
-
-       if (fcntl(fd, F_SETFD, newflags) < 0)
-       {
-               fprintf(stderr, "%s: fcntl F_SETFD: %s\n",
-                       progname, strerror(errno));
-               return -1;
-       }
+               return;
 
-       return 0;
+       fcntl(fd, F_SETFD, newflags); /* never fails */
 }
 
 /*
@@ -256,11 +322,8 @@ swap_uid(void)
 #ifndef SVR4
        int euid = geteuid(), uid = getuid();
 
-       if (euid != uid && setreuid(euid, uid) < 0)
-       {
-               fprintf(stderr, "%s: setreuid: %s\n",
-                       progname, strerror(errno));
-               exit(1);
+       if (euid != uid && setreuid(euid, uid) < 0) {
+               perror_msg_and_die("setreuid");
        }
 #endif
 }
@@ -272,24 +335,20 @@ swap_uid(void)
 #endif
 
 static FILE *
-strace_fopen(const char *path, const char *mode)
+strace_fopen(const char *path)
 {
        FILE *fp;
 
        swap_uid();
-       if ((fp = fopen_for_output(path, mode)) == NULL)
-               fprintf(stderr, "%s: can't fopen '%s': %s\n",
-                       progname, path, strerror(errno));
+       fp = fopen_for_output(path, "w");
+       if (!fp)
+               perror_msg_and_die("Can't fopen '%s'", path);
        swap_uid();
-       if (fp && set_cloexec_flag(fileno(fp)) < 0)
-       {
-               fclose(fp);
-               fp = NULL;
-       }
+       set_cloexec_flag(fileno(fp));
        return fp;
 }
 
-static int popen_pid = -1;
+static int popen_pid = 0;
 
 #ifndef _PATH_BSHELL
 # define _PATH_BSHELL "/bin/sh"
@@ -303,71 +362,48 @@ static int popen_pid = -1;
 static FILE *
 strace_popen(const char *command)
 {
-       int     fds[2];
+       FILE *fp;
+       int fds[2];
 
        swap_uid();
        if (pipe(fds) < 0)
-       {
-               fprintf(stderr, "%s: pipe: %s\n",
-                       progname, strerror(errno));
-               swap_uid();
-               return NULL;
-       }
+               perror_msg_and_die("pipe");
 
-       if (set_cloexec_flag(fds[1]) < 0)
-       {
-               close(fds[0]);
-               close(fds[1]);
-               swap_uid();
-               return NULL;
-       }
+       set_cloexec_flag(fds[1]); /* never fails */
 
-       if ((popen_pid = fork()) == -1)
-       {
-               fprintf(stderr, "%s: fork: %s\n",
-                       progname, strerror(errno));
-               close(fds[0]);
-               close(fds[1]);
-               swap_uid();
-               return NULL;
-       }
+       popen_pid = vfork();
+       if (popen_pid == -1)
+               perror_msg_and_die("vfork");
 
-       if (popen_pid)
-       {
-               /* parent */
-               close(fds[0]);
-               swap_uid();
-               return fdopen(fds[1], "w");
-       } else
-       {
+       if (popen_pid == 0) {
                /* child */
                close(fds[1]);
-               if (fds[0] && (dup2(fds[0], 0) || close(fds[0])))
-               {
-                       fprintf(stderr, "%s: dup2: %s\n",
-                               progname, strerror(errno));
-                       _exit(1);
+               if (fds[0] != 0) {
+                       if (dup2(fds[0], 0))
+                               perror_msg_and_die("dup2");
+                       close(fds[0]);
                }
                execl(_PATH_BSHELL, "sh", "-c", command, NULL);
-               fprintf(stderr, "%s: execl: %s: %s\n",
-                       progname, _PATH_BSHELL, strerror(errno));
-               _exit(1);
+               perror_msg_and_die("Can't execute '%s'", _PATH_BSHELL);
        }
+
+       /* parent */
+       close(fds[0]);
+       swap_uid();
+       fp = fdopen(fds[1], "w");
+       if (!fp)
+               die_out_of_memory();
+       return fp;
 }
 
-static int
+static void
 newoutf(struct tcb *tcp)
 {
        if (outfname && followfork > 1) {
                char name[520 + sizeof(int) * 3];
-               FILE *fp;
-
                sprintf(name, "%.512s.%u", outfname, tcp->pid);
-               if ((fp = strace_fopen(name, "w")) == NULL)
-                       return -1;
-               tcp->outf = fp;
+               tcp->outf = strace_fopen(name);
        }
-       return 0;
 }
 
 static void
@@ -380,7 +416,7 @@ startup_attach(void)
         * Block user interruptions as we would leave the traced
         * process stopped (process state T) if we would terminate in
         * between PTRACE_ATTACH and wait4 () on SIGSTOP.
-        * We rely on cleanup () from this point on.
+        * We rely on cleanup() from this point on.
         */
        if (interactive)
                sigprocmask(SIG_BLOCK, &blocked_set, NULL);
@@ -392,28 +428,31 @@ startup_attach(void)
                }
                if (pid) { /* parent */
                        /*
-                        * Wait for child to attach to straced process
-                        * (our parent). Child SIGKILLs us after it attached.
-                        * Parent's wait() is unblocked by our death,
+                        * Wait for grandchild to attach to straced process
+                        * (grandparent). Grandchild SIGKILLs us after it attached.
+                        * Grandparent's wait() is unblocked by our death,
                         * it proceeds to exec the straced program.
                         */
                        pause();
                        _exit(0); /* paranoia */
                }
+               /* grandchild */
+               /* We will be the tracer process. Remember our new pid: */
+               strace_tracer_pid = getpid();
        }
 
        for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
                tcp = tcbtab[tcbi];
+
                if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED))
                        continue;
 #ifdef LINUX
-               if (tcp->flags & TCB_CLONE_THREAD)
+               if (tcp->flags & TCB_ATTACH_DONE)
                        continue;
 #endif
                /* Reinitialize the output since it may have changed. */
                tcp->outf = outf;
-               if (newoutf(tcp) < 0)
-                       exit(1);
+               newoutf(tcp);
 
 #ifdef USE_PROCFS
                if (proc_open(tcp, 1) < 0) {
@@ -440,20 +479,23 @@ startup_attach(void)
                                        if (tid <= 0)
                                                continue;
                                        ++ntid;
-                                       if (ptrace(PTRACE_ATTACH, tid, (char *) 1, 0) < 0)
+                                       if (ptrace(PTRACE_ATTACH, tid, (char *) 1, 0) < 0) {
                                                ++nerr;
-                                       else if (tid != tcbtab[tcbi]->pid) {
-                                               tcp = alloctcb(tid);
-                                               tcp->flags |= TCB_ATTACHED|TCB_CLONE_THREAD|TCB_CLONE_DETACHED|TCB_FOLLOWFORK;
-                                               tcbtab[tcbi]->nchildren++;
-                                               tcbtab[tcbi]->nclone_threads++;
-                                               tcbtab[tcbi]->nclone_detached++;
-                                               tcp->parent = tcbtab[tcbi];
+                                               if (debug)
+                                                       fprintf(stderr, "attach to pid %d failed\n", tid);
+                                       }
+                                       else {
+                                               if (debug)
+                                                       fprintf(stderr, "attach to pid %d succeeded\n", tid);
+                                               if (tid != tcp->pid) {
+                                                       struct tcb *new_tcp = alloctcb(tid);
+                                                       new_tcp->flags |= TCB_ATTACHED|TCB_ATTACH_DONE;
+                                               }
                                        }
                                        if (interactive) {
                                                sigprocmask(SIG_SETMASK, &empty_set, NULL);
                                                if (interrupted)
-                                                       return;
+                                                       goto ret;
                                                sigprocmask(SIG_BLOCK, &blocked_set, NULL);
                                        }
                                }
@@ -468,18 +510,19 @@ startup_attach(void)
                                        fprintf(stderr, ntid > 1
 ? "Process %u attached with %u threads - interrupt to quit\n"
 : "Process %u attached - interrupt to quit\n",
-                                               tcbtab[tcbi]->pid, ntid);
+                                               tcp->pid, ntid);
                                }
                                continue;
                        } /* if (opendir worked) */
                } /* if (-f) */
-# endif
+# endif /* LINUX */
                if (ptrace(PTRACE_ATTACH, tcp->pid, (char *) 1, 0) < 0) {
                        perror("attach: ptrace(PTRACE_ATTACH, ...)");
                        droptcb(tcp);
                        continue;
                }
-               /* INTERRUPTED is going to be checked at the top of TRACE.  */
+               if (debug)
+                       fprintf(stderr, "attach to pid %d (main) succeeded\n", tcp->pid);
 
                if (daemonized_tracer) {
                        /*
@@ -499,14 +542,23 @@ startup_attach(void)
                        fprintf(stderr,
                                "Process %u attached - interrupt to quit\n",
                                tcp->pid);
+       } /* for each tcbtab[] */
+
+ ret:
+#ifdef LINUX
+       /* TCB_ATTACH_DONE flag is used only in this function */
+       for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
+               tcp = tcbtab[tcbi];
+               tcp->flags &= ~TCB_ATTACH_DONE;
        }
+#endif
 
        if (interactive)
                sigprocmask(SIG_SETMASK, &empty_set, NULL);
 }
 
 static void
-startup_child (char **argv)
+startup_child(char **argv)
 {
        struct stat statbuf;
        const char *filename;
@@ -518,8 +570,7 @@ startup_child (char **argv)
        if (strchr(filename, '/')) {
                if (strlen(filename) > sizeof pathname - 1) {
                        errno = ENAMETOOLONG;
-                       perror("strace: exec");
-                       exit(1);
+                       perror_msg_and_die("exec");
                }
                strcpy(pathname, filename);
        }
@@ -533,7 +584,7 @@ startup_child (char **argv)
                strcpy(pathname, filename);
 #endif /* USE_DEBUGGING_EXEC */
        else {
-               char *path;
+               const char *path;
                int m, n, len;
 
                for (path = getenv("PATH"); path && *path; path += m) {
@@ -567,22 +618,18 @@ startup_child (char **argv)
                }
        }
        if (stat(pathname, &statbuf) < 0) {
-               fprintf(stderr, "%s: %s: command not found\n",
-                       progname, filename);
-               exit(1);
+               perror_msg_and_die("Can't stat '%s'", filename);
        }
        strace_child = pid = fork();
        if (pid < 0) {
-               perror("strace: fork");
-               cleanup();
-               exit(1);
+               perror_msg_and_die("fork");
        }
-       if ((pid != 0 && daemonized_tracer) /* parent: to become a traced process */
-        || (pid == 0 && !daemonized_tracer) /* child: to become a traced process */
+       if ((pid != 0 && daemonized_tracer) /* -D: parent to become a traced process */
+        || (pid == 0 && !daemonized_tracer) /* not -D: child to become a traced process */
        ) {
                pid = getpid();
 #ifdef USE_PROCFS
-               if (outf != stderr) close (fileno (outf));
+               if (outf != stderr) close(fileno(outf));
 #ifdef MIPS
                /* Kludge for SGI, see proc_open for details. */
                sa.sa_handler = foobar;
@@ -596,13 +643,12 @@ startup_child (char **argv)
                kill(pid, SIGSTOP); /* stop HERE */
 #endif /* FREEBSD */
 #else /* !USE_PROCFS */
-               if (outf!=stderr)
-                       close(fileno (outf));
+               if (outf != stderr)
+                       close(fileno(outf));
 
                if (!daemonized_tracer) {
                        if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) {
-                               perror("strace: ptrace(PTRACE_TRACEME, ...)");
-                               exit(1);
+                               perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
                        }
                        if (debug)
                                kill(pid, SIGSTOP);
@@ -623,16 +669,13 @@ startup_child (char **argv)
                         */
                        if (username != NULL) {
                                if (initgroups(username, run_gid) < 0) {
-                                       perror("initgroups");
-                                       exit(1);
+                                       perror_msg_and_die("initgroups");
                                }
                                if (setregid(run_gid, run_egid) < 0) {
-                                       perror("setregid");
-                                       exit(1);
+                                       perror_msg_and_die("setregid");
                                }
                                if (setreuid(run_uid, run_euid) < 0) {
-                                       perror("setreuid");
-                                       exit(1);
+                                       perror_msg_and_die("setreuid");
                                }
                        }
                }
@@ -669,11 +712,13 @@ startup_child (char **argv)
 #endif /* !USE_PROCFS */
 
                execv(pathname, argv);
-               perror("strace: exec");
-               _exit(1);
+               perror_msg_and_die("exec");
        }
 
        /* We are the tracer.  */
+       /* With -D, we are *child* here, IOW: different pid. Fetch it. */
+       strace_tracer_pid = getpid();
+
        tcp = alloctcb(daemonized_tracer ? getppid() : pid);
        if (daemonized_tracer) {
                /* We want subsequent startup_attach() to attach to it.  */
@@ -681,13 +726,221 @@ startup_child (char **argv)
        }
 #ifdef USE_PROCFS
        if (proc_open(tcp, 0) < 0) {
-               fprintf(stderr, "trouble opening proc file\n");
-               cleanup();
-               exit(1);
+               perror_msg_and_die("trouble opening proc file");
        }
 #endif /* USE_PROCFS */
 }
 
+#ifdef LINUX
+static void kill_save_errno(pid_t pid, int sig)
+{
+       int saved_errno = errno;
+
+       (void) kill(pid, sig);
+       errno = saved_errno;
+}
+
+/*
+ * Test whether the kernel support PTRACE_O_TRACECLONE et al options.
+ * First fork a new child, call ptrace with PTRACE_SETOPTIONS on it,
+ * and then see which options are supported by the kernel.
+ */
+static void
+test_ptrace_setoptions_followfork(void)
+{
+       int pid, expected_grandchild = 0, found_grandchild = 0;
+       const unsigned int test_options = PTRACE_O_TRACECLONE |
+                                         PTRACE_O_TRACEFORK |
+                                         PTRACE_O_TRACEVFORK;
+
+       pid = fork();
+       if (pid < 0)
+               perror_msg_and_die("fork");
+       if (pid == 0) {
+               pid = getpid();
+               if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
+                       perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+                                          __func__);
+               kill(pid, SIGSTOP);
+               if (fork() < 0)
+                       perror_msg_and_die("fork");
+               _exit(0);
+       }
+
+       while (1) {
+               int status, tracee_pid;
+
+               errno = 0;
+               tracee_pid = wait(&status);
+               if (tracee_pid <= 0) {
+                       if (errno == EINTR)
+                               continue;
+                       else if (errno == ECHILD)
+                               break;
+                       kill_save_errno(pid, SIGKILL);
+                       perror_msg_and_die("%s: unexpected wait result %d",
+                                          __func__, tracee_pid);
+               }
+               if (WIFEXITED(status)) {
+                       if (WEXITSTATUS(status)) {
+                               if (tracee_pid != pid)
+                                       kill_save_errno(pid, SIGKILL);
+                               error_msg_and_die("%s: unexpected exit status %u",
+                                                 __func__, WEXITSTATUS(status));
+                       }
+                       continue;
+               }
+               if (WIFSIGNALED(status)) {
+                       if (tracee_pid != pid)
+                               kill_save_errno(pid, SIGKILL);
+                       error_msg_and_die("%s: unexpected signal %u",
+                                         __func__, WTERMSIG(status));
+               }
+               if (!WIFSTOPPED(status)) {
+                       if (tracee_pid != pid)
+                               kill_save_errno(tracee_pid, SIGKILL);
+                       kill(pid, SIGKILL);
+                       error_msg_and_die("%s: unexpected wait status %x",
+                                         __func__, status);
+               }
+               if (tracee_pid != pid) {
+                       found_grandchild = tracee_pid;
+                       if (ptrace(PTRACE_CONT, tracee_pid, 0, 0) < 0) {
+                               kill_save_errno(tracee_pid, SIGKILL);
+                               kill_save_errno(pid, SIGKILL);
+                               perror_msg_and_die("PTRACE_CONT doesn't work");
+                       }
+                       continue;
+               }
+               switch (WSTOPSIG(status)) {
+               case SIGSTOP:
+                       if (ptrace(PTRACE_SETOPTIONS, pid, 0, test_options) < 0
+                           && errno != EINVAL && errno != EIO)
+                               perror_msg("PTRACE_SETOPTIONS");
+                       break;
+               case SIGTRAP:
+                       if (status >> 16 == PTRACE_EVENT_FORK) {
+                               long msg = 0;
+
+                               if (ptrace(PTRACE_GETEVENTMSG, pid,
+                                          NULL, (long) &msg) == 0)
+                                       expected_grandchild = msg;
+                       }
+                       break;
+               }
+               if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) {
+                       kill_save_errno(pid, SIGKILL);
+                       perror_msg_and_die("PTRACE_SYSCALL doesn't work");
+               }
+       }
+       if (expected_grandchild && expected_grandchild == found_grandchild) {
+               ptrace_setoptions |= test_options;
+               if (debug)
+                       fprintf(stderr, "ptrace_setoptions = %#x\n",
+                               ptrace_setoptions);
+               return;
+       }
+       error_msg("Test for PTRACE_O_TRACECLONE failed, "
+                 "giving up using this feature.");
+}
+
+/*
+ * Test whether the kernel support PTRACE_O_TRACESYSGOOD.
+ * First fork a new child, call ptrace(PTRACE_SETOPTIONS) on it,
+ * and then see whether it will stop with (SIGTRAP | 0x80).
+ *
+ * Use of this option enables correct handling of user-generated SIGTRAPs,
+ * and SIGTRAPs generated by special instructions such as int3 on x86:
+ * _start:     .globl  _start
+ *             int3
+ *             movl    $42, %ebx
+ *             movl    $1, %eax
+ *             int     $0x80
+ * (compile with: "gcc -nostartfiles -nostdlib -o int3 int3.S")
+ */
+static void
+test_ptrace_setoptions_for_all(void)
+{
+       const unsigned int test_options = PTRACE_O_TRACESYSGOOD |
+                                         PTRACE_O_TRACEEXEC;
+       int pid;
+       int it_worked = 0;
+
+       pid = fork();
+       if (pid < 0)
+               perror_msg_and_die("fork");
+
+       if (pid == 0) {
+               pid = getpid();
+               if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0)
+                       /* Note: exits with exitcode 1 */
+                       perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+                                          __func__);
+               kill(pid, SIGSTOP);
+               _exit(0); /* parent should see entry into this syscall */
+       }
+
+       while (1) {
+               int status, tracee_pid;
+
+               errno = 0;
+               tracee_pid = wait(&status);
+               if (tracee_pid <= 0) {
+                       if (errno == EINTR)
+                               continue;
+                       kill_save_errno(pid, SIGKILL);
+                       perror_msg_and_die("%s: unexpected wait result %d",
+                                          __func__, tracee_pid);
+               }
+               if (WIFEXITED(status)) {
+                       if (WEXITSTATUS(status) == 0)
+                               break;
+                       error_msg_and_die("%s: unexpected exit status %u",
+                                         __func__, WEXITSTATUS(status));
+               }
+               if (WIFSIGNALED(status)) {
+                       error_msg_and_die("%s: unexpected signal %u",
+                                         __func__, WTERMSIG(status));
+               }
+               if (!WIFSTOPPED(status)) {
+                       kill(pid, SIGKILL);
+                       error_msg_and_die("%s: unexpected wait status %x",
+                                         __func__, status);
+               }
+               if (WSTOPSIG(status) == SIGSTOP) {
+                       /*
+                        * We don't check "options aren't accepted" error.
+                        * If it happens, we'll never get (SIGTRAP | 0x80),
+                        * and thus will decide to not use the option.
+                        * IOW: the outcome of the test will be correct.
+                        */
+                       if (ptrace(PTRACE_SETOPTIONS, pid, 0L, test_options) < 0
+                           && errno != EINVAL && errno != EIO)
+                               perror_msg("PTRACE_SETOPTIONS");
+               }
+               if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
+                       it_worked = 1;
+               }
+               if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
+                       kill_save_errno(pid, SIGKILL);
+                       perror_msg_and_die("PTRACE_SYSCALL doesn't work");
+               }
+       }
+
+       if (it_worked) {
+               syscall_trap_sig = (SIGTRAP | 0x80);
+               ptrace_setoptions |= test_options;
+               if (debug)
+                       fprintf(stderr, "ptrace_setoptions = %#x\n",
+                               ptrace_setoptions);
+               return;
+       }
+
+       error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
+                 "giving up using this feature.");
+}
+#endif
+
 int
 main(int argc, char *argv[])
 {
@@ -696,22 +949,20 @@ main(int argc, char *argv[])
        int optF = 0;
        struct sigaction sa;
 
-       static char buf[BUFSIZ];
-
        progname = argv[0] ? argv[0] : "strace";
 
+       strace_tracer_pid = getpid();
+
        /* Allocate the initial tcbtab.  */
        tcbtabsize = argc;      /* Surely enough for all -p args.  */
-       if ((tcbtab = calloc(tcbtabsize, sizeof tcbtab[0])) == NULL) {
-               fprintf(stderr, "%s: out of memory\n", progname);
-               exit(1);
-       }
-       if ((tcbtab[0] = calloc(tcbtabsize, sizeof tcbtab[0][0])) == NULL) {
-               fprintf(stderr, "%s: out of memory\n", progname);
-               exit(1);
-       }
-       for (tcp = tcbtab[0]; tcp < &tcbtab[0][tcbtabsize]; ++tcp)
-               tcbtab[tcp - tcbtab[0]] = &tcbtab[0][tcp - tcbtab[0]];
+       tcbtab = calloc(tcbtabsize, sizeof(tcbtab[0]));
+       if (!tcbtab)
+               die_out_of_memory();
+       tcp = calloc(tcbtabsize, sizeof(*tcp));
+       if (!tcp)
+               die_out_of_memory();
+       for (c = 0; c < tcbtabsize; c++)
+               tcbtab[c] = tcp++;
 
        outf = stderr;
        interactive = 1;
@@ -722,21 +973,28 @@ main(int argc, char *argv[])
        qualify("verbose=all");
        qualify("signal=all");
        while ((c = getopt(argc, argv,
-               "+cdfFhiqrtTvVxz"
+               "+cCdfFhiqrtTvVxyz"
 #ifndef USE_PROCFS
                "D"
 #endif
-               "a:e:o:O:p:s:S:u:E:")) != EOF) {
+               "a:e:o:O:p:s:S:u:E:P:")) != EOF) {
                switch (c) {
                case 'c':
-                       cflag++;
-                       dtime++;
+                       if (cflag == CFLAG_BOTH) {
+                               error_msg_and_die("-c and -C are mutually exclusive options");
+                       }
+                       cflag = CFLAG_ONLY_STATS;
+                       break;
+               case 'C':
+                       if (cflag == CFLAG_ONLY_STATS) {
+                               error_msg_and_die("-c and -C are mutually exclusive options");
+                       }
+                       cflag = CFLAG_BOTH;
                        break;
                case 'd':
                        debug++;
                        break;
 #ifndef USE_PROCFS
-               /* Experimental, not documented in manpage yet. */
                case 'D':
                        daemonized_tracer = 1;
                        break;
@@ -769,6 +1027,9 @@ main(int argc, char *argv[])
                case 'x':
                        xflag++;
                        break;
+               case 'y':
+                       show_fd_path = 1;
+                       break;
                case 'v':
                        qualify("abbrev=none");
                        break;
@@ -781,6 +1042,8 @@ main(int argc, char *argv[])
                        break;
                case 'a':
                        acolumn = atoi(optarg);
+                       if (acolumn < 0)
+                               error_msg_and_die("Bad column width '%s'", optarg);
                        break;
                case 'e':
                        qualify(optarg);
@@ -792,26 +1055,29 @@ main(int argc, char *argv[])
                        set_overhead(atoi(optarg));
                        break;
                case 'p':
-                       if ((pid = atoi(optarg)) <= 0) {
-                               fprintf(stderr, "%s: Invalid process id: %s\n",
-                                       progname, optarg);
+                       pid = atoi(optarg);
+                       if (pid <= 0) {
+                               error_msg("Invalid process id: '%s'", optarg);
                                break;
                        }
-                       if (pid == getpid()) {
-                               fprintf(stderr, "%s: I'm sorry, I can't let you do that, Dave.\n", progname);
+                       if (pid == strace_tracer_pid) {
+                               error_msg("I'm sorry, I can't let you do that, Dave.");
                                break;
                        }
                        tcp = alloc_tcb(pid, 0);
                        tcp->flags |= TCB_ATTACHED;
                        pflag_seen++;
                        break;
+               case 'P':
+                       tracing_paths = 1;
+                       if (pathtrace_select(optarg)) {
+                               error_msg_and_die("Failed to select path '%s'", optarg);
+                       }
+                       break;
                case 's':
                        max_strlen = atoi(optarg);
                        if (max_strlen < 0) {
-                               fprintf(stderr,
-                                       "%s: invalid -s argument: %s\n",
-                                       progname, optarg);
-                               exit(1);
+                               error_msg_and_die("Invalid -s argument: '%s'", optarg);
                        }
                        break;
                case 'S':
@@ -821,11 +1087,8 @@ main(int argc, char *argv[])
                        username = strdup(optarg);
                        break;
                case 'E':
-                       if (putenv(optarg) < 0) {
-                               fprintf(stderr, "%s: out of memory\n",
-                                       progname);
-                               exit(1);
-                       }
+                       if (putenv(optarg) < 0)
+                               die_out_of_memory();
                        break;
                default:
                        usage(stderr, 1);
@@ -833,17 +1096,24 @@ main(int argc, char *argv[])
                }
        }
 
+       acolumn_spaces = malloc(acolumn + 1);
+       if (!acolumn_spaces)
+               die_out_of_memory();
+       memset(acolumn_spaces, ' ', acolumn);
+       acolumn_spaces[acolumn] = '\0';
+
        if ((optind == argc) == !pflag_seen)
                usage(stderr, 1);
 
+       if (pflag_seen && daemonized_tracer) {
+               error_msg_and_die("-D and -p are mutually exclusive options");
+       }
+
        if (!followfork)
                followfork = optF;
 
        if (followfork > 1 && cflag) {
-               fprintf(stderr,
-                       "%s: -c and -ff are mutually exclusive options\n",
-                       progname);
-               exit(1);
+               error_msg_and_die("(-c or -C) and -ff are mutually exclusive options");
        }
 
        /* See if they want to run as another user. */
@@ -851,15 +1121,11 @@ main(int argc, char *argv[])
                struct passwd *pent;
 
                if (getuid() != 0 || geteuid() != 0) {
-                       fprintf(stderr,
-                               "%s: you must be root to use the -u option\n",
-                               progname);
-                       exit(1);
+                       error_msg_and_die("You must be root to use the -u option");
                }
-               if ((pent = getpwnam(username)) == NULL) {
-                       fprintf(stderr, "%s: cannot find user `%s'\n",
-                               progname, username);
-                       exit(1);
+               pent = getpwnam(username);
+               if (pent == NULL) {
+                       error_msg_and_die("Cannot find user '%s'", username);
                }
                run_uid = pent->pw_uid;
                run_gid = pent->pw_gid;
@@ -869,6 +1135,12 @@ main(int argc, char *argv[])
                run_gid = getgid();
        }
 
+#ifdef LINUX
+       if (followfork)
+               test_ptrace_setoptions_followfork();
+       test_ptrace_setoptions_for_all();
+#endif
+
        /* Check if they want to redirect the output. */
        if (outfname) {
                /* See if they want to pipe the output. */
@@ -877,27 +1149,23 @@ main(int argc, char *argv[])
                         * We can't do the <outfname>.PID funny business
                         * when using popen, so prohibit it.
                         */
-                       if (followfork > 1) {
-                               fprintf(stderr, "\
-%s: piping the output and -ff are mutually exclusive options\n",
-                                       progname);
-                               exit(1);
-                       }
-
-                       if ((outf = strace_popen(outfname + 1)) == NULL)
-                               exit(1);
+                       if (followfork > 1)
+                               error_msg_and_die("Piping the output and -ff are mutually exclusive");
+                       outf = strace_popen(outfname + 1);
                }
-               else if (followfork <= 1 &&
-                        (outf = strace_fopen(outfname, "w")) == NULL)
-                       exit(1);
+               else if (followfork <= 1)
+                       outf = strace_fopen(outfname);
        }
 
-       if (!outfname || outfname[0] == '|' || outfname[0] == '!')
+       if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
+               static char buf[BUFSIZ];
                setvbuf(outf, buf, _IOLBF, BUFSIZ);
+       }
        if (outfname && optind < argc) {
                interactive = 0;
                qflag = 1;
        }
+
        /* Valid states here:
           optind < argc        pflag_seen      outfname        interactive
           1                    0               0               1
@@ -969,7 +1237,7 @@ main(int argc, char *argv[])
        exit(exit_code);
 }
 
-void
+static void
 expand_tcbtab(void)
 {
        /* Allocate some more TCBs and expand the table.
@@ -977,21 +1245,15 @@ expand_tcbtab(void)
           callers have pointers and it would be a pain.
           So tcbtab is a table of pointers.  Since we never
           free the TCBs, we allocate a single chunk of many.  */
-       struct tcb **newtab = (struct tcb **)
-               realloc(tcbtab, 2 * tcbtabsize * sizeof tcbtab[0]);
-       struct tcb *newtcbs = (struct tcb *) calloc(tcbtabsize,
-                                                   sizeof *newtcbs);
-       int i;
-       if (newtab == NULL || newtcbs == NULL) {
-               fprintf(stderr, "%s: expand_tcbtab: out of memory\n",
-                       progname);
-               cleanup();
-               exit(1);
-       }
-       for (i = tcbtabsize; i < 2 * tcbtabsize; ++i)
-               newtab[i] = &newtcbs[i - tcbtabsize];
+       int i = tcbtabsize;
+       struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0]));
+       struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0]));
+       if (!newtab || !newtcbs)
+               die_out_of_memory();
        tcbtabsize *= 2;
        tcbtab = newtab;
+       while (i < tcbtabsize)
+               tcbtab[i++] = newtcbs++;
 }
 
 struct tcb *
@@ -1006,28 +1268,22 @@ alloc_tcb(int pid, int command_options_parsed)
        for (i = 0; i < tcbtabsize; i++) {
                tcp = tcbtab[i];
                if ((tcp->flags & TCB_INUSE) == 0) {
+                       memset(tcp, 0, sizeof(*tcp));
                        tcp->pid = pid;
-                       tcp->parent = NULL;
-                       tcp->nchildren = 0;
-                       tcp->nzombies = 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;
-                       tcp->stime.tv_usec = 0;
+#ifdef USE_PROCFS
                        tcp->pfd = -1;
+#endif
                        nprocs++;
+                       if (debug)
+                               fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
                        if (command_options_parsed)
                                newoutf(tcp);
                        return tcp;
                }
        }
-       fprintf(stderr, "%s: bug in alloc_tcb\n", progname);
-       cleanup();
-       exit(1);
+       error_msg_and_die("bug in alloc_tcb");
 }
 
 #ifdef USE_PROCFS
@@ -1049,54 +1305,52 @@ proc_open(struct tcb *tcp, int attaching)
 #ifdef HAVE_MP_PROCFS
        /* Open the process pseudo-files in /proc. */
        sprintf(proc, "/proc/%d/ctl", tcp->pid);
-       if ((tcp->pfd = open(proc, O_WRONLY|O_EXCL)) < 0) {
+       tcp->pfd = open(proc, O_WRONLY|O_EXCL);
+       if (tcp->pfd < 0) {
                perror("strace: open(\"/proc/...\", ...)");
                return -1;
        }
-       if (set_cloexec_flag(tcp->pfd) < 0) {
-               return -1;
-       }
+       set_cloexec_flag(tcp->pfd);
        sprintf(proc, "/proc/%d/status", tcp->pid);
-       if ((tcp->pfd_stat = open(proc, O_RDONLY|O_EXCL)) < 0) {
+       tcp->pfd_stat = open(proc, O_RDONLY|O_EXCL);
+       if (tcp->pfd_stat < 0) {
                perror("strace: open(\"/proc/...\", ...)");
                return -1;
        }
-       if (set_cloexec_flag(tcp->pfd_stat) < 0) {
-               return -1;
-       }
+       set_cloexec_flag(tcp->pfd_stat);
        sprintf(proc, "/proc/%d/as", tcp->pid);
-       if ((tcp->pfd_as = open(proc, O_RDONLY|O_EXCL)) < 0) {
+       tcp->pfd_as = open(proc, O_RDONLY|O_EXCL);
+       if (tcp->pfd_as < 0) {
                perror("strace: open(\"/proc/...\", ...)");
                return -1;
        }
-       if (set_cloexec_flag(tcp->pfd_as) < 0) {
-               return -1;
-       }
+       set_cloexec_flag(tcp->pfd_as);
 #else
        /* Open the process pseudo-file in /proc. */
-#ifndef FREEBSD
+# ifndef FREEBSD
        sprintf(proc, "/proc/%d", tcp->pid);
-       if ((tcp->pfd = open(proc, O_RDWR|O_EXCL)) < 0) {
-#else /* FREEBSD */
+       tcp->pfd = open(proc, O_RDWR|O_EXCL);
+# else
        sprintf(proc, "/proc/%d/mem", tcp->pid);
-       if ((tcp->pfd = open(proc, O_RDWR)) < 0) {
-#endif /* FREEBSD */
+       tcp->pfd = open(proc, O_RDWR);
+# endif
+       if (tcp->pfd < 0) {
                perror("strace: open(\"/proc/...\", ...)");
                return -1;
        }
-       if (set_cloexec_flag(tcp->pfd) < 0) {
-               return -1;
-       }
+       set_cloexec_flag(tcp->pfd);
 #endif
 #ifdef FREEBSD
        sprintf(proc, "/proc/%d/regs", tcp->pid);
-       if ((tcp->pfd_reg = open(proc, O_RDONLY)) < 0) {
+       tcp->pfd_reg = open(proc, O_RDONLY);
+       if (tcp->pfd_reg < 0) {
                perror("strace: open(\"/proc/.../regs\", ...)");
                return -1;
        }
        if (cflag) {
                sprintf(proc, "/proc/%d/status", tcp->pid);
-               if ((tcp->pfd_status = open(proc, O_RDONLY)) < 0) {
+               tcp->pfd_status = open(proc, O_RDONLY);
+               if (tcp->pfd_status < 0) {
                        perror("strace: open(\"/proc/.../status\", ...)");
                        return -1;
                }
@@ -1110,12 +1364,12 @@ proc_open(struct tcb *tcp, int attaching)
                 * condition we have to poll for the event.
                 */
                for (;;) {
-                       if (IOCTL_STATUS (tcp) < 0) {
+                       if (IOCTL_STATUS(tcp) < 0) {
                                perror("strace: PIOCSTATUS");
                                return -1;
                        }
                        if (tcp->status.PR_FLAGS & PR_ASLEEP)
-                           break;
+                               break;
                }
        }
 #ifndef FREEBSD
@@ -1166,22 +1420,22 @@ proc_open(struct tcb *tcp, int attaching)
        premptyset(&syscalls);
        for (i = 1; i < MAX_QUALS; ++i) {
                if (i > (sizeof syscalls) * CHAR_BIT) break;
-               if (qual_flags [i] & QUAL_TRACE) praddset (&syscalls, i);
+               if (qual_flags[i] & QUAL_TRACE) praddset(&syscalls, i);
        }
-       praddset (&syscalls, SYS_execve);
+       praddset(&syscalls, SYS_execve);
        if (followfork) {
-               praddset (&syscalls, SYS_fork);
+               praddset(&syscalls, SYS_fork);
 #ifdef SYS_forkall
-               praddset (&syscalls, SYS_forkall);
+               praddset(&syscalls, SYS_forkall);
 #endif
 #ifdef SYS_fork1
-               praddset (&syscalls, SYS_fork1);
+               praddset(&syscalls, SYS_fork1);
 #endif
 #ifdef SYS_rfork1
-               praddset (&syscalls, SYS_rfork1);
+               praddset(&syscalls, SYS_rfork1);
 #endif
 #ifdef SYS_rforkall
-               praddset (&syscalls, SYS_rforkall);
+               praddset(&syscalls, SYS_rforkall);
 #endif
        }
        if (IOCTL(tcp->pfd, PIOCSENTRY, &syscalls) < 0) {
@@ -1197,7 +1451,7 @@ proc_open(struct tcb *tcp, int attaching)
        premptyset(&signals);
        for (i = 1; i < MAX_QUALS; ++i) {
                if (i > (sizeof signals) * CHAR_BIT) break;
-               if (qual_flags [i] & QUAL_SIGNAL) praddset (&signals, i);
+               if (qual_flags[i] & QUAL_SIGNAL) praddset(&signals, i);
        }
        if (IOCTL(tcp->pfd, PIOCSTRACE, &signals) < 0) {
                perror("PIOCSTRACE");
@@ -1207,7 +1461,7 @@ proc_open(struct tcb *tcp, int attaching)
        premptyset(&faults);
        for (i = 1; i < MAX_QUALS; ++i) {
                if (i > (sizeof faults) * CHAR_BIT) break;
-               if (qual_flags [i] & QUAL_FAULT) praddset (&faults, i);
+               if (qual_flags[i] & QUAL_FAULT) praddset(&faults, i);
        }
        if (IOCTL(tcp->pfd, PIOCSFAULT, &faults) < 0) {
                perror("PIOCSFAULT");
@@ -1215,8 +1469,8 @@ proc_open(struct tcb *tcp, int attaching)
        }
 #else /* FREEBSD */
        /* set events flags. */
-       arg = S_SIG | S_SCE | S_SCX ;
-       if(ioctl(tcp->pfd, PIOCBIS, arg) < 0) {
+       arg = S_SIG | S_SCE | S_SCX;
+       if (ioctl(tcp->pfd, PIOCBIS, arg) < 0) {
                perror("PIOCBIS");
                return -1;
        }
@@ -1232,7 +1486,7 @@ proc_open(struct tcb *tcp, int attaching)
 #ifdef PRSABORT
                /* The child is in a pause(), abort it. */
                arg = PRSABORT;
-               if (IOCTL (tcp->pfd, PIOCRUN, &arg) < 0) {
+               if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0) {
                        perror("PIOCRUN");
                        return -1;
                }
@@ -1244,7 +1498,7 @@ proc_open(struct tcb *tcp, int attaching)
 #endif
                for (;;) {
                        /* Wait for the child to do something. */
-                       if (IOCTL_WSTOP (tcp) < 0) {
+                       if (IOCTL_WSTOP(tcp) < 0) {
                                perror("PIOCWSTOP");
                                return -1;
                        }
@@ -1257,10 +1511,11 @@ proc_open(struct tcb *tcp, int attaching)
                        /* Set it running: maybe execve will be next. */
 #ifndef FREEBSD
                        arg = 0;
-                       if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0) {
-#else /* FREEBSD */
-                       if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0) {
-#endif /* FREEBSD */
+                       if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0)
+#else
+                       if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0)
+#endif
+                       {
                                perror("PIOCRUN");
                                return -1;
                        }
@@ -1272,10 +1527,9 @@ proc_open(struct tcb *tcp, int attaching)
                                kill(tcp->pid, SIGCONT);
 #endif
                }
-#ifndef FREEBSD
        }
-#else /* FREEBSD */
-       else {
+#ifdef FREEBSD
+       else {
                if (attaching < 2) {
                        /* We are attaching to an already running process.
                         * Try to figure out the state of the process in syscalls,
@@ -1319,27 +1573,39 @@ proc_open(struct tcb *tcp, int attaching)
 #endif /* USE_PROCFS */
 
 struct tcb *
-pid2tcb(pid)
-int pid;
+pid2tcb(int pid)
 {
        int i;
-       struct tcb *tcp;
+
+       if (pid <= 0)
+               return NULL;
 
        for (i = 0; i < tcbtabsize; i++) {
-               tcp = tcbtab[i];
-               if (pid && tcp->pid != pid)
-                       continue;
-               if (tcp->flags & TCB_INUSE)
+               struct tcb *tcp = tcbtab[i];
+               if (tcp->pid == pid && (tcp->flags & TCB_INUSE))
                        return tcp;
        }
+
        return NULL;
 }
 
 #ifdef USE_PROCFS
 
 static struct tcb *
-pfd2tcb(pfd)
-int pfd;
+first_used_tcb(void)
+{
+       int i;
+       struct tcb *tcp;
+       for (i = 0; i < tcbtabsize; i++) {
+               tcp = tcbtab[i];
+               if (tcp->flags & TCB_INUSE)
+                       return tcp;
+       }
+       return NULL;
+}
+
+static struct tcb *
+pfd2tcb(int pfd)
 {
        int i;
 
@@ -1356,54 +1622,20 @@ int pfd;
 #endif /* USE_PROCFS */
 
 void
-droptcb(tcp)
-struct tcb *tcp;
+droptcb(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;
-
-       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
-#ifdef TCB_CLONE_DETACHED
-               if (!(tcp->flags & TCB_CLONE_DETACHED))
-#endif
-                       tcp->parent->nzombies++;
-#ifdef LINUX
-               /* Update `tcp->parent->parent->nchildren' and the other fields
-                  like NCLONE_DETACHED, only for zombie group leader that has
-                  already reported and been short-circuited at the top of this
-                  function.  The same condition as at the top of DETACH.  */
-               if ((tcp->flags & TCB_CLONE_THREAD) &&
-                   tcp->parent->nclone_threads == 0 &&
-                   (tcp->parent->flags & TCB_EXITING))
-                       droptcb(tcp->parent);
-#endif
-               tcp->parent = NULL;
-       }
+       if (debug)
+               fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs);
 
-       tcp->flags = 0;
+#ifdef USE_PROCFS
        if (tcp->pfd != -1) {
                close(tcp->pfd);
                tcp->pfd = -1;
-#ifdef FREEBSD
+# ifdef FREEBSD
                if (tcp->pfd_reg != -1) {
                        close(tcp->pfd_reg);
                        tcp->pfd_reg = -1;
@@ -1412,137 +1644,33 @@ struct tcb *tcp;
                        close(tcp->pfd_status);
                        tcp->pfd_status = -1;
                }
-#endif /* !FREEBSD */
-#ifdef USE_PROCFS
-               rebuild_pollv(); /* Note, flags needs to be cleared by now.  */
-#endif
+# endif
+               tcp->flags = 0; /* rebuild_pollv needs it */
+               rebuild_pollv();
        }
+#endif
 
        if (outfname && followfork > 1 && tcp->outf)
                fclose(tcp->outf);
 
-       tcp->outf = 0;
-}
-
-#ifndef USE_PROCFS
-
-static int
-resume(tcp)
-struct tcb *tcp;
-{
-       if (tcp == NULL)
-               return -1;
-
-       if (!(tcp->flags & TCB_SUSPENDED)) {
-               fprintf(stderr, "PANIC: pid %u not suspended\n", tcp->pid);
-               return -1;
-       }
-       tcp->flags &= ~TCB_SUSPENDED;
-#ifdef TCB_CLONE_THREAD
-       if (tcp->flags & TCB_CLONE_THREAD)
-               tcp->parent->nclone_waiting--;
-#endif
-
-       if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0)
-               return -1;
-
-       if (!qflag)
-               fprintf(stderr, "Process %u resumed\n", tcp->pid);
-       return 0;
-}
-
-static int
-resume_from_tcp (struct tcb *tcp)
-{
-       int error = 0;
-       int 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
-
-       return error;
+       memset(tcp, 0, sizeof(*tcp));
 }
 
-#endif /* !USE_PROCFS */
-
 /* detach traced process; continue with sig
    Never call DETACH twice on the same process as both unattached and
    attached-unstopped processes give the same ESRCH.  For unattached process we
    would SIGSTOP it and wait for its SIGSTOP notification forever.  */
 
 static int
-detach(tcp, sig)
-struct tcb *tcp;
-int sig;
+detach(struct tcb *tcp, int sig)
 {
        int error = 0;
 #ifdef LINUX
        int status, catch_sigstop;
-       struct tcb *zombie = NULL;
-
-       /* If the group leader is lingering only because of this other
-          thread now dying, then detach the leader as well.  */
-       if ((tcp->flags & TCB_CLONE_THREAD) &&
-           tcp->parent->nclone_threads == 1 &&
-           (tcp->parent->flags & TCB_EXITING))
-               zombie = tcp->parent;
 #endif
 
        if (tcp->flags & TCB_BPTSET)
-               sig = SIGKILL;
+               clearbpt(tcp);
 
 #ifdef LINUX
        /*
@@ -1560,22 +1688,19 @@ int sig;
         * detached process would be left stopped (process state T).
         */
        catch_sigstop = (tcp->flags & TCB_STARTUP);
-       if ((error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig)) == 0) {
+       error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig);
+       if (error == 0) {
                /* On a clear day, you can see forever. */
        }
        else if (errno != ESRCH) {
                /* Shouldn't happen. */
                perror("detach: ptrace(PTRACE_DETACH, ...)");
        }
-       else if (my_tgkill((tcp->flags & TCB_CLONE_THREAD ? tcp->parent->pid
-                                                         : tcp->pid),
-                          tcp->pid, 0) < 0) {
+       else if (my_tkill(tcp->pid, 0) < 0) {
                if (errno != ESRCH)
                        perror("detach: checking sanity");
        }
-       else if (!catch_sigstop && my_tgkill((tcp->flags & TCB_CLONE_THREAD
-                                             ? tcp->parent->pid : tcp->pid),
-                                            tcp->pid, SIGSTOP) < 0) {
+       else if (!catch_sigstop && my_tkill(tcp->pid, SIGSTOP) < 0) {
                if (errno != ESRCH)
                        perror("detach: stopping child");
        }
@@ -1620,7 +1745,7 @@ int sig;
                                break;
                        }
                        error = ptrace_restart(PTRACE_CONT, tcp,
-                                       WSTOPSIG(status) == SIGTRAP ? 0
+                                       WSTOPSIG(status) == syscall_trap_sig ? 0
                                        : WSTOPSIG(status));
                        if (error < 0)
                                break;
@@ -1636,49 +1761,29 @@ int sig;
        error = ptrace_restart(PTRACE_DETACH, tcp, sig);
 #endif /* SUNOS4 */
 
-#ifndef USE_PROCFS
-       error |= resume_from_tcp (tcp);
-#endif
-
        if (!qflag)
                fprintf(stderr, "Process %u detached\n", tcp->pid);
 
        droptcb(tcp);
 
-#ifdef LINUX
-       if (zombie != NULL) {
-               /* TCP no longer exists therefore you must not detach () it.  */
-               droptcb(zombie);
-       }
-#endif
-
        return error;
 }
 
 #ifdef USE_PROCFS
 
-static void
-reaper(sig)
-int sig;
+static void reaper(int sig)
 {
        int pid;
        int status;
 
        while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
-#if 0
-               struct tcb *tcp;
-
-               tcp = pid2tcb(pid);
-               if (tcp)
-                       droptcb(tcp);
-#endif
        }
 }
 
 #endif /* USE_PROCFS */
 
 static void
-cleanup()
+cleanup(void)
 {
        int i;
        struct tcb *tcp;
@@ -1707,8 +1812,7 @@ cleanup()
 }
 
 static void
-interrupt(sig)
-int sig;
+interrupt(int sig)
 {
        interrupted = 1;
 }
@@ -1721,16 +1825,15 @@ extern char *sys_errlist[];
 #endif /* HAVE_DECL_SYS_ERRLIST */
 
 const char *
-strerror(errno)
-int errno;
+strerror(int err_no)
 {
        static char buf[64];
 
-       if (errno < 1 || errno >= sys_nerr) {
-               sprintf(buf, "Unknown error %d", errno);
+       if (err_no < 1 || err_no >= sys_nerr) {
+               sprintf(buf, "Unknown error %d", err_no);
                return buf;
        }
-       return sys_errlist[errno];
+       return sys_errlist[err_no];
 }
 
 #endif /* HAVE_STERRROR */
@@ -1745,8 +1848,7 @@ extern char *_sys_siglist[];
 #endif
 
 const char *
-strsignal(sig)
-int sig;
+strsignal(int sig)
 {
        static char buf[64];
 
@@ -1766,17 +1868,14 @@ int sig;
 #ifdef USE_PROCFS
 
 static void
-rebuild_pollv()
+rebuild_pollv(void)
 {
        int i, j;
 
-       if (pollv != NULL)
-               free (pollv);
-       pollv = (struct pollfd *) malloc(nprocs * sizeof pollv[0]);
-       if (pollv == NULL) {
-               fprintf(stderr, "%s: out of memory\n", progname);
-               exit(1);
-       }
+       free(pollv);
+       pollv = malloc(nprocs * sizeof(pollv[0]));
+       if (!pollv)
+               die_out_of_memory();
 
        for (i = j = 0; i < tcbtabsize; i++) {
                struct tcb *tcp = tcbtab[i];
@@ -1787,44 +1886,37 @@ rebuild_pollv()
                j++;
        }
        if (j != nprocs) {
-               fprintf(stderr, "strace: proc miscount\n");
-               exit(1);
+               error_msg_and_die("proc miscount");
        }
 }
 
 #ifndef HAVE_POLLABLE_PROCFS
 
 static void
-proc_poll_open()
+proc_poll_open(void)
 {
        int i;
 
        if (pipe(proc_poll_pipe) < 0) {
-               perror("pipe");
-               exit(1);
+               perror_msg_and_die("pipe");
        }
        for (i = 0; i < 2; i++) {
-               if (set_cloexec_flag(proc_poll_pipe[i]) < 0) {
-                       exit(1);
-               }
+               set_cloexec_flag(proc_poll_pipe[i]);
        }
 }
 
 static int
-proc_poll(pollv, nfds, timeout)
-struct pollfd *pollv;
-int nfds;
-int timeout;
+proc_poll(struct pollfd *pollv, int nfds, int timeout)
 {
        int i;
        int n;
        struct proc_pollfd pollinfo;
 
-       if ((n = read(proc_poll_pipe[0], &pollinfo, sizeof(pollinfo))) < 0)
+       n = read(proc_poll_pipe[0], &pollinfo, sizeof(pollinfo));
+       if (n < 0)
                return n;
        if (n != sizeof(struct proc_pollfd)) {
-               fprintf(stderr, "panic: short read: %d\n", n);
-               exit(1);
+               error_msg_and_die("panic: short read: %d", n);
        }
        for (i = 0; i < nprocs; i++) {
                if (pollv[i].fd == pollinfo.fd)
@@ -1837,14 +1929,12 @@ int timeout;
 }
 
 static void
-wakeup_handler(sig)
-int sig;
+wakeup_handler(int sig)
 {
 }
 
 static void
-proc_poller(pfd)
-int pfd;
+proc_poller(int pfd)
 {
        struct proc_pollfd pollinfo;
        struct sigaction sa;
@@ -1858,8 +1948,7 @@ int pfd;
 
        switch (fork()) {
        case -1:
-               perror("fork");
-               _exit(1);
+               perror_msg_and_die("fork");
        case 0:
                break;
        default:
@@ -1882,8 +1971,7 @@ int pfd;
        sigemptyset(&empty_set);
 
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
-               perror("getrlimit(RLIMIT_NOFILE, ...)");
-               _exit(1);
+               perror_msg_and_die("getrlimit(RLIMIT_NOFILE, ...)");
        }
        n = rl.rlim_cur;
        for (i = 0; i < n; i++) {
@@ -1939,7 +2027,7 @@ choose_pfd()
                 * heuristic improves the readability of the trace.
                 */
                tcp = pfd2tcb(pollv[last].fd);
-               if (tcp && (tcp->flags & TCB_INSYSCALL))
+               if (tcp && exiting(tcp))
                        return pollv[last].fd;
        }
 
@@ -1949,8 +2037,7 @@ choose_pfd()
                if (pollv[j].revents & (POLLHUP | POLLERR)) {
                        tcp = pfd2tcb(pollv[j].fd);
                        if (!tcp) {
-                               fprintf(stderr, "strace: lost proc\n");
-                               exit(1);
+                               error_msg_and_die("lost proc");
                        }
                        droptcb(tcp);
                        return -1;
@@ -1960,12 +2047,11 @@ choose_pfd()
                        return pollv[j].fd;
                }
        }
-       fprintf(stderr, "strace: nothing ready\n");
-       exit(1);
+       error_msg_and_die("nothing ready");
 }
 
 static int
-trace()
+trace(void)
 {
 #ifdef POLL_HACK
        struct tcb *in_syscall = NULL;
@@ -1988,7 +2074,7 @@ trace()
 #ifndef HAVE_POLLABLE_PROCFS
                        if (proc_poll_pipe[0] == -1) {
 #endif
-                               tcp = pid2tcb(0);
+                               tcp = first_used_tcb();
                                if (!tcp)
                                        continue;
                                pfd = tcp->pfd;
@@ -2012,7 +2098,8 @@ trace()
                                in_syscall = NULL;
                                pv.fd = tcp->pfd;
                                pv.events = POLLWANT;
-                               if ((what = poll (&pv, 1, 1)) < 0) {
+                               what = poll(&pv, 1, 1);
+                               if (what < 0) {
                                        if (interrupted)
                                                return 0;
                                        continue;
@@ -2042,9 +2129,9 @@ trace()
                }
 
                /* Look up `pfd' in our table. */
-               if ((tcp = pfd2tcb(pfd)) == NULL) {
-                       fprintf(stderr, "unknown pfd: %u\n", pfd);
-                       exit(1);
+               tcp = pfd2tcb(pfd);
+               if (tcp == NULL) {
+                       error_msg_and_die("unknown pfd: %u", pfd);
                }
 #ifdef POLL_HACK
        FOUND:
@@ -2052,15 +2139,15 @@ trace()
                /* Get the status of the process. */
                if (!interrupted) {
 #ifndef FREEBSD
-                       ioctl_result = IOCTL_WSTOP (tcp);
+                       ioctl_result = IOCTL_WSTOP(tcp);
 #else /* FREEBSD */
                        /* Thanks to some scheduling mystery, the first poller
                           sometimes waits for the already processed end of fork
                           event. Doing a non blocking poll here solves the problem. */
                        if (proc_poll_pipe[0] != -1)
-                               ioctl_result = IOCTL_STATUS (tcp);
+                               ioctl_result = IOCTL_STATUS(tcp);
                        else
-                               ioctl_result = IOCTL_WSTOP (tcp);
+                               ioctl_result = IOCTL_WSTOP(tcp);
 #endif /* FREEBSD */
                        ioctl_errno = errno;
 #ifndef HAVE_POLLABLE_PROCFS
@@ -2091,15 +2178,14 @@ trace()
                                droptcb(tcp);
                                continue;
                        default:
-                               perror("PIOCWSTOP");
-                               exit(1);
+                               perror_msg_and_die("PIOCWSTOP");
                        }
                }
 
 #ifdef FREEBSD
                if ((tcp->flags & TCB_STARTUP) && (tcp->status.PR_WHY == PR_SYSEXIT)) {
                        /* discard first event for a syscall we never entered */
-                       IOCTL (tcp->pfd, PIOCRUN, 0);
+                       IOCTL(tcp->pfd, PIOCRUN, 0);
                        continue;
                }
 #endif
@@ -2109,6 +2195,7 @@ trace()
 
                /* set current output file */
                outf = tcp->outf;
+               curcol = tcp->curcol;
 
                if (cflag) {
                        struct timeval stime;
@@ -2116,7 +2203,8 @@ trace()
                        char buf[1024];
                        int len;
 
-                       if ((len = pread(tcp->pfd_status, buf, sizeof(buf) - 1, 0)) > 0) {
+                       len = pread(tcp->pfd_status, buf, sizeof(buf) - 1, 0);
+                       if (len > 0) {
                                buf[len] = '\0';
                                sscanf(buf,
                                       "%*s %*d %*d %*d %*d %*d,%*d %*s %*d,%*d %*d,%*d %ld,%ld",
@@ -2137,8 +2225,7 @@ trace()
                        if (tcp->status.PR_FLAGS & PR_ASLEEP) {
                                tcp->status.PR_WHY = PR_SYSENTRY;
                                if (trace_syscall(tcp) < 0) {
-                                       fprintf(stderr, "syscall trouble\n");
-                                       exit(1);
+                                       error_msg_and_die("syscall trouble");
                                }
                        }
                        break;
@@ -2149,12 +2236,12 @@ trace()
 #endif
                case PR_SYSEXIT:
                        if (trace_syscall(tcp) < 0) {
-                               fprintf(stderr, "syscall trouble\n");
-                               exit(1);
+                               error_msg_and_die("syscall trouble");
                        }
                        break;
                case PR_SIGNALLED:
-                       if (!cflag && (qual_flags[what] & QUAL_SIGNAL)) {
+                       if (cflag != CFLAG_ONLY_STATS
+                           && (qual_flags[what] & QUAL_SIGNAL)) {
                                printleader(tcp);
                                tprintf("--- %s (%s) ---",
                                        signame(what), strsignal(what));
@@ -2170,7 +2257,8 @@ trace()
                        }
                        break;
                case PR_FAULTED:
-                       if (!cflag && (qual_flags[what] & QUAL_FAULT)) {
+                       if (cflag != CFLAGS_ONLY_STATS
+                           && (qual_flags[what] & QUAL_FAULT)) {
                                printleader(tcp);
                                tprintf("=== FAULT %d ===", what);
                                printtrailer();
@@ -2181,18 +2269,19 @@ trace()
                        continue;
 #endif
                default:
-                       fprintf(stderr, "odd stop %d\n", tcp->status.PR_WHY);
-                       exit(1);
+                       error_msg_and_die("odd stop %d", tcp->status.PR_WHY);
                        break;
                }
+               /* Remember current print column before continuing. */
+               tcp->curcol = curcol;
                arg = 0;
 #ifndef FREEBSD
-               if (IOCTL (tcp->pfd, PIOCRUN, &arg) < 0) {
+               if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0)
 #else
-               if (IOCTL (tcp->pfd, PIOCRUN, 0) < 0) {
+               if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0)
 #endif
-                       perror("PIOCRUN");
-                       exit(1);
+               {
+                       perror_msg_and_die("PIOCRUN");
                }
        }
        return 0;
@@ -2200,67 +2289,6 @@ 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 = NULL;
-
-       if (tcp->flags & TCB_CLONE_THREAD)
-               leader = tcp->parent;
-       else if (tcp->nclone_detached > 0)
-               leader = tcp;
-
-       if (sig < 0) {
-               if (leader != NULL && leader != tcp
-                && !(leader->flags & TCB_GROUP_EXITING)
-                && !(tcp->flags & TCB_STARTUP)
-               ) {
-                       fprintf(stderr,
-                               "PANIC: handle_group_exit: %d leader %d\n",
-                               tcp->pid, leader ? leader->pid : -1);
-               }
-               /* TCP no longer exists therefore you must not detach() it.  */
-#ifndef USE_PROCFS
-               resume_from_tcp(tcp);
-#endif
-               droptcb(tcp);   /* Already died.  */
-       }
-       else {
-               /* Mark that we are taking the process down.  */
-               tcp->flags |= TCB_EXITING | TCB_GROUP_EXITING;
-               if (tcp->flags & TCB_ATTACHED) {
-                       detach(tcp, sig);
-                       if (leader != NULL && leader != tcp)
-                               leader->flags |= TCB_GROUP_EXITING;
-               } else {
-                       if (ptrace_restart(PTRACE_CONT, tcp, sig) < 0) {
-                               cleanup();
-                               return -1;
-                       }
-                       if (leader != NULL) {
-                               leader->flags |= TCB_GROUP_EXITING;
-                               if (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;
-               }
-       }
-
-       return 0;
-}
-#endif
-
 static int
 trace()
 {
@@ -2270,9 +2298,10 @@ trace()
        struct tcb *tcp;
 #ifdef LINUX
        struct rusage ru;
-#ifdef __WALL
+       struct rusage *rup = cflag ? &ru : NULL;
+# ifdef __WALL
        static int wait4_options = __WALL;
-#endif
+# endif
 #endif /* LINUX */
 
        while (nprocs != 0) {
@@ -2281,36 +2310,32 @@ trace()
                if (interactive)
                        sigprocmask(SIG_SETMASK, &empty_set, NULL);
 #ifdef LINUX
-#ifdef __WALL
-               pid = wait4(-1, &status, wait4_options, cflag ? &ru : NULL);
+# ifdef __WALL
+               pid = wait4(-1, &status, wait4_options, rup);
                if (pid < 0 && (wait4_options & __WALL) && errno == EINVAL) {
                        /* this kernel does not support __WALL */
                        wait4_options &= ~__WALL;
-                       errno = 0;
-                       pid = wait4(-1, &status, wait4_options,
-                                       cflag ? &ru : NULL);
+                       pid = wait4(-1, &status, wait4_options, rup);
                }
                if (pid < 0 && !(wait4_options & __WALL) && errno == ECHILD) {
                        /* most likely a "cloned" process */
-                       pid = wait4(-1, &status, __WCLONE,
-                                       cflag ? &ru : NULL);
-                       if (pid == -1) {
-                               fprintf(stderr, "strace: clone wait4 "
-                                               "failed: %s\n", strerror(errno));
+                       pid = wait4(-1, &status, __WCLONE, rup);
+                       if (pid < 0) {
+                               perror_msg("wait4(__WCLONE) failed");
                        }
                }
-#else
-               pid = wait4(-1, &status, 0, cflag ? &ru : NULL);
-#endif /* __WALL */
+# else
+               pid = wait4(-1, &status, 0, rup);
+# endif /* __WALL */
 #endif /* LINUX */
 #ifdef SUNOS4
                pid = wait(&status);
-#endif /* SUNOS4 */
+#endif
                wait_errno = errno;
                if (interactive)
                        sigprocmask(SIG_BLOCK, &blocked_set, NULL);
 
-               if (pid == -1) {
+               if (pid < 0) {
                        switch (wait_errno) {
                        case EINTR:
                                continue;
@@ -2321,12 +2346,6 @@ trace()
                                 * version of SunOS sometimes reports
                                 * ECHILD before sending us SIGCHILD.
                                 */
-#if 0
-                               if (nprocs == 0)
-                                       return 0;
-                               fprintf(stderr, "strace: proc miscount\n");
-                               exit(1);
-#endif
                                return 0;
                        default:
                                errno = wait_errno;
@@ -2336,14 +2355,56 @@ trace()
                }
                if (pid == popen_pid) {
                        if (WIFEXITED(status) || WIFSIGNALED(status))
-                               popen_pid = -1;
+                               popen_pid = 0;
                        continue;
                }
-               if (debug)
-                       fprintf(stderr, " [wait(%#x) = %u]\n", status, pid);
+               if (debug) {
+                       char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
+#ifdef LINUX
+                       unsigned ev = (unsigned)status >> 16;
+                       if (ev) {
+                               static const char *const event_names[] = {
+                                       [PTRACE_EVENT_CLONE] = "CLONE",
+                                       [PTRACE_EVENT_FORK]  = "FORK",
+                                       [PTRACE_EVENT_VFORK] = "VFORK",
+                                       [PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE",
+                                       [PTRACE_EVENT_EXEC]  = "EXEC",
+                                       [PTRACE_EVENT_EXIT]  = "EXIT",
+                               };
+                               const char *e;
+                               if (ev < ARRAY_SIZE(event_names))
+                                       e = event_names[ev];
+                               else {
+                                       sprintf(buf, "?? (%u)", ev);
+                                       e = buf;
+                               }
+                               fprintf(stderr, " PTRACE_EVENT_%s", e);
+                       }
+#endif
+                       strcpy(buf, "???");
+                       if (WIFSIGNALED(status))
+#ifdef WCOREDUMP
+                               sprintf(buf, "WIFSIGNALED,%ssig=%s",
+                                               WCOREDUMP(status) ? "core," : "",
+                                               signame(WTERMSIG(status)));
+#else
+                               sprintf(buf, "WIFSIGNALED,sig=%s",
+                                               signame(WTERMSIG(status)));
+#endif
+                       if (WIFEXITED(status))
+                               sprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
+                       if (WIFSTOPPED(status))
+                               sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
+#ifdef WIFCONTINUED
+                       if (WIFCONTINUED(status))
+                               strcpy(buf, "WIFCONTINUED");
+#endif
+                       fprintf(stderr, " [wait(0x%04x) = %u] %s\n", status, pid, buf);
+               }
 
                /* Look up `pid' in our table. */
-               if ((tcp = pid2tcb(pid)) == NULL) {
+               tcp = pid2tcb(pid);
+               if (tcp == NULL) {
 #ifdef LINUX
                        if (followfork) {
                                /* This is needed to go with the CLONE_PTRACE
@@ -2356,10 +2417,9 @@ trace()
                                   child so that we know how to do clearbpt
                                   in the child.  */
                                tcp = alloctcb(pid);
-                               tcp->flags |= TCB_ATTACHED | TCB_SUSPENDED;
+                               tcp->flags |= TCB_ATTACHED;
                                if (!qflag)
-                                       fprintf(stderr, "\
-Process %d attached (waiting for parent)\n",
+                                       fprintf(stderr, "Process %d attached\n",
                                                pid);
                        }
                        else
@@ -2367,79 +2427,55 @@ Process %d attached (waiting for parent)\n",
                                   CLONE_PTRACE itself.  */
 #endif
                        {
-                               fprintf(stderr, "unknown pid: %u\n", pid);
                                if (WIFSTOPPED(status))
                                        ptrace(PTRACE_CONT, pid, (char *) 1, 0);
-                               exit(1);
+                               error_msg_and_die("Unknown pid: %u", pid);
                        }
                }
                /* set current output file */
                outf = tcp->outf;
-               if (cflag) {
+               curcol = tcp->curcol;
 #ifdef LINUX
+               if (cflag) {
                        tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
                        tcp->stime = ru.ru_stime;
-#endif /* !LINUX */
                }
+#endif
 
-               if (tcp->flags & TCB_SUSPENDED) {
-                       /*
-                        * Apparently, doing any ptrace() call on a stopped
-                        * process, provokes the kernel to report the process
-                        * status again on a subsequent wait(), even if the
-                        * process has not been actually restarted.
-                        * Since we have inspected the arguments of suspended
-                        * processes we end up here testing for this case.
-                        */
-                       continue;
-               }
                if (WIFSIGNALED(status)) {
                        if (pid == strace_child)
                                exit_code = 0x100 | WTERMSIG(status);
-                       if (!cflag
+                       if (cflag != CFLAG_ONLY_STATS
                            && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) {
                                printleader(tcp);
+#ifdef WCOREDUMP
                                tprintf("+++ killed by %s %s+++",
                                        signame(WTERMSIG(status)),
-#ifdef WCOREDUMP
-                                       WCOREDUMP(status) ? "(core dumped) " :
+                                       WCOREDUMP(status) ? "(core dumped) " : "");
+#else
+                               tprintf("+++ killed by %s +++",
+                                       signame(WTERMSIG(status)));
 #endif
-                                       "");
                                printtrailer();
                        }
-#ifdef TCB_GROUP_EXITING
-                       handle_group_exit(tcp, -1);
-#else
                        droptcb(tcp);
-#endif
                        continue;
                }
                if (WIFEXITED(status)) {
                        if (pid == strace_child)
                                exit_code = WEXITSTATUS(status);
-                       if (debug)
-                               fprintf(stderr, "pid %u exited with %d\n", pid, WEXITSTATUS(status));
-                       if ((tcp->flags & (TCB_ATTACHED|TCB_STARTUP)) == TCB_ATTACHED
-#ifdef TCB_GROUP_EXITING
-                           && !(tcp->parent && (tcp->parent->flags & TCB_GROUP_EXITING))
-                           && !(tcp->flags & TCB_GROUP_EXITING)
-#endif
-                       ) {
-                               fprintf(stderr,
-                                       "PANIC: attached pid %u exited with %d\n",
-                                       pid, WEXITSTATUS(status));
-                       }
                        if (tcp == tcp_last) {
                                if ((tcp->flags & (TCB_INSYSCALL|TCB_REPRINT)) == TCB_INSYSCALL)
                                        tprintf(" <unfinished ... exit status %d>\n",
                                                WEXITSTATUS(status));
                                tcp_last = NULL;
                        }
-#ifdef TCB_GROUP_EXITING
-                       handle_group_exit(tcp, -1);
-#else
+                       if (!cflag /* && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) */ ) {
+                               printleader(tcp);
+                               tprintf("+++ exited with %d +++", WEXITSTATUS(status));
+                               printtrailer();
+                       }
                        droptcb(tcp);
-#endif
                        continue;
                }
                if (!WIFSTOPPED(status)) {
@@ -2447,9 +2483,11 @@ Process %d attached (waiting for parent)\n",
                        droptcb(tcp);
                        continue;
                }
-               if (debug)
-                       fprintf(stderr, "pid %u stopped, [%s]\n",
-                               pid, signame(WSTOPSIG(status)));
+
+               if (status >> 16) {
+                       /* Ptrace event (we ignore all of them for now) */
+                       goto tracing;
+               }
 
                /*
                 * Interestingly, the process may stop
@@ -2470,7 +2508,7 @@ Process %d attached (waiting for parent)\n",
                        if (tcp->flags & TCB_BPTSET) {
                                /*
                                 * One example is a breakpoint inherited from
-                                * parent through fork ().
+                                * parent through fork().
                                 */
                                if (clearbpt(tcp) < 0) /* Pretty fatal */ {
                                        droptcb(tcp);
@@ -2478,10 +2516,22 @@ Process %d attached (waiting for parent)\n",
                                        return -1;
                                }
                        }
+#ifdef LINUX
+                       if (ptrace_setoptions) {
+                               if (debug)
+                                       fprintf(stderr, "setting opts %x on pid %d\n", ptrace_setoptions, tcp->pid);
+                               if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) {
+                                       if (errno != ESRCH) {
+                                               /* Should never happen, really */
+                                               perror_msg_and_die("PTRACE_SETOPTIONS");
+                                       }
+                               }
+                       }
+#endif
                        goto tracing;
                }
 
-               if (WSTOPSIG(status) != SIGTRAP) {
+               if (WSTOPSIG(status) != syscall_trap_sig) {
                        if (WSTOPSIG(status) == SIGSTOP &&
                                        (tcp->flags & TCB_SIGTRAPPED)) {
                                /*
@@ -2495,57 +2545,53 @@ Process %d attached (waiting for parent)\n",
                                }
                                continue;
                        }
-                       if (!cflag
+                       if (cflag != CFLAG_ONLY_STATS
                            && (qual_flags[WSTOPSIG(status)] & QUAL_SIGNAL)) {
-                               unsigned long addr = 0;
+                               siginfo_t si;
+#if defined(PT_CR_IPSR) && defined(PT_CR_IIP)
                                long pc = 0;
-#if defined(PT_CR_IPSR) && defined(PT_CR_IIP) && defined(PT_GETSIGINFO)
-#                              define PSR_RI   41
-                               struct siginfo si;
-                               long psr;
+                               long psr = 0;
 
                                upeek(tcp, PT_CR_IPSR, &psr);
                                upeek(tcp, PT_CR_IIP, &pc);
 
+# define PSR_RI        41
                                pc += (psr >> PSR_RI) & 0x3;
-                               ptrace(PT_GETSIGINFO, pid, 0, (long) &si);
-                               addr = (unsigned long) si.si_addr;
-#elif defined PTRACE_GETSIGINFO
-                               if (WSTOPSIG(status) == SIGSEGV ||
-                                   WSTOPSIG(status) == SIGBUS) {
-                                       siginfo_t si;
-                                       if (ptrace(PTRACE_GETSIGINFO, pid,
-                                                  0, &si) == 0)
-                                               addr = (unsigned long)
-                                                       si.si_addr;
-                               }
+# define PC_FORMAT_STR " @ %lx"
+# define PC_FORMAT_ARG , pc
+#else
+# define PC_FORMAT_STR ""
+# define PC_FORMAT_ARG /* nothing */
 #endif
                                printleader(tcp);
-                               tprintf("--- %s (%s) @ %lx (%lx) ---",
-                                       signame(WSTOPSIG(status)),
-                                       strsignal(WSTOPSIG(status)), pc, addr);
+                               if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) {
+                                       tprintf("--- ");
+                                       printsiginfo(&si, verbose(tcp));
+                                       tprintf(" (%s)" PC_FORMAT_STR " ---",
+                                               strsignal(WSTOPSIG(status))
+                                               PC_FORMAT_ARG);
+                               } else
+                                       tprintf("--- %s by %s" PC_FORMAT_STR " ---",
+                                               strsignal(WSTOPSIG(status)),
+                                               signame(WSTOPSIG(status))
+                                               PC_FORMAT_ARG);
                                printtrailer();
                        }
-                       if (((tcp->flags & TCB_ATTACHED) ||
-                            tcp->nclone_threads > 0) &&
-                               !sigishandled(tcp, WSTOPSIG(status))) {
-#ifdef TCB_GROUP_EXITING
-                               handle_group_exit(tcp, WSTOPSIG(status));
-#else
-                               detach(tcp, WSTOPSIG(status));
-#endif
-                               continue;
-                       }
                        if (ptrace_restart(PTRACE_SYSCALL, tcp, WSTOPSIG(status)) < 0) {
                                cleanup();
                                return -1;
                        }
-                       tcp->flags &= ~TCB_SUSPENDED;
                        continue;
                }
-               /* we handled the STATUS, we are permitted to interrupt now. */
+
+               /* We handled quick cases, we are permitted to interrupt now. */
                if (interrupted)
                        return 0;
+
+               /* This should be syscall entry or exit.
+                * (Or it still can be that pesky post-execve SIGTRAP!)
+                * Handle it.
+                */
                if (trace_syscall(tcp) < 0 && !tcp->ptrace_errno) {
                        /* ptrace() failed in trace_syscall() with ESRCH.
                         * Likely a result of process disappearing mid-flight.
@@ -2557,7 +2603,7 @@ Process %d attached (waiting for parent)\n",
                        if (tcp->flags & TCB_ATTACHED) {
                                if (tcp_last) {
                                        /* Do we have dangling line "syscall(param, param"?
-                                        * Finish the line then. We cannot
+                                        * Finish the line then.
                                         */
                                        tcp_last->flags |= TCB_REPRINT;
                                        tprintf(" <unfinished ...>");
@@ -2571,28 +2617,9 @@ Process %d attached (waiting for parent)\n",
                        }
                        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_restart(PTRACE_CONT, tcp, 0) < 0) {
-                               cleanup();
-                               return -1;
-                       }
-                       continue;
-               }
-               if (tcp->flags & TCB_SUSPENDED) {
-                       if (!qflag)
-                               fprintf(stderr, "Process %u suspended\n", pid);
-                       continue;
-               }
        tracing:
+               /* Remember current print column before continuing. */
+               tcp->curcol = curcol;
                if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0) {
                        cleanup();
                        return -1;
@@ -2603,49 +2630,47 @@ Process %d attached (waiting for parent)\n",
 
 #endif /* !USE_PROCFS */
 
-static int curcol;
-
-#ifdef __STDC__
-#include <stdarg.h>
-#define VA_START(a, b) va_start(a, b)
-#else
-#include <varargs.h>
-#define VA_START(a, b) va_start(a)
-#endif
-
 void
-#ifdef __STDC__
 tprintf(const char *fmt, ...)
-#else
-tprintf(fmt, va_alist)
-char *fmt;
-va_dcl
-#endif
 {
        va_list args;
 
-       VA_START(args, fmt);
+       va_start(args, fmt);
        if (outf) {
                int n = vfprintf(outf, fmt, args);
-               if (n < 0 && outf != stderr)
-                       perror(outfname == NULL
-                              ? "<writing to pipe>" : outfname);
-               else
+               if (n < 0) {
+                       if (outf != stderr)
+                               perror(outfname == NULL
+                                      ? "<writing to pipe>" : outfname);
+               } else
                        curcol += n;
        }
        va_end(args);
-       return;
 }
 
 void
-printleader(tcp)
-struct tcb *tcp;
+tprints(const char *str)
+{
+       if (outf) {
+               int n = fputs(str, outf);
+               if (n >= 0) {
+                       curcol += strlen(str);
+                       return;
+               }
+               if (outf != stderr)
+                       perror(outfname == NULL
+                              ? "<writing to pipe>" : outfname);
+       }
+}
+
+void
+printleader(struct tcb *tcp)
 {
        if (tcp_last) {
                if (tcp_last->ptrace_errno) {
                        if (tcp_last->flags & TCB_INSYSCALL) {
-                               tprintf(" <unavailable>)");
-                               tabto(acolumn);
+                               tprintf(" <unavailable>) ");
+                               tabto();
                        }
                        tprintf("= ? <unavailable>\n");
                        tcp_last->ptrace_errno = 0;
@@ -2691,11 +2716,10 @@ struct tcb *tcp;
 }
 
 void
-tabto(col)
-int col;
+tabto(void)
 {
-       if (curcol < col)
-               tprintf("%*s", col - curcol, "");
+       if (curcol < acolumn)
+               tprints(acolumn_spaces + curcol);
 }
 
 void