#ifdef HAVE_PRCTL
# include <sys/prctl.h>
#endif
+#include <asm/unistd.h>
+#include "scno.h"
#include "ptrace.h"
#include "printsiginfo.h"
bool stack_trace_enabled = false;
#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)
cflag_t cflag = CFLAG_NONE;
unsigned int followfork = 0;
-unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
+unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC
+ | PTRACE_O_TRACEEXIT;
unsigned int xflag = 0;
bool debug_flag = 0;
bool Tflag = 0;
unsigned int show_fd_path = 0;
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 int exit_code = 0;
+static int exit_code;
static int strace_child = 0;
static int strace_tracer_pid = 0;
#endif /* HAVE_STERRROR */
+static void
+print_version(void)
+{
+ printf("%s -- version %s\n"
+ "Copyright (C) %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, "1991-2017", PACKAGE_URL);
+}
+
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\
*/
error_msg_and_help("invalid -%c argument: '%s'", opt, arg);
}
-#if USE_SEIZE
+static const char *ptrace_attach_cmd;
+
static int
ptrace_attach_or_seize(int pid)
{
+#if USE_SEIZE
int r;
if (!use_seize)
- return ptrace(PTRACE_ATTACH, pid, 0L, 0L);
+ return ptrace_attach_cmd = "PTRACE_ATTACH",
+ ptrace(PTRACE_ATTACH, pid, 0L, 0L);
r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long) ptrace_setoptions);
if (r)
- return r;
+ return ptrace_attach_cmd = "PTRACE_SEIZE", r;
r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
- return r;
-}
+ return ptrace_attach_cmd = "PTRACE_INTERRUPT", r;
#else
-# define ptrace_attach_or_seize(pid) ptrace(PTRACE_ATTACH, (pid), 0, 0)
+ return ptrace_attach_cmd = "PTRACE_ATTACH",
+ ptrace(PTRACE_ATTACH, pid, 0L, 0L);
#endif
+}
/*
* Used when we want to unblock stopped traced process.
* 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;
}
va_start(args, fmt);
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);
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
* 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;
}
}
+static void
+attach_tcb(struct tcb *const tcp)
+{
+ if (ptrace_attach_or_seize(tcp->pid) < 0) {
+ perror_msg("attach: ptrace(%s, %d)",
+ ptrace_attach_cmd, tcp->pid);
+ droptcb(tcp);
+ return;
+ }
+
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
+ newoutf(tcp);
+ if (debug_flag)
+ error_msg("attach to pid %d (main) succeeded", tcp->pid);
+
+ char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
+ DIR *dir;
+ unsigned int ntid = 0, nerr = 0;
+
+ if (followfork && tcp->pid != strace_child &&
+ sprintf(procdir, "/proc/%d/task", tcp->pid) > 0 &&
+ (dir = opendir(procdir)) != NULL) {
+ struct_dirent *de;
+
+ while ((de = read_dir(dir)) != NULL) {
+ if (de->d_fileno == 0)
+ continue;
+
+ int tid = string_to_uint(de->d_name);
+ if (tid <= 0 || tid == tcp->pid)
+ continue;
+
+ ++ntid;
+ if (ptrace_attach_or_seize(tid) < 0) {
+ ++nerr;
+ if (debug_flag)
+ perror_msg("attach: ptrace(%s, %d)",
+ ptrace_attach_cmd, tid);
+ continue;
+ }
+ if (debug_flag)
+ error_msg("attach to pid %d succeeded", tid);
+
+ struct tcb *tid_tcp = alloctcb(tid);
+ tid_tcp->flags |= TCB_ATTACHED | TCB_STARTUP |
+ post_attach_sigstop;
+ newoutf(tid_tcp);
+ }
+
+ closedir(dir);
+ }
+
+ if (!qflag) {
+ if (ntid > nerr)
+ error_msg("Process %u attached"
+ " with %u threads",
+ tcp->pid, ntid - nerr + 1);
+ else
+ error_msg("Process %u attached",
+ tcp->pid);
+ }
+}
+
static void
startup_attach(void)
{
if (tcp->pid == parent_pid || tcp->pid == strace_tracer_pid) {
errno = EPERM;
- perror_msg("attach: %d", tcp->pid);
+ perror_msg("attach: pid %d", tcp->pid);
droptcb(tcp);
continue;
}
- if (followfork && tcp->pid != strace_child) {
- char procdir[sizeof("/proc/%d/task") + sizeof(int) * 3];
- DIR *dir;
-
- sprintf(procdir, "/proc/%d/task", tcp->pid);
- dir = opendir(procdir);
- if (dir != NULL) {
- unsigned int ntid = 0, nerr = 0;
- struct_dirent *de;
- while ((de = read_dir(dir)) != NULL) {
- struct tcb *cur_tcp;
- int tid;
+ attach_tcb(tcp);
- if (de->d_fileno == 0)
- continue;
- tid = string_to_uint(de->d_name);
- if (tid <= 0)
- continue;
- ++ntid;
- if (ptrace_attach_or_seize(tid) < 0) {
- ++nerr;
- if (debug_flag)
- error_msg("attach to pid %d failed", tid);
- continue;
- }
- if (debug_flag)
- error_msg("attach to pid %d succeeded", tid);
- cur_tcp = tcp;
- if (tid != tcp->pid)
- cur_tcp = alloctcb(tid);
- cur_tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
- newoutf(cur_tcp);
- }
- closedir(dir);
- if (interactive) {
- sigprocmask(SIG_SETMASK, &empty_set, NULL);
- if (interrupted)
- goto ret;
- sigprocmask(SIG_BLOCK, &blocked_set, NULL);
- }
- ntid -= nerr;
- if (ntid == 0) {
- perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
- droptcb(tcp);
- continue;
- }
- if (!qflag) {
- if (ntid > 1)
- error_msg("Process %u attached"
- " with %u threads",
- tcp->pid, ntid);
- else
- error_msg("Process %u attached",
- tcp->pid);
- }
- if (!(tcp->flags & TCB_ATTACHED)) {
- /* -p PID, we failed to attach to PID itself
- * but did attach to some of its sibling threads.
- * Drop PID's tcp.
- */
- droptcb(tcp);
- }
- continue;
- } /* if (opendir worked) */
- } /* if (-f) */
- if (ptrace_attach_or_seize(tcp->pid) < 0) {
- perror_msg("attach: ptrace(PTRACE_ATTACH, ...)");
- droptcb(tcp);
- continue;
+ if (interactive) {
+ sigprocmask(SIG_SETMASK, &empty_set, NULL);
+ if (interrupted)
+ goto ret;
+ sigprocmask(SIG_BLOCK, &blocked_set, NULL);
}
- tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
- newoutf(tcp);
- if (debug_flag)
- error_msg("attach to pid %d (main) succeeded", tcp->pid);
-
- if (!qflag)
- error_msg("Process %u attached", tcp->pid);
} /* for each tcbtab[] */
if (daemonized_tracer) {
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)
{
if (pipe(fds))
perror_msg_and_die("pipe");
close(fds[1]);
+ set_cloexec_flag(fds[0]);
return fds[0];
}
+/* placeholder fds status for stdin and stdout */
+static bool fd_is_placeholder[2];
+
+/*
+ * Ensure that all standard file descriptors are open by opening placeholder
+ * file descriptors for those standard file descriptors that are not open.
+ *
+ * The information which descriptors have been made open is saved
+ * in fd_is_placeholder for later use.
+ */
+static void
+ensure_standard_fds_opened(void)
+{
+ int fd;
+
+ while ((fd = open_dummy_desc()) <= 2) {
+ if (fd == 2)
+ break;
+ fd_is_placeholder[fd] = true;
+ }
+
+ if (fd > 2)
+ close(fd);
+}
+
+/*
+ * Redirect stdin and stdout unless they have been opened earlier
+ * by ensure_standard_fds_opened as placeholders.
+ */
+static void
+redirect_standard_fds(void)
+{
+ int i;
+
+ /*
+ * It might be a good idea to redirect stderr as well,
+ * but we sometimes need to print error messages.
+ */
+ for (i = 0; i <= 1; ++i) {
+ if (!fd_is_placeholder[i]) {
+ close(i);
+ open_dummy_desc();
+ }
+ }
+}
+
static void
startup_child(char **argv)
{
}
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.
if (ptrace_attach_or_seize(pid)) {
kill_save_errno(pid, SIGKILL);
- perror_msg_and_die("Can't attach to %d", pid);
+ perror_msg_and_die("attach: ptrace(%s, %d)",
+ ptrace_attach_cmd, pid);
}
if (!NOMMU_SYSTEM)
kill(pid, SIGCONT);
}
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 {
/* 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
if (WIFSIGNALED(status)) {
return;
}
- error_msg_and_die("%s: unexpected wait status %x",
- __func__, status);
+ error_msg_and_die("%s: unexpected wait status %#x",
+ __func__, status);
}
}
#else /* !USE_SEIZE */
break;
case 'r':
rflag = 1;
- /* fall through to tflag++ */
+ break;
case 't':
tflag++;
break;
qualify("abbrev=none");
break;
case 'V':
- printf("%s -- version %s\n", PACKAGE_NAME, VERSION);
+ print_version();
exit(0);
break;
case 'z':
die_out_of_memory();
break;
case 'I':
- opt_intr = string_to_uint(optarg);
- if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS)
+ opt_intr = string_to_uint_upto(optarg, NUM_INTR_OPTS - 1);
+ if (opt_intr <= 0)
error_opt_arg(c, optarg);
break;
default:
error_msg("-%c has no effect with -c", 'y');
}
+ if (rflag) {
+ if (tflag > 1)
+ error_msg("-tt has no effect with -r");
+ tflag = 1;
+ }
+
#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. */
error_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) {
}
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)
* 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;
startup_child(argv);
}
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
+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) {
+ current_tcp = printing_tcp;
+ tprints(" <unfinished ...>\n");
+ fflush(printing_tcp->outf);
+ printing_tcp->curcol = 0;
+ 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 ...>");
+ }
+ tprints(") ");
+ tabto();
+ tprints("= ?\n");
+ line_ended();
+}
+
/* Returns true iff the main trace loop has to continue. */
static bool
trace(void)
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;
+ if (detach_on_execve) {
+ if (tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
+ tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
+ } else {
+ detach(tcp); /* do "-b execve" thingy */
+ return true;
+ }
}
- skip_one_b_execve = 0;
}
/* Set current output file */
sig = WSTOPSIG(status);
- if (event != 0) {
- /* Ptrace event */
+ switch (event) {
+ case 0:
+ break;
+ case PTRACE_EVENT_EXIT:
+ print_event_exit(tcp);
+ goto restart_tracee_with_sig_0;
#if USE_SEIZE
- if (event == PTRACE_EVENT_STOP) {
+ case PTRACE_EVENT_STOP:
/*
* PTRACE_INTERRUPT-stop or group-stop.
* PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
stopped = true;
goto show_stopsig;
}
- }
+ /* fall through */
#endif
- goto restart_tracee_with_sig_0;
+ default:
+ goto restart_tracee_with_sig_0;
}
/*
* 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;
+ stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, &si) < 0;
#if USE_SEIZE
show_stopsig:
#endif
* This should be syscall entry or exit.
* Handle it.
*/
- if (trace_syscall(tcp) < 0) {
+ sig = 0;
+ if (trace_syscall(tcp, &sig) < 0) {
/*
* ptrace() failed in trace_syscall().
* Likely a result of process disappearing mid-flight.
*/
return true;
}
+ goto restart_tracee;
restart_tracee_with_sig_0:
sig = 0;
{
init(argc, argv);
+ exit_code = !nprocs;
+
while (trace())
;