]> granicus.if.org Git - strace/blobdiff - strace.c
travis: add x86 musl
[strace] / strace.c
index b714255f41e1b6aeb9af95c4533520932e04d65b..ffb6baec2a6be16a5200d92cc33a9ec0c994fd5d 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>
@@ -44,6 +45,7 @@
 #endif
 
 #include "ptrace.h"
+#include "printsiginfo.h"
 
 /* In some libc, these aren't declared. Do it ourself: */
 extern char **environ;
@@ -136,7 +138,7 @@ 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 exit_code;
 static int strace_child = 0;
 static int strace_tracer_pid = 0;
 
@@ -194,51 +196,68 @@ strerror(int err_no)
 #endif /* HAVE_STERRROR */
 
 static void
-usage(FILE *ofp, int exitval)
+usage(void)
 {
-       fprintf(ofp, "\
-usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\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[df] [-I n] [-e expr]... [-O overhead] [-S sortby]\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\
--c -- count time, calls, and errors for each syscall and report summary\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\
--i -- print instruction pointer at time of syscall\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\
--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, 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\
--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\
+\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 protocol specific information 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\
+"  -k             obtain stack trace between each syscall (experimental)\n\
 "
 #endif
 /* ancient, no one should use it
@@ -248,11 +267,11 @@ usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\n\
 -z -- print only succeeding syscalls\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;
@@ -308,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;
@@ -324,37 +354,32 @@ void perror_msg_and_die(const char *fmt, ...)
        die();
 }
 
-void die_out_of_memory(void)
-{
-       static bool recursed = 0;
-       if (recursed)
-               exit(1);
-       recursed = 1;
-       error_msg_and_die("Out of memory");
-}
-
 static void
 error_opt_arg(int opt, const char *arg)
 {
-       error_msg_and_die("Invalid -%c argument: '%s'", opt, arg);
+       error_msg_and_help("invalid -%c argument: '%s'", opt, arg);
 }
 
-#if USE_SEIZE
+static const char *ptrace_attach_cmd;
+
 static int
 ptrace_attach_or_seize(int pid)
 {
+#if USE_SEIZE
        int r;
        if (!use_seize)
-               return ptrace(PTRACE_ATTACH, pid, 0L, 0L);
+               return ptrace_attach_cmd = "PTRACE_ATTACH",
+                      ptrace(PTRACE_ATTACH, pid, 0L, 0L);
        r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long) ptrace_setoptions);
        if (r)
-               return r;
+               return ptrace_attach_cmd = "PTRACE_SEIZE", r;
        r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
-       return r;
-}
+       return ptrace_attach_cmd = "PTRACE_INTERRUPT", r;
 #else
-# define ptrace_attach_or_seize(pid) ptrace(PTRACE_ATTACH, (pid), 0, 0)
+               return ptrace_attach_cmd = "PTRACE_ATTACH",
+                      ptrace(PTRACE_ATTACH, pid, 0L, 0L);
 #endif
+}
 
 /*
  * Used when we want to unblock stopped traced process.
@@ -380,10 +405,8 @@ ptrace_restart(int op, struct tcb *tcp, int sig)
                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:
         *
@@ -426,7 +449,8 @@ set_cloexec_flag(int fd)
        fcntl(fd, F_SETFD, newflags); /* never fails */
 }
 
-static void kill_save_errno(pid_t pid, int sig)
+static void
+kill_save_errno(pid_t pid, int sig)
 {
        int saved_errno = errno;
 
@@ -670,20 +694,25 @@ newoutf(struct tcb *tcp)
 static void
 expand_tcbtab(void)
 {
-       /* Allocate some more TCBs and expand the table.
+       /* Allocate some (more) TCBs (and expand the table).
           We don't want to relocate the TCBs because our
           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.  */
-       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)
-               die_out_of_memory();
-       tcbtabsize *= 2;
-       tcbtab = newtab;
-       while (i < tcbtabsize)
-               tcbtab[i++] = newtcbs++;
+       unsigned int new_tcbtabsize, alloc_tcbtabsize;
+       struct tcb *newtcbs;
+
+       if (tcbtabsize) {
+               alloc_tcbtabsize = tcbtabsize;
+               new_tcbtabsize = tcbtabsize * 2;
+       } else {
+               new_tcbtabsize = alloc_tcbtabsize = 1;
+       }
+
+       newtcbs = xcalloc(alloc_tcbtabsize, sizeof(newtcbs[0]));
+       tcbtab = xreallocarray(tcbtab, new_tcbtabsize, sizeof(tcbtab[0]));
+       while (tcbtabsize < new_tcbtabsize)
+               tcbtab[tcbtabsize++] = newtcbs++;
 }
 
 static struct tcb *
@@ -711,19 +740,53 @@ alloctcb(int pid)
 
                        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;
                }
        }
        error_msg_and_die("bug in alloctcb");
 }
 
+void *
+get_tcb_priv_data(const struct tcb *tcp)
+{
+       return tcp->_priv_data;
+}
+
+int
+set_tcb_priv_data(struct tcb *tcp, void *const priv_data,
+                 void (*const free_priv_data)(void *))
+{
+       if (tcp->_priv_data)
+               return -1;
+
+       tcp->_free_priv_data = free_priv_data;
+       tcp->_priv_data = priv_data;
+
+       return 0;
+}
+
+void
+free_tcb_priv_data(struct tcb *tcp)
+{
+       if (tcp->_priv_data) {
+               if (tcp->_free_priv_data) {
+                       tcp->_free_priv_data(tcp->_priv_data);
+                       tcp->_free_priv_data = NULL;
+               }
+               tcp->_priv_data = NULL;
+       }
+}
+
 static void
 droptcb(struct tcb *tcp)
 {
        if (tcp->pid == 0)
                return;
 
+       free_tcb_priv_data(tcp);
+
 #ifdef USE_LIBUNWIND
        if (stack_trace_enabled) {
                unwind_tcb_fin(tcp);
@@ -732,7 +795,8 @@ droptcb(struct tcb *tcp)
 
        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 (followfork >= 2) {
@@ -770,10 +834,6 @@ detach(struct tcb *tcp)
         * before detaching.  Arghh.  We go through hoops
         * to make a clean break of things.
         */
-#if defined(SPARC)
-# undef PTRACE_DETACH
-# define PTRACE_DETACH PTRACE_SUNDETACH
-#endif
 
        if (!(tcp->flags & TCB_ATTACHED))
                goto drop;
@@ -861,8 +921,8 @@ detach(struct tcb *tcp)
                }
                sig = WSTOPSIG(status);
                if (debug_flag)
-                       fprintf(stderr, "detach wait: event:%d sig:%d\n",
-                                       (unsigned)status >> 16, sig);
+                       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*/) {
@@ -915,7 +975,7 @@ detach(struct tcb *tcp)
 
  drop:
        if (!qflag && (tcp->flags & TCB_ATTACHED))
-               fprintf(stderr, "Process %u detached\n", tcp->pid);
+               error_msg("Process %u detached", tcp->pid);
 
        droptcb(tcp);
 }
@@ -948,9 +1008,73 @@ process_opt_p_list(char *opt)
        }
 }
 
+static void
+attach_tcb(struct tcb *const tcp)
+{
+       if (ptrace_attach_or_seize(tcp->pid) < 0) {
+               perror_msg("attach: ptrace(%s, %d)",
+                          ptrace_attach_cmd, tcp->pid);
+               droptcb(tcp);
+               return;
+       }
+
+       tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
+       newoutf(tcp);
+       if (debug_flag)
+               error_msg("attach to pid %d (main) succeeded", tcp->pid);
+
+       char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
+       DIR *dir;
+       unsigned int ntid = 0, nerr = 0;
+
+       if (followfork && tcp->pid != strace_child &&
+           sprintf(procdir, "/proc/%d/task", tcp->pid) > 0 &&
+           (dir = opendir(procdir)) != NULL) {
+               struct_dirent *de;
+
+               while ((de = read_dir(dir)) != NULL) {
+                       if (de->d_fileno == 0)
+                               continue;
+
+                       int tid = string_to_uint(de->d_name);
+                       if (tid <= 0 || tid == tcp->pid)
+                               continue;
+
+                       ++ntid;
+                       if (ptrace_attach_or_seize(tid) < 0) {
+                               ++nerr;
+                               if (debug_flag)
+                                       perror_msg("attach: ptrace(%s, %d)",
+                                                  ptrace_attach_cmd, tid);
+                               continue;
+                       }
+                       if (debug_flag)
+                               error_msg("attach to pid %d succeeded", tid);
+
+                       struct tcb *tid_tcp = alloctcb(tid);
+                       tid_tcp->flags |= TCB_ATTACHED | TCB_STARTUP |
+                                         post_attach_sigstop;
+                       newoutf(tid_tcp);
+               }
+
+               closedir(dir);
+       }
+
+       if (!qflag) {
+               if (ntid > nerr)
+                       error_msg("Process %u attached"
+                                 " with %u threads",
+                                 tcp->pid, ntid - nerr + 1);
+               else
+                       error_msg("Process %u attached",
+                                 tcp->pid);
+       }
+}
+
 static void
 startup_attach(void)
 {
+       pid_t parent_pid = strace_tracer_pid;
        unsigned int tcbi;
        struct tcb *tcp;
 
@@ -993,94 +1117,32 @@ startup_attach(void)
                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;
-
-                       sprintf(procdir, "/proc/%d/task", tcp->pid);
-                       dir = opendir(procdir);
-                       if (dir != NULL) {
-                               unsigned int ntid = 0, nerr = 0;
-                               struct_dirent *de;
-
-                               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;
-                                       ++ntid;
-                                       if (ptrace_attach_or_seize(tid) < 0) {
-                                               ++nerr;
-                                               if (debug_flag)
-                                                       fprintf(stderr, "attach to pid %d failed\n", tid);
-                                               continue;
-                                       }
-                                       if (debug_flag)
-                                               fprintf(stderr, "attach to pid %d succeeded\n", tid);
-                                       cur_tcp = tcp;
-                                       if (tid != tcp->pid)
-                                               cur_tcp = alloctcb(tid);
-                                       cur_tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
-                                       newoutf(cur_tcp);
-                               }
-                               closedir(dir);
-                               if (interactive) {
-                                       sigprocmask(SIG_SETMASK, &empty_set, NULL);
-                                       if (interrupted)
-                                               goto ret;
-                                       sigprocmask(SIG_BLOCK, &blocked_set, NULL);
-                               }
-                               ntid -= nerr;
-                               if (ntid == 0) {
-                                       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 (!(tcp->flags & TCB_ATTACHED)) {
-                                       /* -p PID, we failed to attach to PID itself
-                                        * but did attach to some of its sibling threads.
-                                        * Drop PID's tcp.
-                                        */
-                                       droptcb(tcp);
-                               }
-                               continue;
-                       } /* if (opendir worked) */
-               } /* if (-f) */
-               if (ptrace_attach_or_seize(tcp->pid) < 0) {
-                       perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
+               if (tcp->pid == parent_pid || tcp->pid == strace_tracer_pid) {
+                       errno = EPERM;
+                       perror_msg("attach: pid %d", tcp->pid);
                        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);
 
-               if (daemonized_tracer) {
-                       /*
-                        * Make parent go away.
-                        * Also makes grandparent's wait() unblock.
-                        */
-                       kill(getppid(), SIGKILL);
-               }
+               attach_tcb(tcp);
 
-               if (!qflag)
-                       fprintf(stderr,
-                               "Process %u attached\n",
-                               tcp->pid);
+               if (interactive) {
+                       sigprocmask(SIG_SETMASK, &empty_set, NULL);
+                       if (interrupted)
+                               goto ret;
+                       sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+               }
        } /* for each tcbtab[] */
 
+       if (daemonized_tracer) {
+               /*
+                * Make parent go away.
+                * Also makes grandparent's wait() unblock.
+                */
+               kill(parent_pid, SIGKILL);
+               strace_child = 0;
+       }
+
  ret:
        if (interactive)
                sigprocmask(SIG_SETMASK, &empty_set, NULL);
@@ -1097,7 +1159,8 @@ struct exec_params {
        char *pathname;
 };
 static struct exec_params params_for_tracee;
-static void __attribute__ ((noinline, noreturn))
+
+static void ATTRIBUTE_NOINLINE ATTRIBUTE_NORETURN
 exec_or_die(void)
 {
        struct exec_params *params = &params_for_tracee;
@@ -1152,6 +1215,70 @@ exec_or_die(void)
        perror_msg_and_die("exec");
 }
 
+/*
+ * Open a dummy descriptor for use as a placeholder.
+ * The descriptor is O_RDONLY with FD_CLOEXEC flag set.
+ * A read attempt from such descriptor ends with EOF,
+ * a write attempt is rejected with EBADF.
+ */
+static int
+open_dummy_desc(void)
+{
+       int fds[2];
+
+       if (pipe(fds))
+               perror_msg_and_die("pipe");
+       close(fds[1]);
+       set_cloexec_flag(fds[0]);
+       return fds[0];
+}
+
+/* placeholder fds status for stdin and stdout */
+static bool fd_is_placeholder[2];
+
+/*
+ * Ensure that all standard file descriptors are open by opening placeholder
+ * file descriptors for those standard file descriptors that are not open.
+ *
+ * The information which descriptors have been made open is saved
+ * in fd_is_placeholder for later use.
+ */
+static void
+ensure_standard_fds_opened(void)
+{
+       int fd;
+
+       while ((fd = open_dummy_desc()) <= 2) {
+               if (fd == 2)
+                       break;
+               fd_is_placeholder[fd] = true;
+       }
+
+       if (fd > 2)
+               close(fd);
+}
+
+/*
+ * Redirect stdin and stdout unless they have been opened earlier
+ * by ensure_standard_fds_opened as placeholders.
+ */
+static void
+redirect_standard_fds(void)
+{
+       int i;
+
+       /*
+        * It might be a good idea to redirect stderr as well,
+        * but we sometimes need to print error messages.
+        */
+       for (i = 0; i <= 1; ++i) {
+               if (!fd_is_placeholder[i]) {
+                       close(i);
+                       open_dummy_desc();
+               }
+       }
+}
+
 static void
 startup_child(char **argv)
 {
@@ -1232,7 +1359,7 @@ startup_child(char **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;
+       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)
@@ -1280,7 +1407,8 @@ startup_child(char **argv)
 
                        if (ptrace_attach_or_seize(pid)) {
                                kill_save_errno(pid, SIGKILL);
-                               perror_msg_and_die("Can't attach to %d", pid);
+                               perror_msg_and_die("attach: ptrace(%s, %d)",
+                                                  ptrace_attach_cmd, pid);
                        }
                        if (!NOMMU_SYSTEM)
                                kill(pid, SIGCONT);
@@ -1293,11 +1421,10 @@ startup_child(char **argv)
                newoutf(tcp);
        }
        else {
-               /* With -D, we are *child* here, IOW: different pid. Fetch it: */
+               /* With -D, we are *child* here, the tracee is our parent. */
+               strace_child = strace_tracer_pid;
                strace_tracer_pid = getpid();
-               /* The tracee is our parent: */
-               pid = getppid();
-               alloctcb(pid);
+               alloctcb(strace_child);
                /* attaching will be done later, by startup_attach */
                /* note: we don't do newoutf(tcp) here either! */
 
@@ -1322,6 +1449,18 @@ startup_child(char **argv)
                 * to create a genuine separate stack and execute on it.
                 */
        }
+       /*
+        * A case where straced process is part of a pipe:
+        * { sleep 1; yes | head -n99999; } | strace -o/dev/null sh -c 'exec <&-; sleep 9'
+        * If strace won't close its fd#0, closing it in tracee is not enough:
+        * the pipe is still open, it has a reader. Thus, "head" will not get its
+        * SIGPIPE at once, on the first write.
+        *
+        * Preventing it by redirecting strace's stdin/out.
+        * (Don't leave fds 0 and 1 closed, this is bad practice: future opens
+        * will reuse them, unexpectedly making a newly opened object "stdin").
+        */
+       redirect_standard_fds();
 }
 
 #if USE_SEIZE
@@ -1352,7 +1491,7 @@ test_ptrace_seize(void)
        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);
@@ -1420,13 +1559,11 @@ 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, i;
        int optF = 0;
-       unsigned int tcbi;
        struct sigaction sa;
 
        progname = argv[0] ? argv[0] : "strace";
@@ -1442,17 +1579,6 @@ init(int argc, char *argv[])
 
        os_release = get_os_release();
 
-       /* 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 (tcbi = 0; tcbi < tcbtabsize; tcbi++)
-               tcbtab[tcbi] = tcp++;
-
        shared_log = stderr;
        set_sortby(DEFAULT_SORTBY);
        set_personality(DEFAULT_PERSONALITY);
@@ -1479,13 +1605,13 @@ init(int argc, char *argv[])
                        break;
                case 'c':
                        if (cflag == CFLAG_BOTH) {
-                               error_msg_and_die("-c and -C are mutually exclusive");
+                               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");
+                               error_msg_and_help("-c and -C are mutually exclusive");
                        }
                        cflag = CFLAG_BOTH;
                        break;
@@ -1502,7 +1628,7 @@ init(int argc, char *argv[])
                        followfork++;
                        break;
                case 'h':
-                       usage(stdout, 0);
+                       usage();
                        break;
                case 'i':
                        iflag = 1;
@@ -1547,7 +1673,7 @@ init(int argc, char *argv[])
                        qualify(optarg);
                        break;
                case 'o':
-                       outfname = strdup(optarg);
+                       outfname = xstrdup(optarg);
                        break;
                case 'O':
                        i = string_to_uint(optarg);
@@ -1571,7 +1697,7 @@ init(int argc, char *argv[])
                        set_sortby(optarg);
                        break;
                case 'u':
-                       username = strdup(optarg);
+                       username = xstrdup(optarg);
                        break;
 #ifdef USE_LIBUNWIND
                case 'k':
@@ -1588,36 +1714,34 @@ init(int argc, char *argv[])
                                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");
+       if (!argv[0] && daemonized_tracer) {
+               error_msg_and_help("PROG [ARGS] must be specified with -D");
        }
 
        if (!followfork)
                followfork = optF;
 
        if (followfork >= 2 && cflag) {
-               error_msg_and_die("(-c or -C) and -ff are mutually exclusive");
+               error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
        }
 
        if (count_wallclock && !cflag) {
-               error_msg_and_die("-w must be given with (-c or -C)");
+               error_msg_and_help("-w must be given with (-c or -C)");
        }
 
        if (cflag == CFLAG_ONLY_STATS) {
@@ -1666,9 +1790,22 @@ init(int argc, char *argv[])
                                     PTRACE_O_TRACEFORK |
                                     PTRACE_O_TRACEVFORK;
        if (debug_flag)
-               fprintf(stderr, "ptrace_setoptions = %#x\n", ptrace_setoptions);
+               error_msg("ptrace_setoptions = %#x", ptrace_setoptions);
        test_ptrace_seize();
 
+       /*
+        * Is something weird with our stdin and/or stdout -
+        * for example, may they be not open? In this case,
+        * ensure that none of the future opens uses them.
+        *
+        * This was seen in the wild when /proc/sys/kernel/core_pattern
+        * was set to "|/bin/strace -o/tmp/LOG PROG":
+        * kernel runs coredump helper with fd#0 open but fd#1 closed (!),
+        * therefore LOG gets opened to fd#1, and fd#1 is closed by
+        * "don't hold up stdin/out open" code soon after.
+        */
+       ensure_standard_fds_opened();
+
        /* Check if they want to redirect the output. */
        if (outfname) {
                /* See if they want to pipe the output. */
@@ -1678,7 +1815,7 @@ 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");
+                               error_msg_and_help("piping the output and -ff are mutually exclusive");
                        shared_log = strace_popen(outfname + 1);
                }
                else if (followfork < 2)
@@ -1690,23 +1827,22 @@ init(int argc, char *argv[])
        }
 
        if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
-               char *buf = malloc(BUFSIZ);
-               if (!buf)
-                       die_out_of_memory();
+               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;
 
        /* argv[0]      -pPID   -oFILE  Default interactive setting
-        * yes          0       0       INTR_WHILE_WAIT
+        * yes          *       0       INTR_WHILE_WAIT
         * no           1       0       INTR_WHILE_WAIT
-        * yes          0       1       INTR_NEVER
+        * yes          *       1       INTR_NEVER
         * no           1       1       INTR_WHILE_WAIT
         */
 
@@ -1799,8 +1935,7 @@ cleanup(void)
                if (!tcp->pid)
                        continue;
                if (debug_flag)
-                       fprintf(stderr,
-                               "cleanup: looking at pid %u\n", tcp->pid);
+                       error_msg("cleanup: looking at pid %u", tcp->pid);
                if (tcp->pid == strace_child) {
                        kill(tcp->pid, SIGCONT);
                        kill(tcp->pid, fatal_sig);
@@ -1861,7 +1996,7 @@ print_debug_info(const int pid, int status)
                        e = "STOP";
                sprintf(evbuf, ",EVENT_%s (%u)", e, event);
        }
-       fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
+       error_msg("[wait(0x%06x) = %u] %s%s", status, pid, buf, evbuf);
 }
 
 static struct tcb *
@@ -1886,13 +2021,13 @@ maybe_allocate_tcb(const int pid, int status)
                tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
                newoutf(tcp);
                if (!qflag)
-                       fprintf(stderr, "Process %d attached\n", pid);
+                       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);
+               ptrace(PTRACE_CONT, pid, NULL, 0);
                error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
                return NULL;
        }
@@ -1997,7 +2132,7 @@ print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
                printleader(tcp);
                if (si) {
                        tprintf("--- %s ", signame(sig));
-                       printsiginfo(si, verbose(tcp));
+                       printsiginfo(si);
                        tprints(" ---\n");
                } else
                        tprintf("--- stopped by %s ---\n", signame(sig));
@@ -2005,19 +2140,18 @@ print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
        }
 }
 
-static bool
+static void
 startup_tcb(struct tcb *tcp)
 {
        if (debug_flag)
-               fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n",
-                       tcp->pid);
+               error_msg("pid %d has TCB_STARTUP, initializing it", tcp->pid);
 
        tcp->flags &= ~TCB_STARTUP;
 
        if (!use_seize) {
                if (debug_flag)
-                       fprintf(stderr, "setting opts 0x%x on pid %d\n",
-                               ptrace_setoptions, tcp->pid);
+                       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 */
@@ -2025,8 +2159,6 @@ startup_tcb(struct tcb *tcp)
                        }
                }
        }
-
-       return true;
 }
 
 /* Returns true iff the main trace loop has to continue. */
@@ -2045,8 +2177,26 @@ trace(void)
        if (interrupted)
                return false;
 
-       if (popen_pid != 0 && nprocs == 0)
-               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);
@@ -2151,8 +2301,9 @@ trace(void)
 
        /* Is this the very first time we see this tracee stopped? */
        if (tcp->flags & TCB_STARTUP) {
-               if (!startup_tcb(tcp))
-                       return false;
+               startup_tcb(tcp);
+               if (get_scno(tcp) == 1)
+                       tcp->s_prev_ent = tcp->s_ent;
        }
 
        sig = WSTOPSIG(status);
@@ -2187,13 +2338,13 @@ trace(void)
         */
        if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
                if (debug_flag)
-                       fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
+                       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;
+               siginfo_t si = {};
 
                /*
                 * True if tracee is stopped by signal
@@ -2270,23 +2421,8 @@ main(int argc, char *argv[])
 {
        init(argc, argv);
 
-       /*
-        * Run main tracing loop.
-        *
-        * 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...)
-        */
+       exit_code = !nprocs;
+
        while (trace())
                ;