]> granicus.if.org Git - strace/blobdiff - strace.c
tests: move F_OFD_SETLK* checks from fcntl64.c to fcntl-common.c
[strace] / strace.c
index 07d5adac903bd2926687a8552bb04ce0d218d845..4b3748571ec69480049d12100bb46413b3f6de30 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -3,7 +3,7 @@
  * Copyright (c) 1993 Branko Lankester <branko@hacktic.nl>
  * Copyright (c) 1993, 1994, 1995, 1996 Rick Sladkey <jrs@world.std.com>
  * Copyright (c) 1996-1999 Wichert Akkerman <wichert@cistron.nl>
- * Copyright (c) 1999-2017 The strace developers.
+ * Copyright (c) 1999-2018 The strace developers.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 #include "defs.h"
 #include <stdarg.h>
-#include <sys/param.h>
+#include <limits.h>
 #include <fcntl.h>
+#include "ptrace.h"
 #include <signal.h>
 #include <sys/resource.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
+#ifdef HAVE_PATHS_H
+# include <paths.h>
+#endif
 #include <pwd.h>
 #include <grp.h>
 #include <dirent.h>
+#include <locale.h>
 #include <sys/utsname.h>
 #ifdef HAVE_PRCTL
 # include <sys/prctl.h>
 #endif
 #include <asm/unistd.h>
 
+#include "largefile_wrappers.h"
+#include "mmap_cache.h"
+#include "number_set.h"
 #include "scno.h"
-#include "ptrace.h"
 #include "printsiginfo.h"
+#include "trace_event.h"
+#include "xstring.h"
+#include "delay.h"
 
 /* In some libc, these aren't declared. Do it ourself: */
 extern char **environ;
 extern int optind;
 extern char *optarg;
 
-#ifdef USE_LIBUNWIND
+#ifdef ENABLE_STACKTRACE
 /* if this is true do the stack trace for every system call */
-bool stack_trace_enabled = false;
+bool stack_trace_enabled;
 #endif
 
 #define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
@@ -74,27 +84,27 @@ bool stack_trace_enabled = false;
 const unsigned int syscall_trap_sig = SIGTRAP | 0x80;
 
 cflag_t cflag = CFLAG_NONE;
-unsigned int followfork = 0;
+unsigned int followfork;
 unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC
                                 | PTRACE_O_TRACEEXIT;
-unsigned int xflag = 0;
-bool debug_flag = 0;
-bool Tflag = 0;
-bool iflag = 0;
-bool count_wallclock = 0;
-unsigned int qflag = 0;
-static unsigned int tflag = 0;
-static bool rflag = 0;
-static bool print_pid_pfx = 0;
+unsigned int xflag;
+bool debug_flag;
+bool Tflag;
+bool iflag;
+bool count_wallclock;
+unsigned int qflag;
+static unsigned int tflag;
+static bool rflag;
+static bool print_pid_pfx;
 
 /* -I n */
 enum {
-    INTR_NOT_SET        = 0,
-    INTR_ANYWHERE       = 1, /* don't block/ignore any signals */
-    INTR_WHILE_WAIT     = 2, /* block fatal signals while decoding syscall. default */
-    INTR_NEVER          = 3, /* block fatal signals. default if '-o FILE PROG' */
-    INTR_BLOCK_TSTP_TOO = 4, /* block fatal signals and SIGTSTP (^Z) */
-    NUM_INTR_OPTS
+       INTR_NOT_SET        = 0,
+       INTR_ANYWHERE       = 1, /* don't block/ignore any signals */
+       INTR_WHILE_WAIT     = 2, /* block fatal signals while decoding syscall. default */
+       INTR_NEVER          = 3, /* block fatal signals. default if '-o FILE PROG' */
+       INTR_BLOCK_TSTP_TOO = 4, /* block fatal signals and SIGTSTP (^Z) */
+       NUM_INTR_OPTS
 };
 static int opt_intr;
 /* We play with signal mask only if this mode is active: */
@@ -112,29 +122,24 @@ static int opt_intr;
  * wait() etc. Without -D, strace process gets lodged in between,
  * disrupting parent<->child link.
  */
-static bool daemonized_tracer = 0;
+static bool daemonized_tracer;
 
-#if USE_SEIZE
 static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
-# define use_seize (post_attach_sigstop == 0)
-#else
-# define post_attach_sigstop TCB_IGNORE_ONE_SIGSTOP
-# define use_seize 0
-#endif
+#define use_seize (post_attach_sigstop == 0)
 
 /* Sometimes we want to print only succeeding syscalls. */
-bool not_failing_only = 0;
+bool not_failing_only;
 
 /* Show path associated with fd arguments */
-unsigned int show_fd_path = 0;
+unsigned int show_fd_path;
 
-static bool detach_on_execve = 0;
+static bool detach_on_execve;
 
 static int exit_code;
-static int strace_child = 0;
-static int strace_tracer_pid = 0;
+static int strace_child;
+static int strace_tracer_pid;
 
-static char *username = NULL;
+static const char *username;
 static uid_t run_uid;
 static gid_t run_gid;
 
@@ -142,30 +147,40 @@ unsigned int max_strlen = DEFAULT_STRLEN;
 static int acolumn = DEFAULT_ACOLUMN;
 static char *acolumn_spaces;
 
-static char *outfname = NULL;
+/* Default output style for xlat entities */
+enum xlat_style xlat_verbosity = XLAT_STYLE_ABBREV;
+
+static const char *outfname;
 /* If -ff, points to stderr. Else, it's our common output log */
 static FILE *shared_log;
+static bool open_append;
 
-struct tcb *printing_tcp = NULL;
+struct tcb *printing_tcp;
 static struct tcb *current_tcp;
 
 static struct tcb **tcbtab;
-static unsigned int nprocs, tcbtabsize;
-static const char *progname;
+static unsigned int nprocs;
+static size_t tcbtabsize;
+
+#ifndef HAVE_PROGRAM_INVOCATION_NAME
+char *program_invocation_name;
+#endif
 
 unsigned os_release; /* generated from uname()'s u.release */
 
 static void detach(struct tcb *tcp);
 static void cleanup(void);
 static void interrupt(int sig);
-static sigset_t start_set, blocked_set;
 
 #ifdef HAVE_SIG_ATOMIC_T
-static volatile sig_atomic_t interrupted;
+static volatile sig_atomic_t interrupted, restart_failed;
 #else
-static volatile int interrupted;
+static volatile int interrupted, restart_failed;
 #endif
 
+static sigset_t timer_set;
+static void timer_sighandler(int);
+
 #ifndef HAVE_STRERROR
 
 #if !HAVE_DECL_SYS_ERRLIST
@@ -179,7 +194,7 @@ strerror(int err_no)
        static char buf[sizeof("Unknown error %d") + sizeof(int)*3];
 
        if (err_no < 1 || err_no >= sys_nerr) {
-               sprintf(buf, "Unknown error %d", err_no);
+               xsprintf(buf, "Unknown error %d", err_no);
                return buf;
        }
        return sys_errlist[err_no];
@@ -190,11 +205,36 @@ strerror(int err_no)
 static void
 print_version(void)
 {
+       static const char features[] =
+#ifdef ENABLE_STACKTRACE
+               " stack-trace=" USE_UNWINDER
+#endif
+#ifdef USE_DEMANGLE
+               " stack-demangle"
+#endif
+#if SUPPORTED_PERSONALITIES > 1
+# if defined HAVE_M32_MPERS
+               " m32-mpers"
+# else
+               " no-m32-mpers"
+# endif
+#endif /* SUPPORTED_PERSONALITIES > 1 */
+#if SUPPORTED_PERSONALITIES > 2
+# if defined HAVE_MX32_MPERS
+               " mx32-mpers"
+# else
+               " no-mx32-mpers"
+# endif
+#endif /* SUPPORTED_PERSONALITIES > 2 */
+               "";
+
        printf("%s -- version %s\n"
               "Copyright (c) 1991-%s The strace developers <%s>.\n"
               "This is free software; see the source for copying conditions.  There is NO\n"
               "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
               PACKAGE_NAME, PACKAGE_VERSION, COPYRIGHT_YEAR, PACKAGE_URL);
+       printf("\nOptional features enabled:%s\n",
+              features[0] ? features : " (none)");
 }
 
 static void
@@ -211,9 +251,9 @@ Output format:\n\
   -a column      alignment COLUMN for printing syscall results (default %d)\n\
   -i             print instruction pointer at time of syscall\n\
 "
-#ifdef USE_LIBUNWIND
+#ifdef ENABLE_STACKTRACE
 "\
-  -k             obtain stack trace between each syscall (experimental)\n\
+  -k             obtain stack trace between each syscall\n\
 "
 #endif
 "\
@@ -275,88 +315,16 @@ Miscellaneous:\n\
        exit(0);
 }
 
-static void ATTRIBUTE_NORETURN
+void ATTRIBUTE_NORETURN
 die(void)
 {
        if (strace_tracer_pid == getpid()) {
                cflag = 0;
                cleanup();
+               exit(1);
        }
-       exit(1);
-}
-
-static void verror_msg(int err_no, const char *fmt, va_list p)
-{
-       char *msg;
-
-       fflush(NULL);
-
-       /* We want to print entire message with single fprintf to ensure
-        * message integrity if stderr is shared with other programs.
-        * Thus we use vasprintf + single fprintf.
-        */
-       msg = NULL;
-       if (vasprintf(&msg, fmt, p) >= 0) {
-               if (err_no)
-                       fprintf(stderr, "%s: %s: %s\n", progname, msg, strerror(err_no));
-               else
-                       fprintf(stderr, "%s: %s\n", progname, msg);
-               free(msg);
-       } else {
-               /* malloc in vasprintf failed, try it without malloc */
-               fprintf(stderr, "%s: ", progname);
-               vfprintf(stderr, fmt, p);
-               if (err_no)
-                       fprintf(stderr, ": %s\n", strerror(err_no));
-               else
-                       putc('\n', stderr);
-       }
-       /* We don't switch stderr to buffered, thus fprintf(stderr)
-        * always flushes its output and this is not necessary: */
-       /* fflush(stderr); */
-}
-
-void error_msg(const char *fmt, ...)
-{
-       va_list p;
-       va_start(p, fmt);
-       verror_msg(0, fmt, p);
-       va_end(p);
-}
 
-void error_msg_and_die(const char *fmt, ...)
-{
-       va_list p;
-       va_start(p, fmt);
-       verror_msg(0, fmt, p);
-       die();
-}
-
-void 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;
-       va_start(p, fmt);
-       verror_msg(errno, fmt, p);
-       va_end(p);
-}
-
-void perror_msg_and_die(const char *fmt, ...)
-{
-       va_list p;
-       va_start(p, fmt);
-       verror_msg(errno, fmt, p);
-       die();
+       _exit(1);
 }
 
 static void
@@ -370,7 +338,6 @@ static const char *ptrace_attach_cmd;
 static int
 ptrace_attach_or_seize(int pid)
 {
-#if USE_SEIZE
        int r;
        if (!use_seize)
                return ptrace_attach_cmd = "PTRACE_ATTACH",
@@ -380,10 +347,6 @@ ptrace_attach_or_seize(int pid)
                return ptrace_attach_cmd = "PTRACE_SEIZE", r;
        r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
        return ptrace_attach_cmd = "PTRACE_INTERRUPT", r;
-#else
-               return ptrace_attach_cmd = "PTRACE_ATTACH",
-                      ptrace(PTRACE_ATTACH, pid, 0L, 0L);
-#endif
 }
 
 /*
@@ -458,7 +421,8 @@ set_cloexec_flag(int fd)
        if (flags == newflags)
                return;
 
-       fcntl(fd, F_SETFD, newflags); /* never fails */
+       if (fcntl(fd, F_SETFD, newflags)) /* never fails */
+               perror_msg_and_die("fcntl(%d, F_SETFD, %#x)", fd, newflags);
 }
 
 static void
@@ -484,35 +448,13 @@ swap_uid(void)
        }
 }
 
-#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 *
 strace_fopen(const char *path)
 {
        FILE *fp;
 
        swap_uid();
-       fp = fopen_for_output(path, "w");
+       fp = fopen_stream(path, open_append ? "a" : "w");
        if (!fp)
                perror_msg_and_die("Can't fopen '%s'", path);
        swap_uid();
@@ -520,7 +462,7 @@ strace_fopen(const char *path)
        return fp;
 }
 
-static int popen_pid = 0;
+static int popen_pid;
 
 #ifndef _PATH_BSHELL
 # define _PATH_BSHELL "/bin/sh"
@@ -566,10 +508,23 @@ strace_popen(const char *command)
        swap_uid();
        fp = fdopen(fds[1], "w");
        if (!fp)
-               die_out_of_memory();
+               perror_msg_and_die("fdopen");
        return fp;
 }
 
+static void
+outf_perror(const struct tcb * const tcp)
+{
+       if (tcp->outf == stderr)
+               return;
+
+       /* This is ugly, but we don't store separate file names */
+       if (followfork >= 2)
+               perror_msg("%s.%u", outfname, tcp->pid);
+       else
+               perror_msg("%s", outfname);
+}
+
 ATTRIBUTE_FORMAT((printf, 1, 0))
 static void
 tvprintf(const char *const fmt, va_list args)
@@ -577,8 +532,8 @@ tvprintf(const char *const fmt, va_list args)
        if (current_tcp) {
                int n = vfprintf(current_tcp->outf, fmt, args);
                if (n < 0) {
-                       if (current_tcp->outf != stderr)
-                               perror_msg("%s", outfname);
+                       /* very unlikely due to vfprintf buffering */
+                       outf_perror(current_tcp);
                } else
                        current_tcp->curcol += n;
        }
@@ -606,8 +561,8 @@ tprints(const char *str)
                        current_tcp->curcol += strlen(str);
                        return;
                }
-               if (current_tcp->outf != stderr)
-                       perror_msg("%s", outfname);
+               /* very unlikely due to fputs_unlocked buffering */
+               outf_perror(current_tcp);
        }
 }
 
@@ -632,12 +587,19 @@ tprintf_comment(const char *fmt, ...)
        va_end(args);
 }
 
+static void
+flush_tcp_output(const struct tcb *const tcp)
+{
+       if (fflush(tcp->outf))
+               outf_perror(tcp);
+}
+
 void
 line_ended(void)
 {
        if (current_tcp) {
                current_tcp->curcol = 0;
-               fflush(current_tcp->outf);
+               flush_tcp_output(current_tcp);
        }
        if (printing_tcp) {
                printing_tcp->curcol = 0;
@@ -645,6 +607,16 @@ line_ended(void)
        }
 }
 
+void
+set_current_tcp(const struct tcb *tcp)
+{
+       current_tcp = (struct tcb *) tcp;
+
+       /* Sync current_personality and stuff */
+       if (current_tcp)
+               set_personality(current_tcp->currpers);
+}
+
 void
 printleader(struct tcb *tcp)
 {
@@ -655,7 +627,7 @@ printleader(struct tcb *tcp)
                printing_tcp = tcp;
 
        if (printing_tcp) {
-               current_tcp = printing_tcp;
+               set_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
@@ -669,7 +641,7 @@ printleader(struct tcb *tcp)
        }
 
        printing_tcp = tcp;
-       current_tcp = tcp;
+       set_current_tcp(tcp);
        current_tcp->curcol = 0;
 
        if (print_pid_pfx)
@@ -678,32 +650,47 @@ printleader(struct tcb *tcp)
                tprintf("[pid %5u] ", tcp->pid);
 
        if (tflag) {
-               char str[sizeof("HH:MM:SS")];
-               struct timeval tv, dtv;
-               static struct timeval otv;
-
-               gettimeofday(&tv, NULL);
-               if (rflag) {
-                       if (otv.tv_sec == 0)
-                               otv = tv;
-                       tv_sub(&dtv, &tv, &otv);
-                       tprintf("%6ld.%06ld ",
-                               (long) dtv.tv_sec, (long) dtv.tv_usec);
-                       otv = tv;
-               }
-               else if (tflag > 2) {
-                       tprintf("%ld.%06ld ",
-                               (long) tv.tv_sec, (long) tv.tv_usec);
-               }
-               else {
-                       time_t local = tv.tv_sec;
-                       strftime(str, sizeof(str), "%T", localtime(&local));
+               struct timespec ts;
+               clock_gettime(CLOCK_REALTIME, &ts);
+
+               if (tflag > 2) {
+                       tprintf("%lld.%06ld ",
+                               (long long) ts.tv_sec, (long) ts.tv_nsec / 1000);
+               } else {
+                       time_t local = ts.tv_sec;
+                       char str[MAX(sizeof("HH:MM:SS"), sizeof(ts.tv_sec) * 3)];
+                       struct tm *tm = localtime(&local);
+
+                       if (tm)
+                               strftime(str, sizeof(str), "%T", tm);
+                       else
+                               xsprintf(str, "%lld", (long long) local);
                        if (tflag > 1)
-                               tprintf("%s.%06ld ", str, (long) tv.tv_usec);
+                               tprintf("%s.%06ld ",
+                                       str, (long) ts.tv_nsec / 1000);
                        else
                                tprintf("%s ", str);
                }
        }
+
+       if (rflag) {
+               struct timespec ts;
+               clock_gettime(CLOCK_MONOTONIC, &ts);
+
+               static struct timespec ots;
+               if (ots.tv_sec == 0)
+                       ots = ts;
+
+               struct timespec dts;
+               ts_sub(&dts, &ts, &ots);
+               ots = ts;
+
+               tprintf("%s%6ld.%06ld%s ",
+                       tflag ? "(+" : "",
+                       (long) dts.tv_sec, (long) dts.tv_nsec / 1000,
+                       tflag ? ")" : "");
+       }
+
        if (iflag)
                print_pc(tcp);
 }
@@ -720,14 +707,20 @@ tabto(void)
  * may create bogus empty FILE.<nonexistant_pid>, and then die.
  */
 static void
-newoutf(struct tcb *tcp)
+after_successful_attach(struct tcb *tcp, const unsigned int flags)
 {
+       tcp->flags |= TCB_ATTACHED | TCB_STARTUP | flags;
        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);
+               char name[PATH_MAX];
+               xsprintf(name, "%s.%u", outfname, tcp->pid);
                tcp->outf = strace_fopen(name);
        }
+
+#ifdef ENABLE_STACKTRACE
+       if (stack_trace_enabled)
+               unwind_tcb_init(tcp);
+#endif
 }
 
 static void
@@ -738,20 +731,18 @@ 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.  */
-       unsigned int new_tcbtabsize, alloc_tcbtabsize;
+       size_t old_tcbtabsize;
        struct tcb *newtcbs;
+       struct tcb **tcb_ptr;
 
-       if (tcbtabsize) {
-               alloc_tcbtabsize = tcbtabsize;
-               new_tcbtabsize = tcbtabsize * 2;
-       } else {
-               new_tcbtabsize = alloc_tcbtabsize = 1;
-       }
+       old_tcbtabsize = tcbtabsize;
 
-       newtcbs = xcalloc(alloc_tcbtabsize, sizeof(newtcbs[0]));
-       tcbtab = xreallocarray(tcbtab, new_tcbtabsize, sizeof(tcbtab[0]));
-       while (tcbtabsize < new_tcbtabsize)
-               tcbtab[tcbtabsize++] = newtcbs++;
+       tcbtab = xgrowarray(tcbtab, &tcbtabsize, sizeof(tcbtab[0]));
+       newtcbs = xcalloc(tcbtabsize - old_tcbtabsize, sizeof(newtcbs[0]));
+
+       for (tcb_ptr = tcbtab + old_tcbtabsize;
+           tcb_ptr < tcbtab + tcbtabsize; tcb_ptr++, newtcbs++)
+               *tcb_ptr = newtcbs;
 }
 
 static struct tcb *
@@ -771,16 +762,9 @@ alloctcb(int pid)
 #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)
-                               error_msg("new tcb for pid %d, active tcbs:%d",
-                                         tcp->pid, nprocs);
+                       debug_msg("new tcb for pid %d, active tcbs:%d",
+                                 tcp->pid, nprocs);
                        return tcp;
                }
        }
@@ -830,16 +814,16 @@ droptcb(struct tcb *tcp)
 
        free_tcb_priv_data(tcp);
 
-#ifdef USE_LIBUNWIND
-       if (stack_trace_enabled) {
+#ifdef ENABLE_STACKTRACE
+       if (stack_trace_enabled)
                unwind_tcb_fin(tcp);
-       }
 #endif
 
+       if (tcp->mmap_cache)
+               tcp->mmap_cache->free_fn(tcp, __func__);
+
        nprocs--;
-       if (debug_flag)
-               error_msg("dropped tcb for pid %d, %d remain",
-                         tcp->pid, nprocs);
+       debug_msg("dropped tcb for pid %d, %d remain", tcp->pid, nprocs);
 
        if (tcp->outf) {
                if (followfork >= 2) {
@@ -849,12 +833,12 @@ droptcb(struct tcb *tcp)
                } else {
                        if (printing_tcp == tcp && tcp->curcol != 0)
                                fprintf(tcp->outf, " <detached ...>\n");
-                       fflush(tcp->outf);
+                       flush_tcp_output(tcp);
                }
        }
 
        if (current_tcp == tcp)
-               current_tcp = NULL;
+               set_current_tcp(NULL);
        if (printing_tcp == tcp)
                printing_tcp = NULL;
 
@@ -895,14 +879,14 @@ detach(struct tcb *tcp)
        }
        if (errno != ESRCH) {
                /* Shouldn't happen. */
-               perror_msg("detach: ptrace(PTRACE_DETACH,%u)", tcp->pid);
+               perror_func_msg("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);
+                       perror_func_msg("tkill(%u,0)", tcp->pid);
                /* else: process doesn't exist. */
                goto drop;
        }
@@ -918,14 +902,13 @@ detach(struct tcb *tcp)
                if (!error)
                        goto wait_loop;
                if (errno != ESRCH)
-                       perror_msg("detach: ptrace(PTRACE_INTERRUPT,%u)", tcp->pid);
-       }
-       else {
+                       perror_func_msg("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);
+                       perror_func_msg("tkill(%u,SIGSTOP)", tcp->pid);
        }
        /* Either process doesn't exist, or some weird error. */
        goto drop;
@@ -946,7 +929,7 @@ detach(struct tcb *tcp)
                         * ^^^  WRONG! We expect this PID to exist,
                         * and want to emit a message otherwise:
                         */
-                       perror_msg("detach: waitpid(%u)", tcp->pid);
+                       perror_func_msg("waitpid(%u)", tcp->pid);
                        break;
                }
                if (!WIFSTOPPED(status)) {
@@ -963,9 +946,8 @@ detach(struct tcb *tcp)
                        break;
                }
                sig = WSTOPSIG(status);
-               if (debug_flag)
-                       error_msg("detach wait: event:%d sig:%d",
-                                 (unsigned)status >> 16, sig);
+               debug_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*/) {
@@ -1032,7 +1014,7 @@ process_opt_p_list(char *opt)
                 * pidof uses space as delim, pgrep uses newline. :(
                 */
                int pid;
-               char *delim = opt + strcspn(opt, ", \n\t");
+               char *delim = opt + strcspn(opt, "\n\t ,");
                char c = *delim;
 
                *delim = '\0';
@@ -1061,17 +1043,16 @@ attach_tcb(struct tcb *const 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);
+       after_successful_attach(tcp, TCB_GRABBED | post_attach_sigstop);
+       debug_msg("attach to pid %d (main) succeeded", tcp->pid);
 
-       char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
+       static const char task_path[] = "/proc/%d/task";
+       char procdir[sizeof(task_path) + 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 &&
+           xsprintf(procdir, task_path, tcp->pid) > 0 &&
            (dir = opendir(procdir)) != NULL) {
                struct_dirent *de;
 
@@ -1086,18 +1067,14 @@ attach_tcb(struct tcb *const tcp)
                        ++ntid;
                        if (ptrace_attach_or_seize(tid) < 0) {
                                ++nerr;
-                               if (debug_flag)
-                                       perror_msg("attach: ptrace(%s, %d)",
-                                                  ptrace_attach_cmd, tid);
+                               debug_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);
+                       after_successful_attach(alloctcb(tid),
+                                               TCB_GRABBED | post_attach_sigstop);
+                       debug_msg("attach to pid %d succeeded", tid);
                }
 
                closedir(dir);
@@ -1121,20 +1098,11 @@ startup_attach(void)
        unsigned int tcbi;
        struct tcb *tcp;
 
-       /*
-        * Block user interruptions as we would leave the traced
-        * process stopped (process state T) if we would terminate in
-        * between PTRACE_ATTACH and wait4() on SIGSTOP.
-        * We rely on cleanup() from this point on.
-        */
-       if (interactive)
-               sigprocmask(SIG_SETMASK, &blocked_set, NULL);
-
        if (daemonized_tracer) {
                pid_t pid = fork();
-               if (pid < 0) {
-                       perror_msg_and_die("fork");
-               }
+               if (pid < 0)
+                       perror_func_msg_and_die("fork");
+
                if (pid) { /* parent */
                        /*
                         * Wait for grandchild to attach to straced process
@@ -1169,12 +1137,8 @@ startup_attach(void)
 
                attach_tcb(tcp);
 
-               if (interactive) {
-                       sigprocmask(SIG_SETMASK, &start_set, NULL);
-                       if (interrupted)
-                               goto ret;
-                       sigprocmask(SIG_SETMASK, &blocked_set, NULL);
-               }
+               if (interrupted)
+                       return;
        } /* for each tcbtab[] */
 
        if (daemonized_tracer) {
@@ -1185,10 +1149,6 @@ startup_attach(void)
                kill(parent_pid, SIGKILL);
                strace_child = 0;
        }
-
- ret:
-       if (interactive)
-               sigprocmask(SIG_SETMASK, &start_set, NULL);
 }
 
 /* Stack-o-phobic exec helper, in the hope to work around
@@ -1200,6 +1160,7 @@ struct exec_params {
        gid_t run_egid;
        char **argv;
        char *pathname;
+       struct sigaction child_sa;
 };
 static struct exec_params params_for_tracee;
 
@@ -1230,8 +1191,7 @@ exec_or_die(void)
                if (setreuid(run_uid, params->run_euid) < 0) {
                        perror_msg_and_die("setreuid");
                }
-       }
-       else if (geteuid() != 0)
+       } else if (geteuid() != 0)
                if (setreuid(run_uid, run_uid) < 0) {
                        perror_msg_and_die("setreuid");
                }
@@ -1254,6 +1214,9 @@ exec_or_die(void)
                alarm(0);
        }
 
+       if (params_for_tracee.child_sa.sa_handler != SIG_DFL)
+               sigaction(SIGCHLD, &params_for_tracee.child_sa, NULL);
+
        execv(params->pathname, params->argv);
        perror_msg_and_die("exec");
 }
@@ -1270,7 +1233,7 @@ open_dummy_desc(void)
        int fds[2];
 
        if (pipe(fds))
-               perror_msg_and_die("pipe");
+               perror_func_msg_and_die("pipe");
        close(fds[1]);
        set_cloexec_flag(fds[0]);
        return fds[0];
@@ -1360,15 +1323,13 @@ startup_child(char **argv)
                        if (colon) {
                                n = colon - path;
                                m = n + 1;
-                       }
-                       else
+                       } else
                                m = n = strlen(path);
                        if (n == 0) {
                                if (!getcwd(pathname, PATH_MAX))
                                        continue;
                                len = strlen(pathname);
-                       }
-                       else if (n > sizeof pathname - 1)
+                       } else if (n > sizeof(pathname) - 1)
                                continue;
                        else {
                                strncpy(pathname, path, n);
@@ -1410,9 +1371,9 @@ startup_child(char **argv)
 #endif
 
        pid = fork();
-       if (pid < 0) {
-               perror_msg_and_die("fork");
-       }
+       if (pid < 0)
+               perror_func_msg_and_die("fork");
+
        if ((pid != 0 && daemonized_tracer)
         || (pid == 0 && !daemonized_tracer)
        ) {
@@ -1458,19 +1419,20 @@ startup_child(char **argv)
                                kill(pid, SIGCONT);
                }
                tcp = alloctcb(pid);
-               tcp->flags |= TCB_ATTACHED | TCB_STARTUP
-                           | TCB_SKIP_DETACH_ON_FIRST_EXEC
-                           | (NOMMU_SYSTEM ? 0 : (TCB_HIDE_LOG | post_attach_sigstop));
-               newoutf(tcp);
-       }
-       else {
+               after_successful_attach(tcp, TCB_SKIP_DETACH_ON_FIRST_EXEC
+                                            | (NOMMU_SYSTEM ? 0
+                                               : (TCB_HIDE_LOG
+                                                  | post_attach_sigstop)));
+       } else {
                /* With -D, we are *child* here, the tracee is our parent. */
                strace_child = strace_tracer_pid;
                strace_tracer_pid = getpid();
                tcp = alloctcb(strace_child);
                tcp->flags |= TCB_SKIP_DETACH_ON_FIRST_EXEC | TCB_HIDE_LOG;
-               /* attaching will be done later, by startup_attach */
-               /* note: we don't do newoutf(tcp) here either! */
+               /*
+                * Attaching will be done later, by startup_attach.
+                * Note: we don't do after_successful_attach() here either!
+                */
 
                /* NOMMU BUG! -D mode is active, we (child) return,
                 * and we will scribble over parent's stack!
@@ -1507,7 +1469,6 @@ startup_child(char **argv)
        redirect_standard_fds();
 }
 
-#if USE_SEIZE
 static void
 test_ptrace_seize(void)
 {
@@ -1521,7 +1482,7 @@ test_ptrace_seize(void)
 
        pid = fork();
        if (pid < 0)
-               perror_msg_and_die("fork");
+               perror_func_msg_and_die("fork");
 
        if (pid == 0) {
                pause();
@@ -1534,8 +1495,8 @@ 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) {
-               error_msg("PTRACE_SEIZE doesn't work");
+       } else {
+               debug_msg("PTRACE_SEIZE doesn't work");
        }
 
        kill(pid, SIGKILL);
@@ -1548,19 +1509,15 @@ test_ptrace_seize(void)
                if (tracee_pid <= 0) {
                        if (errno == EINTR)
                                continue;
-                       perror_msg_and_die("%s: unexpected wait result %d",
-                                        __func__, tracee_pid);
+                       perror_func_msg_and_die("unexpected wait result %d",
+                                               tracee_pid);
                }
-               if (WIFSIGNALED(status)) {
+               if (WIFSIGNALED(status))
                        return;
-               }
-               error_msg_and_die("%s: unexpected wait status %#x",
-                                 __func__, status);
+
+               error_func_msg_and_die("unexpected wait status %#x", status);
        }
 }
-#else /* !USE_SEIZE */
-# define test_ptrace_seize() ((void)0)
-#endif
 
 static unsigned
 get_os_release(void)
@@ -1578,12 +1535,12 @@ get_os_release(void)
                        error_msg_and_die("Bad OS release string: '%s'", u.release);
                /* Note: this open-codes KERNEL_VERSION(): */
                rel = (rel << 8) | atoi(p);
-               if (rel >= KERNEL_VERSION(1,0,0))
+               if (rel >= KERNEL_VERSION(1, 0, 0))
                        break;
                while (*p >= '0' && *p <= '9')
                        p++;
                if (*p != '.') {
-                       if (rel >= KERNEL_VERSION(0,1,0)) {
+                       if (rel >= KERNEL_VERSION(0, 1, 0)) {
                                /* "X.Y-something" means "X.Y.0" */
                                rel <<= 8;
                                break;
@@ -1596,12 +1553,8 @@ get_os_release(void)
 }
 
 static void
-set_sigaction(int signo, void (*sighandler)(int), struct sigaction *oldact)
+set_sighandler(int signo, void (*sighandler)(int), struct sigaction *oldact)
 {
-       /* if signal handler is a function, add the signal to blocked_set */
-       if (sighandler != SIG_IGN && sighandler != SIG_DFL)
-               sigaddset(&blocked_set, signo);
-
        const struct sigaction sa = { .sa_handler = sighandler };
        sigaction(signo, &sa, oldact);
 }
@@ -1620,14 +1573,11 @@ init(int argc, char *argv[])
        int c, i;
        int optF = 0;
 
-       progname = argv[0] ? argv[0] : "strace";
-
-       /* Make sure SIGCHLD has the default action so that waitpid
-          definitely works without losing track of children.  The user
-          should not have given us a bogus state to inherit, but he might
-          have.  Arguably we should detect SIG_IGN here and pass it on
-          to children, but probably noone really needs that.  */
-       signal(SIGCHLD, SIG_DFL);
+       if (!program_invocation_name || !*program_invocation_name) {
+               static char name[] = "strace";
+               program_invocation_name =
+                       (argc > 0 && argv[0] && *argv[0]) ? argv[0] : name;
+       }
 
        strace_tracer_pid = getpid();
 
@@ -1643,14 +1593,20 @@ init(int argc, char *argv[])
 # error Bug in DEFAULT_QUAL_FLAGS
 #endif
        qualify("signal=all");
-       while ((c = getopt(argc, argv,
-               "+b:cCdfFhiqrtTvVwxyz"
-#ifdef USE_LIBUNWIND
-               "k"
+       while ((c = getopt(argc, argv, "+"
+#ifdef ENABLE_STACKTRACE
+           "k"
 #endif
-               "D"
-               "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
+           "a:Ab:cCdDe:E:fFhiI:o:O:p:P:qrs:S:tTu:vVwxX:yz")) != EOF) {
                switch (c) {
+               case 'a':
+                       acolumn = string_to_uint(optarg);
+                       if (acolumn < 0)
+                               error_opt_arg(c, optarg);
+                       break;
+               case 'A':
+                       open_append = true;
+                       break;
                case 'b':
                        if (strcmp(optarg, "execve") != 0)
                                error_msg_and_die("Syscall '%s' for -b isn't supported",
@@ -1675,59 +1631,37 @@ init(int argc, char *argv[])
                case 'D':
                        daemonized_tracer = 1;
                        break;
-               case 'F':
-                       optF = 1;
+               case 'e':
+                       qualify(optarg);
+                       break;
+               case 'E':
+                       if (putenv(optarg) < 0)
+                               perror_msg_and_die("putenv");
                        break;
                case 'f':
                        followfork++;
                        break;
+               case 'F':
+                       optF = 1;
+                       break;
                case 'h':
                        usage();
                        break;
                case 'i':
                        iflag = 1;
                        break;
-               case 'q':
-                       qflag++;
-                       break;
-               case 'r':
-                       rflag = 1;
-                       break;
-               case 't':
-                       tflag++;
-                       break;
-               case 'T':
-                       Tflag = 1;
-                       break;
-               case 'w':
-                       count_wallclock = 1;
-                       break;
-               case 'x':
-                       xflag++;
-                       break;
-               case 'y':
-                       show_fd_path++;
-                       break;
-               case 'v':
-                       qualify("abbrev=none");
-                       break;
-               case 'V':
-                       print_version();
-                       exit(0);
-                       break;
-               case 'z':
-                       not_failing_only = 1;
-                       break;
-               case 'a':
-                       acolumn = string_to_uint(optarg);
-                       if (acolumn < 0)
+               case 'I':
+                       opt_intr = string_to_uint_upto(optarg, NUM_INTR_OPTS - 1);
+                       if (opt_intr <= 0)
                                error_opt_arg(c, optarg);
                        break;
-               case 'e':
-                       qualify(optarg);
+#ifdef ENABLE_STACKTRACE
+               case 'k':
+                       stack_trace_enabled = true;
                        break;
+#endif
                case 'o':
-                       outfname = xstrdup(optarg);
+                       outfname = optarg;
                        break;
                case 'O':
                        i = string_to_uint(optarg);
@@ -1741,54 +1675,85 @@ init(int argc, char *argv[])
                case 'P':
                        pathtrace_select(optarg);
                        break;
+               case 'q':
+                       qflag++;
+                       break;
+               case 'r':
+                       rflag = 1;
+                       break;
                case 's':
                        i = string_to_uint(optarg);
-                       if (i < 0)
+                       if (i < 0 || (unsigned int) i > -1U / 4)
                                error_opt_arg(c, optarg);
                        max_strlen = i;
                        break;
                case 'S':
                        set_sortby(optarg);
                        break;
+               case 't':
+                       tflag++;
+                       break;
+               case 'T':
+                       Tflag = 1;
+                       break;
                case 'u':
-                       username = xstrdup(optarg);
+                       username = optarg;
                        break;
-#ifdef USE_LIBUNWIND
-               case 'k':
-                       stack_trace_enabled = true;
+               case 'v':
+                       qualify("abbrev=none");
                        break;
-#endif
-               case 'E':
-                       if (putenv(optarg) < 0)
-                               die_out_of_memory();
+               case 'V':
+                       print_version();
+                       exit(0);
                        break;
-               case 'I':
-                       opt_intr = string_to_uint_upto(optarg, NUM_INTR_OPTS - 1);
-                       if (opt_intr <= 0)
+               case 'w':
+                       count_wallclock = 1;
+                       break;
+               case 'x':
+                       xflag++;
+                       break;
+               case 'X':
+                       if (!strcmp(optarg, "raw"))
+                               xlat_verbosity = XLAT_STYLE_RAW;
+                       else if (!strcmp(optarg, "abbrev"))
+                               xlat_verbosity = XLAT_STYLE_ABBREV;
+                       else if (!strcmp(optarg, "verbose"))
+                               xlat_verbosity = XLAT_STYLE_VERBOSE;
+                       else
                                error_opt_arg(c, optarg);
                        break;
+               case 'y':
+                       show_fd_path++;
+                       break;
+               case 'z':
+                       not_failing_only = 1;
+                       break;
                default:
                        error_msg_and_help(NULL);
                        break;
                }
        }
-       argv += optind;
-       /* argc -= optind; - no need, argc is not used below */
 
-       acolumn_spaces = xmalloc(acolumn + 1);
-       memset(acolumn_spaces, ' ', acolumn);
-       acolumn_spaces[acolumn] = '\0';
+       argv += optind;
+       argc -= optind;
 
-       if (!argv[0] && !nprocs) {
+       if (argc < 0 || (!nprocs && !argc)) {
                error_msg_and_help("must have PROG [ARGS] or -p PID");
        }
 
-       if (!argv[0] && daemonized_tracer) {
+       if (!argc && daemonized_tracer) {
                error_msg_and_help("PROG [ARGS] must be specified with -D");
        }
 
-       if (!followfork)
-               followfork = optF;
+       if (optF) {
+               if (followfork) {
+                       error_msg("deprecated option -F ignored");
+               } else {
+                       error_msg("option -F is deprecated, "
+                                 "please use -f instead");
+                       followfork = optF;
+               }
+       }
 
        if (followfork >= 2 && cflag) {
                error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
@@ -1801,7 +1766,7 @@ init(int argc, char *argv[])
        if (cflag == CFLAG_ONLY_STATS) {
                if (iflag)
                        error_msg("-%c has no effect with -c", 'i');
-#ifdef USE_LIBUNWIND
+#ifdef ENABLE_STACKTRACE
                if (stack_trace_enabled)
                        error_msg("-%c has no effect with -c", 'k');
 #endif
@@ -1815,21 +1780,15 @@ init(int argc, char *argv[])
                        error_msg("-%c has no effect with -c", 'y');
        }
 
-       if (rflag) {
-               if (tflag > 1)
-                       error_msg("-tt has no effect with -r");
-               tflag = 1;
-       }
+       acolumn_spaces = xmalloc(acolumn + 1);
+       memset(acolumn_spaces, ' ', acolumn);
+       acolumn_spaces[acolumn] = '\0';
 
-#ifdef USE_LIBUNWIND
-       if (stack_trace_enabled) {
-               unsigned int tcbi;
+       set_sighandler(SIGCHLD, SIG_DFL, &params_for_tracee.child_sa);
 
+#ifdef ENABLE_STACKTRACE
+       if (stack_trace_enabled)
                unwind_init();
-               for (tcbi = 0; tcbi < tcbtabsize; ++tcbi) {
-                       unwind_tcb_init(tcbtab[tcbi]);
-               }
-       }
 #endif
 
        /* See if they want to run as another user. */
@@ -1845,8 +1804,7 @@ init(int argc, char *argv[])
                }
                run_uid = pent->pw_uid;
                run_gid = pent->pw_gid;
-       }
-       else {
+       } else {
                run_uid = getuid();
                run_gid = getgid();
        }
@@ -1855,8 +1813,7 @@ init(int argc, char *argv[])
                ptrace_setoptions |= PTRACE_O_TRACECLONE |
                                     PTRACE_O_TRACEFORK |
                                     PTRACE_O_TRACEVFORK;
-       if (debug_flag)
-               error_msg("ptrace_setoptions = %#x", ptrace_setoptions);
+       debug_msg("ptrace_setoptions = %#x", ptrace_setoptions);
        test_ptrace_seize();
 
        /*
@@ -1881,11 +1838,15 @@ init(int argc, char *argv[])
                         * when using popen, so prohibit it.
                         */
                        if (followfork >= 2)
-                               error_msg_and_help("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)
+               } else if (followfork < 2) {
                        shared_log = strace_fopen(outfname);
+               } else if (strlen(outfname) >= PATH_MAX - sizeof(int) * 3) {
+                       errno = ENAMETOOLONG;
+                       perror_msg_and_die("%s", outfname);
+               }
        } else {
                /* -ff without -o FILE is the same as single -f */
                if (followfork >= 2)
@@ -1904,7 +1865,7 @@ init(int argc, char *argv[])
         * no           1       1       INTR_WHILE_WAIT
         */
 
-       if (outfname && argv[0]) {
+       if (outfname && argc) {
                if (!opt_intr)
                        opt_intr = INTR_NEVER;
                if (!qflag)
@@ -1913,37 +1874,39 @@ init(int argc, char *argv[])
        if (!opt_intr)
                opt_intr = INTR_WHILE_WAIT;
 
-       sigprocmask(SIG_SETMASK, NULL, &start_set);
-       memcpy(&blocked_set, &start_set, sizeof(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]) {
+       if (argc) {
                startup_child(argv);
        }
 
-       set_sigaction(SIGTTOU, SIG_IGN, NULL);
-       set_sigaction(SIGTTIN, SIG_IGN, NULL);
+       set_sighandler(SIGTTOU, SIG_IGN, NULL);
+       set_sighandler(SIGTTIN, SIG_IGN, NULL);
        if (opt_intr != INTR_ANYWHERE) {
                if (opt_intr == INTR_BLOCK_TSTP_TOO)
-                       set_sigaction(SIGTSTP, SIG_IGN, NULL);
+                       set_sighandler(SIGTSTP, SIG_IGN, NULL);
                /*
                 * In interactive mode (if no -o OUTFILE, or -p PID is used),
-                * fatal signals are blocked while syscall stop is processed,
-                * and acted on in between, when waiting for new syscall stops.
-                * In non-interactive mode, signals are ignored.
+                * fatal signals are handled asynchronously and acted
+                * when waiting for process state changes.
+                * In non-interactive mode these signals are ignored.
                 */
-               set_sigaction(SIGHUP, interactive ? interrupt : SIG_IGN, NULL);
-               set_sigaction(SIGINT, interactive ? interrupt : SIG_IGN, NULL);
-               set_sigaction(SIGQUIT, interactive ? interrupt : SIG_IGN, NULL);
-               set_sigaction(SIGPIPE, interactive ? interrupt : SIG_IGN, NULL);
-               set_sigaction(SIGTERM, interactive ? interrupt : SIG_IGN, NULL);
+               set_sighandler(SIGHUP, interactive ? interrupt : SIG_IGN, NULL);
+               set_sighandler(SIGINT, interactive ? interrupt : SIG_IGN, NULL);
+               set_sighandler(SIGQUIT, interactive ? interrupt : SIG_IGN, NULL);
+               set_sighandler(SIGPIPE, interactive ? interrupt : SIG_IGN, NULL);
+               set_sighandler(SIGTERM, interactive ? interrupt : SIG_IGN, NULL);
        }
 
+       sigemptyset(&timer_set);
+       sigaddset(&timer_set, SIGALRM);
+       sigprocmask(SIG_BLOCK, &timer_set, NULL);
+       set_sighandler(SIGALRM, timer_sighandler, NULL);
+
        if (nprocs != 0 || daemonized_tracer)
                startup_attach();
 
@@ -1956,17 +1919,25 @@ init(int argc, char *argv[])
 }
 
 static struct tcb *
-pid2tcb(int pid)
+pid2tcb(const int pid)
 {
-       unsigned int i;
-
        if (pid <= 0)
                return NULL;
 
-       for (i = 0; i < tcbtabsize; i++) {
-               struct tcb *tcp = tcbtab[i];
+#define PID2TCB_CACHE_SIZE 1024U
+#define PID2TCB_CACHE_MASK (PID2TCB_CACHE_SIZE - 1)
+
+       static struct tcb *pid2tcb_cache[PID2TCB_CACHE_SIZE];
+       struct tcb **const ptcp = &pid2tcb_cache[pid & PID2TCB_CACHE_MASK];
+       struct tcb *tcp = *ptcp;
+
+       if (tcp && tcp->pid == pid)
+               return tcp;
+
+       for (unsigned int i = 0; i < tcbtabsize; ++i) {
+               tcp = tcbtab[i];
                if (tcp->pid == pid)
-                       return tcp;
+                       return *ptcp = tcp;
        }
 
        return NULL;
@@ -1988,8 +1959,7 @@ cleanup(void)
                tcp = tcbtab[i];
                if (!tcp->pid)
                        continue;
-               if (debug_flag)
-                       error_msg("cleanup: looking at pid %u", tcp->pid);
+               debug_func_msg("looking at pid %u", tcp->pid);
                if (tcp->pid == strace_child) {
                        kill(tcp->pid, SIGCONT);
                        kill(tcp->pid, fatal_sig);
@@ -2016,22 +1986,17 @@ print_debug_info(const int pid, int status)
        strcpy(buf, "???");
        if (WIFSIGNALED(status))
 #ifdef WCOREDUMP
-               sprintf(buf, "WIFSIGNALED,%ssig=%s",
+               xsprintf(buf, "WIFSIGNALED,%ssig=%s",
                                WCOREDUMP(status) ? "core," : "",
                                signame(WTERMSIG(status)));
 #else
-               sprintf(buf, "WIFSIGNALED,sig=%s",
+               xsprintf(buf, "WIFSIGNALED,sig=%s",
                                signame(WTERMSIG(status)));
 #endif
        if (WIFEXITED(status))
-               sprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
+               xsprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
        if (WIFSTOPPED(status))
-               sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
-#ifdef WIFCONTINUED
-       /* Should never be seen */
-       if (WIFCONTINUED(status))
-               strcpy(buf, "WIFCONTINUED");
-#endif
+               xsprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
        evbuf[0] = '\0';
        if (event != 0) {
                static const char *const event_names[] = {
@@ -2048,7 +2013,7 @@ print_debug_info(const int pid, int status)
                        e = event_names[event];
                else if (event == PTRACE_EVENT_STOP)
                        e = "STOP";
-               sprintf(evbuf, ",EVENT_%s (%u)", e, event);
+               xsprintf(evbuf, ",EVENT_%s (%u)", e, event);
        }
        error_msg("[wait(0x%06x) = %u] %s%s", status, pid, buf, evbuf);
 }
@@ -2072,17 +2037,20 @@ maybe_allocate_tcb(const int pid, int status)
        if (followfork) {
                /* We assume it's a fork/vfork/clone child */
                struct tcb *tcp = alloctcb(pid);
-               tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
-               newoutf(tcp);
+               after_successful_attach(tcp, post_attach_sigstop);
                if (!qflag)
                        error_msg("Process %d attached", pid);
                return tcp;
        } else {
-               /* This can happen if a clone call used
-                * CLONE_PTRACE itself.
+               /*
+                * This can happen if a clone call misused CLONE_PTRACE itself.
+                *
+                * There used to be a dance around possible re-injection of
+                * WSTOPSIG(status), but it was later removed as the only
+                * observable stop here is the initial ptrace-stop.
                 */
-               ptrace(PTRACE_CONT, pid, NULL, 0);
-               error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
+               ptrace(PTRACE_DETACH, pid, NULL, 0L);
+               error_msg("Detached unknown pid %d", pid);
                return NULL;
        }
 }
@@ -2145,7 +2113,7 @@ print_signalled(struct tcb *tcp, const int pid, int status)
        }
 
        if (cflag != CFLAG_ONLY_STATS
-           && is_number_in_set(WTERMSIG(status), &signal_set)) {
+           && is_number_in_set(WTERMSIG(status), signal_set)) {
                printleader(tcp);
 #ifdef WCOREDUMP
                tprintf("+++ killed by %s %s+++\n",
@@ -2180,7 +2148,7 @@ print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
 {
        if (cflag != CFLAG_ONLY_STATS
            && !hide_log(tcp)
-           && is_number_in_set(sig, &signal_set)) {
+           && is_number_in_set(sig, signal_set)) {
                printleader(tcp);
                if (si) {
                        tprintf("--- %s ", signame(sig));
@@ -2195,15 +2163,13 @@ print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
 static void
 startup_tcb(struct tcb *tcp)
 {
-       if (debug_flag)
-               error_msg("pid %d has TCB_STARTUP, initializing it", tcp->pid);
+       debug_msg("pid %d has TCB_STARTUP, initializing it", tcp->pid);
 
        tcp->flags &= ~TCB_STARTUP;
 
        if (!use_seize) {
-               if (debug_flag)
-                       error_msg("setting opts 0x%x on pid %d",
-                                 ptrace_setoptions, tcp->pid);
+               debug_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 */
@@ -2212,7 +2178,7 @@ startup_tcb(struct tcb *tcp)
                }
        }
 
-       if (get_scno(tcp) == 1)
+       if ((tcp->flags & TCB_GRABBED) && (get_scno(tcp) == 1))
                tcp->s_prev_ent = tcp->s_ent;
 }
 
@@ -2226,11 +2192,11 @@ print_event_exit(struct tcb *tcp)
 
        if (followfork < 2 && printing_tcp && printing_tcp != tcp
            && printing_tcp->curcol != 0) {
-               current_tcp = printing_tcp;
+               set_current_tcp(printing_tcp);
                tprints(" <unfinished ...>\n");
-               fflush(printing_tcp->outf);
+               flush_tcp_output(printing_tcp);
                printing_tcp->curcol = 0;
-               current_tcp = tcp;
+               set_current_tcp(tcp);
        }
 
        if ((followfork < 2 && printing_tcp != tcp)
@@ -2247,31 +2213,28 @@ print_event_exit(struct tcb *tcp)
                 */
                tprints(" <unfinished ...>");
        }
+
+       printing_tcp = tcp;
        tprints(") ");
        tabto();
        tprints("= ?\n");
        line_ended();
 }
 
-/* Returns true iff the main trace loop has to continue. */
-static bool
-trace(void)
+static enum trace_event
+next_event(int *pstatus, siginfo_t *si)
 {
        int pid;
-       int wait_errno;
        int status;
-       bool stopped;
-       unsigned int sig;
-       unsigned int event;
        struct tcb *tcp;
        struct rusage ru;
 
        if (interrupted)
-               return false;
+               return TE_BREAK;
 
        /*
         * Used to exit simply when nprocs hits zero, but in this testcase:
-        *  int main() { _exit(!!fork()); }
+        *  int main(void) { _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:
@@ -2287,21 +2250,51 @@ trace(void)
                 * on exit. Oh well...
                 */
                if (nprocs == 0)
-                       return false;
+                       return TE_BREAK;
        }
 
-       if (interactive)
-               sigprocmask(SIG_SETMASK, &start_set, NULL);
-       pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
-       wait_errno = errno;
-       if (interactive)
-               sigprocmask(SIG_SETMASK, &blocked_set, NULL);
+       const bool unblock_delay_timer = is_delay_timer_armed();
+
+       /*
+        * The window of opportunity to handle expirations
+        * of the delay timer opens here.
+        *
+        * Unblock the signal handler for the delay timer
+        * iff the delay timer is already created.
+        */
+       if (unblock_delay_timer)
+               sigprocmask(SIG_UNBLOCK, &timer_set, NULL);
+
+       /*
+        * If the delay timer has expired, then its expiration
+        * has been handled already by the signal handler.
+        *
+        * If the delay timer expires during wait4(),
+        * then the system call will be interrupted and
+        * the expiration will be handled by the signal handler.
+        */
+       pid = wait4(-1, pstatus, __WALL, (cflag ? &ru : NULL));
+       const int wait_errno = errno;
+
+       /*
+        * The window of opportunity to handle expirations
+        * of the delay timer closes here.
+        *
+        * Block the signal handler for the delay timer
+        * iff it was unblocked earlier.
+        */
+       if (unblock_delay_timer) {
+               sigprocmask(SIG_BLOCK, &timer_set, NULL);
+
+               if (restart_failed)
+                       return TE_BREAK;
+       }
 
        if (pid < 0) {
                if (wait_errno == EINTR)
-                       return true;
+                       return TE_NEXT;
                if (nprocs == 0 && wait_errno == ECHILD)
-                       return false;
+                       return TE_BREAK;
                /*
                 * If nprocs > 0, ECHILD is not expected,
                 * treat it as any other error here:
@@ -2310,10 +2303,12 @@ trace(void)
                perror_msg_and_die("wait4(__WALL)");
        }
 
+       status = *pstatus;
+
        if (pid == popen_pid) {
                if (!WIFSTOPPED(status))
                        popen_pid = 0;
-               return true;
+               return TE_NEXT;
        }
 
        if (debug_flag)
@@ -2325,14 +2320,205 @@ trace(void)
        if (!tcp) {
                tcp = maybe_allocate_tcb(pid, status);
                if (!tcp)
-                       return true;
+                       return TE_NEXT;
        }
 
-       clear_regs();
+       clear_regs(tcp);
+
+       /* Set current output file */
+       set_current_tcp(tcp);
+
+       if (cflag) {
+               struct timespec stime = {
+                       .tv_sec = ru.ru_stime.tv_sec,
+                       .tv_nsec = ru.ru_stime.tv_usec * 1000
+               };
+               ts_sub(&tcp->dtime, &stime, &tcp->stime);
+               tcp->stime = stime;
+       }
+
+       if (WIFSIGNALED(status))
+               return TE_SIGNALLED;
+
+       if (WIFEXITED(status))
+               return TE_EXITED;
+
+       /*
+        * As WCONTINUED flag has not been specified to wait4,
+        * it cannot be WIFCONTINUED(status), so the only case
+        * that remains is WIFSTOPPED(status).
+        */
+
+       /* Is this the very first time we see this tracee stopped? */
+       if (tcp->flags & TCB_STARTUP)
+               startup_tcb(tcp);
+
+       const unsigned int sig = WSTOPSIG(status);
+       const unsigned int event = (unsigned int) status >> 16;
+
+       switch (event) {
+       case 0:
+               /*
+                * Is this post-attach SIGSTOP?
+                * Interestingly, the process may stop
+                * with STOPSIG equal to some other signal
+                * than SIGSTOP if we happened to attach
+                * just before the process takes a signal.
+                */
+               if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
+                       debug_func_msg("ignored SIGSTOP on pid %d", tcp->pid);
+                       tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
+                       return TE_RESTART;
+               } else if (sig == syscall_trap_sig) {
+                       return TE_SYSCALL_STOP;
+               } else {
+                       *si = (siginfo_t) {};
+                       /*
+                        * 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...
+                        */
+                       bool stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, si) < 0;
+                       return stopped ? TE_GROUP_STOP : TE_SIGNAL_DELIVERY_STOP;
+               }
+               break;
+       case PTRACE_EVENT_STOP:
+               /*
+                * PTRACE_INTERRUPT-stop or group-stop.
+                * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+                */
+               switch (sig) {
+               case SIGSTOP:
+               case SIGTSTP:
+               case SIGTTIN:
+               case SIGTTOU:
+                       return TE_GROUP_STOP;
+               }
+               return TE_RESTART;
+       case PTRACE_EVENT_EXEC:
+               return TE_STOP_BEFORE_EXECVE;
+       case PTRACE_EVENT_EXIT:
+               return TE_STOP_BEFORE_EXIT;
+       default:
+               return TE_RESTART;
+       }
+}
+
+static int
+trace_syscall(struct tcb *tcp, unsigned int *sig)
+{
+       if (entering(tcp)) {
+               int res = syscall_entering_decode(tcp);
+               switch (res) {
+               case 0:
+                       return 0;
+               case 1:
+                       res = syscall_entering_trace(tcp, sig);
+               }
+               syscall_entering_finish(tcp, res);
+               return res;
+       } else {
+               struct timespec ts = {};
+               int res = syscall_exiting_decode(tcp, &ts);
+               if (res != 0) {
+                       res = syscall_exiting_trace(tcp, &ts, res);
+               }
+               syscall_exiting_finish(tcp);
+               return res;
+       }
+}
+
+/* Returns true iff the main trace loop has to continue. */
+static bool
+dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
+{
+       unsigned int restart_op = PTRACE_SYSCALL;
+       unsigned int restart_sig = 0;
+
+       switch (ret) {
+       case TE_BREAK:
+               return false;
+
+       case TE_NEXT:
+               return true;
+
+       case TE_RESTART:
+               break;
+
+       case TE_SYSCALL_STOP:
+               if (trace_syscall(current_tcp, &restart_sig) < 0) {
+                       /*
+                        * ptrace() failed in trace_syscall().
+                        * Likely a result of process disappearing mid-flight.
+                        * Observed case: exit_group() or SIGKILL terminating
+                        * all processes in thread group.
+                        * We assume that ptrace error was caused by process death.
+                        * We used to detach(current_tcp) here, but since we no
+                        * longer implement "detach before death" policy/hack,
+                        * we can let this process to report its death to us
+                        * normally, via WIFEXITED or WIFSIGNALED wait status.
+                        */
+                       return true;
+               }
+               break;
+
+       case TE_SIGNAL_DELIVERY_STOP:
+               restart_sig = WSTOPSIG(*pstatus);
+               print_stopped(current_tcp, si, restart_sig);
+               break;
+
+       case TE_SIGNALLED:
+               print_signalled(current_tcp, current_tcp->pid, *pstatus);
+               droptcb(current_tcp);
+               return true;
+
+       case TE_GROUP_STOP:
+               restart_sig = WSTOPSIG(*pstatus);
+               print_stopped(current_tcp, NULL, restart_sig);
+               if (use_seize) {
+                       /*
+                        * This ends ptrace-stop, but does *not* end group-stop.
+                        * This makes stopping signals work properly on straced
+                        * process (that is, process really stops. It used to
+                        * continue to run).
+                        */
+                       restart_op = PTRACE_LISTEN;
+                       restart_sig = 0;
+               }
+               break;
+
+       case TE_EXITED:
+               print_exited(current_tcp, current_tcp->pid, *pstatus);
+               droptcb(current_tcp);
+               return true;
+
+       case TE_STOP_BEFORE_EXECVE:
+               /*
+                * Check that we are inside syscall now (next event after
+                * PTRACE_EVENT_EXEC should be for syscall exiting).  If it is
+                * not the case, we might have a situation when we attach to a
+                * process and the first thing we see is a PTRACE_EVENT_EXEC
+                * and all the following syscall state tracking is screwed up
+                * otherwise.
+                */
+               if (entering(current_tcp)) {
+                       int ret;
+
+                       error_msg("Stray PTRACE_EVENT_EXEC from pid %d"
+                                 ", trying to recover...",
+                                 current_tcp->pid);
 
-       event = (unsigned int) status >> 16;
+                       current_tcp->flags |= TCB_RECOVERING;
+                       ret = trace_syscall(current_tcp, &restart_sig);
+                       current_tcp->flags &= ~TCB_RECOVERING;
+
+                       if (ret < 0) {
+                               /* The reason is described in TE_SYSCALL_STOP */
+                               return true;
+                       }
+               }
 
-       if (event == PTRACE_EVENT_EXEC) {
                /*
                 * Under Linux, execve changes pid to thread leader's pid,
                 * and we see this changed pid on EVENT_EXEC and later,
@@ -2348,181 +2534,116 @@ trace(void)
                 * PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
                 * On 2.6 and earlier, it can return garbage.
                 */
-               if (os_release >= KERNEL_VERSION(3,0,0))
-                       tcp = maybe_switch_tcbs(tcp, pid);
+               if (os_release >= KERNEL_VERSION(3, 0, 0))
+                       set_current_tcp(maybe_switch_tcbs(current_tcp,
+                                                         current_tcp->pid));
 
                if (detach_on_execve) {
-                       if (tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
-                               tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
+                       if (current_tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
+                               current_tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
                        } else {
-                               detach(tcp); /* do "-b execve" thingy */
+                               detach(current_tcp); /* do "-b execve" thingy */
                                return true;
                        }
                }
-       }
+               break;
 
-       /* Set current output file */
-       current_tcp = tcp;
-
-       if (cflag) {
-               tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
-               tcp->stime = ru.ru_stime;
+       case TE_STOP_BEFORE_EXIT:
+               print_event_exit(current_tcp);
+               break;
        }
 
-       if (WIFSIGNALED(status)) {
-               print_signalled(tcp, pid, status);
-               droptcb(tcp);
-               return true;
-       }
+       /* We handled quick cases, we are permitted to interrupt now. */
+       if (interrupted)
+               return false;
 
-       if (WIFEXITED(status)) {
-               print_exited(tcp, pid, status);
-               droptcb(tcp);
+       /* If the process is being delayed, do not ptrace_restart just yet */
+       if (syscall_delayed(current_tcp))
                return true;
-       }
 
-       if (!WIFSTOPPED(status)) {
-               /*
-                * Neither signalled, exited or stopped.
-                * How could that be?
-                */
-               error_msg("pid %u not stopped!", pid);
-               droptcb(tcp);
-               return true;
+       if (ptrace_restart(restart_op, current_tcp, restart_sig) < 0) {
+               /* Note: ptrace_restart emitted error message */
+               exit_code = 1;
+               return false;
        }
+       return true;
+}
 
-       /* Is this the very first time we see this tracee stopped? */
-       if (tcp->flags & TCB_STARTUP) {
-               startup_tcb(tcp);
-       }
+static bool
+restart_delayed_tcb(struct tcb *const tcp)
+{
+       debug_func_msg("pid %d", tcp->pid);
 
-       sig = WSTOPSIG(status);
+       tcp->flags &= ~TCB_DELAYED;
 
-       switch (event) {
-               case 0:
-                       break;
-               case PTRACE_EVENT_EXIT:
-                       print_event_exit(tcp);
-                       goto restart_tracee_with_sig_0;
-#if USE_SEIZE
-               case PTRACE_EVENT_STOP:
-                       /*
-                        * PTRACE_INTERRUPT-stop or group-stop.
-                        * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
-                        */
-                       switch (sig) {
-                               case SIGSTOP:
-                               case SIGTSTP:
-                               case SIGTTIN:
-                               case SIGTTOU:
-                                       stopped = true;
-                                       goto show_stopsig;
-                       }
-                       /* fall through */
-#endif
-               default:
-                       goto restart_tracee_with_sig_0;
-       }
+       struct tcb *const prev_tcp = current_tcp;
+       current_tcp = tcp;
+       bool ret = dispatch_event(TE_RESTART, NULL, NULL);
+       current_tcp = prev_tcp;
 
-       /*
-        * Is this post-attach SIGSTOP?
-        * Interestingly, the process may stop
-        * with STOPSIG equal to some other signal
-        * than SIGSTOP if we happend to attach
-        * just before the process takes a signal.
-        */
-       if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
-               if (debug_flag)
-                       error_msg("ignored SIGSTOP on pid %d", tcp->pid);
-               tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
-               goto restart_tracee_with_sig_0;
-       }
+       return ret;
+}
 
-       if (sig != syscall_trap_sig) {
-               siginfo_t si = {};
+static bool
+restart_delayed_tcbs(void)
+{
+       struct tcb *tcp_next = NULL;
+       struct timespec ts_now;
 
-               /*
-                * 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, &si) < 0;
-#if USE_SEIZE
-show_stopsig:
-#endif
-               print_stopped(tcp, stopped ? NULL : &si, sig);
+       clock_gettime(CLOCK_MONOTONIC, &ts_now);
 
-               if (!stopped)
-                       /* It's signal-delivery-stop. Inject the signal */
-                       goto restart_tracee;
+       for (size_t i = 0; i < tcbtabsize; i++) {
+               struct tcb *tcp = tcbtab[i];
 
-               /* It's group-stop */
-               if (use_seize) {
-                       /*
-                        * This ends ptrace-stop, but does *not* end group-stop.
-                        * This makes stopping signals work properly on straced process
-                        * (that is, process really stops. It used to continue to run).
-                        */
-                       if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
-                               /* Note: ptrace_restart emitted error message */
-                               exit_code = 1;
-                               return false;
+               if (tcp->pid && syscall_delayed(tcp)) {
+                       if (ts_cmp(&ts_now, &tcp->delay_expiration_time) > 0) {
+                               if (!restart_delayed_tcb(tcp))
+                                       return false;
+                       } else {
+                               /* Check whether this tcb is the next.  */
+                               if (!tcp_next ||
+                                   ts_cmp(&tcp_next->delay_expiration_time,
+                                          &tcp->delay_expiration_time) > 0) {
+                                       tcp_next = tcp;
+                               }
                        }
-                       return true;
                }
-               /* We don't have PTRACE_LISTEN support... */
-               goto restart_tracee;
        }
 
-       /* We handled quick cases, we are permitted to interrupt now. */
-       if (interrupted)
-               return false;
-
-       /*
-        * This should be syscall entry or exit.
-        * Handle it.
-        */
-       sig = 0;
-       if (trace_syscall(tcp, &sig) < 0) {
-               /*
-                * ptrace() failed in trace_syscall().
-                * Likely a result of process disappearing mid-flight.
-                * Observed case: exit_group() or SIGKILL terminating
-                * all processes in thread group.
-                * We assume that ptrace error was caused by process death.
-                * We used to detach(tcp) here, but since we no longer
-                * implement "detach before death" policy/hack,
-                * we can let this process to report its death to us
-                * normally, via WIFEXITED or WIFSIGNALED wait status.
-                */
-               return true;
-       }
-       goto restart_tracee;
-
-restart_tracee_with_sig_0:
-       sig = 0;
-
-restart_tracee:
-       if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
-               /* Note: ptrace_restart emitted error message */
-               exit_code = 1;
-               return false;
-       }
+       if (tcp_next)
+               arm_delay_timer(tcp_next);
 
        return true;
 }
 
-int
-main(int argc, char *argv[])
+/*
+ * As this signal handler does a lot of work that is not suitable
+ * for signal handlers, extra care must be taken to ensure that
+ * it is enabled only in those places where it's safe.
+ */
+static void
+timer_sighandler(int sig)
 {
-       init(argc, argv);
+       delay_timer_expired();
 
-       exit_code = !nprocs;
+       if (restart_failed)
+               return;
 
-       while (trace())
-               ;
+       int saved_errno = errno;
+
+       if (!restart_delayed_tcbs())
+               restart_failed = 1;
 
+       errno = saved_errno;
+}
+
+#ifdef ENABLE_COVERAGE_GCOV
+extern void __gcov_flush(void);
+#endif
+
+static void ATTRIBUTE_NORETURN
+terminate(void)
+{
        cleanup();
        fflush(NULL);
        if (shared_log != stderr)
@@ -2539,11 +2660,38 @@ main(int argc, char *argv[])
                /* Child was killed by a signal, mimic that.  */
                exit_code &= 0xff;
                signal(exit_code, SIG_DFL);
+#ifdef ENABLE_COVERAGE_GCOV
+               __gcov_flush();
+#endif
                raise(exit_code);
+
+               /* Unblock the signal.  */
+               sigset_t mask;
+               sigemptyset(&mask);
+               sigaddset(&mask, exit_code);
+#ifdef ENABLE_COVERAGE_GCOV
+               __gcov_flush();
+#endif
+               sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
                /* Paranoia - what if this signal is not fatal?
                   Exit with 128 + signo then.  */
                exit_code += 128;
        }
+       exit(exit_code);
+}
+
+int
+main(int argc, char *argv[])
+{
+       setlocale(LC_ALL, "");
+       init(argc, argv);
 
-       return exit_code;
+       exit_code = !nprocs;
+
+       int status;
+       siginfo_t si;
+       while (dispatch_event(next_event(&status, &si), &status, &si))
+               ;
+       terminate();
 }