extern int optind;
extern char *optarg;
+#ifdef USE_LIBUNWIND
+/* if this is true do the stack trace for every system call */
+bool stack_trace_enabled = false;
+#endif
#if defined __NR_tkill
# define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig))
bool need_fork_exec_workarounds = 0;
bool debug_flag = 0;
bool Tflag = 0;
+bool iflag = 0;
+bool count_wallclock = 0;
unsigned int qflag = 0;
/* Which WSTOPSIG(status) value marks syscall traps? */
static unsigned int syscall_trap_sig = SIGTRAP;
static unsigned int tflag = 0;
-static bool iflag = 0;
static bool rflag = 0;
static bool print_pid_pfx = 0;
bool not_failing_only = 0;
/* Show path associated with fd arguments */
-bool show_fd_path = 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? */
unsigned os_release; /* generated from uname()'s u.release */
static void detach(struct tcb *tcp);
-static int trace(void);
static void cleanup(void);
static void interrupt(int sig);
static sigset_t empty_set, blocked_set;
-p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\
-c -- count time, calls, and errors for each syscall and report summary\n\
-C -- like -c but also print regular output\n\
+-w -- summarise syscall latency (default is system time)\n\
-d -- enable debug output to stderr\n\
-D -- run tracer process as a detached grandchild, not as parent\n\
-f -- follow forks, -ff -- with output into separate files\n\
-v -- verbose mode: print unabbreviated argv, stat, termios, etc. args\n\
-x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\
-y -- print paths associated with file descriptor arguments\n\
+-yy -- print ip:port pairs associated with socket file descriptors\n\
-h -- print help message, -V -- print version\n\
-a column -- alignment COLUMN for printing syscall results (default %d)\n\
-b execve -- detach on this syscall\n\
-E var -- remove var from the environment for command\n\
-P path -- trace accesses to path\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\
*/
{
int r;
if (!use_seize)
- return ptrace(PTRACE_ATTACH, pid, 0, 0);
- r = ptrace(PTRACE_SEIZE, pid, 0, 0);
+ return ptrace(PTRACE_ATTACH, pid, 0L, 0L);
+ r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long)ptrace_setoptions);
if (r)
return r;
- r = ptrace(PTRACE_INTERRUPT, pid, 0, 0);
+ r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
return r;
}
#else
}
}
-#if _LFS64_LARGEFILE
-# define fopen_for_output fopen64
+#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
strace_popen(const char *command)
{
FILE *fp;
+ int pid;
int fds[2];
swap_uid();
set_cloexec_flag(fds[1]); /* never fails */
- popen_pid = vfork();
- if (popen_pid == -1)
+ pid = vfork();
+ if (pid < 0)
perror_msg_and_die("vfork");
- if (popen_pid == 0) {
+ if (pid == 0) {
/* child */
close(fds[1]);
if (fds[0] != 0) {
}
/* parent */
+ popen_pid = pid;
close(fds[0]);
swap_uid();
fp = fdopen(fds[1], "w");
va_end(args);
}
+#ifndef HAVE_FPUTS_UNLOCKED
+# define fputs_unlocked fputs
+#endif
+
void
tprints(const char *str)
{
}
}
if (iflag)
- printcall(tcp);
+ print_pc(tcp);
}
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. */
- int i = tcbtabsize;
+ unsigned int i = tcbtabsize;
struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0]));
struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0]));
if (!newtab || !newtcbs)
static struct tcb *
alloctcb(int pid)
{
- int i;
+ unsigned int i;
struct tcb *tcp;
if (nprocs == tcbtabsize)
for (i = 0; i < tcbtabsize; i++) {
tcp = tcbtab[i];
- if ((tcp->flags & TCB_INUSE) == 0) {
+ if (!tcp->pid) {
memset(tcp, 0, sizeof(*tcp));
tcp->pid = pid;
- tcp->flags = TCB_INUSE;
#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)
fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
if (tcp->pid == 0)
return;
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled) {
+ unwind_tcb_fin(tcp);
+ }
+#endif
+
nprocs--;
if (debug_flag)
fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs);
* 3. Attach SIGSTOP was already pending (TCB_IGNORE_ONE_SIGSTOP set)
*/
for (;;) {
- int sig;
+ unsigned int sig;
if (waitpid(tcp->pid, &status, __WALL) < 0) {
if (errno == EINTR)
continue;
static void
startup_attach(void)
{
- int tcbi;
+ unsigned int tcbi;
struct tcb *tcp;
/*
for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
tcp = tcbtab[tcbi];
- if (!(tcp->flags & TCB_INUSE))
+ if (!tcp->pid)
continue;
/* Is this a process we should attach to, but not yet attached? */
#endif /* USE_DEBUGGING_EXEC */
else {
const char *path;
- int m, n, len;
+ size_t m, n, len;
for (path = getenv("PATH"); path && *path; path += m) {
const char *colon = strchr(path, ':');
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
#endif
- strace_child = pid = fork();
+ pid = fork();
if (pid < 0) {
perror_msg_and_die("fork");
}
/* We are the tracer */
if (!daemonized_tracer) {
+ strace_child = pid;
if (!use_seize) {
/* child did PTRACE_TRACEME, nothing to do in parent */
} else {
}
tcp = alloctcb(pid);
if (!NOMMU_SYSTEM)
- tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP | post_attach_sigstop;
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
else
- tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP;
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP;
newoutf(tcp);
}
else {
*
* Use of this option enables correct handling of user-generated SIGTRAPs,
* and SIGTRAPs generated by special instructions such as int3 on x86:
- * _start: .globl _start
- * int3
- * movl $42, %ebx
- * movl $1, %eax
- * int $0x80
- * (compile with: "gcc -nostartfiles -nostdlib -o int3 int3.S")
+
+# compile with: gcc -nostartfiles -nostdlib -o int3 int3.S
+_start: .globl _start
+ int3
+ movl $42, %ebx
+ movl $1, %eax
+ int $0x80
*/
static int
test_ptrace_setoptions_for_all(void)
struct tcb *tcp;
int c, i;
int optF = 0;
+ unsigned int tcbi;
struct sigaction sa;
progname = argv[0] ? argv[0] : "strace";
tcp = calloc(tcbtabsize, sizeof(*tcp));
if (!tcp)
die_out_of_memory();
- for (c = 0; c < tcbtabsize; c++)
- tcbtab[c] = tcp++;
+ for (tcbi = 0; tcbi < tcbtabsize; tcbi++)
+ tcbtab[tcbi] = tcp++;
shared_log = stderr;
set_sortby(DEFAULT_SORTBY);
#endif
qualify("signal=all");
while ((c = getopt(argc, argv,
- "+b:cCdfFhiqrtTvVxyz"
+ "+b:cCdfFhiqrtTvVwxyz"
+#ifdef USE_LIBUNWIND
+ "k"
+#endif
"D"
"a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
switch (c) {
case 'T':
Tflag = 1;
break;
+ case 'w':
+ count_wallclock = 1;
+ break;
case 'x':
xflag++;
break;
case 'y':
- show_fd_path = 1;
+ show_fd_path++;
break;
case 'v':
qualify("abbrev=none");
case 'u':
username = strdup(optarg);
break;
+#ifdef USE_LIBUNWIND
+ case 'k':
+ stack_trace_enabled = true;
+ break;
+#endif
case 'E':
if (putenv(optarg) < 0)
die_out_of_memory();
error_msg_and_die("(-c or -C) and -ff are mutually exclusive");
}
+ if (count_wallclock && !cflag) {
+ error_msg_and_die("-w must be given with (-c or -C)");
+ }
+
+ if (cflag == CFLAG_ONLY_STATS) {
+ if (iflag)
+ error_msg("-%c has no effect with -c", 'i');
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ error_msg("-%c has no effect with -c", 'k');
+#endif
+ if (rflag)
+ error_msg("-%c has no effect with -c", 'r');
+ if (tflag)
+ error_msg("-%c has no effect with -c", 't');
+ if (Tflag)
+ error_msg("-%c has no effect with -c", 'T');
+ if (show_fd_path)
+ error_msg("-%c has no effect with -c", 'y');
+ }
+
+#ifdef USE_LIBUNWIND
+ if (stack_trace_enabled)
+ unwind_init();
+#endif
+
/* See if they want to run as another user. */
if (username != NULL) {
struct passwd *pent;
static struct tcb *
pid2tcb(int pid)
{
- int i;
+ unsigned int i;
if (pid <= 0)
return NULL;
for (i = 0; i < tcbtabsize; i++) {
struct tcb *tcp = tcbtab[i];
- if (tcp->pid == pid && (tcp->flags & TCB_INUSE))
+ if (tcp->pid == pid)
return tcp;
}
static void
cleanup(void)
{
- int i;
+ unsigned int i;
struct tcb *tcp;
int fatal_sig;
for (i = 0; i < tcbtabsize; i++) {
tcp = tcbtab[i];
- if (!(tcp->flags & TCB_INUSE))
+ if (!tcp->pid)
continue;
if (debug_flag)
fprintf(stderr,
"cleanup: looking at pid %u\n", tcp->pid);
- if (tcp->flags & TCB_STRACE_CHILD) {
+ if (tcp->pid == strace_child) {
kill(tcp->pid, SIGCONT);
kill(tcp->pid, fatal_sig);
}
interrupted = sig;
}
-static int
+static void
trace(void)
{
struct rusage ru;
- while (nprocs != 0) {
+ /* Used to be "while (nprocs != 0)", but in this testcase:
+ * int main() { _exit(!!fork()); }
+ * under strace -f, parent sometimes (rarely) manages
+ * to exit before we see the first stop of the child,
+ * and we are losing track of it:
+ * 19923 clone(...) = 19924
+ * 19923 exit_group(1) = ?
+ * 19923 +++ exited with 1 +++
+ * Waiting for ECHILD works better.
+ * (However, if -o|logger is in use, we can't do that.
+ * Can work around that by double-forking the logger,
+ * but that loses the ability to wait for its completion on exit.
+ * Oh well...)
+ */
+ while (1) {
int pid;
int wait_errno;
- int status, sig;
+ int status;
int stopped;
- struct tcb *tcp;
+ unsigned int sig;
unsigned event;
+ struct tcb *tcp;
if (interrupted)
- return 0;
+ return;
+
+ if (popen_pid != 0 && nprocs == 0)
+ return;
if (interactive)
sigprocmask(SIG_SETMASK, &empty_set, NULL);
if (pid < 0) {
if (wait_errno == EINTR)
continue;
- if (wait_errno == ECHILD)
- /* Should not happen since nprocs > 0 */
- return 0;
+ if (nprocs == 0 && wait_errno == ECHILD)
+ return;
+ /* If nprocs > 0, ECHILD is not expected,
+ * treat it as any other error here:
+ */
errno = wait_errno;
- perror_msg("wait4(__WALL)");
- return -1;
+ perror_msg_and_die("wait4(__WALL)");
}
if (pid == popen_pid) {
- if (WIFEXITED(status) || WIFSIGNALED(status))
+ if (!WIFSTOPPED(status))
popen_pid = 0;
continue;
}
event = ((unsigned)status >> 16);
if (debug_flag) {
char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
- char evbuf[sizeof(",PTRACE_EVENT_?? (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
+ char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
strcpy(buf, "???");
if (WIFSIGNALED(status))
#ifdef WCOREDUMP
if (WIFSTOPPED(status))
sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
#ifdef WIFCONTINUED
+ /* Should never be seen */
if (WIFCONTINUED(status))
strcpy(buf, "WIFCONTINUED");
#endif
[PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE",
[PTRACE_EVENT_EXEC] = "EXEC",
[PTRACE_EVENT_EXIT] = "EXIT",
+ /* [PTRACE_EVENT_STOP (=128)] would make biggish array */
};
- const char *e;
+ const char *e = "??";
if (event < ARRAY_SIZE(event_names))
e = event_names[event];
- else {
- sprintf(buf, "?? (%u)", event);
- e = buf;
- }
- sprintf(evbuf, ",PTRACE_EVENT_%s", e);
+ else if (event == PTRACE_EVENT_STOP)
+ e = "STOP";
+ sprintf(evbuf, ",EVENT_%s (%u)", e, event);
}
- fprintf(stderr, " [wait(0x%04x) = %u] %s%s\n", status, pid, buf, evbuf);
+ fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
}
/* Look up 'pid' in our table. */
tcp = pid2tcb(pid);
if (!tcp) {
+ if (!WIFSTOPPED(status)) {
+ /* This can happen if we inherited
+ * an unknown child. Example:
+ * (sleep 1 & exec strace sleep 2)
+ */
+ error_msg("Exit of unknown pid %u seen", pid);
+ continue;
+ }
if (followfork) {
+ /* We assume it's a fork/vfork/clone child */
tcp = alloctcb(pid);
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
newoutf(tcp);
pid);
} else {
/* This can happen if a clone call used
- CLONE_PTRACE itself. */
- if (WIFSTOPPED(status))
- ptrace(PTRACE_CONT, pid, (char *) 0, 0);
- error_msg_and_die("Unknown pid: %u", pid);
+ * CLONE_PTRACE itself.
+ */
+ ptrace(PTRACE_CONT, pid, (char *) 0, 0);
+ error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
+ continue;
}
}
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
goto dont_switch_tcbs;
+ /* Avoid truncation in pid2tcb() param passing */
if (old_pid <= 0 || old_pid == pid)
goto dont_switch_tcbs;
+ if ((unsigned long) old_pid > UINT_MAX)
+ goto dont_switch_tcbs;
execve_thread = pid2tcb(old_pid);
/* It should be !NULL, but I feel paranoid */
if (!execve_thread)
if (clearbpt(tcp) < 0) {
/* Pretty fatal */
droptcb(tcp);
- cleanup();
- return -1;
+ exit_code = 1;
+ return;
}
}
- if (ptrace_setoptions) {
+ if (!use_seize && ptrace_setoptions) {
if (debug_flag)
- fprintf(stderr, "setting opts %x on pid %d\n", ptrace_setoptions, tcp->pid);
+ fprintf(stderr, "setting opts 0x%x on pid %d\n", ptrace_setoptions, tcp->pid);
if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) {
if (errno != ESRCH) {
/* Should never happen, really */
&& !hide_log_until_execve
&& (qual_flags[sig] & QUAL_SIGNAL)
) {
-#if defined(PT_CR_IPSR) && defined(PT_CR_IIP)
- long pc = 0;
- long psr = 0;
-
- upeek(tcp, PT_CR_IPSR, &psr);
- upeek(tcp, PT_CR_IIP, &pc);
-
-# define PSR_RI 41
- pc += (psr >> PSR_RI) & 0x3;
-# define PC_FORMAT_STR " @ %lx"
-# define PC_FORMAT_ARG , pc
-#else
-# define PC_FORMAT_STR ""
-# define PC_FORMAT_ARG /* nothing */
-#endif
printleader(tcp);
if (!stopped) {
tprintf("--- %s ", signame(sig));
printsiginfo(&si, verbose(tcp));
- tprintf(PC_FORMAT_STR " ---\n"
- PC_FORMAT_ARG);
+ tprints(" ---\n");
} else
- tprintf("--- stopped by %s" PC_FORMAT_STR " ---\n",
- signame(sig)
- PC_FORMAT_ARG);
+ tprintf("--- stopped by %s ---\n",
+ signame(sig));
line_ended();
}
* (that is, process really stops. It used to continue to run).
*/
if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
- cleanup();
- return -1;
+ /* Note: ptrace_restart emitted error message */
+ exit_code = 1;
+ return;
}
continue;
}
/* We handled quick cases, we are permitted to interrupt now. */
if (interrupted)
- return 0;
+ return;
/* This should be syscall entry or exit.
* (Or it still can be that pesky post-execve SIGTRAP!)
sig = 0;
restart_tracee:
if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
- cleanup();
- return -1;
+ /* Note: ptrace_restart emitted error message */
+ exit_code = 1;
+ return;
}
- }
- return 0;
+ } /* while (1) */
}
int
init(argc, argv);
/* Run main tracing loop */
- if (trace() < 0)
- return 1;
+ trace();
cleanup();
fflush(NULL);