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;
\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\
* 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;
}
if (tcp->pid == 0)
return;
+ int p;
+ for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
+ free(tcp->fault_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;
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)
{
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
break;
case 'r':
rflag = 1;
- /* fall through to tflag++ */
+ break;
case 't':
tflag++;
break;
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
{
init(argc, argv);
+ exit_code = !nprocs;
+
while (trace())
;