]> granicus.if.org Git - strace/blobdiff - strace.c
tests: add ftruncate64.test and truncate64.test
[strace] / strace.c
index 0ea640fd5216d152cbbd5371afcb564aa479d4c5..7792a5c88e609d78e4aa7ba12d9ca7bd555c36fb 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -32,6 +32,7 @@
 #include <stdarg.h>
 #include <sys/param.h>
 #include <fcntl.h>
+#include <signal.h>
 #include <sys/resource.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <grp.h>
 #include <dirent.h>
 #include <sys/utsname.h>
-#if defined(IA64)
-# include <asm/ptrace_offsets.h>
+#ifdef HAVE_PRCTL
+# include <sys/prctl.h>
 #endif
+
+#include "ptrace.h"
+#include "printsiginfo.h"
+
 /* In some libc, these aren't declared. Do it ourself: */
 extern char **environ;
 extern int optind;
 extern char *optarg;
 
+#ifdef USE_LIBUNWIND
+/* if this is true do the stack trace for every system call */
+bool stack_trace_enabled = false;
+#endif
 
 #if defined __NR_tkill
 # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
@@ -54,24 +63,31 @@ extern char *optarg;
    /* 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!"
+# warning "tkill(2) not available, risk of strace hangs!"
 # define my_tkill(tid, sig) kill((tid), (sig))
 #endif
 
-#undef KERNEL_VERSION
-#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+/* Glue for systems without a MMU that cannot provide fork() */
+#if !defined(HAVE_FORK)
+# undef NOMMU_SYSTEM
+# define NOMMU_SYSTEM 1
+#endif
+#if NOMMU_SYSTEM
+# define fork() vfork()
+#endif
+
+const unsigned int syscall_trap_sig = SIGTRAP | 0x80;
 
 cflag_t cflag = CFLAG_NONE;
 unsigned int followfork = 0;
-unsigned int ptrace_setoptions = 0;
+unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
 unsigned int xflag = 0;
 bool debug_flag = 0;
 bool Tflag = 0;
-bool qflag = 0;
-/* Which WSTOPSIG(status) value marks syscall traps? */
-static unsigned int syscall_trap_sig = SIGTRAP;
+bool iflag = 0;
+bool count_wallclock = 0;
+unsigned int qflag = 0;
 static unsigned int tflag = 0;
-static bool iflag = 0;
 static bool rflag = 0;
 static bool print_pid_pfx = 0;
 
@@ -102,7 +118,7 @@ static int opt_intr;
  */
 static bool daemonized_tracer = 0;
 
-#ifdef USE_SEIZE
+#if USE_SEIZE
 static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
 # define use_seize (post_attach_sigstop == 0)
 #else
@@ -114,13 +130,13 @@ static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
 bool not_failing_only = 0;
 
 /* Show path associated with fd arguments */
-bool show_fd_path = 0;
-
-/* are we filtering traces based on paths? */
-bool tracing_paths = 0;
+unsigned int show_fd_path = 0;
 
 static bool detach_on_execve = 0;
-static bool skip_startup_execve = 0;
+/* Are we "strace PROG" and need to skip detach on first execve? */
+static bool skip_one_b_execve = 0;
+/* Are we "strace PROG" and need to hide everything until execve? */
+bool hide_log_until_execve = 0;
 
 static int exit_code = 0;
 static int strace_child = 0;
@@ -131,20 +147,23 @@ static uid_t run_uid;
 static gid_t run_gid;
 
 unsigned int max_strlen = DEFAULT_STRLEN;
-static unsigned int acolumn = DEFAULT_ACOLUMN;
+static int acolumn = DEFAULT_ACOLUMN;
 static char *acolumn_spaces;
+
 static char *outfname = NULL;
-static FILE *outf;
+/* If -ff, points to stderr. Else, it's our common output log */
+static FILE *shared_log;
+
 struct tcb *printing_tcp = NULL;
-static unsigned int curcol;
+static struct tcb *current_tcp;
+
 static struct tcb **tcbtab;
 static unsigned int nprocs, tcbtabsize;
 static const char *progname;
 
-static unsigned os_release; /* generated from uname()'s u.release */
+unsigned os_release; /* generated from uname()'s u.release */
 
-static int detach(struct tcb *tcp);
-static int trace(void);
+static void detach(struct tcb *tcp);
 static void cleanup(void);
 static void interrupt(int sig);
 static sigset_t empty_set, blocked_set;
@@ -177,60 +196,82 @@ strerror(int err_no)
 #endif /* HAVE_STERRROR */
 
 static void
-usage(FILE *ofp, int exitval)
+usage()
 {
-       fprintf(ofp, "\
-usage: strace [-CdDffhiqrtttTvVxxy] [-I n] [-a column] [-e expr]... [-o file]\n\
-              [-p pid]... [-s strsize] [-u username] [-E var=val]...\n\
-              [-P path] [PROG [ARGS]]\n\
-   or: strace -c [-D] [-I n] [-e expr]... [-O overhead] [-S sortby] [-E var=val]...\n\
-              [PROG [ARGS]]\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\
--d -- enable debug output to stderr\n\
--D -- run tracer process as a detached grandchild, not as parent\n\
--f -- follow forks, -ff -- with output into separate files\n\
--F -- attempt to follow vforks (deprecated, use -f)\n\
--i -- print instruction pointer at time of syscall\n\
--I interruptible\n\
-   1: no signals are blocked\n\
-   2: fatal signals are blocked while decoding syscall (default)\n\
-   3: fatal signals are always blocked (default if '-o FILE PROG')\n\
-   4: fatal signals and SIGTSTP (^Z) are always blocked\n\
-      (useful to make 'strace -o FILE PROG' not stop on ^Z)\n\
--q -- suppress messages about attaching, detaching, etc.\n\
--r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs\n\
--T -- print time spent in each syscall\n\
--v -- verbose mode: print unabbreviated argv, stat, termios, 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\
--h -- print help message\n\
--V -- print version\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\
--o file -- send trace output to FILE instead of stderr\n\
--O overhead -- set overhead for tracing syscalls to OVERHEAD usecs\n\
--p pid -- trace process with process id PID, may be repeated\n\
--s strsize -- limit length of print strings to STRSIZE chars (default %d)\n\
--S sortby -- sort syscall counts by: time, calls, name, nothing (default %s)\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\
+       printf("\
+usage: strace [-CdffhiqrtttTvVwxxy] [-I n] [-e expr]...\n\
+              [-a column] [-o file] [-s strsize] [-P path]...\n\
+              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\
+   or: strace -c[dfw] [-I n] [-e expr]... [-O overhead] [-S sortby]\n\
+              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\
+\n\
+Output format:\n\
+  -a column      alignment COLUMN for printing syscall results (default %d)\n\
+  -i             print instruction pointer at time of syscall\n\
+  -o file        send trace output to FILE instead of stderr\n\
+  -q             suppress messages about attaching, detaching, etc.\n\
+  -r             print relative timestamp\n\
+  -s strsize     limit length of print strings to STRSIZE chars (default %d)\n\
+  -t             print absolute timestamp\n\
+  -tt            print absolute timestamp with usecs\n\
+  -T             print time spent in each syscall\n\
+  -x             print non-ascii strings in hex\n\
+  -xx            print all strings in hex\n\
+  -y             print paths associated with file descriptor arguments\n\
+  -yy            print ip:port pairs associated with socket file descriptors\n\
+\n\
+Statistics:\n\
+  -c             count time, calls, and errors for each syscall and report summary\n\
+  -C             like -c but also print regular output\n\
+  -O overhead    set overhead for tracing syscalls to OVERHEAD usecs\n\
+  -S sortby      sort syscall counts by: time, calls, name, nothing (default %s)\n\
+  -w             summarise syscall latency (default is system time)\n\
+\n\
+Filtering:\n\
+  -e expr        a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
+     options:    trace, abbrev, verbose, raw, signal, read, write\n\
+  -P path        trace accesses to path\n\
+\n\
+Tracing:\n\
+  -b execve      detach on execve syscall\n\
+  -D             run tracer process as a detached grandchild, not as parent\n\
+  -f             follow forks\n\
+  -ff            follow forks with output into separate files\n\
+  -I interruptible\n\
+     1:          no signals are blocked\n\
+     2:          fatal signals are blocked while decoding syscall (default)\n\
+     3:          fatal signals are always blocked (default if '-o FILE PROG')\n\
+     4:          fatal signals and SIGTSTP (^Z) are always blocked\n\
+                 (useful to make 'strace -o FILE PROG' not stop on ^Z)\n\
+\n\
+Startup:\n\
+  -E var         remove var from the environment for command\n\
+  -E var=val     put var=val in the environment for command\n\
+  -p pid         trace process with process id PID, may be repeated\n\
+  -u username    run command as username handling setuid and/or setgid\n\
+\n\
+Miscellaneous:\n\
+  -d             enable debug output to stderr\n\
+  -v             verbose mode: print unabbreviated argv, stat, termios, etc. args\n\
+  -h             print help message\n\
+  -V             print version\n\
 "
+#ifdef USE_LIBUNWIND
+"  -k             obtain stack trace between each syscall (experimental)\n\
+"
+#endif
+/* ancient, no one should use it
+-F -- attempt to follow vforks (deprecated, use -f)\n\
+ */
 /* this is broken, so don't document it
 -z -- print only succeeding syscalls\n\
  */
-/* experimental, don't document it yet (option letter may change in the future!)
--b -- detach on successful execve\n\
- */
 , DEFAULT_ACOLUMN, DEFAULT_STRLEN, DEFAULT_SORTBY);
-       exit(exitval);
+       exit(0);
 }
 
-static void die(void) __attribute__ ((noreturn));
-static void die(void)
+static void ATTRIBUTE_NORETURN
+die(void)
 {
        if (strace_tracer_pid == getpid()) {
                cflag = 0;
@@ -286,6 +327,17 @@ void error_msg_and_die(const char *fmt, ...)
        die();
 }
 
+void error_msg_and_help(const char *fmt, ...)
+{
+       if (fmt != NULL) {
+               va_list p;
+               va_start(p, fmt);
+               verror_msg(0, fmt, p);
+       }
+       fprintf(stderr, "Try '%s -h' for more information.\n", progname);
+       die();
+}
+
 void perror_msg(const char *fmt, ...)
 {
        va_list p;
@@ -302,40 +354,77 @@ void perror_msg_and_die(const char *fmt, ...)
        die();
 }
 
-void die_out_of_memory(void)
+static void
+error_opt_arg(int opt, const char *arg)
 {
-       static bool recursed = 0;
-       if (recursed)
-               exit(1);
-       recursed = 1;
-       error_msg_and_die("Out of memory");
+       error_msg_and_help("invalid -%c argument: '%s'", opt, arg);
 }
 
-/* Glue for systems without a MMU that cannot provide fork() */
-#ifdef HAVE_FORK
-# define strace_vforked 0
-#else
-# define strace_vforked 1
-# define fork()         vfork()
-#endif
-
-#ifdef USE_SEIZE
+#if USE_SEIZE
 static int
 ptrace_attach_or_seize(int pid)
 {
        int r;
        if (!use_seize)
-               return ptrace(PTRACE_ATTACH, pid, 0, 0);
-       r = ptrace(PTRACE_SEIZE, pid, 0, PTRACE_SEIZE_DEVEL);
+               return ptrace(PTRACE_ATTACH, pid, 0L, 0L);
+       r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long) ptrace_setoptions);
        if (r)
                return r;
-       r = ptrace(PTRACE_INTERRUPT, pid, 0, 0);
+       r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
        return r;
 }
 #else
 # define ptrace_attach_or_seize(pid) ptrace(PTRACE_ATTACH, (pid), 0, 0)
 #endif
 
+/*
+ * Used when we want to unblock stopped traced process.
+ * Should be only used with PTRACE_CONT, PTRACE_DETACH and PTRACE_SYSCALL.
+ * Returns 0 on success or if error was ESRCH
+ * (presumably process was killed while we talk to it).
+ * Otherwise prints error message and returns -1.
+ */
+static int
+ptrace_restart(int op, struct tcb *tcp, int sig)
+{
+       int err;
+       const char *msg;
+
+       errno = 0;
+       ptrace(op, tcp->pid, (void *) 0, (long) sig);
+       err = errno;
+       if (!err)
+               return 0;
+
+       msg = "SYSCALL";
+       if (op == PTRACE_CONT)
+               msg = "CONT";
+       if (op == PTRACE_DETACH)
+               msg = "DETACH";
+#ifdef PTRACE_LISTEN
+       if (op == PTRACE_LISTEN)
+               msg = "LISTEN";
+#endif
+       /*
+        * Why curcol != 0? Otherwise sometimes we get this:
+        *
+        * 10252 kill(10253, SIGKILL)              = 0
+        *  <ptrace(SYSCALL,10252):No such process>10253 ...next decode...
+        *
+        * 10252 died after we retrieved syscall exit data,
+        * but before we tried to restart it. Log looks ugly.
+        */
+       if (current_tcp && current_tcp->curcol != 0) {
+               tprintf(" <ptrace(%s):%s>\n", msg, strerror(err));
+               line_ended();
+       }
+       if (err == ESRCH)
+               return 0;
+       errno = err;
+       perror_msg("ptrace(PTRACE_%s,pid:%d,sig:%d)", msg, tcp->pid, sig);
+       return -1;
+}
+
 static void
 set_cloexec_flag(int fd)
 {
@@ -380,10 +469,26 @@ swap_uid(void)
        }
 }
 
-#if _LFS64_LARGEFILE
-# define fopen_for_output fopen64
+#ifdef _LARGEFILE64_SOURCE
+# ifdef HAVE_FOPEN64
+#  define fopen_for_output fopen64
+# else
+#  define fopen_for_output fopen
+# endif
+# define struct_stat struct stat64
+# define stat_file stat64
+# define struct_dirent struct dirent64
+# define read_dir readdir64
+# define struct_rlimit struct rlimit64
+# define set_rlimit setrlimit64
 #else
 # define fopen_for_output fopen
+# define struct_stat struct stat
+# define stat_file stat
+# define struct_dirent struct dirent
+# define read_dir readdir
+# define struct_rlimit struct rlimit
+# define set_rlimit setrlimit
 #endif
 
 static FILE *
@@ -415,6 +520,7 @@ static FILE *
 strace_popen(const char *command)
 {
        FILE *fp;
+       int pid;
        int fds[2];
 
        swap_uid();
@@ -423,11 +529,11 @@ strace_popen(const char *command)
 
        set_cloexec_flag(fds[1]); /* never fails */
 
-       popen_pid = vfork();
-       if (popen_pid == -1)
+       pid = vfork();
+       if (pid < 0)
                perror_msg_and_die("vfork");
 
-       if (popen_pid == 0) {
+       if (pid == 0) {
                /* child */
                close(fds[1]);
                if (fds[0] != 0) {
@@ -440,6 +546,7 @@ strace_popen(const char *command)
        }
 
        /* parent */
+       popen_pid = pid;
        close(fds[0]);
        swap_uid();
        fp = fdopen(fds[1], "w");
@@ -454,42 +561,46 @@ tprintf(const char *fmt, ...)
        va_list args;
 
        va_start(args, fmt);
-       if (outf) {
-               int n = vfprintf(outf, fmt, args);
+       if (current_tcp) {
+               int n = strace_vfprintf(current_tcp->outf, fmt, args);
                if (n < 0) {
-                       if (outf != stderr)
-                               perror(outfname == NULL
-                                      ? "<writing to pipe>" : outfname);
+                       if (current_tcp->outf != stderr)
+                               perror_msg("%s", outfname);
                } else
-                       curcol += n;
+                       current_tcp->curcol += n;
        }
        va_end(args);
 }
 
+#ifndef HAVE_FPUTS_UNLOCKED
+# define fputs_unlocked fputs
+#endif
+
 void
 tprints(const char *str)
 {
-       if (outf) {
-               int n = fputs(str, outf);
+       if (current_tcp) {
+               int n = fputs_unlocked(str, current_tcp->outf);
                if (n >= 0) {
-                       curcol += strlen(str);
+                       current_tcp->curcol += strlen(str);
                        return;
                }
-               if (outf != stderr)
-                       perror(outfname == NULL
-                              ? "<writing to pipe>" : outfname);
+               if (current_tcp->outf != stderr)
+                       perror_msg("%s", outfname);
        }
 }
 
 void
 line_ended(void)
 {
-       curcol = 0;
-       fflush(outf);
-       if (!printing_tcp)
-               return;
-       printing_tcp->curcol = 0;
-       printing_tcp = NULL;
+       if (current_tcp) {
+               current_tcp->curcol = 0;
+               fflush(current_tcp->outf);
+       }
+       if (printing_tcp) {
+               printing_tcp->curcol = 0;
+               printing_tcp = NULL;
+       }
 }
 
 void
@@ -502,17 +613,7 @@ printleader(struct tcb *tcp)
                printing_tcp = tcp;
 
        if (printing_tcp) {
-               outf = printing_tcp->outf;
-               curcol = printing_tcp->curcol;
-               if (printing_tcp->ptrace_errno) {
-                       if (printing_tcp->flags & TCB_INSYSCALL) {
-                               tprints(" <unavailable>) ");
-                               tabto();
-                       }
-                       tprints("= ? <unavailable>\n");
-                       printing_tcp->ptrace_errno = 0;
-                       printing_tcp->curcol = 0;
-               }
+               current_tcp = printing_tcp;
                if (printing_tcp->curcol != 0 && (followfork < 2 || printing_tcp == tcp)) {
                        /*
                         * case 1: we have a shared log (i.e. not -ff), and last line
@@ -521,14 +622,13 @@ printleader(struct tcb *tcp)
                         * didn't finish ("SIGKILL nuked us after syscall entry" etc).
                         */
                        tprints(" <unfinished ...>\n");
-                       printing_tcp->flags |= TCB_REPRINT;
                        printing_tcp->curcol = 0;
                }
        }
 
        printing_tcp = tcp;
-       outf = tcp->outf;
-       curcol = 0;
+       current_tcp = tcp;
+       current_tcp->curcol = 0;
 
        if (print_pid_pfx)
                tprintf("%-5d ", tcp->pid);
@@ -563,14 +663,14 @@ printleader(struct tcb *tcp)
                }
        }
        if (iflag)
-               printcall(tcp);
+               print_pc(tcp);
 }
 
 void
 tabto(void)
 {
-       if (curcol < acolumn)
-               tprints(acolumn_spaces + curcol);
+       if (current_tcp->curcol < acolumn)
+               tprints(acolumn_spaces + current_tcp->curcol);
 }
 
 /* Should be only called directly *after successful attach* to a tracee.
@@ -580,7 +680,7 @@ tabto(void)
 static void
 newoutf(struct tcb *tcp)
 {
-       tcp->outf = outf; /* if not -ff mode, the same file is for all */
+       tcp->outf = shared_log; /* if not -ff mode, the same file is for all */
        if (followfork >= 2) {
                char name[520 + sizeof(int) * 3];
                sprintf(name, "%.512s.%u", outfname, tcp->pid);
@@ -596,11 +696,10 @@ 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.  */
-       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();
+       unsigned int i = tcbtabsize;
+       struct tcb *newtcbs = xcalloc(tcbtabsize, sizeof(newtcbs[0]));
+       struct tcb **newtab = xreallocarray(tcbtab, tcbtabsize * 2,
+                                           sizeof(tcbtab[0]));
        tcbtabsize *= 2;
        tcbtab = newtab;
        while (i < tcbtabsize)
@@ -610,7 +709,7 @@ expand_tcbtab(void)
 static struct tcb *
 alloctcb(int pid)
 {
-       int i;
+       unsigned int i;
        struct tcb *tcp;
 
        if (nprocs == tcbtabsize)
@@ -618,17 +717,22 @@ alloctcb(int pid)
 
        for (i = 0; i < tcbtabsize; i++) {
                tcp = tcbtab[i];
-               if ((tcp->flags & TCB_INUSE) == 0) {
+               if (!tcp->pid) {
                        memset(tcp, 0, sizeof(*tcp));
                        tcp->pid = pid;
-                       tcp->flags = TCB_INUSE;
-                       /* tcp->outf = outf; - not needed? */
 #if SUPPORTED_PERSONALITIES > 1
                        tcp->currpers = current_personality;
 #endif
+
+#ifdef USE_LIBUNWIND
+                       if (stack_trace_enabled)
+                               unwind_tcb_init(tcp);
+#endif
+
                        nprocs++;
                        if (debug_flag)
-                               fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
+                               error_msg("new tcb for pid %d, active tcbs:%d",
+                                         tcp->pid, nprocs);
                        return tcp;
                }
        }
@@ -641,17 +745,22 @@ droptcb(struct tcb *tcp)
        if (tcp->pid == 0)
                return;
 
+#ifdef USE_LIBUNWIND
+       if (stack_trace_enabled) {
+               unwind_tcb_fin(tcp);
+       }
+#endif
+
        nprocs--;
        if (debug_flag)
-               fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs);
+               error_msg("dropped tcb for pid %d, %d remain",
+                         tcp->pid, nprocs);
 
        if (tcp->outf) {
-               if (outfname && followfork >= 2) {
+               if (followfork >= 2) {
                        if (tcp->curcol != 0)
                                fprintf(tcp->outf, " <detached ...>\n");
                        fclose(tcp->outf);
-                       if (outf == tcp->outf)
-                               outf = NULL;
                } else {
                        if (printing_tcp == tcp && tcp->curcol != 0)
                                fprintf(tcp->outf, " <detached ...>\n");
@@ -659,25 +768,24 @@ droptcb(struct tcb *tcp)
                }
        }
 
+       if (current_tcp == tcp)
+               current_tcp = NULL;
        if (printing_tcp == tcp)
                printing_tcp = NULL;
 
        memset(tcp, 0, sizeof(*tcp));
 }
 
-/* detach traced process; continue with sig
+/* Detach traced process.
  * 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
+static void
 detach(struct tcb *tcp)
 {
        int error;
-       int status, sigstop_expected;
-
-       if (tcp->flags & TCB_BPTSET)
-               clearbpt(tcp);
+       int status;
 
        /*
         * Linux wrongly insists the child be stopped
@@ -685,90 +793,153 @@ detach(struct tcb *tcp)
         * to make a clean break of things.
         */
 #if defined(SPARC)
-#undef PTRACE_DETACH
-#define PTRACE_DETACH PTRACE_SUNDETACH
+# undef PTRACE_DETACH
+# define PTRACE_DETACH PTRACE_SUNDETACH
 #endif
 
-       error = 0;
-       sigstop_expected = 0;
-       if (tcp->flags & TCB_ATTACHED) {
+       if (!(tcp->flags & TCB_ATTACHED))
+               goto drop;
+
+       /* We attached but possibly didn't see the expected SIGSTOP.
+        * We must catch exactly one as otherwise the detached process
+        * would be left stopped (process state T).
+        */
+       if (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)
+               goto wait_loop;
+
+       error = ptrace(PTRACE_DETACH, tcp->pid, 0, 0);
+       if (!error) {
+               /* On a clear day, you can see forever. */
+               goto drop;
+       }
+       if (errno != ESRCH) {
+               /* Shouldn't happen. */
+               perror_msg("detach: ptrace(PTRACE_DETACH,%u)", tcp->pid);
+               goto drop;
+       }
+       /* ESRCH: process is either not stopped or doesn't exist. */
+       if (my_tkill(tcp->pid, 0) < 0) {
+               if (errno != ESRCH)
+                       /* Shouldn't happen. */
+                       perror_msg("detach: tkill(%u,0)", tcp->pid);
+               /* else: process doesn't exist. */
+               goto drop;
+       }
+       /* Process is not stopped, need to stop it. */
+       if (use_seize) {
                /*
-                * We attached but possibly didn't see the expected SIGSTOP.
-                * We must catch exactly one as otherwise the detached process
-                * would be left stopped (process state T).
+                * With SEIZE, tracee can be in group-stop already.
+                * In this state sending it another SIGSTOP does nothing.
+                * Need to use INTERRUPT.
+                * Testcase: trying to ^C a "strace -p <stopped_process>".
                 */
-               sigstop_expected = (tcp->flags & TCB_IGNORE_ONE_SIGSTOP);
-               error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, 0);
-               if (error == 0) {
-                       /* On a clear day, you can see forever. */
+               error = ptrace(PTRACE_INTERRUPT, tcp->pid, 0, 0);
+               if (!error)
+                       goto wait_loop;
+               if (errno != ESRCH)
+                       perror_msg("detach: ptrace(PTRACE_INTERRUPT,%u)", tcp->pid);
+       }
+       else {
+               error = my_tkill(tcp->pid, SIGSTOP);
+               if (!error)
+                       goto wait_loop;
+               if (errno != ESRCH)
+                       perror_msg("detach: tkill(%u,SIGSTOP)", tcp->pid);
+       }
+       /* Either process doesn't exist, or some weird error. */
+       goto drop;
+
+ wait_loop:
+       /* We end up here in three cases:
+        * 1. We sent PTRACE_INTERRUPT (use_seize case)
+        * 2. We sent SIGSTOP (!use_seize)
+        * 3. Attach SIGSTOP was already pending (TCB_IGNORE_ONE_SIGSTOP set)
+        */
+       for (;;) {
+               unsigned int sig;
+               if (waitpid(tcp->pid, &status, __WALL) < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       /*
+                        * if (errno == ECHILD) break;
+                        * ^^^  WRONG! We expect this PID to exist,
+                        * and want to emit a message otherwise:
+                        */
+                       perror_msg("detach: waitpid(%u)", tcp->pid);
+                       break;
                }
-               else if (errno != ESRCH) {
-                       /* Shouldn't happen. */
-                       perror("detach: ptrace(PTRACE_DETACH, ...)");
+               if (!WIFSTOPPED(status)) {
+                       /*
+                        * Tracee exited or was killed by signal.
+                        * We shouldn't normally reach this place:
+                        * we don't want to consume exit status.
+                        * Consider "strace -p PID" being ^C-ed:
+                        * we want merely to detach from PID.
+                        *
+                        * However, we _can_ end up here if tracee
+                        * was SIGKILLed.
+                        */
+                       break;
                }
-               else if (my_tkill(tcp->pid, 0) < 0) {
-                       if (errno != ESRCH)
-                               perror("detach: checking sanity");
+               sig = WSTOPSIG(status);
+               if (debug_flag)
+                       error_msg("detach wait: event:%d sig:%d",
+                                 (unsigned)status >> 16, sig);
+               if (use_seize) {
+                       unsigned event = (unsigned)status >> 16;
+                       if (event == PTRACE_EVENT_STOP /*&& sig == SIGTRAP*/) {
+                               /*
+                                * sig == SIGTRAP: PTRACE_INTERRUPT stop.
+                                * sig == other: process was already stopped
+                                * with this stopping sig (see tests/detach-stopped).
+                                * Looks like re-injecting this sig is not necessary
+                                * in DETACH for the tracee to remain stopped.
+                                */
+                               sig = 0;
+                       }
+                       /*
+                        * PTRACE_INTERRUPT is not guaranteed to produce
+                        * the above event if other ptrace-stop is pending.
+                        * See tests/detach-sleeping testcase:
+                        * strace got SIGINT while tracee is sleeping.
+                        * We sent PTRACE_INTERRUPT.
+                        * We see syscall exit, not PTRACE_INTERRUPT stop.
+                        * We won't get PTRACE_INTERRUPT stop
+                        * if we would CONT now. Need to DETACH.
+                        */
+                       if (sig == syscall_trap_sig)
+                               sig = 0;
+                       /* else: not sure in which case we can be here.
+                        * Signal stop? Inject it while detaching.
+                        */
+                       ptrace_restart(PTRACE_DETACH, tcp, sig);
+                       break;
                }
-               else if (!sigstop_expected && my_tkill(tcp->pid, SIGSTOP) < 0) {
-                       if (errno != ESRCH)
-                               perror("detach: stopping child");
+               /* Note: this check has to be after use_seize check */
+               /* (else, in use_seize case SIGSTOP will be mistreated) */
+               if (sig == SIGSTOP) {
+                       /* Detach, suppressing SIGSTOP */
+                       ptrace_restart(PTRACE_DETACH, tcp, 0);
+                       break;
                }
-               else
-                       sigstop_expected = 1;
-       }
-
-       if (sigstop_expected) {
-               for (;;) {
-#ifdef __WALL
-                       if (waitpid(tcp->pid, &status, __WALL) < 0) {
-                               if (errno == ECHILD) /* Already gone.  */
-                                       break;
-                               if (errno != EINVAL) {
-                                       perror("detach: waiting");
-                                       break;
-                               }
-#endif /* __WALL */
-                               /* No __WALL here.  */
-                               if (waitpid(tcp->pid, &status, 0) < 0) {
-                                       if (errno != ECHILD) {
-                                               perror("detach: waiting");
-                                               break;
-                                       }
-#ifdef __WCLONE
-                                       /* If no processes, try clones.  */
-                                       if (waitpid(tcp->pid, &status, __WCLONE) < 0) {
-                                               if (errno != ECHILD)
-                                                       perror("detach: waiting");
-                                               break;
-                                       }
-#endif /* __WCLONE */
-                               }
-#ifdef __WALL
-                       }
-#endif
-                       if (!WIFSTOPPED(status)) {
-                               /* Au revoir, mon ami. */
-                               break;
-                       }
-                       if (WSTOPSIG(status) == SIGSTOP) {
-                               ptrace_restart(PTRACE_DETACH, tcp, 0);
-                               break;
-                       }
-                       error = ptrace_restart(PTRACE_CONT, tcp,
-                                       WSTOPSIG(status) == syscall_trap_sig ? 0
-                                       : WSTOPSIG(status));
-                       if (error < 0)
-                               break;
+               if (sig == syscall_trap_sig)
+                       sig = 0;
+               /* Can't detach just yet, may need to wait for SIGSTOP */
+               error = ptrace_restart(PTRACE_CONT, tcp, sig);
+               if (error < 0) {
+                       /* Should not happen.
+                        * Note: ptrace_restart returns 0 on ESRCH, so it's not it.
+                        * ptrace_restart already emitted error message.
+                        */
+                       break;
                }
        }
 
+ drop:
        if (!qflag && (tcp->flags & TCB_ATTACHED))
-               fprintf(stderr, "Process %u detached\n", tcp->pid);
+               error_msg("Process %u detached", tcp->pid);
 
        droptcb(tcp);
-
-       return error;
 }
 
 static void
@@ -784,16 +955,12 @@ process_opt_p_list(char *opt)
                char c = *delim;
 
                *delim = '\0';
-               pid = atoi(opt); /* TODO: stricter parsing of the number? */
+               pid = string_to_uint(opt);
                if (pid <= 0) {
-                       error_msg("Invalid process id: '%s'", opt);
-                       *delim = c;
-                       return;
+                       error_msg_and_die("Invalid process id: '%s'", opt);
                }
                if (pid == strace_tracer_pid) {
-                       error_msg("I'm sorry, I can't let you do that, Dave.");
-                       *delim = c;
-                       return;
+                       error_msg_and_die("I'm sorry, I can't let you do that, Dave.");
                }
                *delim = c;
                alloctcb(pid);
@@ -806,7 +973,7 @@ process_opt_p_list(char *opt)
 static void
 startup_attach(void)
 {
-       int tcbi;
+       unsigned int tcbi;
        struct tcb *tcp;
 
        /*
@@ -841,14 +1008,13 @@ startup_attach(void)
        for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
                tcp = tcbtab[tcbi];
 
-               if (!(tcp->flags & TCB_INUSE))
+               if (!tcp->pid)
                        continue;
 
                /* Is this a process we should attach to, but not yet attached? */
                if (tcp->flags & TCB_ATTACHED)
                        continue; /* no, we already attached it */
 
-
                if (followfork && !daemonized_tracer) {
                        char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
                        DIR *dir;
@@ -857,14 +1023,15 @@ startup_attach(void)
                        dir = opendir(procdir);
                        if (dir != NULL) {
                                unsigned int ntid = 0, nerr = 0;
-                               struct dirent *de;
+                               struct_dirent *de;
 
-                               while ((de = readdir(dir)) != NULL) {
+                               while ((de = read_dir(dir)) != NULL) {
                                        struct tcb *cur_tcp;
                                        int tid;
 
                                        if (de->d_fileno == 0)
                                                continue;
+                                       /* we trust /proc filesystem */
                                        tid = atoi(de->d_name);
                                        if (tid <= 0)
                                                continue;
@@ -872,11 +1039,11 @@ startup_attach(void)
                                        if (ptrace_attach_or_seize(tid) < 0) {
                                                ++nerr;
                                                if (debug_flag)
-                                                       fprintf(stderr, "attach to pid %d failed\n", tid);
+                                                       error_msg("attach to pid %d failed", tid);
                                                continue;
                                        }
                                        if (debug_flag)
-                                               fprintf(stderr, "attach to pid %d succeeded\n", tid);
+                                               error_msg("attach to pid %d succeeded", tid);
                                        cur_tcp = tcp;
                                        if (tid != tcp->pid)
                                                cur_tcp = alloctcb(tid);
@@ -892,15 +1059,18 @@ startup_attach(void)
                                }
                                ntid -= nerr;
                                if (ntid == 0) {
-                                       perror("attach: ptrace(PTRACE_ATTACH, ...)");
+                                       perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
                                        droptcb(tcp);
                                        continue;
                                }
                                if (!qflag) {
-                                       fprintf(stderr, ntid > 1
-? "Process %u attached with %u threads\n"
-: "Process %u attached\n",
-                                               tcp->pid, ntid);
+                                       if (ntid > 1)
+                                               error_msg("Process %u attached"
+                                                         " with %u threads",
+                                                         tcp->pid, ntid);
+                                       else
+                                               error_msg("Process %u attached",
+                                                         tcp->pid);
                                }
                                if (!(tcp->flags & TCB_ATTACHED)) {
                                        /* -p PID, we failed to attach to PID itself
@@ -913,14 +1083,14 @@ startup_attach(void)
                        } /* if (opendir worked) */
                } /* if (-f) */
                if (ptrace_attach_or_seize(tcp->pid) < 0) {
-                       perror("attach: ptrace(PTRACE_ATTACH, ...)");
+                       perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
                        droptcb(tcp);
                        continue;
                }
                tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
                newoutf(tcp);
                if (debug_flag)
-                       fprintf(stderr, "attach to pid %d (main) succeeded\n", tcp->pid);
+                       error_msg("attach to pid %d (main) succeeded", tcp->pid);
 
                if (daemonized_tracer) {
                        /*
@@ -931,9 +1101,7 @@ startup_attach(void)
                }
 
                if (!qflag)
-                       fprintf(stderr,
-                               "Process %u attached\n",
-                               tcp->pid);
+                       error_msg("Process %u attached", tcp->pid);
        } /* for each tcbtab[] */
 
  ret:
@@ -941,21 +1109,91 @@ startup_attach(void)
                sigprocmask(SIG_SETMASK, &empty_set, NULL);
 }
 
+/* Stack-o-phobic exec helper, in the hope to work around
+ * NOMMU + "daemonized tracer" difficulty.
+ */
+struct exec_params {
+       int fd_to_close;
+       uid_t run_euid;
+       gid_t run_egid;
+       char **argv;
+       char *pathname;
+};
+static struct exec_params params_for_tracee;
+
+static void ATTRIBUTE_NOINLINE ATTRIBUTE_NORETURN
+exec_or_die(void)
+{
+       struct exec_params *params = &params_for_tracee;
+
+       if (params->fd_to_close >= 0)
+               close(params->fd_to_close);
+       if (!daemonized_tracer && !use_seize) {
+               if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
+                       perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
+               }
+       }
+
+       if (username != NULL) {
+               /*
+                * It is important to set groups before we
+                * lose privileges on setuid.
+                */
+               if (initgroups(username, run_gid) < 0) {
+                       perror_msg_and_die("initgroups");
+               }
+               if (setregid(run_gid, params->run_egid) < 0) {
+                       perror_msg_and_die("setregid");
+               }
+               if (setreuid(run_uid, params->run_euid) < 0) {
+                       perror_msg_and_die("setreuid");
+               }
+       }
+       else if (geteuid() != 0)
+               if (setreuid(run_uid, run_uid) < 0) {
+                       perror_msg_and_die("setreuid");
+               }
+
+       if (!daemonized_tracer) {
+               /*
+                * Induce a ptrace stop. Tracer (our parent)
+                * will resume us with PTRACE_SYSCALL and display
+                * the immediately following execve syscall.
+                * Can't do this on NOMMU systems, we are after
+                * vfork: parent is blocked, stopping would deadlock.
+                */
+               if (!NOMMU_SYSTEM)
+                       kill(getpid(), SIGSTOP);
+       } else {
+               alarm(3);
+               /* we depend on SIGCHLD set to SIG_DFL by init code */
+               /* if it happens to be SIG_IGN'ed, wait won't block */
+               wait(NULL);
+               alarm(0);
+       }
+
+       execv(params->pathname, params->argv);
+       perror_msg_and_die("exec");
+}
+
 static void
 startup_child(char **argv)
 {
-       struct stat statbuf;
+       struct_stat statbuf;
        const char *filename;
-       char pathname[MAXPATHLEN];
-       int pid = 0;
+       size_t filename_len;
+       char pathname[PATH_MAX];
+       int pid;
        struct tcb *tcp;
 
        filename = argv[0];
+       filename_len = strlen(filename);
+
+       if (filename_len > sizeof(pathname) - 1) {
+               errno = ENAMETOOLONG;
+               perror_msg_and_die("exec");
+       }
        if (strchr(filename, '/')) {
-               if (strlen(filename) > sizeof pathname - 1) {
-                       errno = ENAMETOOLONG;
-                       perror_msg_and_die("exec");
-               }
                strcpy(pathname, filename);
        }
 #ifdef USE_DEBUGGING_EXEC
@@ -964,12 +1202,12 @@ startup_child(char **argv)
         * first regardless of the path but doing that gives
         * security geeks a panic attack.
         */
-       else if (stat(filename, &statbuf) == 0)
+       else if (stat_file(filename, &statbuf) == 0)
                strcpy(pathname, filename);
 #endif /* USE_DEBUGGING_EXEC */
        else {
                const char *path;
-               int m, n, len;
+               size_t m, n, len;
 
                for (path = getenv("PATH"); path && *path; path += m) {
                        const char *colon = strchr(path, ':');
@@ -980,7 +1218,7 @@ startup_child(char **argv)
                        else
                                m = n = strlen(path);
                        if (n == 0) {
-                               if (!getcwd(pathname, MAXPATHLEN))
+                               if (!getcwd(pathname, PATH_MAX))
                                        continue;
                                len = strlen(pathname);
                        }
@@ -992,8 +1230,10 @@ startup_child(char **argv)
                        }
                        if (len && pathname[len - 1] != '/')
                                pathname[len++] = '/';
+                       if (filename_len + len > sizeof(pathname) - 1)
+                               continue;
                        strcpy(pathname + len, filename);
-                       if (stat(pathname, &statbuf) == 0 &&
+                       if (stat_file(pathname, &statbuf) == 0 &&
                            /* Accept only regular files
                               with some execute bits set.
                               XXX not perfect, might still fail */
@@ -1001,80 +1241,50 @@ startup_child(char **argv)
                            (statbuf.st_mode & 0111))
                                break;
                }
+               if (!path || !*path)
+                       pathname[0] = '\0';
        }
-       if (stat(pathname, &statbuf) < 0) {
+       if (stat_file(pathname, &statbuf) < 0) {
                perror_msg_and_die("Can't stat '%s'", filename);
        }
-       strace_child = pid = fork();
+
+       params_for_tracee.fd_to_close = (shared_log != stderr) ? fileno(shared_log) : -1;
+       params_for_tracee.run_euid = (statbuf.st_mode & S_ISUID) ? statbuf.st_uid : run_uid;
+       params_for_tracee.run_egid = (statbuf.st_mode & S_ISGID) ? statbuf.st_gid : run_gid;
+       params_for_tracee.argv = argv;
+       /*
+        * On NOMMU, can be safely freed only after execve in tracee.
+        * It's hard to know when that happens, so we just leak it.
+        */
+       params_for_tracee.pathname = NOMMU_SYSTEM ? xstrdup(pathname) : pathname;
+
+#if defined HAVE_PRCTL && defined PR_SET_PTRACER && defined PR_SET_PTRACER_ANY
+       if (daemonized_tracer)
+               prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
+#endif
+
+       pid = fork();
        if (pid < 0) {
                perror_msg_and_die("fork");
        }
-       if ((pid != 0 && daemonized_tracer) /* -D: parent to become a traced process */
-        || (pid == 0 && !daemonized_tracer) /* not -D: child to become a traced process */
+       if ((pid != 0 && daemonized_tracer)
+        || (pid == 0 && !daemonized_tracer)
        ) {
-               pid = getpid();
-               if (outf != stderr)
-                       close(fileno(outf));
-               if (!daemonized_tracer && !use_seize) {
-                       if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
-                               perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
-                       }
-               }
-
-               if (username != NULL) {
-                       uid_t run_euid = run_uid;
-                       gid_t run_egid = run_gid;
-
-                       if (statbuf.st_mode & S_ISUID)
-                               run_euid = statbuf.st_uid;
-                       if (statbuf.st_mode & S_ISGID)
-                               run_egid = statbuf.st_gid;
-                       /*
-                        * It is important to set groups before we
-                        * lose privileges on setuid.
-                        */
-                       if (initgroups(username, run_gid) < 0) {
-                               perror_msg_and_die("initgroups");
-                       }
-                       if (setregid(run_gid, run_egid) < 0) {
-                               perror_msg_and_die("setregid");
-                       }
-                       if (setreuid(run_uid, run_euid) < 0) {
-                               perror_msg_and_die("setreuid");
-                       }
-               }
-               else if (geteuid() != 0)
-                       setreuid(run_uid, run_uid);
-
-               if (!daemonized_tracer) {
-                       /*
-                        * Induce a ptrace stop. Tracer (our parent)
-                        * will resume us with PTRACE_SYSCALL and display
-                        * the immediately following execve syscall.
-                        * Can't do this on NOMMU systems, we are after
-                        * vfork: parent is blocked, stopping would deadlock.
-                        */
-                       if (!strace_vforked)
-                               kill(pid, SIGSTOP);
-               } else {
-                       alarm(3);
-                       /* we depend on SIGCHLD set to SIG_DFL by init code */
-                       /* if it happens to be SIG_IGN'ed, wait won't block */
-                       wait(NULL);
-                       alarm(0);
-               }
-
-               execv(pathname, argv);
-               perror_msg_and_die("exec");
+               /* We are to become the tracee. Two cases:
+                * -D: we are parent
+                * not -D: we are child
+                */
+               exec_or_die();
        }
 
        /* We are the tracer */
 
        if (!daemonized_tracer) {
+               strace_child = pid;
                if (!use_seize) {
                        /* child did PTRACE_TRACEME, nothing to do in parent */
                } else {
-                       if (!strace_vforked) {
+                       if (!NOMMU_SYSTEM) {
                                /* Wait until child stopped itself */
                                int status;
                                while (waitpid(pid, &status, WSTOPPED) < 0) {
@@ -1087,7 +1297,7 @@ startup_child(char **argv)
                                        perror_msg_and_die("Unexpected wait status %x", status);
                                }
                        }
-                       /* Else: vforked case, we have no way to sync.
+                       /* Else: NOMMU case, we have no way to sync.
                         * Just attach to it as soon as possible.
                         * This means that we may miss a few first syscalls...
                         */
@@ -1096,233 +1306,60 @@ startup_child(char **argv)
                                kill_save_errno(pid, SIGKILL);
                                perror_msg_and_die("Can't attach to %d", pid);
                        }
-                       if (!strace_vforked)
+                       if (!NOMMU_SYSTEM)
                                kill(pid, SIGCONT);
                }
                tcp = alloctcb(pid);
-               if (!strace_vforked)
-                       tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP | post_attach_sigstop;
+               if (!NOMMU_SYSTEM)
+                       tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
                else
-                       tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP;
+                       tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
                newoutf(tcp);
        }
        else {
-               /* With -D, *we* are child here, IOW: different pid. Fetch it: */
+               /* With -D, we are *child* here, IOW: different pid. Fetch it: */
                strace_tracer_pid = getpid();
                /* The tracee is our parent: */
                pid = getppid();
                alloctcb(pid);
                /* attaching will be done later, by startup_attach */
                /* note: we don't do newoutf(tcp) here either! */
-       }
-}
-
-/*
- * 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, 0L, 0L, 0L) < 0)
-                       perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
-                                          __func__);
-               kill_save_errno(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;
-                       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_save_errno(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_flag)
-                       fprintf(stderr, "ptrace_setoptions = %#x\n",
-                               ptrace_setoptions);
-               return;
+               /* NOMMU BUG! -D mode is active, we (child) return,
+                * and we will scribble over parent's stack!
+                * When parent later unpauses, it segfaults.
+                *
+                * We work around it
+                * (1) by declaring exec_or_die() NORETURN,
+                * hopefully compiler will just jump to it
+                * instead of call (won't push anything to stack),
+                * (2) by trying very hard in exec_or_die()
+                * to not use any stack,
+                * (3) having a really big (PATH_MAX) stack object
+                * in this function, which creates a "buffer" between
+                * child's and parent's stack pointers.
+                * This may save us if (1) and (2) failed
+                * and compiler decided to use stack in exec_or_die() anyway
+                * (happens on i386 because of stack parameter passing).
+                *
+                * A cleaner solution is to use makecontext + setcontext
+                * to create a genuine separate stack and execute on it.
+                */
        }
-       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")
- */
+#if USE_SEIZE
 static void
-test_ptrace_setoptions_for_all(void)
+test_ptrace_seize(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_flag)
-                       fprintf(stderr, "ptrace_setoptions = %#x\n",
-                               ptrace_setoptions);
+       /* Need fork for test. NOMMU has no forks */
+       if (NOMMU_SYSTEM) {
+               post_attach_sigstop = 0; /* this sets use_seize to 1 */
                return;
        }
 
-       error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
-                 "giving up using this feature.");
-}
-
-# ifdef USE_SEIZE
-static void
-test_ptrace_seize(void)
-{
-       int pid;
-
        pid = fork();
        if (pid < 0)
                perror_msg_and_die("fork");
@@ -1336,10 +1373,10 @@ test_ptrace_seize(void)
         * attaching tracee continues to run unless a trap condition occurs.
         * PTRACE_SEIZE doesn't affect signal or group stop state.
         */
-       if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_SEIZE_DEVEL) == 0) {
+       if (ptrace(PTRACE_SEIZE, pid, 0, 0) == 0) {
                post_attach_sigstop = 0; /* this sets use_seize to 1 */
        } else if (debug_flag) {
-               fprintf(stderr, "PTRACE_SEIZE doesn't work\n");
+               error_msg("PTRACE_SEIZE doesn't work");
        }
 
        kill(pid, SIGKILL);
@@ -1362,9 +1399,9 @@ test_ptrace_seize(void)
                                __func__, status);
        }
 }
-# else /* !USE_SEIZE */
-#  define test_ptrace_seize() ((void)0)
-# endif
+#else /* !USE_SEIZE */
+# define test_ptrace_seize() ((void)0)
+#endif
 
 static unsigned
 get_os_release(void)
@@ -1386,8 +1423,14 @@ get_os_release(void)
                        break;
                while (*p >= '0' && *p <= '9')
                        p++;
-               if (*p != '.')
+               if (*p != '.') {
+                       if (rel >= KERNEL_VERSION(0,1,0)) {
+                               /* "X.Y-something" means "X.Y.0" */
+                               rel <<= 8;
+                               break;
+                       }
                        error_msg_and_die("Bad OS release string: '%s'", u.release);
+               }
                p++;
        }
        return rel;
@@ -1401,12 +1444,13 @@ get_os_release(void)
  * Don't want main() to inline us and defeat the reason
  * we have a separate function.
  */
-static void __attribute__ ((noinline))
+static void ATTRIBUTE_NOINLINE
 init(int argc, char *argv[])
 {
        struct tcb *tcp;
-       int c;
+       int c, i;
        int optF = 0;
+       unsigned int tcbi;
        struct sigaction sa;
 
        progname = argv[0] ? argv[0] : "strace";
@@ -1424,39 +1468,44 @@ init(int argc, char *argv[])
 
        /* Allocate the initial tcbtab.  */
        tcbtabsize = argc;      /* Surely enough for all -p args.  */
-       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++;
+       tcbtab = xcalloc(tcbtabsize, sizeof(tcbtab[0]));
+       tcp = xcalloc(tcbtabsize, sizeof(*tcp));
+       for (tcbi = 0; tcbi < tcbtabsize; tcbi++)
+               tcbtab[tcbi] = tcp++;
 
-       outf = stderr;
+       shared_log = stderr;
        set_sortby(DEFAULT_SORTBY);
        set_personality(DEFAULT_PERSONALITY);
        qualify("trace=all");
        qualify("abbrev=all");
        qualify("verbose=all");
+#if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
+# error Bug in DEFAULT_QUAL_FLAGS
+#endif
        qualify("signal=all");
        while ((c = getopt(argc, argv,
-               "+bcCdfFhiqrtTvVxyz"
+               "+b:cCdfFhiqrtTvVwxyz"
+#ifdef USE_LIBUNWIND
+               "k"
+#endif
                "D"
                "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
                switch (c) {
                case 'b':
+                       if (strcmp(optarg, "execve") != 0)
+                               error_msg_and_die("Syscall '%s' for -b isn't supported",
+                                       optarg);
                        detach_on_execve = 1;
                        break;
                case 'c':
                        if (cflag == CFLAG_BOTH) {
-                               error_msg_and_die("-c and -C are mutually exclusive options");
+                               error_msg_and_help("-c and -C are mutually exclusive");
                        }
                        cflag = CFLAG_ONLY_STATS;
                        break;
                case 'C':
                        if (cflag == CFLAG_ONLY_STATS) {
-                               error_msg_and_die("-c and -C are mutually exclusive options");
+                               error_msg_and_help("-c and -C are mutually exclusive");
                        }
                        cflag = CFLAG_BOTH;
                        break;
@@ -1473,13 +1522,13 @@ init(int argc, char *argv[])
                        followfork++;
                        break;
                case 'h':
-                       usage(stdout, 0);
+                       usage();
                        break;
                case 'i':
                        iflag = 1;
                        break;
                case 'q':
-                       qflag = 1;
+                       qflag++;
                        break;
                case 'r':
                        rflag = 1;
@@ -1490,11 +1539,14 @@ init(int argc, char *argv[])
                case 'T':
                        Tflag = 1;
                        break;
+               case 'w':
+                       count_wallclock = 1;
+                       break;
                case 'x':
                        xflag++;
                        break;
                case 'y':
-                       show_fd_path = 1;
+                       show_fd_path++;
                        break;
                case 'v':
                        qualify("abbrev=none");
@@ -1507,79 +1559,108 @@ init(int argc, char *argv[])
                        not_failing_only = 1;
                        break;
                case 'a':
-                       acolumn = atoi(optarg);
+                       acolumn = string_to_uint(optarg);
                        if (acolumn < 0)
-                               error_msg_and_die("Bad column width '%s'", optarg);
+                               error_opt_arg(c, optarg);
                        break;
                case 'e':
                        qualify(optarg);
                        break;
                case 'o':
-                       outfname = strdup(optarg);
+                       outfname = xstrdup(optarg);
                        break;
                case 'O':
-                       set_overhead(atoi(optarg));
+                       i = string_to_uint(optarg);
+                       if (i < 0)
+                               error_opt_arg(c, optarg);
+                       set_overhead(i);
                        break;
                case 'p':
                        process_opt_p_list(optarg);
                        break;
                case 'P':
-                       tracing_paths = 1;
-                       if (pathtrace_select(optarg)) {
-                               error_msg_and_die("Failed to select path '%s'", optarg);
-                       }
+                       pathtrace_select(optarg);
                        break;
                case 's':
-                       max_strlen = atoi(optarg);
-                       if (max_strlen < 0) {
-                               error_msg_and_die("Invalid -%c argument: '%s'", c, optarg);
-                       }
+                       i = string_to_uint(optarg);
+                       if (i < 0)
+                               error_opt_arg(c, optarg);
+                       max_strlen = i;
                        break;
                case 'S':
                        set_sortby(optarg);
                        break;
                case 'u':
-                       username = strdup(optarg);
+                       username = xstrdup(optarg);
                        break;
+#ifdef USE_LIBUNWIND
+               case 'k':
+                       stack_trace_enabled = true;
+                       break;
+#endif
                case 'E':
                        if (putenv(optarg) < 0)
                                die_out_of_memory();
                        break;
                case 'I':
-                       opt_intr = atoi(optarg);
-                       if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS) {
-                               error_msg_and_die("Invalid -%c argument: '%s'", c, optarg);
-                       }
+                       opt_intr = string_to_uint(optarg);
+                       if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS)
+                               error_opt_arg(c, optarg);
                        break;
                default:
-                       usage(stderr, 1);
+                       error_msg_and_help(NULL);
                        break;
                }
        }
        argv += optind;
        /* argc -= optind; - no need, argc is not used below */
 
-       acolumn_spaces = malloc(acolumn + 1);
-       if (!acolumn_spaces)
-               die_out_of_memory();
+       acolumn_spaces = xmalloc(acolumn + 1);
        memset(acolumn_spaces, ' ', acolumn);
        acolumn_spaces[acolumn] = '\0';
 
        /* Must have PROG [ARGS], or -p PID. Not both. */
-       if (!argv[0] == !nprocs)
-               usage(stderr, 1);
+       if (!argv[0] == !nprocs) {
+               error_msg_and_help("must have PROG [ARGS] or -p PID");
+       }
 
        if (nprocs != 0 && daemonized_tracer) {
-               error_msg_and_die("-D and -p are mutually exclusive options");
+               error_msg_and_help("-D and -p are mutually exclusive");
        }
 
        if (!followfork)
                followfork = optF;
 
        if (followfork >= 2 && cflag) {
-               error_msg_and_die("(-c or -C) and -ff are mutually exclusive options");
+               error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
+       }
+
+       if (count_wallclock && !cflag) {
+               error_msg_and_help("-w must be given with (-c or -C)");
        }
 
+       if (cflag == CFLAG_ONLY_STATS) {
+               if (iflag)
+                       error_msg("-%c has no effect with -c", 'i');
+#ifdef USE_LIBUNWIND
+               if (stack_trace_enabled)
+                       error_msg("-%c has no effect with -c", 'k');
+#endif
+               if (rflag)
+                       error_msg("-%c has no effect with -c", 'r');
+               if (tflag)
+                       error_msg("-%c has no effect with -c", 't');
+               if (Tflag)
+                       error_msg("-%c has no effect with -c", 'T');
+               if (show_fd_path)
+                       error_msg("-%c has no effect with -c", 'y');
+       }
+
+#ifdef USE_LIBUNWIND
+       if (stack_trace_enabled)
+               unwind_init();
+#endif
+
        /* See if they want to run as another user. */
        if (username != NULL) {
                struct passwd *pent;
@@ -1600,8 +1681,11 @@ init(int argc, char *argv[])
        }
 
        if (followfork)
-               test_ptrace_setoptions_followfork();
-       test_ptrace_setoptions_for_all();
+               ptrace_setoptions |= PTRACE_O_TRACECLONE |
+                                    PTRACE_O_TRACEFORK |
+                                    PTRACE_O_TRACEVFORK;
+       if (debug_flag)
+               error_msg("ptrace_setoptions = %#x", ptrace_setoptions);
        test_ptrace_seize();
 
        /* Check if they want to redirect the output. */
@@ -1613,11 +1697,11 @@ init(int argc, char *argv[])
                         * when using popen, so prohibit it.
                         */
                        if (followfork >= 2)
-                               error_msg_and_die("Piping the output and -ff are mutually exclusive");
-                       outf = strace_popen(outfname + 1);
+                               error_msg_and_help("piping the output and -ff are mutually exclusive");
+                       shared_log = strace_popen(outfname + 1);
                }
                else if (followfork < 2)
-                       outf = strace_fopen(outfname);
+                       shared_log = strace_fopen(outfname);
        } else {
                /* -ff without -o FILE is the same as single -f */
                if (followfork >= 2)
@@ -1625,15 +1709,14 @@ init(int argc, char *argv[])
        }
 
        if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
-               char *buf = malloc(BUFSIZ);
-               if (!buf)
-                       die_out_of_memory();
-               setvbuf(outf, buf, _IOLBF, BUFSIZ);
+               char *buf = xmalloc(BUFSIZ);
+               setvbuf(shared_log, buf, _IOLBF, BUFSIZ);
        }
        if (outfname && argv[0]) {
                if (!opt_intr)
                        opt_intr = INTR_NEVER;
-               qflag = 1;
+               if (!qflag)
+                       qflag = 1;
        }
        if (!opt_intr)
                opt_intr = INTR_WHILE_WAIT;
@@ -1645,17 +1728,21 @@ init(int argc, char *argv[])
         * no           1       1       INTR_WHILE_WAIT
         */
 
-       /* STARTUP_CHILD must be called before the signal handlers get
-          installed below as they are inherited into the spawned process.
-          Also we do not need to be protected by them as during interruption
-          in the STARTUP_CHILD mode we kill the spawned process anyway.  */
+       sigemptyset(&empty_set);
+       sigemptyset(&blocked_set);
+
+       /* startup_child() must be called before the signal handlers get
+        * installed below as they are inherited into the spawned process.
+        * Also we do not need to be protected by them as during interruption
+        * in the startup_child() mode we kill the spawned process anyway.
+        */
        if (argv[0]) {
-               skip_startup_execve = 1;
+               if (!NOMMU_SYSTEM || daemonized_tracer)
+                       hide_log_until_execve = 1;
+               skip_one_b_execve = 1;
                startup_child(argv);
        }
 
-       sigemptyset(&empty_set);
-       sigemptyset(&blocked_set);
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
@@ -1699,14 +1786,14 @@ init(int argc, char *argv[])
 static struct tcb *
 pid2tcb(int pid)
 {
-       int i;
+       unsigned int i;
 
        if (pid <= 0)
                return NULL;
 
        for (i = 0; i < tcbtabsize; i++) {
                struct tcb *tcp = tcbtab[i];
-               if (tcp->pid == pid && (tcp->flags & TCB_INUSE))
+               if (tcp->pid == pid)
                        return tcp;
        }
 
@@ -1716,7 +1803,7 @@ pid2tcb(int pid)
 static void
 cleanup(void)
 {
-       int i;
+       unsigned int i;
        struct tcb *tcp;
        int fatal_sig;
 
@@ -1727,19 +1814,18 @@ cleanup(void)
 
        for (i = 0; i < tcbtabsize; i++) {
                tcp = tcbtab[i];
-               if (!(tcp->flags & TCB_INUSE))
+               if (!tcp->pid)
                        continue;
                if (debug_flag)
-                       fprintf(stderr,
-                               "cleanup: looking at pid %u\n", tcp->pid);
-               if (tcp->flags & TCB_STRACE_CHILD) {
+                       error_msg("cleanup: looking at pid %u", tcp->pid);
+               if (tcp->pid == strace_child) {
                        kill(tcp->pid, SIGCONT);
                        kill(tcp->pid, fatal_sig);
                }
                detach(tcp);
        }
        if (cflag)
-               call_summary(outf);
+               call_summary(shared_log);
 }
 
 static void
@@ -1748,120 +1834,300 @@ interrupt(int sig)
        interrupted = sig;
 }
 
-static int
-trace(void)
+static void
+print_debug_info(const int pid, int status)
 {
-       struct rusage ru;
-       struct rusage *rup = cflag ? &ru : NULL;
-# ifdef __WALL
-       static int wait4_options = __WALL;
-# endif
+       const unsigned int event = (unsigned int) status >> 16;
+       char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
+       char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
 
-       while (nprocs != 0) {
-               int pid;
-               int wait_errno;
-               int status, sig;
-               int stopped;
-               struct tcb *tcp;
-               unsigned event;
-
-               if (interrupted)
-                       return 0;
-               if (interactive)
-                       sigprocmask(SIG_SETMASK, &empty_set, 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;
-                       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, rup);
-                       if (pid < 0) {
-                               perror_msg("wait4(__WCLONE) failed");
-                       }
-               }
-# else
-               pid = wait4(-1, &status, 0, rup);
-# endif /* __WALL */
-               wait_errno = errno;
-               if (interactive)
-                       sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+       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
+       /* Should never be seen */
+       if (WIFCONTINUED(status))
+               strcpy(buf, "WIFCONTINUED");
+#endif
+       evbuf[0] = '\0';
+       if (event != 0) {
+               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",
+                       /* [PTRACE_EVENT_STOP (=128)] would make biggish array */
+               };
+               const char *e = "??";
+               if (event < ARRAY_SIZE(event_names))
+                       e = event_names[event];
+               else if (event == PTRACE_EVENT_STOP)
+                       e = "STOP";
+               sprintf(evbuf, ",EVENT_%s (%u)", e, event);
+       }
+       error_msg("[wait(0x%06x) = %u] %s%s", status, pid, buf, evbuf);
+}
 
-               if (pid < 0) {
-                       switch (wait_errno) {
-                       case EINTR:
-                               continue;
-                       case ECHILD:
-                               /*
-                                * We would like to verify this case
-                                * but sometimes a race in Solbourne's
-                                * version of SunOS sometimes reports
-                                * ECHILD before sending us SIGCHILD.
-                                */
-                               return 0;
-                       default:
-                               errno = wait_errno;
-                               perror_msg("wait");
-                               return -1;
-                       }
-               }
-               if (pid == popen_pid) {
-                       if (WIFEXITED(status) || WIFSIGNALED(status))
-                               popen_pid = 0;
-                       continue;
+static struct tcb *
+maybe_allocate_tcb(const int pid, int status)
+{
+       if (!WIFSTOPPED(status)) {
+               if (detach_on_execve && pid == strace_child) {
+                       /* example: strace -bexecve sh -c 'exec true' */
+                       strace_child = 0;
+                       return NULL;
                }
+               /*
+                * This can happen if we inherited an unknown child.
+                * Example: (sleep 1 & exec strace true)
+                */
+               error_msg("Exit of unknown pid %u ignored", pid);
+               return NULL;
+       }
+       if (followfork) {
+               /* We assume it's a fork/vfork/clone child */
+               struct tcb *tcp = alloctcb(pid);
+               tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
+               newoutf(tcp);
+               if (!qflag)
+                       error_msg("Process %d attached", pid);
+               return tcp;
+       } else {
+               /* This can happen if a clone call used
+                * CLONE_PTRACE itself.
+                */
+               ptrace(PTRACE_CONT, pid, (char *) 0, 0);
+               error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
+               return NULL;
+       }
+}
+
+static struct tcb *
+maybe_switch_tcbs(struct tcb *tcp, const int pid)
+{
+       FILE *fp;
+       struct tcb *execve_thread;
+       long old_pid = 0;
+
+       if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
+               return tcp;
+       /* Avoid truncation in pid2tcb() param passing */
+       if (old_pid <= 0 || old_pid == pid)
+               return tcp;
+       if ((unsigned long) old_pid > UINT_MAX)
+               return tcp;
+       execve_thread = pid2tcb(old_pid);
+       /* It should be !NULL, but I feel paranoid */
+       if (!execve_thread)
+               return tcp;
+
+       if (execve_thread->curcol != 0) {
+               /*
+                * One case we are here is -ff:
+                * try "strace -oLOG -ff test/threaded_execve"
+                */
+               fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
+               /*execve_thread->curcol = 0; - no need, see code below */
+       }
+       /* Swap output FILEs (needed for -ff) */
+       fp = execve_thread->outf;
+       execve_thread->outf = tcp->outf;
+       tcp->outf = fp;
+       /* And their column positions */
+       execve_thread->curcol = tcp->curcol;
+       tcp->curcol = 0;
+       /* Drop leader, but close execve'd thread outfile (if -ff) */
+       droptcb(tcp);
+       /* Switch to the thread, reusing leader's outfile and pid */
+       tcp = execve_thread;
+       tcp->pid = pid;
+       if (cflag != CFLAG_ONLY_STATS) {
+               printleader(tcp);
+               tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
+               line_ended();
+               tcp->flags |= TCB_REPRINT;
+       }
+
+       return tcp;
+}
+
+static void
+print_signalled(struct tcb *tcp, const int pid, int status)
+{
+       if (pid == strace_child) {
+               exit_code = 0x100 | WTERMSIG(status);
+               strace_child = 0;
+       }
 
-               event = ((unsigned)status >> 16);
-               if (debug_flag) {
-                       char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
-                       char evbuf[sizeof(",PTRACE_EVENT_?? (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
-                       strcpy(buf, "???");
-                       if (WIFSIGNALED(status))
+       if (cflag != CFLAG_ONLY_STATS
+        && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
+       ) {
+               printleader(tcp);
 #ifdef WCOREDUMP
-                               sprintf(buf, "WIFSIGNALED,%ssig=%s",
-                                               WCOREDUMP(status) ? "core," : "",
-                                               signame(WTERMSIG(status)));
+               tprintf("+++ killed by %s %s+++\n",
+                       signame(WTERMSIG(status)),
+                       WCOREDUMP(status) ? "(core dumped) " : "");
 #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");
+               tprintf("+++ killed by %s +++\n",
+                       signame(WTERMSIG(status)));
 #endif
-                       evbuf[0] = '\0';
-                       if (event != 0) {
-                               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 (event < ARRAY_SIZE(event_names))
-                                       e = event_names[event];
-                               else {
-                                       sprintf(buf, "?? (%u)", event);
-                                       e = buf;
-                               }
-                               sprintf(evbuf, ",PTRACE_EVENT_%s", e);
+               line_ended();
+       }
+}
+
+static void
+print_exited(struct tcb *tcp, const int pid, int status)
+{
+       if (pid == strace_child) {
+               exit_code = WEXITSTATUS(status);
+               strace_child = 0;
+       }
+
+       if (cflag != CFLAG_ONLY_STATS &&
+           qflag < 2) {
+               printleader(tcp);
+               tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
+               line_ended();
+       }
+}
+
+static void
+print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
+{
+       if (cflag != CFLAG_ONLY_STATS
+           && !hide_log_until_execve
+           && (qual_flags[sig] & QUAL_SIGNAL)
+          ) {
+               printleader(tcp);
+               if (si) {
+                       tprintf("--- %s ", signame(sig));
+                       printsiginfo(si, verbose(tcp));
+                       tprints(" ---\n");
+               } else
+                       tprintf("--- stopped by %s ---\n", signame(sig));
+               line_ended();
+       }
+}
+
+static void
+startup_tcb(struct tcb *tcp)
+{
+       if (debug_flag)
+               error_msg("pid %d has TCB_STARTUP, initializing it", tcp->pid);
+
+       tcp->flags &= ~TCB_STARTUP;
+
+       if (!use_seize) {
+               if (debug_flag)
+                       error_msg("setting opts 0x%x on pid %d",
+                                 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");
                        }
-                       fprintf(stderr, " [wait(0x%04x) = %u] %s%s\n", status, pid, buf, evbuf);
                }
+       }
+}
+
+/* Returns true iff the main trace loop has to continue. */
+static bool
+trace(void)
+{
+       int pid;
+       int wait_errno;
+       int status;
+       bool stopped;
+       unsigned int sig;
+       unsigned int event;
+       struct tcb *tcp;
+       struct rusage ru;
+
+       if (interrupted)
+               return false;
+
+       /*
+        * Used to exit simply when nprocs hits zero, but in this testcase:
+        *  int main() { _exit(!!fork()); }
+        * under strace -f, parent sometimes (rarely) manages
+        * to exit before we see the first stop of the child,
+        * and we are losing track of it:
+        *  19923 clone(...) = 19924
+        *  19923 exit_group(1)     = ?
+        *  19923 +++ exited with 1 +++
+        * Exiting only when wait() returns ECHILD works better.
+        */
+       if (popen_pid != 0) {
+               /* However, if -o|logger is in use, we can't do that.
+                * Can work around that by double-forking the logger,
+                * but that loses the ability to wait for its completion
+                * on exit. Oh well...
+                */
+               if (nprocs == 0)
+                       return false;
+       }
+
+       if (interactive)
+               sigprocmask(SIG_SETMASK, &empty_set, NULL);
+       pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
+       wait_errno = errno;
+       if (interactive)
+               sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+
+       if (pid < 0) {
+               if (wait_errno == EINTR)
+                       return true;
+               if (nprocs == 0 && wait_errno == ECHILD)
+                       return false;
+               /*
+                * If nprocs > 0, ECHILD is not expected,
+                * treat it as any other error here:
+                */
+               errno = wait_errno;
+               perror_msg_and_die("wait4(__WALL)");
+       }
+
+       if (pid == popen_pid) {
+               if (!WIFSTOPPED(status))
+                       popen_pid = 0;
+               return true;
+       }
+
+       if (debug_flag)
+               print_debug_info(pid, status);
+
+       /* Look up 'pid' in our table. */
+       tcp = pid2tcb(pid);
+
+       if (!tcp) {
+               tcp = maybe_allocate_tcb(pid, status);
+               if (!tcp)
+                       return true;
+       }
 
-               /* Look up 'pid' in our table. */
-               tcp = pid2tcb(pid);
+       if (WIFSTOPPED(status))
+               get_regs(pid);
+       else
+               clear_regs();
 
-               /* Under Linux, execve changes pid to thread leader's pid,
+       event = (unsigned int) status >> 16;
+
+       if (event == PTRACE_EVENT_EXEC) {
+               /*
+                * Under Linux, execve changes pid to thread leader's pid,
                 * and we see this changed pid on EVENT_EXEC and later,
                 * execve sysexit. Leader "disappears" without exit
                 * notification. Let user know that, drop leader's tcb,
@@ -1875,288 +2141,161 @@ trace(void)
                 * PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
                 * On 2.6 and earlier, it can return garbage.
                 */
-               if (event == PTRACE_EVENT_EXEC && os_release >= KERNEL_VERSION(3,0,0)) {
-                       long old_pid = 0;
-                       if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) >= 0
-                        && old_pid > 0
-                        && old_pid != pid
-                       ) {
-                               struct tcb *execve_thread = pid2tcb(old_pid);
-                               if (tcp) {
-                                       outf = tcp->outf;
-                                       curcol = tcp->curcol;
-                                       if (execve_thread) {
-                                               if (execve_thread->curcol != 0) {
-                                                       /*
-                                                        * One case we are here is -ff:
-                                                        * try "strace -oLOG -ff test/threaded_execve"
-                                                        */
-                                                       fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
-                                                       execve_thread->curcol = 0;
-                                               }
-                                               /* swap output FILEs (needed for -ff) */
-                                               tcp->outf = execve_thread->outf;
-                                               tcp->curcol = execve_thread->curcol;
-                                               execve_thread->outf = outf;
-                                               execve_thread->curcol = curcol;
-                                       }
-                                       droptcb(tcp);
-                               }
-                               tcp = execve_thread;
-                               if (tcp) {
-                                       tcp->pid = pid;
-                                       tcp->flags |= TCB_REPRINT;
-                                       if (!cflag) {
-                                               printleader(tcp);
-                                               tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
-                                               line_ended();
-                                       }
-                               }
-                       }
-               }
+               if (os_release >= KERNEL_VERSION(3,0,0))
+                       tcp = maybe_switch_tcbs(tcp, pid);
 
-               if (event == PTRACE_EVENT_EXEC && detach_on_execve) {
-                       if (!skip_startup_execve)
-                               detach(tcp);
-                       /* This was initial execve for "strace PROG". Skip. */
-                       skip_startup_execve = 0;
+               if (detach_on_execve && !skip_one_b_execve) {
+                       detach(tcp); /* do "-b execve" thingy */
+                       return true;
                }
+               skip_one_b_execve = 0;
+       }
 
-               if (tcp == NULL) {
-                       if (followfork) {
-                               /* This is needed to go with the CLONE_PTRACE
-                                  changes in process.c/util.c: we might see
-                                  the child's initial trap before we see the
-                                  parent return from the clone syscall.
-                                  Leave the child suspended until the parent
-                                  returns from its system call.  Only then
-                                  will we have the association of parent and
-                                  child so that we know how to do clearbpt
-                                  in the child.  */
-                               tcp = alloctcb(pid);
-                               tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
-                               newoutf(tcp);
-                               if (!qflag)
-                                       fprintf(stderr, "Process %d attached\n",
-                                               pid);
-                       }
-                       else
-                               /* This can happen if a clone call used
-                                  CLONE_PTRACE itself.  */
-                       {
-                               if (WIFSTOPPED(status))
-                                       ptrace(PTRACE_CONT, pid, (char *) 0, 0);
-                               error_msg_and_die("Unknown pid: %u", pid);
-                       }
-               }
+       /* Set current output file */
+       current_tcp = tcp;
 
-               /* Set current output file */
-               outf = tcp->outf;
-               curcol = tcp->curcol;
+       if (cflag) {
+               tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
+               tcp->stime = ru.ru_stime;
+       }
 
-               if (cflag) {
-                       tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
-                       tcp->stime = ru.ru_stime;
-               }
+       if (WIFSIGNALED(status)) {
+               print_signalled(tcp, pid, status);
+               droptcb(tcp);
+               return true;
+       }
 
-               if (WIFSIGNALED(status)) {
-                       if (pid == strace_child)
-                               exit_code = 0x100 | WTERMSIG(status);
-                       if (cflag != CFLAG_ONLY_STATS
-                           && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) {
-                               printleader(tcp);
-#ifdef WCOREDUMP
-                               tprintf("+++ killed by %s %s+++\n",
-                                       signame(WTERMSIG(status)),
-                                       WCOREDUMP(status) ? "(core dumped) " : "");
-#else
-                               tprintf("+++ killed by %s +++\n",
-                                       signame(WTERMSIG(status)));
-#endif
-                               line_ended();
-                       }
-                       droptcb(tcp);
-                       continue;
-               }
-               if (WIFEXITED(status)) {
-                       if (pid == strace_child)
-                               exit_code = WEXITSTATUS(status);
-                       if (!cflag /* && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) */ ) {
-                               printleader(tcp);
-                               tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
-                               line_ended();
-                       }
-                       droptcb(tcp);
-                       continue;
-               }
-               if (!WIFSTOPPED(status)) {
-                       fprintf(stderr, "PANIC: pid %u not stopped\n", pid);
-                       droptcb(tcp);
-                       continue;
-               }
+       if (WIFEXITED(status)) {
+               print_exited(tcp, pid, status);
+               droptcb(tcp);
+               return true;
+       }
 
-               /* Is this the very first time we see this tracee stopped? */
-               if (tcp->flags & TCB_STARTUP) {
-                       if (debug_flag)
-                               fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n", tcp->pid);
-                       tcp->flags &= ~TCB_STARTUP;
-                       if (tcp->flags & TCB_BPTSET) {
-                               /*
-                                * One example is a breakpoint inherited from
-                                * parent through fork().
-                                */
-                               if (clearbpt(tcp) < 0) {
-                                       /* Pretty fatal */
-                                       droptcb(tcp);
-                                       cleanup();
-                                       return -1;
-                               }
-                       }
-                       if (ptrace_setoptions) {
-                               if (debug_flag)
-                                       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");
-                                       }
-                               }
-                       }
-               }
+       if (!WIFSTOPPED(status)) {
+               /*
+                * Neither signalled, exited or stopped.
+                * How could that be?
+                */
+               error_msg("pid %u not stopped!", pid);
+               droptcb(tcp);
+               return true;
+       }
 
-               sig = WSTOPSIG(status);
+       /* Is this the very first time we see this tracee stopped? */
+       if (tcp->flags & TCB_STARTUP) {
+               startup_tcb(tcp);
+               if (get_scno(tcp) == 1)
+                       tcp->s_prev_ent = tcp->s_ent;
+       }
 
-               if (event != 0) {
-                       /* Ptrace event */
-#ifdef USE_SEIZE
-                       if (event == PTRACE_EVENT_STOP || event == PTRACE_EVENT_STOP1) {
-                               /*
-                                * PTRACE_INTERRUPT-stop or group-stop.
-                                * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
-                                */
-                               if (sig == SIGSTOP
-                                || sig == SIGTSTP
-                                || sig == SIGTTIN
-                                || sig == SIGTTOU
-                               ) {
-                                       stopped = 1;
+       sig = WSTOPSIG(status);
+
+       if (event != 0) {
+               /* Ptrace event */
+#if USE_SEIZE
+               if (event == PTRACE_EVENT_STOP) {
+                       /*
+                        * PTRACE_INTERRUPT-stop or group-stop.
+                        * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+                        */
+                       switch (sig) {
+                               case SIGSTOP:
+                               case SIGTSTP:
+                               case SIGTTIN:
+                               case SIGTTOU:
+                                       stopped = true;
                                        goto show_stopsig;
-                               }
                        }
-#endif
-                       goto restart_tracee_with_sig_0;
                }
+#endif
+               goto restart_tracee_with_sig_0;
+       }
 
-               /* Is this post-attach SIGSTOP?
-                * Interestingly, the process may stop
-                * with STOPSIG equal to some other signal
-                * than SIGSTOP if we happend to attach
-                * just before the process takes a signal.
-                */
-               if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
-                       if (debug_flag)
-                               fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
-                       tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
-                       goto restart_tracee_with_sig_0;
-               }
+       /*
+        * Is this post-attach SIGSTOP?
+        * Interestingly, the process may stop
+        * with STOPSIG equal to some other signal
+        * than SIGSTOP if we happend to attach
+        * just before the process takes a signal.
+        */
+       if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
+               if (debug_flag)
+                       error_msg("ignored SIGSTOP on pid %d", tcp->pid);
+               tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
+               goto restart_tracee_with_sig_0;
+       }
 
-               if (sig != syscall_trap_sig) {
-                       siginfo_t si;
+       if (sig != syscall_trap_sig) {
+               siginfo_t si = {};
 
-                       /* Nonzero (true) if tracee is stopped by signal
-                        * (as opposed to "tracee received signal").
-                        */
-                       stopped = (ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0);
-#ifdef USE_SEIZE
- show_stopsig:
-#endif
-                       if (cflag != CFLAG_ONLY_STATS
-                           && (qual_flags[sig] & QUAL_SIGNAL)) {
-#if defined(PT_CR_IPSR) && defined(PT_CR_IIP)
-                               long pc = 0;
-                               long psr = 0;
-
-                               upeek(tcp, PT_CR_IPSR, &psr);
-                               upeek(tcp, PT_CR_IIP, &pc);
-
-# define PSR_RI        41
-                               pc += (psr >> PSR_RI) & 0x3;
-# define PC_FORMAT_STR " @ %lx"
-# define PC_FORMAT_ARG , pc
-#else
-# define PC_FORMAT_STR ""
-# define PC_FORMAT_ARG /* nothing */
+               /*
+                * True if tracee is stopped by signal
+                * (as opposed to "tracee received signal").
+                * TODO: shouldn't we check for errno == EINVAL too?
+                * We can get ESRCH instead, you know...
+                */
+               stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0;
+#if USE_SEIZE
+show_stopsig:
 #endif
-                               printleader(tcp);
-                               if (!stopped) {
-                                       tprintf("--- %s ", signame(sig));
-                                       printsiginfo(&si, verbose(tcp));
-                                       tprintf(PC_FORMAT_STR " ---\n"
-                                               PC_FORMAT_ARG);
-                               } else
-                                       tprintf("--- stopped by %s" PC_FORMAT_STR " ---\n",
-                                               signame(sig)
-                                               PC_FORMAT_ARG);
-                               line_ended();
-                       }
+               print_stopped(tcp, stopped ? NULL : &si, sig);
 
-                       if (!stopped)
-                               /* It's signal-delivery-stop. Inject the signal */
-                               goto restart_tracee;
+               if (!stopped)
+                       /* It's signal-delivery-stop. Inject the signal */
+                       goto restart_tracee;
 
-                       /* It's group-stop */
-#ifdef USE_SEIZE
-                       if (use_seize) {
-                               /*
-                                * This ends ptrace-stop, but does *not* end group-stop.
-                                * This makes stopping signals work properly on straced process
-                                * (that is, process really stops. It used to continue to run).
-                                */
-                               if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
-                                       cleanup();
-                                       return -1;
-                               }
-                               tcp->curcol = curcol;
-                               continue;
+               /* It's group-stop */
+               if (use_seize) {
+                       /*
+                        * This ends ptrace-stop, but does *not* end group-stop.
+                        * This makes stopping signals work properly on straced process
+                        * (that is, process really stops. It used to continue to run).
+                        */
+                       if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
+                               /* Note: ptrace_restart emitted error message */
+                               exit_code = 1;
+                               return false;
                        }
-                       /* We don't have PTRACE_LISTEN support... */
-#endif
-                       goto restart_tracee;
+                       return true;
                }
+               /* We don't have PTRACE_LISTEN support... */
+               goto restart_tracee;
+       }
 
-               /* We handled quick cases, we are permitted to interrupt now. */
-               if (interrupted)
-                       return 0;
+       /* We handled quick cases, we are permitted to interrupt now. */
+       if (interrupted)
+               return false;
 
-               /* This should be syscall entry or exit.
-                * (Or it still can be that pesky post-execve SIGTRAP!)
-                * Handle it.
+       /*
+        * This should be syscall entry or exit.
+        * Handle it.
+        */
+       if (trace_syscall(tcp) < 0) {
+               /*
+                * ptrace() failed in trace_syscall().
+                * Likely a result of process disappearing mid-flight.
+                * Observed case: exit_group() or SIGKILL terminating
+                * all processes in thread group.
+                * We assume that ptrace error was caused by process death.
+                * We used to detach(tcp) here, but since we no longer
+                * implement "detach before death" policy/hack,
+                * we can let this process to report its death to us
+                * normally, via WIFEXITED or WIFSIGNALED wait status.
                 */
-               if (trace_syscall(tcp) < 0 && !tcp->ptrace_errno) {
-                       /* ptrace() failed in trace_syscall() with ESRCH.
-                        * Likely a result of process disappearing mid-flight.
-                        * Observed case: exit_group() terminating
-                        * all processes in thread group.
-                        * We assume that ptrace error was caused by process death.
-                        * We used to detach(tcp) here, but since we no longer
-                        * implement "detach before death" policy/hack,
-                        * we can let this process to report its death to us
-                        * normally, via WIFEXITED or WIFSIGNALED wait status.
-                        */
-                       tcp->curcol = curcol;
-                       continue;
-               }
- restart_tracee_with_sig_0:
-               sig = 0;
- restart_tracee:
-               /* Remember current print column before continuing. */
-               tcp->curcol = curcol;
-               if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
-                       cleanup();
-                       return -1;
-               }
+               return true;
        }
-       return 0;
+
+restart_tracee_with_sig_0:
+       sig = 0;
+
+restart_tracee:
+       if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
+               /* Note: ptrace_restart emitted error message */
+               exit_code = 1;
+               return false;
+       }
+
+       return true;
 }
 
 int
@@ -2164,16 +2303,21 @@ main(int argc, char *argv[])
 {
        init(argc, argv);
 
-       /* Run main tracing loop */
-       if (trace() < 0)
-               return 1;
+       while (trace())
+               ;
 
        cleanup();
        fflush(NULL);
+       if (shared_log != stderr)
+               fclose(shared_log);
+       if (popen_pid) {
+               while (waitpid(popen_pid, NULL, 0) < 0 && errno == EINTR)
+                       ;
+       }
        if (exit_code > 0xff) {
                /* Avoid potential core file clobbering.  */
-               struct rlimit rlim = {0, 0};
-               setrlimit(RLIMIT_CORE, &rlim);
+               struct_rlimit rlim = {0, 0};
+               set_rlimit(RLIMIT_CORE, &rlim);
 
                /* Child was killed by a signal, mimic that.  */
                exit_code &= 0xff;