* 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-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>
#ifdef HAVE_PRCTL
# include <sys/prctl.h>
#endif
+#include <asm/unistd.h>
-#include "ptrace.h"
+#include "largefile_wrappers.h"
+#include "number_set.h"
+#include "scno.h"
#include "printsiginfo.h"
+#include "trace_event.h"
+#include "xstring.h"
/* In some libc, these aren't declared. Do it ourself: */
extern char **environ;
#ifdef USE_LIBUNWIND
/* if this is true do the stack trace for every system call */
-bool stack_trace_enabled = false;
+bool stack_trace_enabled;
#endif
-#if defined __NR_tkill
-# define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
-#else
- /* kill() may choose arbitrarily the target task of the process group
- while we later wait on a that specific TID. PID process waits become
- TID task specific waits for a process under ptrace(2). */
-# warning "tkill(2) not available, risk of strace hangs!"
-# define my_tkill(tid, sig) kill((tid), (sig))
-#endif
+#define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
/* Glue for systems without a MMU that cannot provide fork() */
#if !defined(HAVE_FORK)
const unsigned int syscall_trap_sig = SIGTRAP | 0x80;
cflag_t cflag = CFLAG_NONE;
-unsigned int followfork = 0;
-unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
-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 followfork;
+unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC
+ | PTRACE_O_TRACEEXIT;
+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: */
* 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;
#endif
/* 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;
-/* Are we "strace PROG" and need to skip detach on first execve? */
-static bool skip_one_b_execve = 0;
-/* Are we "strace PROG" and need to hide everything until execve? */
-bool hide_log_until_execve = 0;
+static bool detach_on_execve;
-static int exit_code = 0;
-static int strace_child = 0;
-static int strace_tracer_pid = 0;
+static int exit_code;
+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;
static int acolumn = DEFAULT_ACOLUMN;
static char *acolumn_spaces;
-static char *outfname = NULL;
+static const char *outfname;
/* If -ff, points to stderr. Else, it's our common output log */
static FILE *shared_log;
-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 empty_set, blocked_set;
+static sigset_t start_set, blocked_set;
#ifdef HAVE_SIG_ATOMIC_T
static volatile sig_atomic_t interrupted;
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];
#endif /* HAVE_STERRROR */
+static void
+print_version(void)
+{
+ static const char features[] =
+#ifdef USE_LIBUNWIND
+ " stack-unwind"
+#endif /* USE_LIBUNWIND */
+#ifdef USE_DEMANGLE
+ " stack-demangle"
+#endif /* USE_DEMANGLE */
+#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
usage(void)
{
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
+"\
+ -k obtain stack trace between each syscall (experimental)\n\
+"
+#endif
+"\
-o file send trace output to FILE instead of stderr\n\
-q suppress messages about attaching, detaching, etc.\n\
-r print relative timestamp\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\
+ options: trace, abbrev, verbose, raw, signal, read, write, fault\n\
-P path trace accesses to path\n\
\n\
Tracing:\n\
-h print help message\n\
-V print version\n\
"
-#ifdef USE_LIBUNWIND
-" -k obtain stack trace between each syscall (experimental)\n\
-"
-#endif
/* ancient, no one should use it
-F -- attempt to follow vforks (deprecated, use -f)\n\
*/
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
* Otherwise prints error message and returns -1.
*/
static int
-ptrace_restart(int op, struct tcb *tcp, int sig)
+ptrace_restart(const unsigned int op, struct tcb *const tcp, unsigned int sig)
{
int err;
const char *msg;
errno = 0;
- ptrace(op, tcp->pid, (void *) 0, (long) sig);
+ ptrace(op, tcp->pid, 0L, (unsigned long) sig);
err = errno;
if (!err)
return 0;
- msg = "SYSCALL";
- if (op == PTRACE_CONT)
- msg = "CONT";
- if (op == PTRACE_DETACH)
- msg = "DETACH";
-#ifdef PTRACE_LISTEN
- if (op == PTRACE_LISTEN)
- msg = "LISTEN";
-#endif
+ switch (op) {
+ case PTRACE_CONT:
+ msg = "CONT";
+ break;
+ case PTRACE_DETACH:
+ msg = "DETACH";
+ break;
+ case PTRACE_LISTEN:
+ msg = "LISTEN";
+ break;
+ default:
+ msg = "SYSCALL";
+ }
+
/*
* Why curcol != 0? Otherwise sometimes we get this:
*
if (err == ESRCH)
return 0;
errno = err;
- perror_msg("ptrace(PTRACE_%s,pid:%d,sig:%d)", msg, tcp->pid, sig);
+ perror_msg("ptrace(PTRACE_%s,pid:%d,sig:%u)", msg, tcp->pid, sig);
return -1;
}
}
}
-#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)
{
return fp;
}
-static int popen_pid = 0;
+static int popen_pid;
#ifndef _PATH_BSHELL
# define _PATH_BSHELL "/bin/sh"
swap_uid();
fp = fdopen(fds[1], "w");
if (!fp)
- die_out_of_memory();
+ perror_msg_and_die("fdopen");
return fp;
}
-void
-tprintf(const char *fmt, ...)
+static void
+outf_perror(const struct tcb * const tcp)
{
- va_list args;
+ if (tcp->outf == stderr)
+ return;
- va_start(args, fmt);
+ /* 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)
+{
if (current_tcp) {
- int n = strace_vfprintf(current_tcp->outf, fmt, args);
+ 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;
}
+}
+
+void
+tprintf(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ tvprintf(fmt, args);
va_end(args);
}
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);
}
}
+void
+tprints_comment(const char *const str)
+{
+ if (str && *str)
+ tprintf(" /* %s */", str);
+}
+
+void
+tprintf_comment(const char *fmt, ...)
+{
+ if (!fmt || !*fmt)
+ return;
+
+ va_list args;
+ va_start(args, fmt);
+ tprints(" /* ");
+ tvprintf(fmt, args);
+ tprints(" */");
+ 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;
}
}
+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)
{
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
}
printing_tcp = tcp;
- current_tcp = tcp;
+ set_current_tcp(tcp);
current_tcp->curcol = 0;
if (print_pid_pfx)
tprintf("%6ld.%06ld ",
(long) dtv.tv_sec, (long) dtv.tv_usec);
otv = tv;
- }
- else if (tflag > 2) {
+ } else if (tflag > 2) {
tprintf("%ld.%06ld ",
(long) tv.tv_sec, (long) tv.tv_usec);
- }
- else {
+ } else {
time_t local = tv.tv_sec;
strftime(str, sizeof(str), "%T", localtime(&local));
if (tflag > 1)
{
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);
}
}
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;
+
+ tcbtab = xgrowarray(tcbtab, &tcbtabsize, sizeof(tcbtab[0]));
+ newtcbs = xcalloc(tcbtabsize - old_tcbtabsize, sizeof(newtcbs[0]));
- newtcbs = xcalloc(alloc_tcbtabsize, sizeof(newtcbs[0]));
- tcbtab = xreallocarray(tcbtab, new_tcbtabsize, sizeof(tcbtab[0]));
- while (tcbtabsize < new_tcbtabsize)
- tcbtab[tcbtabsize++] = newtcbs++;
+ for (tcb_ptr = tcbtab + old_tcbtabsize;
+ tcb_ptr < tcbtab + tcbtabsize; tcb_ptr++, newtcbs++)
+ *tcb_ptr = newtcbs;
}
static struct tcb *
#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;
}
}
if (tcp->pid == 0)
return;
+ int p;
+ for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
+ free(tcp->inject_vec[p]);
+
free_tcb_priv_data(tcp);
#ifdef USE_LIBUNWIND
}
#endif
+ mmap_cache_delete(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) {
} 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;
* 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;
}
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;
}
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;
* ^^^ 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)) {
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*/) {
* 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';
return;
}
- tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
+ tcp->flags |= TCB_ATTACHED | TCB_GRABBED | TCB_STARTUP |
+ post_attach_sigstop;
newoutf(tcp);
- if (debug_flag)
- error_msg("attach to pid %d (main) succeeded", tcp->pid);
+ 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;
++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);
+ debug_msg("attach to pid %d succeeded", tid);
struct tcb *tid_tcp = alloctcb(tid);
- tid_tcp->flags |= TCB_ATTACHED | TCB_STARTUP |
- post_attach_sigstop;
+ tid_tcp->flags |= TCB_ATTACHED | TCB_GRABBED |
+ TCB_STARTUP | post_attach_sigstop;
newoutf(tid_tcp);
}
* We rely on cleanup() from this point on.
*/
if (interactive)
- sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+ 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
attach_tcb(tcp);
if (interactive) {
- sigprocmask(SIG_SETMASK, &empty_set, NULL);
+ sigprocmask(SIG_SETMASK, &start_set, NULL);
if (interrupted)
goto ret;
- sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+ sigprocmask(SIG_SETMASK, &blocked_set, NULL);
}
} /* for each tcbtab[] */
ret:
if (interactive)
- sigprocmask(SIG_SETMASK, &empty_set, NULL);
+ sigprocmask(SIG_SETMASK, &start_set, NULL);
}
/* Stack-o-phobic exec helper, in the hope to work around
gid_t run_egid;
char **argv;
char *pathname;
+ struct sigaction child_sa;
};
static struct exec_params params_for_tracee;
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");
}
alarm(0);
}
+ if (params_for_tracee.child_sa.sa_handler != SIG_DFL)
+ sigaction(SIGCHLD, ¶ms_for_tracee.child_sa, NULL);
+
execv(params->pathname, params->argv);
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");
+ perror_func_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)
{
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);
#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)
) {
}
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
kill_save_errno(pid, SIGKILL);
- perror_msg_and_die("Unexpected wait status %x", status);
+ perror_msg_and_die("Unexpected wait status %#x",
+ status);
}
}
/* Else: NOMMU case, we have no way to sync.
kill(pid, SIGCONT);
}
tcp = alloctcb(pid);
- if (!NOMMU_SYSTEM)
- tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
- else
- tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP
+ | TCB_SKIP_DETACH_ON_FIRST_EXEC
+ | (NOMMU_SYSTEM ? 0 : (TCB_HIDE_LOG | post_attach_sigstop));
newoutf(tcp);
- }
- else {
+ } else {
/* With -D, we are *child* here, the tracee is our parent. */
strace_child = strace_tracer_pid;
strace_tracer_pid = getpid();
- alloctcb(strace_child);
+ 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! */
* 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 closing strace's stdin/out.
+ * 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").
*/
- close(0);
- open_dummy_desc(); /* opens to fd#0 */
- dup2(0, 1);
-#if 0
- /* A good idea too, but we sometimes need to print error messages */
- if (shared_log != stderr)
- dup2(0, 2);
-#endif
+ redirect_standard_fds();
}
#if USE_SEIZE
pid = fork();
if (pid < 0)
- perror_msg_and_die("fork");
+ perror_func_msg_and_die("fork");
if (pid == 0) {
pause();
*/
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);
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 */
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;
return rel;
}
+static void
+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);
+}
+
/*
* Initialization part of main() was eating much stack (~0.5k),
* which was unused after init.
{
int c, i;
int optF = 0;
- struct sigaction sa;
- 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();
# error Bug in DEFAULT_QUAL_FLAGS
#endif
qualify("signal=all");
- while ((c = getopt(argc, argv,
- "+b:cCdfFhiqrtTvVwxyz"
+ while ((c = getopt(argc, argv, "+"
#ifdef USE_LIBUNWIND
- "k"
+ "k"
#endif
- "D"
- "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
+ "a:b:cCdDe:E:fFhiI:o:O:p:P:qrs:S:tTu:vVwxyz")) != EOF) {
switch (c) {
+ case 'a':
+ acolumn = string_to_uint(optarg);
+ if (acolumn < 0)
+ error_opt_arg(c, optarg);
+ break;
case 'b':
if (strcmp(optarg, "execve") != 0)
error_msg_and_die("Syscall '%s' for -b isn't supported",
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;
- /* fall through to tflag++ */
- 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':
- printf("%s -- version %s\n", PACKAGE_NAME, 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 USE_LIBUNWIND
+ case 'k':
+ stack_trace_enabled = true;
break;
+#endif
case 'o':
- outfname = xstrdup(optarg);
+ outfname = optarg;
break;
case 'O':
i = string_to_uint(optarg);
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(optarg);
- if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS)
- error_opt_arg(c, optarg);
+ case 'w':
+ count_wallclock = 1;
+ break;
+ case 'x':
+ xflag++;
+ 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");
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';
+
+ sigprocmask(SIG_SETMASK, NULL, &start_set);
+ memcpy(&blocked_set, &start_set, sizeof(blocked_set));
+
+ set_sighandler(SIGCHLD, SIG_DFL, ¶ms_for_tracee.child_sa);
+
#ifdef USE_LIBUNWIND
- if (stack_trace_enabled)
+ if (stack_trace_enabled) {
+ unsigned int tcbi;
+
unwind_init();
+ for (tcbi = 0; tcbi < tcbtabsize; ++tcbi) {
+ unwind_tcb_init(tcbtab[tcbi]);
+ }
+ }
#endif
/* See if they want to run as another user. */
}
run_uid = pent->pw_uid;
run_gid = pent->pw_gid;
- }
- else {
+ } else {
run_uid = getuid();
run_gid = getgid();
}
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();
- if (fcntl(0, F_GETFD) == -1 || fcntl(1, F_GETFD) == -1) {
- /*
- * Something weird with our stdin and/or stdout -
- * for example, may 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.
- */
- int fd = open_dummy_desc();
- while (fd >= 0 && fd < 2)
- fd = dup(fd);
- if (fd > 2)
- close(fd);
- }
+ /*
+ * 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) {
* 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)
}
if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
- char *buf = xmalloc(BUFSIZ);
- setvbuf(shared_log, buf, _IOLBF, BUFSIZ);
+ setvbuf(shared_log, NULL, _IOLBF, 0);
}
- if (outfname && argv[0]) {
- if (!opt_intr)
- opt_intr = INTR_NEVER;
- if (!qflag)
- qflag = 1;
- }
- if (!opt_intr)
- opt_intr = INTR_WHILE_WAIT;
- /* argv[0] -pPID -oFILE Default interactive setting
+ /*
+ * argv[0] -pPID -oFILE Default interactive setting
* yes * 0 INTR_WHILE_WAIT
* no 1 0 INTR_WHILE_WAIT
* yes * 1 INTR_NEVER
* no 1 1 INTR_WHILE_WAIT
*/
- sigemptyset(&empty_set);
- sigemptyset(&blocked_set);
+ if (outfname && argc) {
+ if (!opt_intr)
+ opt_intr = INTR_NEVER;
+ if (!qflag)
+ qflag = 1;
+ }
+ if (!opt_intr)
+ opt_intr = INTR_WHILE_WAIT;
- /* startup_child() must be called before the signal handlers get
+ /*
+ * 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 (!NOMMU_SYSTEM || daemonized_tracer)
- hide_log_until_execve = 1;
- skip_one_b_execve = 1;
+ if (argc) {
startup_child(argv);
}
- sa.sa_handler = SIG_IGN;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = 0;
- sigaction(SIGTTOU, &sa, NULL); /* SIG_IGN */
- sigaction(SIGTTIN, &sa, NULL); /* SIG_IGN */
+ set_sighandler(SIGTTOU, SIG_IGN, NULL);
+ set_sighandler(SIGTTIN, SIG_IGN, NULL);
if (opt_intr != INTR_ANYWHERE) {
if (opt_intr == INTR_BLOCK_TSTP_TOO)
- sigaction(SIGTSTP, &sa, NULL); /* SIG_IGN */
+ 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.
*/
- if (opt_intr == INTR_WHILE_WAIT) {
- sigaddset(&blocked_set, SIGHUP);
- sigaddset(&blocked_set, SIGINT);
- sigaddset(&blocked_set, SIGQUIT);
- sigaddset(&blocked_set, SIGPIPE);
- sigaddset(&blocked_set, SIGTERM);
- sa.sa_handler = interrupt;
- }
- /* SIG_IGN, or set handler for these */
- sigaction(SIGHUP, &sa, NULL);
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGQUIT, &sa, NULL);
- sigaction(SIGPIPE, &sa, NULL);
- sigaction(SIGTERM, &sa, 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);
}
+
if (nprocs != 0 || daemonized_tracer)
startup_attach();
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);
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[] = {
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);
}
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;
}
}
struct tcb *execve_thread;
long old_pid = 0;
- if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
+ if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &old_pid) < 0)
return tcp;
/* Avoid truncation in pid2tcb() param passing */
if (old_pid <= 0 || old_pid == pid)
}
if (cflag != CFLAG_ONLY_STATS
- && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
- ) {
+ && is_number_in_set(WTERMSIG(status), signal_set)) {
printleader(tcp);
#ifdef WCOREDUMP
tprintf("+++ killed by %s %s+++\n",
print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
{
if (cflag != CFLAG_ONLY_STATS
- && !hide_log_until_execve
- && (qual_flags[sig] & QUAL_SIGNAL)
- ) {
+ && !hide_log(tcp)
+ && is_number_in_set(sig, signal_set)) {
printleader(tcp);
if (si) {
tprintf("--- %s ", signame(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 */
}
}
}
+
+ if ((tcp->flags & TCB_GRABBED) && (get_scno(tcp) == 1))
+ tcp->s_prev_ent = tcp->s_ent;
}
-/* Returns true iff the main trace loop has to continue. */
-static bool
-trace(void)
+static void
+print_event_exit(struct tcb *tcp)
+{
+ if (entering(tcp) || filtered(tcp) || hide_log(tcp)
+ || cflag == CFLAG_ONLY_STATS) {
+ return;
+ }
+
+ if (followfork < 2 && printing_tcp && printing_tcp != tcp
+ && printing_tcp->curcol != 0) {
+ set_current_tcp(printing_tcp);
+ tprints(" <unfinished ...>\n");
+ flush_tcp_output(printing_tcp);
+ printing_tcp->curcol = 0;
+ set_current_tcp(tcp);
+ }
+
+ if ((followfork < 2 && printing_tcp != tcp)
+ || (tcp->flags & TCB_REPRINT)) {
+ tcp->flags &= ~TCB_REPRINT;
+ printleader(tcp);
+ tprintf("<... %s resumed>", tcp->s_ent->sys_name);
+ }
+
+ if (!(tcp->sys_func_rval & RVAL_DECODED)) {
+ /*
+ * The decoder has probably decided to print something
+ * on exiting syscall which is not going to happen.
+ */
+ tprints(" <unfinished ...>");
+ }
+
+ printing_tcp = tcp;
+ tprints(") ");
+ tabto();
+ tprints("= ?\n");
+ line_ended();
+}
+
+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:
* on exit. Oh well...
*/
if (nprocs == 0)
- return false;
+ return TE_BREAK;
}
if (interactive)
- sigprocmask(SIG_SETMASK, &empty_set, NULL);
- pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
+ sigprocmask(SIG_SETMASK, &start_set, NULL);
+ pid = wait4(-1, pstatus, __WALL, (cflag ? &ru : NULL));
wait_errno = errno;
if (interactive)
- sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+ sigprocmask(SIG_SETMASK, &blocked_set, NULL);
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:
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)
if (!tcp) {
tcp = maybe_allocate_tcb(pid, status);
if (!tcp)
- return true;
+ return TE_NEXT;
}
- if (WIFSTOPPED(status))
- get_regs(pid);
- else
- clear_regs();
-
- event = (unsigned int) status >> 16;
-
- if (event == PTRACE_EVENT_EXEC) {
- /*
- * Under Linux, execve changes pid to thread leader's pid,
- * and we see this changed pid on EVENT_EXEC and later,
- * execve sysexit. Leader "disappears" without exit
- * notification. Let user know that, drop leader's tcb,
- * and fix up pid in execve thread's tcb.
- * Effectively, execve thread's tcb replaces leader's tcb.
- *
- * BTW, leader is 'stuck undead' (doesn't report WIFEXITED
- * on exit syscall) in multithreaded programs exactly
- * in order to handle this case.
- *
- * 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 (detach_on_execve && !skip_one_b_execve) {
- detach(tcp); /* do "-b execve" thingy */
- return true;
- }
- skip_one_b_execve = 0;
- }
+ clear_regs(tcp);
/* Set current output file */
- current_tcp = tcp;
+ set_current_tcp(tcp);
if (cflag) {
tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
tcp->stime = ru.ru_stime;
}
- if (WIFSIGNALED(status)) {
- print_signalled(tcp, pid, status);
- droptcb(tcp);
- return true;
- }
+ if (WIFSIGNALED(status))
+ return TE_SIGNALLED;
- if (WIFEXITED(status)) {
- print_exited(tcp, pid, status);
- droptcb(tcp);
- return true;
- }
+ if (WIFEXITED(status))
+ return TE_EXITED;
- if (!WIFSTOPPED(status)) {
- /*
- * Neither signalled, exited or stopped.
- * How could that be?
- */
- error_msg("pid %u not stopped!", pid);
- droptcb(tcp);
- return true;
- }
+ /*
+ * 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) {
+ if (tcp->flags & TCB_STARTUP)
startup_tcb(tcp);
- if (get_scno(tcp) == 1)
- tcp->s_prev_ent = tcp->s_ent;
- }
- sig = WSTOPSIG(status);
+ const unsigned int sig = WSTOPSIG(status);
+ const unsigned int event = (unsigned int) status >> 16;
- if (event != 0) {
- /* Ptrace event */
-#if USE_SEIZE
- if (event == PTRACE_EVENT_STOP) {
+ 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) {};
/*
- * PTRACE_INTERRUPT-stop or group-stop.
- * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+ * 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...
*/
- switch (sig) {
- case SIGSTOP:
- case SIGTSTP:
- case SIGTTIN:
- case SIGTTOU:
- stopped = true;
- goto show_stopsig;
- }
+ bool stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, si) < 0;
+ return stopped ? TE_GROUP_STOP : TE_SIGNAL_DELIVERY_STOP;
+ }
+ break;
+#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:
+ return TE_GROUP_STOP;
}
+ return TE_RESTART;
#endif
- goto restart_tracee_with_sig_0;
+ case PTRACE_EVENT_EXEC:
+ return TE_STOP_BEFORE_EXECVE;
+ case PTRACE_EVENT_EXIT:
+ return TE_STOP_BEFORE_EXIT;
+ default:
+ return TE_RESTART;
}
+}
- /*
- * 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;
+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 timeval tv = {};
+ int res = syscall_exiting_decode(tcp, &tv);
+ if (res != 0) {
+ res = syscall_exiting_trace(tcp, tv, 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;
- if (sig != syscall_trap_sig) {
- siginfo_t si = {};
+ switch (ret) {
+ case TE_BREAK:
+ return false;
- /*
- * True if tracee is stopped by signal
- * (as opposed to "tracee received signal").
- * TODO: shouldn't we check for errno == EINVAL too?
- * We can get ESRCH instead, you know...
- */
- stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0;
-#if USE_SEIZE
-show_stopsig:
-#endif
- print_stopped(tcp, stopped ? NULL : &si, sig);
+ case TE_NEXT:
+ return true;
+
+ case TE_RESTART:
+ break;
- if (!stopped)
- /* It's signal-delivery-stop. Inject the signal */
- goto restart_tracee;
+ 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;
- /* It's group-stop */
+ 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).
+ * 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;
- }
- return true;
+ restart_op = PTRACE_LISTEN;
+ restart_sig = 0;
}
- /* We don't have PTRACE_LISTEN support... */
- goto restart_tracee;
- }
+ break;
- /* We handled quick cases, we are permitted to interrupt now. */
- if (interrupted)
- return false;
+ case TE_EXITED:
+ print_exited(current_tcp, current_tcp->pid, *pstatus);
+ droptcb(current_tcp);
+ return true;
- /*
- * This should be syscall entry or exit.
- * Handle it.
- */
- if (trace_syscall(tcp) < 0) {
+ case TE_STOP_BEFORE_EXECVE:
/*
- * 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.
+ * 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.
*/
- return true;
+ if (entering(current_tcp)) {
+ int ret;
+
+ error_msg("Stray PTRACE_EVENT_EXEC from pid %d"
+ ", trying to recover...",
+ current_tcp->pid);
+
+ 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;
+ }
+ }
+
+ /*
+ * Under Linux, execve changes pid to thread leader's pid,
+ * and we see this changed pid on EVENT_EXEC and later,
+ * execve sysexit. Leader "disappears" without exit
+ * notification. Let user know that, drop leader's tcb,
+ * and fix up pid in execve thread's tcb.
+ * Effectively, execve thread's tcb replaces leader's tcb.
+ *
+ * BTW, leader is 'stuck undead' (doesn't report WIFEXITED
+ * on exit syscall) in multithreaded programs exactly
+ * in order to handle this case.
+ *
+ * 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))
+ set_current_tcp(maybe_switch_tcbs(current_tcp,
+ current_tcp->pid));
+
+ if (detach_on_execve) {
+ if (current_tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
+ current_tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
+ } else {
+ detach(current_tcp); /* do "-b execve" thingy */
+ return true;
+ }
+ }
+ break;
+
+ case TE_STOP_BEFORE_EXIT:
+ print_event_exit(current_tcp);
+ break;
}
-restart_tracee_with_sig_0:
- sig = 0;
+ /* We handled quick cases, we are permitted to interrupt now. */
+ if (interrupted)
+ return false;
-restart_tracee:
- if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
+ if (ptrace_restart(restart_op, current_tcp, restart_sig) < 0) {
/* Note: ptrace_restart emitted error message */
exit_code = 1;
return false;
}
-
return true;
}
-int
-main(int argc, char *argv[])
-{
- init(argc, argv);
-
- while (trace())
- ;
+#ifdef ENABLE_COVERAGE_GCOV
+extern void __gcov_flush(void);
+#endif
+static void ATTRIBUTE_NORETURN
+terminate(void)
+{
cleanup();
fflush(NULL);
if (shared_log != stderr)
/* 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);
+}
- return exit_code;
+int
+main(int argc, char *argv[])
+{
+ init(argc, argv);
+
+ exit_code = !nprocs;
+
+ int status;
+ siginfo_t si;
+ while (dispatch_event(next_event(&status, &si), &status, &si))
+ ;
+ terminate();
}