]> granicus.if.org Git - strace/blobdiff - strace.c
Always compile sys_prctl parser
[strace] / strace.c
index ad29bb40003bfe9355032820ed0d16235da7f4bc..aae7505f4e47c4d3f71b7030feb2f6bdb5fcf33e 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -39,6 +39,9 @@
 #include <grp.h>
 #include <dirent.h>
 #include <sys/utsname.h>
+#ifdef HAVE_PRCTL
+# include <sys/prctl.h>
+#endif
 #if defined(IA64)
 # include <asm/ptrace_offsets.h>
 #endif
@@ -47,6 +50,10 @@ 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 +61,32 @@ 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
 
 cflag_t cflag = CFLAG_NONE;
 unsigned int followfork = 0;
 unsigned int ptrace_setoptions = 0;
 unsigned int xflag = 0;
+bool need_fork_exec_workarounds = 0;
 bool debug_flag = 0;
 bool Tflag = 0;
-bool qflag = 0;
+bool iflag = 0;
+bool count_wallclock = 0;
+unsigned int qflag = 0;
 /* Which WSTOPSIG(status) value marks syscall traps? */
 static unsigned int syscall_trap_sig = SIGTRAP;
 static unsigned int tflag = 0;
-static bool iflag = 0;
 static bool rflag = 0;
 static bool print_pid_pfx = 0;
 
@@ -102,7 +117,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 +129,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 +146,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;
@@ -180,35 +198,36 @@ static void
 usage(FILE *ofp, int exitval)
 {
        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\
+usage: strace [-CdffhiqrtttTvVxxy] [-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[df] [-I n] [-e expr]... [-O overhead] [-S sortby]\n\
+              -p pid... / [-D] [-E var=val]... [-u username] 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\
+-C -- like -c but also print regular output\n\
+-w -- summarise syscall latency (default is system time)\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\
+-yy -- print ip:port pairs associated with socket file descriptors\n\
+-h -- print help message, -V -- print version\n\
 -a column -- alignment COLUMN for printing syscall results (default %d)\n\
+-b execve -- detach on this syscall\n\
 -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
-   options: trace, abbrev, verbose, raw, signal, read, or write\n\
+   options: trace, abbrev, verbose, raw, signal, read, write\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\
 -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\
@@ -219,12 +238,16 @@ usage: strace [-CdDffhiqrtttTvVxxy] [-I n] [-a column] [-e expr]... [-o file]\n\
 -E var -- remove var from the environment for command\n\
 -P path -- trace accesses to path\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);
 }
@@ -311,25 +334,23 @@ void die_out_of_memory(void)
        error_msg_and_die("Out of memory");
 }
 
-/* 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
+static void
+error_opt_arg(int opt, const char *arg)
+{
+       error_msg_and_die("Invalid -%c argument: '%s'", opt, arg);
+}
 
-#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
@@ -373,7 +394,7 @@ ptrace_restart(int op, struct tcb *tcp, int sig)
         * 10252 died after we retrieved syscall exit data,
         * but before we tried to restart it. Log looks ugly.
         */
-       if (curcol != 0) {
+       if (current_tcp && current_tcp->curcol != 0) {
                tprintf(" <ptrace(%s):%s>\n", msg, strerror(err));
                line_ended();
        }
@@ -428,10 +449,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 *
@@ -463,6 +500,7 @@ static FILE *
 strace_popen(const char *command)
 {
        FILE *fp;
+       int pid;
        int fds[2];
 
        swap_uid();
@@ -471,11 +509,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) {
@@ -488,6 +526,7 @@ strace_popen(const char *command)
        }
 
        /* parent */
+       popen_pid = pid;
        close(fds[0]);
        swap_uid();
        fp = fdopen(fds[1], "w");
@@ -502,42 +541,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
@@ -550,8 +593,7 @@ printleader(struct tcb *tcp)
                printing_tcp = tcp;
 
        if (printing_tcp) {
-               outf = printing_tcp->outf;
-               curcol = printing_tcp->curcol;
+               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
@@ -560,14 +602,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);
@@ -602,14 +643,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.
@@ -619,7 +660,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);
@@ -635,7 +676,7 @@ 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;
+       unsigned int i = tcbtabsize;
        struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0]));
        struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0]));
        if (!newtab || !newtcbs)
@@ -649,7 +690,7 @@ expand_tcbtab(void)
 static struct tcb *
 alloctcb(int pid)
 {
-       int i;
+       unsigned int i;
        struct tcb *tcp;
 
        if (nprocs == tcbtabsize)
@@ -657,14 +698,18 @@ 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);
@@ -680,17 +725,21 @@ 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);
 
        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");
@@ -698,22 +747,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;
+       int status;
 
        if (tcp->flags & TCB_BPTSET)
                clearbpt(tcp);
@@ -724,90 +775,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)
+                       fprintf(stderr, "detach wait: event:%d sig:%d\n",
+                                       (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);
 
        droptcb(tcp);
-
-       return error;
 }
 
 static void
@@ -823,16 +937,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);
@@ -845,7 +955,7 @@ process_opt_p_list(char *opt)
 static void
 startup_attach(void)
 {
-       int tcbi;
+       unsigned int tcbi;
        struct tcb *tcp;
 
        /*
@@ -880,7 +990,7 @@ 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? */
@@ -895,14 +1005,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;
@@ -930,7 +1041,7 @@ startup_attach(void)
                                }
                                ntid -= nerr;
                                if (ntid == 0) {
-                                       perror("attach: ptrace(PTRACE_ATTACH, ...)");
+                                       perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
                                        droptcb(tcp);
                                        continue;
                                }
@@ -951,7 +1062,7 @@ 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;
                }
@@ -979,13 +1090,79 @@ 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, 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;
+       char pathname[PATH_MAX];
+       int pid;
        struct tcb *tcp;
 
        filename = argv[0];
@@ -1002,12 +1179,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, ':');
@@ -1018,7 +1195,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);
                        }
@@ -1031,7 +1208,7 @@ startup_child(char **argv)
                        if (len && pathname[len - 1] != '/')
                                pathname[len++] = '/';
                        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 */
@@ -1040,79 +1217,47 @@ startup_child(char **argv)
                                break;
                }
        }
-       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 ? strdup(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) {
@@ -1125,7 +1270,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...
                         */
@@ -1134,24 +1279,45 @@ 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! */
+
+               /* 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.
+                */
        }
 }
 
@@ -1160,7 +1326,7 @@ startup_child(char **argv)
  * First fork a new child, call ptrace with PTRACE_SETOPTIONS on it,
  * and then see which options are supported by the kernel.
  */
-static void
+static int
 test_ptrace_setoptions_followfork(void)
 {
        int pid, expected_grandchild = 0, found_grandchild = 0;
@@ -1168,6 +1334,10 @@ test_ptrace_setoptions_followfork(void)
                                          PTRACE_O_TRACEFORK |
                                          PTRACE_O_TRACEVFORK;
 
+       /* Need fork for test. NOMMU has no forks */
+       if (NOMMU_SYSTEM)
+               goto worked; /* be bold, and pretend that test succeeded */
+
        pid = fork();
        if (pid < 0)
                perror_msg_and_die("fork");
@@ -1249,14 +1419,16 @@ test_ptrace_setoptions_followfork(void)
                }
        }
        if (expected_grandchild && expected_grandchild == found_grandchild) {
+ worked:
                ptrace_setoptions |= test_options;
                if (debug_flag)
                        fprintf(stderr, "ptrace_setoptions = %#x\n",
                                ptrace_setoptions);
-               return;
+               return 0;
        }
        error_msg("Test for PTRACE_O_TRACECLONE failed, "
                  "giving up using this feature.");
+       return 1;
 }
 
 /*
@@ -1266,14 +1438,15 @@ test_ptrace_setoptions_followfork(void)
  *
  * 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")
+
+# compile with: gcc -nostartfiles -nostdlib -o int3 int3.S
+_start:                .globl  _start
+               int3
+               movl    $42, %ebx
+               movl    $1, %eax
+               int     $0x80
  */
-static void
+static int
 test_ptrace_setoptions_for_all(void)
 {
        const unsigned int test_options = PTRACE_O_TRACESYSGOOD |
@@ -1281,6 +1454,10 @@ test_ptrace_setoptions_for_all(void)
        int pid;
        int it_worked = 0;
 
+       /* Need fork for test. NOMMU has no forks */
+       if (NOMMU_SYSTEM)
+               goto worked; /* be bold, and pretend that test succeeded */
+
        pid = fork();
        if (pid < 0)
                perror_msg_and_die("fork");
@@ -1343,24 +1520,32 @@ test_ptrace_setoptions_for_all(void)
        }
 
        if (it_worked) {
+ worked:
                syscall_trap_sig = (SIGTRAP | 0x80);
                ptrace_setoptions |= test_options;
                if (debug_flag)
                        fprintf(stderr, "ptrace_setoptions = %#x\n",
                                ptrace_setoptions);
-               return;
+               return 0;
        }
 
        error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
                  "giving up using this feature.");
+       return 1;
 }
 
-# ifdef USE_SEIZE
+#if USE_SEIZE
 static void
 test_ptrace_seize(void)
 {
        int pid;
 
+       /* Need fork for test. NOMMU has no forks */
+       if (NOMMU_SYSTEM) {
+               post_attach_sigstop = 0; /* this sets use_seize to 1 */
+               return;
+       }
+
        pid = fork();
        if (pid < 0)
                perror_msg_and_die("fork");
@@ -1374,7 +1559,7 @@ 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");
@@ -1400,9 +1585,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)
@@ -1424,8 +1609,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;
@@ -1443,8 +1634,9 @@ 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";
@@ -1468,33 +1660,42 @@ init(int argc, char *argv[])
        tcp = calloc(tcbtabsize, sizeof(*tcp));
        if (!tcp)
                die_out_of_memory();
-       for (c = 0; c < tcbtabsize; c++)
-               tcbtab[c] = 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_die("-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_die("-c and -C are mutually exclusive");
                        }
                        cflag = CFLAG_BOTH;
                        break;
@@ -1517,7 +1718,7 @@ init(int argc, char *argv[])
                        iflag = 1;
                        break;
                case 'q':
-                       qflag = 1;
+                       qflag++;
                        break;
                case 'r':
                        rflag = 1;
@@ -1528,11 +1729,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");
@@ -1545,9 +1749,9 @@ 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);
@@ -1556,22 +1760,22 @@ init(int argc, char *argv[])
                        outfname = strdup(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);
@@ -1579,15 +1783,19 @@ init(int argc, char *argv[])
                case 'u':
                        username = strdup(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);
@@ -1608,16 +1816,42 @@ init(int argc, char *argv[])
                usage(stderr, 1);
 
        if (nprocs != 0 && daemonized_tracer) {
-               error_msg_and_die("-D and -p are mutually exclusive options");
+               error_msg_and_die("-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_die("(-c or -C) and -ff are mutually exclusive");
+       }
+
+       if (count_wallclock && !cflag) {
+               error_msg_and_die("-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;
@@ -1637,9 +1871,14 @@ init(int argc, char *argv[])
                run_gid = getgid();
        }
 
+       /*
+        * On any reasonably recent Linux kernel (circa about 2.5.46)
+        * need_fork_exec_workarounds should stay 0 after these tests:
+        */
+       /*need_fork_exec_workarounds = 0; - already is */
        if (followfork)
-               test_ptrace_setoptions_followfork();
-       test_ptrace_setoptions_for_all();
+               need_fork_exec_workarounds = test_ptrace_setoptions_followfork();
+       need_fork_exec_workarounds |= test_ptrace_setoptions_for_all();
        test_ptrace_seize();
 
        /* Check if they want to redirect the output. */
@@ -1652,10 +1891,10 @@ init(int argc, char *argv[])
                         */
                        if (followfork >= 2)
                                error_msg_and_die("Piping the output and -ff are mutually exclusive");
-                       outf = strace_popen(outfname + 1);
+                       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)
@@ -1666,7 +1905,7 @@ init(int argc, char *argv[])
                char *buf = malloc(BUFSIZ);
                if (!buf)
                        die_out_of_memory();
-               setvbuf(outf, buf, _IOLBF, BUFSIZ);
+               setvbuf(shared_log, buf, _IOLBF, BUFSIZ);
        }
        if (outfname && argv[0]) {
                if (!opt_intr)
@@ -1683,17 +1922,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;
@@ -1737,14 +1980,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;
        }
 
@@ -1754,7 +1997,7 @@ pid2tcb(int pid)
 static void
 cleanup(void)
 {
-       int i;
+       unsigned int i;
        struct tcb *tcp;
        int fatal_sig;
 
@@ -1765,19 +2008,19 @@ 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) {
+               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
@@ -1786,68 +2029,61 @@ interrupt(int sig)
        interrupted = sig;
 }
 
-static int
+static void
 trace(void)
 {
        struct rusage ru;
-       struct rusage *rup = cflag ? &ru : NULL;
-# ifdef __WALL
-       static int wait4_options = __WALL;
-# endif
 
-       while (nprocs != 0) {
+       /* Used to be "while (nprocs != 0)", 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 +++
+        * Waiting for ECHILD works better.
+        * (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...)
+        */
+       while (1) {
                int pid;
                int wait_errno;
-               int status, sig;
+               int status;
                int stopped;
-               struct tcb *tcp;
+               unsigned int sig;
                unsigned event;
+               struct tcb *tcp;
 
                if (interrupted)
-                       return 0;
+                       return;
+
+               if (popen_pid != 0 && nprocs == 0)
+                       return;
+
                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 */
+               pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
                wait_errno = errno;
                if (interactive)
                        sigprocmask(SIG_BLOCK, &blocked_set, NULL);
 
                if (pid < 0) {
-                       switch (wait_errno) {
-                       case EINTR:
+                       if (wait_errno == 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 (nprocs == 0 && wait_errno == ECHILD)
+                               return;
+                       /* 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 (WIFEXITED(status) || WIFSIGNALED(status))
+                       if (!WIFSTOPPED(status))
                                popen_pid = 0;
                        continue;
                }
@@ -1855,7 +2091,7 @@ trace(void)
                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];
+                       char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
                        strcpy(buf, "???");
                        if (WIFSIGNALED(status))
 #ifdef WCOREDUMP
@@ -1871,6 +2107,7 @@ trace(void)
                        if (WIFSTOPPED(status))
                                sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
 #ifdef WIFCONTINUED
+                       /* Should never be seen */
                        if (WIFCONTINUED(status))
                                strcpy(buf, "WIFCONTINUED");
 #endif
@@ -1883,22 +2120,52 @@ trace(void)
                                        [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;
+                               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);
+                               else if (event == PTRACE_EVENT_STOP)
+                                       e = "STOP";
+                               sprintf(evbuf, ",EVENT_%s (%u)", e, event);
                        }
-                       fprintf(stderr, " [wait(0x%04x) = %u] %s%s\n", status, pid, buf, evbuf);
+                       fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
                }
 
                /* Look up 'pid' in our table. */
                tcp = pid2tcb(pid);
 
+               if (!tcp) {
+                       if (!WIFSTOPPED(status)) {
+                               /* This can happen if we inherited
+                                * an unknown child. Example:
+                                * (sleep 1 & exec strace sleep 2)
+                                */
+                               error_msg("Exit of unknown pid %u seen", pid);
+                               continue;
+                       }
+                       if (followfork) {
+                               /* We assume it's a fork/vfork/clone 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.
+                                */
+                               ptrace(PTRACE_CONT, pid, (char *) 0, 0);
+                               error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
+                               continue;
+                       }
+               }
+
+               clear_regs();
+               if (WIFSTOPPED(status))
+                       get_regs(pid);
+
                /* 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
@@ -1914,83 +2181,59 @@ trace(void)
                 * On 2.6 and earlier, it can return garbage.
                 */
                if (event == PTRACE_EVENT_EXEC && os_release >= KERNEL_VERSION(3,0,0)) {
+                       FILE *fp;
+                       struct tcb *execve_thread;
                        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 (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 (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);
+                       if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
+                               goto dont_switch_tcbs;
+                       /* Avoid truncation in pid2tcb() param passing */
+                       if (old_pid <= 0 || old_pid == pid)
+                               goto dont_switch_tcbs;
+                       if ((unsigned long) old_pid > UINT_MAX)
+                               goto dont_switch_tcbs;
+                       execve_thread = pid2tcb(old_pid);
+                       /* It should be !NULL, but I feel paranoid */
+                       if (!execve_thread)
+                               goto dont_switch_tcbs;
+
+                       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 */
                        }
-                       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);
+                       /* 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;
                        }
                }
+ dont_switch_tcbs:
+
+               if (event == PTRACE_EVENT_EXEC) {
+                       if (detach_on_execve && !skip_one_b_execve)
+                               detach(tcp); /* do "-b execve" thingy */
+                       skip_one_b_execve = 0;
+               }
 
                /* Set current output file */
-               outf = tcp->outf;
-               curcol = tcp->curcol;
+               current_tcp = tcp;
 
                if (cflag) {
                        tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
@@ -2001,7 +2244,8 @@ trace(void)
                        if (pid == strace_child)
                                exit_code = 0x100 | WTERMSIG(status);
                        if (cflag != CFLAG_ONLY_STATS
-                           && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) {
+                        && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
+                       ) {
                                printleader(tcp);
 #ifdef WCOREDUMP
                                tprintf("+++ killed by %s %s+++\n",
@@ -2019,7 +2263,8 @@ trace(void)
                if (WIFEXITED(status)) {
                        if (pid == strace_child)
                                exit_code = WEXITSTATUS(status);
-                       if (!cflag /* && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) */ ) {
+                       if (cflag != CFLAG_ONLY_STATS &&
+                           qflag < 2) {
                                printleader(tcp);
                                tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
                                line_ended();
@@ -2046,13 +2291,13 @@ trace(void)
                                if (clearbpt(tcp) < 0) {
                                        /* Pretty fatal */
                                        droptcb(tcp);
-                                       cleanup();
-                                       return -1;
+                                       exit_code = 1;
+                                       return;
                                }
                        }
-                       if (ptrace_setoptions) {
+                       if (!use_seize && ptrace_setoptions) {
                                if (debug_flag)
-                                       fprintf(stderr, "setting opts %x on pid %d\n", ptrace_setoptions, tcp->pid);
+                                       fprintf(stderr, "setting opts 0x%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 */
@@ -2066,8 +2311,8 @@ trace(void)
 
                if (event != 0) {
                        /* Ptrace event */
-#ifdef USE_SEIZE
-                       if (event == PTRACE_EVENT_STOP || event == PTRACE_EVENT_STOP1) {
+#if USE_SEIZE
+                       if (event == PTRACE_EVENT_STOP) {
                                /*
                                 * PTRACE_INTERRUPT-stop or group-stop.
                                 * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
@@ -2103,38 +2348,25 @@ trace(void)
 
                        /* Nonzero (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);
-#ifdef USE_SEIZE
+#if 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 */
-#endif
+                           && !hide_log_until_execve
+                           && (qual_flags[sig] & QUAL_SIGNAL)
+                          ) {
                                printleader(tcp);
                                if (!stopped) {
                                        tprintf("--- %s ", signame(sig));
                                        printsiginfo(&si, verbose(tcp));
-                                       tprintf(PC_FORMAT_STR " ---\n"
-                                               PC_FORMAT_ARG);
+                                       tprints(" ---\n");
                                } else
-                                       tprintf("--- stopped by %s" PC_FORMAT_STR " ---\n",
-                                               signame(sig)
-                                               PC_FORMAT_ARG);
+                                       tprintf("--- stopped by %s ---\n",
+                                               signame(sig));
                                line_ended();
                        }
 
@@ -2143,7 +2375,6 @@ trace(void)
                                goto restart_tracee;
 
                        /* It's group-stop */
-#ifdef USE_SEIZE
                        if (use_seize) {
                                /*
                                 * This ends ptrace-stop, but does *not* end group-stop.
@@ -2151,20 +2382,19 @@ trace(void)
                                 * (that is, process really stops. It used to continue to run).
                                 */
                                if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
-                                       cleanup();
-                                       return -1;
+                                       /* Note: ptrace_restart emitted error message */
+                                       exit_code = 1;
+                                       return;
                                }
-                               tcp->curcol = curcol;
                                continue;
                        }
                        /* We don't have PTRACE_LISTEN support... */
-#endif
                        goto restart_tracee;
                }
 
                /* We handled quick cases, we are permitted to interrupt now. */
                if (interrupted)
-                       return 0;
+                       return;
 
                /* This should be syscall entry or exit.
                 * (Or it still can be that pesky post-execve SIGTRAP!)
@@ -2181,21 +2411,17 @@ trace(void)
                         * 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. */
                if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
-                       tcp->curcol = curcol;
-                       cleanup();
-                       return -1;
+                       /* Note: ptrace_restart emitted error message */
+                       exit_code = 1;
+                       return;
                }
-               tcp->curcol = curcol;
-       }
-       return 0;
+       } /* while (1) */
 }
 
 int
@@ -2204,15 +2430,20 @@ main(int argc, char *argv[])
        init(argc, argv);
 
        /* Run main tracing loop */
-       if (trace() < 0)
-               return 1;
+       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;