#include "defs.h"
#include <sys/types.h>
+#include <stdarg.h>
#include <signal.h>
#include <errno.h>
#include <sys/param.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
-#include <limits.h>
#include <dirent.h>
#ifdef LINUX
# include <asm/unistd.h>
-# if defined __NR_tgkill
-# define my_tgkill(pid, tid, sig) syscall (__NR_tgkill, (pid), (tid), (sig))
-# elif defined __NR_tkill
-# define my_tgkill(pid, tid, sig) syscall (__NR_tkill, (tid), (sig))
+# 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 "Neither tkill(2) nor tgkill(2) available, risk of strace hangs!"
-# define my_tgkill(pid, tid, sig) kill ((tid), (sig))
+# define my_tkill(tid, sig) kill((tid), (sig))
# endif
#endif
int debug = 0, followfork = 0;
-int dtime = 0, cflag = 0, xflag = 0, qflag = 0;
+unsigned int ptrace_setoptions = 0;
+/* Which WSTOPSIG(status) value marks syscall traps? */
+static unsigned int syscall_trap_sig = SIGTRAP;
+int dtime = 0, xflag = 0, qflag = 0;
+cflag_t cflag = CFLAG_NONE;
static int iflag = 0, interactive = 0, pflag_seen = 0, rflag = 0, tflag = 0;
/*
* daemonized_tracer supports -D option.
/* Sometimes we want to print only succeeding syscalls. */
int not_failing_only = 0;
+/* Show path associated with fd arguments */
+int show_fd_path = 0;
+
+/* are we filtering traces based on paths? */
+int tracing_paths = 0;
+
static int exit_code = 0;
static int strace_child = 0;
+static int strace_tracer_pid = 0;
static char *username = NULL;
-uid_t run_uid;
-gid_t run_gid;
+static uid_t run_uid;
+static gid_t run_gid;
-int acolumn = DEFAULT_ACOLUMN;
int max_strlen = DEFAULT_STRLEN;
+static int acolumn = DEFAULT_ACOLUMN;
+static char *acolumn_spaces;
static char *outfname = NULL;
-FILE *outf;
-struct tcb **tcbtab;
-unsigned int nprocs, tcbtabsize;
-char *progname;
-extern char **environ;
-
-static int detach P((struct tcb *tcp, int sig));
-static int trace P((void));
-static void cleanup P((void));
-static void interrupt P((int sig));
+static FILE *outf;
+static int curcol;
+static struct tcb **tcbtab;
+static unsigned int nprocs, tcbtabsize;
+static const char *progname;
+
+static int detach(struct tcb *tcp, int sig);
+static int trace(void);
+static void cleanup(void);
+static void interrupt(int sig);
static sigset_t empty_set, blocked_set;
#ifdef HAVE_SIG_ATOMIC_T
static volatile sig_atomic_t interrupted;
#else /* !HAVE_SIG_ATOMIC_T */
-#ifdef __STDC__
static volatile int interrupted;
-#else /* !__STDC__ */
-static int interrupted;
-#endif /* !__STDC__ */
#endif /* !HAVE_SIG_ATOMIC_T */
#ifdef USE_PROCFS
-static struct tcb *pfd2tcb P((int pfd));
-static void reaper P((int sig));
-static void rebuild_pollv P((void));
+static struct tcb *pfd2tcb(int pfd);
+static void reaper(int sig);
+static void rebuild_pollv(void);
static struct pollfd *pollv;
#ifndef HAVE_POLLABLE_PROCFS
-static void proc_poll_open P((void));
-static void proc_poller P((int pfd));
+static void proc_poll_open(void);
+static void proc_poller(int pfd);
struct proc_pollfd {
int fd;
#endif /* USE_PROCFS */
static void
-usage(ofp, exitval)
-FILE *ofp;
-int exitval;
+usage(FILE *ofp, int exitval)
{
fprintf(ofp, "\
-usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
+usage: strace [-CdDffhiqrtttTvVxxy] [-a column] [-e expr] ... [-o file]\n\
[-p pid] ... [-s strsize] [-u username] [-E var=val] ...\n\
- [command [arg ...]]\n\
- or: strace -c -D [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\n\
+ [-P path] [command [arg ...]]\n\
+ or: strace -c [-D] [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\n\
[command [arg ...]]\n\
-c -- count time, calls, and errors for each syscall and report summary\n\
+-C -- like -c but also print regular output while processes are running\n\
-f -- follow forks, -ff -- with output into separate files\n\
-F -- attempt to follow vforks, -h -- print help message\n\
-i -- print instruction pointer at time of syscall\n\
-T -- print time spent in each syscall, -V -- print version\n\
-v -- verbose mode: print unabbreviated argv, stat, termio[s], 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\
-a column -- alignment COLUMN for printing syscall results (default %d)\n\
-e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
options: trace, abbrev, verbose, raw, signal, read, or write\n\
-u username -- run command as username handling setuid and/or setgid\n\
-E var=val -- put var=val in the environment for command\n\
-E var -- remove var from the environment for command\n\
+-P path -- trace accesses to path\n\
" /* this is broken, so don't document it
-z -- print only succeeding syscalls\n\
*/
exit(exitval);
}
+static void die(void) __attribute__ ((noreturn));
+static void die(void)
+{
+ if (strace_tracer_pid == getpid()) {
+ cflag = 0;
+ cleanup();
+ }
+ exit(1);
+}
+
+static void verror_msg(int err_no, const char *fmt, va_list p)
+{
+ fflush(NULL);
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, fmt, p);
+ if (err_no)
+ fprintf(stderr, ": %s\n", strerror(err_no));
+ else
+ putc('\n', stderr);
+ 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 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();
+}
+
+void die_out_of_memory(void)
+{
+ static bool recursed = 0;
+ if (recursed)
+ exit(1);
+ recursed = 1;
+ error_msg_and_die("Out of memory");
+}
+
#ifdef SVR4
#ifdef MIPS
void
# define fork() vfork()
#endif
-static int
+static void
set_cloexec_flag(int fd)
{
- int flags, newflags;
-
- if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
- {
- fprintf(stderr, "%s: fcntl F_GETFD: %s\n",
- progname, strerror(errno));
- return -1;
+ int flags, newflags;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0) {
+ /* Can happen only if fd is bad.
+ * Should never happen: if it does, we have a bug
+ * in the caller. Therefore we just abort
+ * instead of propagating the error.
+ */
+ perror_msg_and_die("fcntl(%d, F_GETFD)", fd);
}
newflags = flags | FD_CLOEXEC;
if (flags == newflags)
- return 0;
-
- if (fcntl(fd, F_SETFD, newflags) < 0)
- {
- fprintf(stderr, "%s: fcntl F_SETFD: %s\n",
- progname, strerror(errno));
- return -1;
- }
+ return;
- return 0;
+ fcntl(fd, F_SETFD, newflags); /* never fails */
}
/*
#ifndef SVR4
int euid = geteuid(), uid = getuid();
- if (euid != uid && setreuid(euid, uid) < 0)
- {
- fprintf(stderr, "%s: setreuid: %s\n",
- progname, strerror(errno));
- exit(1);
+ if (euid != uid && setreuid(euid, uid) < 0) {
+ perror_msg_and_die("setreuid");
}
#endif
}
#endif
static FILE *
-strace_fopen(const char *path, const char *mode)
+strace_fopen(const char *path)
{
FILE *fp;
swap_uid();
- if ((fp = fopen_for_output(path, mode)) == NULL)
- fprintf(stderr, "%s: can't fopen '%s': %s\n",
- progname, path, strerror(errno));
+ fp = fopen_for_output(path, "w");
+ if (!fp)
+ perror_msg_and_die("Can't fopen '%s'", path);
swap_uid();
- if (fp && set_cloexec_flag(fileno(fp)) < 0)
- {
- fclose(fp);
- fp = NULL;
- }
+ set_cloexec_flag(fileno(fp));
return fp;
}
-static int popen_pid = -1;
+static int popen_pid = 0;
#ifndef _PATH_BSHELL
# define _PATH_BSHELL "/bin/sh"
static FILE *
strace_popen(const char *command)
{
- int fds[2];
+ FILE *fp;
+ int fds[2];
swap_uid();
if (pipe(fds) < 0)
- {
- fprintf(stderr, "%s: pipe: %s\n",
- progname, strerror(errno));
- swap_uid();
- return NULL;
- }
+ perror_msg_and_die("pipe");
- if (set_cloexec_flag(fds[1]) < 0)
- {
- close(fds[0]);
- close(fds[1]);
- swap_uid();
- return NULL;
- }
+ set_cloexec_flag(fds[1]); /* never fails */
- if ((popen_pid = fork()) == -1)
- {
- fprintf(stderr, "%s: fork: %s\n",
- progname, strerror(errno));
- close(fds[0]);
- close(fds[1]);
- swap_uid();
- return NULL;
- }
+ popen_pid = vfork();
+ if (popen_pid == -1)
+ perror_msg_and_die("vfork");
- if (popen_pid)
- {
- /* parent */
- close(fds[0]);
- swap_uid();
- return fdopen(fds[1], "w");
- } else
- {
+ if (popen_pid == 0) {
/* child */
close(fds[1]);
- if (fds[0] && (dup2(fds[0], 0) || close(fds[0])))
- {
- fprintf(stderr, "%s: dup2: %s\n",
- progname, strerror(errno));
- _exit(1);
+ if (fds[0] != 0) {
+ if (dup2(fds[0], 0))
+ perror_msg_and_die("dup2");
+ close(fds[0]);
}
execl(_PATH_BSHELL, "sh", "-c", command, NULL);
- fprintf(stderr, "%s: execl: %s: %s\n",
- progname, _PATH_BSHELL, strerror(errno));
- _exit(1);
+ perror_msg_and_die("Can't execute '%s'", _PATH_BSHELL);
}
+
+ /* parent */
+ close(fds[0]);
+ swap_uid();
+ fp = fdopen(fds[1], "w");
+ if (!fp)
+ die_out_of_memory();
+ return fp;
}
-static int
+static void
newoutf(struct tcb *tcp)
{
if (outfname && followfork > 1) {
char name[520 + sizeof(int) * 3];
- FILE *fp;
-
sprintf(name, "%.512s.%u", outfname, tcp->pid);
- if ((fp = strace_fopen(name, "w")) == NULL)
- return -1;
- tcp->outf = fp;
+ tcp->outf = strace_fopen(name);
}
- return 0;
}
static void
* Block user interruptions as we would leave the traced
* process stopped (process state T) if we would terminate in
* between PTRACE_ATTACH and wait4 () on SIGSTOP.
- * We rely on cleanup () from this point on.
+ * We rely on cleanup() from this point on.
*/
if (interactive)
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
}
if (pid) { /* parent */
/*
- * Wait for child to attach to straced process
- * (our parent). Child SIGKILLs us after it attached.
- * Parent's wait() is unblocked by our death,
+ * Wait for grandchild to attach to straced process
+ * (grandparent). Grandchild SIGKILLs us after it attached.
+ * Grandparent's wait() is unblocked by our death,
* it proceeds to exec the straced program.
*/
pause();
_exit(0); /* paranoia */
}
+ /* grandchild */
+ /* We will be the tracer process. Remember our new pid: */
+ strace_tracer_pid = getpid();
}
for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
tcp = tcbtab[tcbi];
+
if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED))
continue;
#ifdef LINUX
- if (tcp->flags & TCB_CLONE_THREAD)
+ if (tcp->flags & TCB_ATTACH_DONE)
continue;
#endif
/* Reinitialize the output since it may have changed. */
tcp->outf = outf;
- if (newoutf(tcp) < 0)
- exit(1);
+ newoutf(tcp);
#ifdef USE_PROCFS
if (proc_open(tcp, 1) < 0) {
if (tid <= 0)
continue;
++ntid;
- if (ptrace(PTRACE_ATTACH, tid, (char *) 1, 0) < 0)
+ if (ptrace(PTRACE_ATTACH, tid, (char *) 1, 0) < 0) {
++nerr;
- else if (tid != tcbtab[tcbi]->pid) {
- tcp = alloctcb(tid);
- tcp->flags |= TCB_ATTACHED|TCB_CLONE_THREAD|TCB_CLONE_DETACHED|TCB_FOLLOWFORK;
- tcbtab[tcbi]->nchildren++;
- tcbtab[tcbi]->nclone_threads++;
- tcbtab[tcbi]->nclone_detached++;
- tcp->parent = tcbtab[tcbi];
+ if (debug)
+ fprintf(stderr, "attach to pid %d failed\n", tid);
+ }
+ else {
+ if (debug)
+ fprintf(stderr, "attach to pid %d succeeded\n", tid);
+ if (tid != tcp->pid) {
+ struct tcb *new_tcp = alloctcb(tid);
+ new_tcp->flags |= TCB_ATTACHED|TCB_ATTACH_DONE;
+ }
}
if (interactive) {
sigprocmask(SIG_SETMASK, &empty_set, NULL);
if (interrupted)
- return;
+ goto ret;
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
}
}
fprintf(stderr, ntid > 1
? "Process %u attached with %u threads - interrupt to quit\n"
: "Process %u attached - interrupt to quit\n",
- tcbtab[tcbi]->pid, ntid);
+ tcp->pid, ntid);
}
continue;
} /* if (opendir worked) */
} /* if (-f) */
-# endif
+# endif /* LINUX */
if (ptrace(PTRACE_ATTACH, tcp->pid, (char *) 1, 0) < 0) {
perror("attach: ptrace(PTRACE_ATTACH, ...)");
droptcb(tcp);
continue;
}
- /* INTERRUPTED is going to be checked at the top of TRACE. */
+ if (debug)
+ fprintf(stderr, "attach to pid %d (main) succeeded\n", tcp->pid);
if (daemonized_tracer) {
/*
fprintf(stderr,
"Process %u attached - interrupt to quit\n",
tcp->pid);
+ } /* for each tcbtab[] */
+
+ ret:
+#ifdef LINUX
+ /* TCB_ATTACH_DONE flag is used only in this function */
+ for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
+ tcp = tcbtab[tcbi];
+ tcp->flags &= ~TCB_ATTACH_DONE;
}
+#endif
if (interactive)
sigprocmask(SIG_SETMASK, &empty_set, NULL);
}
static void
-startup_child (char **argv)
+startup_child(char **argv)
{
struct stat statbuf;
const char *filename;
if (strchr(filename, '/')) {
if (strlen(filename) > sizeof pathname - 1) {
errno = ENAMETOOLONG;
- perror("strace: exec");
- exit(1);
+ perror_msg_and_die("exec");
}
strcpy(pathname, filename);
}
strcpy(pathname, filename);
#endif /* USE_DEBUGGING_EXEC */
else {
- char *path;
+ const char *path;
int m, n, len;
for (path = getenv("PATH"); path && *path; path += m) {
}
}
if (stat(pathname, &statbuf) < 0) {
- fprintf(stderr, "%s: %s: command not found\n",
- progname, filename);
- exit(1);
+ perror_msg_and_die("Can't stat '%s'", filename);
}
strace_child = pid = fork();
if (pid < 0) {
- perror("strace: fork");
- cleanup();
- exit(1);
+ perror_msg_and_die("fork");
}
- if ((pid != 0 && daemonized_tracer) /* parent: to become a traced process */
- || (pid == 0 && !daemonized_tracer) /* child: to become a traced process */
+ if ((pid != 0 && daemonized_tracer) /* -D: parent to become a traced process */
+ || (pid == 0 && !daemonized_tracer) /* not -D: child to become a traced process */
) {
pid = getpid();
#ifdef USE_PROCFS
- if (outf != stderr) close (fileno (outf));
+ if (outf != stderr) close(fileno(outf));
#ifdef MIPS
/* Kludge for SGI, see proc_open for details. */
sa.sa_handler = foobar;
kill(pid, SIGSTOP); /* stop HERE */
#endif /* FREEBSD */
#else /* !USE_PROCFS */
- if (outf!=stderr)
- close(fileno (outf));
+ if (outf != stderr)
+ close(fileno(outf));
if (!daemonized_tracer) {
if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) {
- perror("strace: ptrace(PTRACE_TRACEME, ...)");
- exit(1);
+ perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)");
}
if (debug)
kill(pid, SIGSTOP);
*/
if (username != NULL) {
if (initgroups(username, run_gid) < 0) {
- perror("initgroups");
- exit(1);
+ perror_msg_and_die("initgroups");
}
if (setregid(run_gid, run_egid) < 0) {
- perror("setregid");
- exit(1);
+ perror_msg_and_die("setregid");
}
if (setreuid(run_uid, run_euid) < 0) {
- perror("setreuid");
- exit(1);
+ perror_msg_and_die("setreuid");
}
}
}
#endif /* !USE_PROCFS */
execv(pathname, argv);
- perror("strace: exec");
- _exit(1);
+ perror_msg_and_die("exec");
}
/* We are the tracer. */
+ /* With -D, we are *child* here, IOW: different pid. Fetch it. */
+ strace_tracer_pid = getpid();
+
tcp = alloctcb(daemonized_tracer ? getppid() : pid);
if (daemonized_tracer) {
/* We want subsequent startup_attach() to attach to it. */
}
#ifdef USE_PROCFS
if (proc_open(tcp, 0) < 0) {
- fprintf(stderr, "trouble opening proc file\n");
- cleanup();
- exit(1);
+ perror_msg_and_die("trouble opening proc file");
}
#endif /* USE_PROCFS */
}
+#ifdef LINUX
+static void kill_save_errno(pid_t pid, int sig)
+{
+ int saved_errno = errno;
+
+ (void) kill(pid, sig);
+ errno = saved_errno;
+}
+
+/*
+ * Test whether the kernel support PTRACE_O_TRACECLONE et al options.
+ * First fork a new child, call ptrace with PTRACE_SETOPTIONS on it,
+ * and then see which options are supported by the kernel.
+ */
+static void
+test_ptrace_setoptions_followfork(void)
+{
+ int pid, expected_grandchild = 0, found_grandchild = 0;
+ const unsigned int test_options = PTRACE_O_TRACECLONE |
+ PTRACE_O_TRACEFORK |
+ PTRACE_O_TRACEVFORK;
+
+ pid = fork();
+ if (pid < 0)
+ perror_msg_and_die("fork");
+ if (pid == 0) {
+ pid = getpid();
+ if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
+ perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+ __func__);
+ kill(pid, SIGSTOP);
+ if (fork() < 0)
+ perror_msg_and_die("fork");
+ _exit(0);
+ }
+
+ while (1) {
+ int status, tracee_pid;
+
+ errno = 0;
+ tracee_pid = wait(&status);
+ if (tracee_pid <= 0) {
+ if (errno == EINTR)
+ continue;
+ else if (errno == ECHILD)
+ break;
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("%s: unexpected wait result %d",
+ __func__, tracee_pid);
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected exit status %u",
+ __func__, WEXITSTATUS(status));
+ }
+ continue;
+ }
+ if (WIFSIGNALED(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected signal %u",
+ __func__, WTERMSIG(status));
+ }
+ if (!WIFSTOPPED(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(tracee_pid, SIGKILL);
+ kill(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected wait status %x",
+ __func__, status);
+ }
+ if (tracee_pid != pid) {
+ found_grandchild = tracee_pid;
+ if (ptrace(PTRACE_CONT, tracee_pid, 0, 0) < 0) {
+ kill_save_errno(tracee_pid, SIGKILL);
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("PTRACE_CONT doesn't work");
+ }
+ continue;
+ }
+ switch (WSTOPSIG(status)) {
+ case SIGSTOP:
+ if (ptrace(PTRACE_SETOPTIONS, pid, 0, test_options) < 0
+ && errno != EINVAL && errno != EIO)
+ perror_msg("PTRACE_SETOPTIONS");
+ break;
+ case SIGTRAP:
+ if (status >> 16 == PTRACE_EVENT_FORK) {
+ long msg = 0;
+
+ if (ptrace(PTRACE_GETEVENTMSG, pid,
+ NULL, (long) &msg) == 0)
+ expected_grandchild = msg;
+ }
+ break;
+ }
+ if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) {
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("PTRACE_SYSCALL doesn't work");
+ }
+ }
+ if (expected_grandchild && expected_grandchild == found_grandchild) {
+ ptrace_setoptions |= test_options;
+ if (debug)
+ fprintf(stderr, "ptrace_setoptions = %#x\n",
+ ptrace_setoptions);
+ return;
+ }
+ error_msg("Test for PTRACE_O_TRACECLONE failed, "
+ "giving up using this feature.");
+}
+
+/*
+ * Test whether the kernel support PTRACE_O_TRACESYSGOOD.
+ * First fork a new child, call ptrace(PTRACE_SETOPTIONS) on it,
+ * and then see whether it will stop with (SIGTRAP | 0x80).
+ *
+ * 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")
+ */
+static void
+test_ptrace_setoptions_for_all(void)
+{
+ const unsigned int test_options = PTRACE_O_TRACESYSGOOD |
+ PTRACE_O_TRACEEXEC;
+ int pid;
+ int it_worked = 0;
+
+ pid = fork();
+ if (pid < 0)
+ perror_msg_and_die("fork");
+
+ if (pid == 0) {
+ pid = getpid();
+ if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0)
+ /* Note: exits with exitcode 1 */
+ perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+ __func__);
+ kill(pid, SIGSTOP);
+ _exit(0); /* parent should see entry into this syscall */
+ }
+
+ while (1) {
+ int status, tracee_pid;
+
+ errno = 0;
+ tracee_pid = wait(&status);
+ if (tracee_pid <= 0) {
+ if (errno == EINTR)
+ continue;
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("%s: unexpected wait result %d",
+ __func__, tracee_pid);
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == 0)
+ break;
+ error_msg_and_die("%s: unexpected exit status %u",
+ __func__, WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ error_msg_and_die("%s: unexpected signal %u",
+ __func__, WTERMSIG(status));
+ }
+ if (!WIFSTOPPED(status)) {
+ kill(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected wait status %x",
+ __func__, status);
+ }
+ if (WSTOPSIG(status) == SIGSTOP) {
+ /*
+ * We don't check "options aren't accepted" error.
+ * If it happens, we'll never get (SIGTRAP | 0x80),
+ * and thus will decide to not use the option.
+ * IOW: the outcome of the test will be correct.
+ */
+ if (ptrace(PTRACE_SETOPTIONS, pid, 0L, test_options) < 0
+ && errno != EINVAL && errno != EIO)
+ perror_msg("PTRACE_SETOPTIONS");
+ }
+ if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
+ it_worked = 1;
+ }
+ if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("PTRACE_SYSCALL doesn't work");
+ }
+ }
+
+ if (it_worked) {
+ syscall_trap_sig = (SIGTRAP | 0x80);
+ ptrace_setoptions |= test_options;
+ if (debug)
+ fprintf(stderr, "ptrace_setoptions = %#x\n",
+ ptrace_setoptions);
+ return;
+ }
+
+ error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
+ "giving up using this feature.");
+}
+#endif
+
int
main(int argc, char *argv[])
{
int optF = 0;
struct sigaction sa;
- static char buf[BUFSIZ];
-
progname = argv[0] ? argv[0] : "strace";
+ strace_tracer_pid = getpid();
+
/* Allocate the initial tcbtab. */
tcbtabsize = argc; /* Surely enough for all -p args. */
- if ((tcbtab = calloc(tcbtabsize, sizeof tcbtab[0])) == NULL) {
- fprintf(stderr, "%s: out of memory\n", progname);
- exit(1);
- }
- if ((tcbtab[0] = calloc(tcbtabsize, sizeof tcbtab[0][0])) == NULL) {
- fprintf(stderr, "%s: out of memory\n", progname);
- exit(1);
- }
- for (tcp = tcbtab[0]; tcp < &tcbtab[0][tcbtabsize]; ++tcp)
- tcbtab[tcp - tcbtab[0]] = &tcbtab[0][tcp - tcbtab[0]];
+ tcbtab = calloc(tcbtabsize, sizeof(tcbtab[0]));
+ if (!tcbtab)
+ die_out_of_memory();
+ tcp = calloc(tcbtabsize, sizeof(*tcp));
+ if (!tcp)
+ die_out_of_memory();
+ for (c = 0; c < tcbtabsize; c++)
+ tcbtab[c] = tcp++;
outf = stderr;
interactive = 1;
qualify("verbose=all");
qualify("signal=all");
while ((c = getopt(argc, argv,
- "+cdfFhiqrtTvVxz"
+ "+cCdfFhiqrtTvVxyz"
#ifndef USE_PROCFS
"D"
#endif
- "a:e:o:O:p:s:S:u:E:")) != EOF) {
+ "a:e:o:O:p:s:S:u:E:P:")) != EOF) {
switch (c) {
case 'c':
- cflag++;
- dtime++;
+ if (cflag == CFLAG_BOTH) {
+ error_msg_and_die("-c and -C are mutually exclusive options");
+ }
+ cflag = CFLAG_ONLY_STATS;
+ break;
+ case 'C':
+ if (cflag == CFLAG_ONLY_STATS) {
+ error_msg_and_die("-c and -C are mutually exclusive options");
+ }
+ cflag = CFLAG_BOTH;
break;
case 'd':
debug++;
break;
#ifndef USE_PROCFS
- /* Experimental, not documented in manpage yet. */
case 'D':
daemonized_tracer = 1;
break;
case 'x':
xflag++;
break;
+ case 'y':
+ show_fd_path = 1;
+ break;
case 'v':
qualify("abbrev=none");
break;
break;
case 'a':
acolumn = atoi(optarg);
+ if (acolumn < 0)
+ error_msg_and_die("Bad column width '%s'", optarg);
break;
case 'e':
qualify(optarg);
set_overhead(atoi(optarg));
break;
case 'p':
- if ((pid = atoi(optarg)) <= 0) {
- fprintf(stderr, "%s: Invalid process id: %s\n",
- progname, optarg);
+ pid = atoi(optarg);
+ if (pid <= 0) {
+ error_msg("Invalid process id: '%s'", optarg);
break;
}
- if (pid == getpid()) {
- fprintf(stderr, "%s: I'm sorry, I can't let you do that, Dave.\n", progname);
+ if (pid == strace_tracer_pid) {
+ error_msg("I'm sorry, I can't let you do that, Dave.");
break;
}
tcp = alloc_tcb(pid, 0);
tcp->flags |= TCB_ATTACHED;
pflag_seen++;
break;
+ case 'P':
+ tracing_paths = 1;
+ if (pathtrace_select(optarg)) {
+ error_msg_and_die("Failed to select path '%s'", optarg);
+ }
+ break;
case 's':
max_strlen = atoi(optarg);
if (max_strlen < 0) {
- fprintf(stderr,
- "%s: invalid -s argument: %s\n",
- progname, optarg);
- exit(1);
+ error_msg_and_die("Invalid -s argument: '%s'", optarg);
}
break;
case 'S':
username = strdup(optarg);
break;
case 'E':
- if (putenv(optarg) < 0) {
- fprintf(stderr, "%s: out of memory\n",
- progname);
- exit(1);
- }
+ if (putenv(optarg) < 0)
+ die_out_of_memory();
break;
default:
usage(stderr, 1);
}
}
+ acolumn_spaces = malloc(acolumn + 1);
+ if (!acolumn_spaces)
+ die_out_of_memory();
+ memset(acolumn_spaces, ' ', acolumn);
+ acolumn_spaces[acolumn] = '\0';
+
if ((optind == argc) == !pflag_seen)
usage(stderr, 1);
+ if (pflag_seen && daemonized_tracer) {
+ error_msg_and_die("-D and -p are mutually exclusive options");
+ }
+
if (!followfork)
followfork = optF;
if (followfork > 1 && cflag) {
- fprintf(stderr,
- "%s: -c and -ff are mutually exclusive options\n",
- progname);
- exit(1);
+ error_msg_and_die("(-c or -C) and -ff are mutually exclusive options");
}
/* See if they want to run as another user. */
struct passwd *pent;
if (getuid() != 0 || geteuid() != 0) {
- fprintf(stderr,
- "%s: you must be root to use the -u option\n",
- progname);
- exit(1);
+ error_msg_and_die("You must be root to use the -u option");
}
- if ((pent = getpwnam(username)) == NULL) {
- fprintf(stderr, "%s: cannot find user `%s'\n",
- progname, username);
- exit(1);
+ pent = getpwnam(username);
+ if (pent == NULL) {
+ error_msg_and_die("Cannot find user '%s'", username);
}
run_uid = pent->pw_uid;
run_gid = pent->pw_gid;
run_gid = getgid();
}
+#ifdef LINUX
+ if (followfork)
+ test_ptrace_setoptions_followfork();
+ test_ptrace_setoptions_for_all();
+#endif
+
/* Check if they want to redirect the output. */
if (outfname) {
/* See if they want to pipe the output. */
* We can't do the <outfname>.PID funny business
* when using popen, so prohibit it.
*/
- if (followfork > 1) {
- fprintf(stderr, "\
-%s: piping the output and -ff are mutually exclusive options\n",
- progname);
- exit(1);
- }
-
- if ((outf = strace_popen(outfname + 1)) == NULL)
- exit(1);
+ if (followfork > 1)
+ error_msg_and_die("Piping the output and -ff are mutually exclusive");
+ outf = strace_popen(outfname + 1);
}
- else if (followfork <= 1 &&
- (outf = strace_fopen(outfname, "w")) == NULL)
- exit(1);
+ else if (followfork <= 1)
+ outf = strace_fopen(outfname);
}
- if (!outfname || outfname[0] == '|' || outfname[0] == '!')
+ if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
+ static char buf[BUFSIZ];
setvbuf(outf, buf, _IOLBF, BUFSIZ);
+ }
if (outfname && optind < argc) {
interactive = 0;
qflag = 1;
}
+
/* Valid states here:
optind < argc pflag_seen outfname interactive
1 0 0 1
exit(exit_code);
}
-void
+static void
expand_tcbtab(void)
{
/* Allocate some more TCBs and expand the table.
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. */
- struct tcb **newtab = (struct tcb **)
- realloc(tcbtab, 2 * tcbtabsize * sizeof tcbtab[0]);
- struct tcb *newtcbs = (struct tcb *) calloc(tcbtabsize,
- sizeof *newtcbs);
- int i;
- if (newtab == NULL || newtcbs == NULL) {
- fprintf(stderr, "%s: expand_tcbtab: out of memory\n",
- progname);
- cleanup();
- exit(1);
- }
- for (i = tcbtabsize; i < 2 * tcbtabsize; ++i)
- newtab[i] = &newtcbs[i - tcbtabsize];
+ int i = tcbtabsize;
+ struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0]));
+ struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0]));
+ if (!newtab || !newtcbs)
+ die_out_of_memory();
tcbtabsize *= 2;
tcbtab = newtab;
+ while (i < tcbtabsize)
+ tcbtab[i++] = newtcbs++;
}
struct tcb *
for (i = 0; i < tcbtabsize; i++) {
tcp = tcbtab[i];
if ((tcp->flags & TCB_INUSE) == 0) {
+ memset(tcp, 0, sizeof(*tcp));
tcp->pid = pid;
- tcp->parent = NULL;
- tcp->nchildren = 0;
- tcp->nzombies = 0;
-#ifdef TCB_CLONE_THREAD
- tcp->nclone_threads = tcp->nclone_detached = 0;
- tcp->nclone_waiting = 0;
-#endif
tcp->flags = TCB_INUSE | TCB_STARTUP;
tcp->outf = outf; /* Initialise to current out file */
- tcp->stime.tv_sec = 0;
- tcp->stime.tv_usec = 0;
+#ifdef USE_PROCFS
tcp->pfd = -1;
+#endif
nprocs++;
+ if (debug)
+ fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
if (command_options_parsed)
newoutf(tcp);
return tcp;
}
}
- fprintf(stderr, "%s: bug in alloc_tcb\n", progname);
- cleanup();
- exit(1);
+ error_msg_and_die("bug in alloc_tcb");
}
#ifdef USE_PROCFS
#ifdef HAVE_MP_PROCFS
/* Open the process pseudo-files in /proc. */
sprintf(proc, "/proc/%d/ctl", tcp->pid);
- if ((tcp->pfd = open(proc, O_WRONLY|O_EXCL)) < 0) {
+ tcp->pfd = open(proc, O_WRONLY|O_EXCL);
+ if (tcp->pfd < 0) {
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if (set_cloexec_flag(tcp->pfd) < 0) {
- return -1;
- }
+ set_cloexec_flag(tcp->pfd);
sprintf(proc, "/proc/%d/status", tcp->pid);
- if ((tcp->pfd_stat = open(proc, O_RDONLY|O_EXCL)) < 0) {
+ tcp->pfd_stat = open(proc, O_RDONLY|O_EXCL);
+ if (tcp->pfd_stat < 0) {
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if (set_cloexec_flag(tcp->pfd_stat) < 0) {
- return -1;
- }
+ set_cloexec_flag(tcp->pfd_stat);
sprintf(proc, "/proc/%d/as", tcp->pid);
- if ((tcp->pfd_as = open(proc, O_RDONLY|O_EXCL)) < 0) {
+ tcp->pfd_as = open(proc, O_RDONLY|O_EXCL);
+ if (tcp->pfd_as < 0) {
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if (set_cloexec_flag(tcp->pfd_as) < 0) {
- return -1;
- }
+ set_cloexec_flag(tcp->pfd_as);
#else
/* Open the process pseudo-file in /proc. */
-#ifndef FREEBSD
+# ifndef FREEBSD
sprintf(proc, "/proc/%d", tcp->pid);
- if ((tcp->pfd = open(proc, O_RDWR|O_EXCL)) < 0) {
-#else /* FREEBSD */
+ tcp->pfd = open(proc, O_RDWR|O_EXCL);
+# else
sprintf(proc, "/proc/%d/mem", tcp->pid);
- if ((tcp->pfd = open(proc, O_RDWR)) < 0) {
-#endif /* FREEBSD */
+ tcp->pfd = open(proc, O_RDWR);
+# endif
+ if (tcp->pfd < 0) {
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if (set_cloexec_flag(tcp->pfd) < 0) {
- return -1;
- }
+ set_cloexec_flag(tcp->pfd);
#endif
#ifdef FREEBSD
sprintf(proc, "/proc/%d/regs", tcp->pid);
- if ((tcp->pfd_reg = open(proc, O_RDONLY)) < 0) {
+ tcp->pfd_reg = open(proc, O_RDONLY);
+ if (tcp->pfd_reg < 0) {
perror("strace: open(\"/proc/.../regs\", ...)");
return -1;
}
if (cflag) {
sprintf(proc, "/proc/%d/status", tcp->pid);
- if ((tcp->pfd_status = open(proc, O_RDONLY)) < 0) {
+ tcp->pfd_status = open(proc, O_RDONLY);
+ if (tcp->pfd_status < 0) {
perror("strace: open(\"/proc/.../status\", ...)");
return -1;
}
* condition we have to poll for the event.
*/
for (;;) {
- if (IOCTL_STATUS (tcp) < 0) {
+ if (IOCTL_STATUS(tcp) < 0) {
perror("strace: PIOCSTATUS");
return -1;
}
if (tcp->status.PR_FLAGS & PR_ASLEEP)
- break;
+ break;
}
}
#ifndef FREEBSD
premptyset(&syscalls);
for (i = 1; i < MAX_QUALS; ++i) {
if (i > (sizeof syscalls) * CHAR_BIT) break;
- if (qual_flags [i] & QUAL_TRACE) praddset (&syscalls, i);
+ if (qual_flags[i] & QUAL_TRACE) praddset(&syscalls, i);
}
- praddset (&syscalls, SYS_execve);
+ praddset(&syscalls, SYS_execve);
if (followfork) {
- praddset (&syscalls, SYS_fork);
+ praddset(&syscalls, SYS_fork);
#ifdef SYS_forkall
- praddset (&syscalls, SYS_forkall);
+ praddset(&syscalls, SYS_forkall);
#endif
#ifdef SYS_fork1
- praddset (&syscalls, SYS_fork1);
+ praddset(&syscalls, SYS_fork1);
#endif
#ifdef SYS_rfork1
- praddset (&syscalls, SYS_rfork1);
+ praddset(&syscalls, SYS_rfork1);
#endif
#ifdef SYS_rforkall
- praddset (&syscalls, SYS_rforkall);
+ praddset(&syscalls, SYS_rforkall);
#endif
}
if (IOCTL(tcp->pfd, PIOCSENTRY, &syscalls) < 0) {
premptyset(&signals);
for (i = 1; i < MAX_QUALS; ++i) {
if (i > (sizeof signals) * CHAR_BIT) break;
- if (qual_flags [i] & QUAL_SIGNAL) praddset (&signals, i);
+ if (qual_flags[i] & QUAL_SIGNAL) praddset(&signals, i);
}
if (IOCTL(tcp->pfd, PIOCSTRACE, &signals) < 0) {
perror("PIOCSTRACE");
premptyset(&faults);
for (i = 1; i < MAX_QUALS; ++i) {
if (i > (sizeof faults) * CHAR_BIT) break;
- if (qual_flags [i] & QUAL_FAULT) praddset (&faults, i);
+ if (qual_flags[i] & QUAL_FAULT) praddset(&faults, i);
}
if (IOCTL(tcp->pfd, PIOCSFAULT, &faults) < 0) {
perror("PIOCSFAULT");
}
#else /* FREEBSD */
/* set events flags. */
- arg = S_SIG | S_SCE | S_SCX ;
- if(ioctl(tcp->pfd, PIOCBIS, arg) < 0) {
+ arg = S_SIG | S_SCE | S_SCX;
+ if (ioctl(tcp->pfd, PIOCBIS, arg) < 0) {
perror("PIOCBIS");
return -1;
}
#ifdef PRSABORT
/* The child is in a pause(), abort it. */
arg = PRSABORT;
- if (IOCTL (tcp->pfd, PIOCRUN, &arg) < 0) {
+ if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0) {
perror("PIOCRUN");
return -1;
}
#endif
for (;;) {
/* Wait for the child to do something. */
- if (IOCTL_WSTOP (tcp) < 0) {
+ if (IOCTL_WSTOP(tcp) < 0) {
perror("PIOCWSTOP");
return -1;
}
/* Set it running: maybe execve will be next. */
#ifndef FREEBSD
arg = 0;
- if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0) {
-#else /* FREEBSD */
- if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0) {
-#endif /* FREEBSD */
+ if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0)
+#else
+ if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0)
+#endif
+ {
perror("PIOCRUN");
return -1;
}
kill(tcp->pid, SIGCONT);
#endif
}
-#ifndef FREEBSD
}
-#else /* FREEBSD */
- } else {
+#ifdef FREEBSD
+ else {
if (attaching < 2) {
/* We are attaching to an already running process.
* Try to figure out the state of the process in syscalls,
#endif /* USE_PROCFS */
struct tcb *
-pid2tcb(pid)
-int pid;
+pid2tcb(int pid)
{
int i;
- struct tcb *tcp;
+
+ if (pid <= 0)
+ return NULL;
for (i = 0; i < tcbtabsize; i++) {
- tcp = tcbtab[i];
- if (pid && tcp->pid != pid)
- continue;
- if (tcp->flags & TCB_INUSE)
+ struct tcb *tcp = tcbtab[i];
+ if (tcp->pid == pid && (tcp->flags & TCB_INUSE))
return tcp;
}
+
return NULL;
}
#ifdef USE_PROCFS
static struct tcb *
-pfd2tcb(pfd)
-int pfd;
+first_used_tcb(void)
+{
+ int i;
+ struct tcb *tcp;
+ for (i = 0; i < tcbtabsize; i++) {
+ tcp = tcbtab[i];
+ if (tcp->flags & TCB_INUSE)
+ return tcp;
+ }
+ return NULL;
+}
+
+static struct tcb *
+pfd2tcb(int pfd)
{
int i;
#endif /* USE_PROCFS */
void
-droptcb(tcp)
-struct tcb *tcp;
+droptcb(struct tcb *tcp)
{
if (tcp->pid == 0)
return;
-#ifdef TCB_CLONE_THREAD
- if (tcp->nclone_threads > 0) {
- /* There are other threads left in this process, but this
- is the one whose PID represents the whole process.
- We need to keep this record around as a zombie until
- all the threads die. */
- tcp->flags |= TCB_EXITING;
- return;
- }
-#endif
+
nprocs--;
- tcp->pid = 0;
-
- if (tcp->parent != NULL) {
- tcp->parent->nchildren--;
-#ifdef TCB_CLONE_THREAD
- if (tcp->flags & TCB_CLONE_DETACHED)
- tcp->parent->nclone_detached--;
- if (tcp->flags & TCB_CLONE_THREAD)
- tcp->parent->nclone_threads--;
-#endif
-#ifdef TCB_CLONE_DETACHED
- if (!(tcp->flags & TCB_CLONE_DETACHED))
-#endif
- tcp->parent->nzombies++;
-#ifdef LINUX
- /* Update `tcp->parent->parent->nchildren' and the other fields
- like NCLONE_DETACHED, only for zombie group leader that has
- already reported and been short-circuited at the top of this
- function. The same condition as at the top of DETACH. */
- if ((tcp->flags & TCB_CLONE_THREAD) &&
- tcp->parent->nclone_threads == 0 &&
- (tcp->parent->flags & TCB_EXITING))
- droptcb(tcp->parent);
-#endif
- tcp->parent = NULL;
- }
+ if (debug)
+ fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs);
- tcp->flags = 0;
+#ifdef USE_PROCFS
if (tcp->pfd != -1) {
close(tcp->pfd);
tcp->pfd = -1;
-#ifdef FREEBSD
+# ifdef FREEBSD
if (tcp->pfd_reg != -1) {
close(tcp->pfd_reg);
tcp->pfd_reg = -1;
close(tcp->pfd_status);
tcp->pfd_status = -1;
}
-#endif /* !FREEBSD */
-#ifdef USE_PROCFS
- rebuild_pollv(); /* Note, flags needs to be cleared by now. */
-#endif
+# endif
+ tcp->flags = 0; /* rebuild_pollv needs it */
+ rebuild_pollv();
}
+#endif
if (outfname && followfork > 1 && tcp->outf)
fclose(tcp->outf);
- tcp->outf = 0;
-}
-
-#ifndef USE_PROCFS
-
-static int
-resume(tcp)
-struct tcb *tcp;
-{
- if (tcp == NULL)
- return -1;
-
- if (!(tcp->flags & TCB_SUSPENDED)) {
- fprintf(stderr, "PANIC: pid %u not suspended\n", tcp->pid);
- return -1;
- }
- tcp->flags &= ~TCB_SUSPENDED;
-#ifdef TCB_CLONE_THREAD
- if (tcp->flags & TCB_CLONE_THREAD)
- tcp->parent->nclone_waiting--;
-#endif
-
- if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0)
- return -1;
-
- if (!qflag)
- fprintf(stderr, "Process %u resumed\n", tcp->pid);
- return 0;
-}
-
-static int
-resume_from_tcp (struct tcb *tcp)
-{
- int error = 0;
- int resumed = 0;
-
- /* XXX This won't always be quite right (but it never was).
- A waiter with argument 0 or < -1 is waiting for any pid in
- a particular pgrp, which this child might or might not be
- in. The waiter will only wake up if it's argument is -1
- or if it's waiting for tcp->pid's pgrp. It makes a
- difference to wake up a waiter when there might be more
- traced children, because it could get a false ECHILD
- error. OTOH, if this was the last child in the pgrp, then
- it ought to wake up and get ECHILD. We would have to
- search the system for all pid's in the pgrp to be sure.
-
- && (t->waitpid == -1 ||
- (t->waitpid == 0 && getpgid (tcp->pid) == getpgid (t->pid))
- || (t->waitpid < 0 && t->waitpid == -getpid (t->pid)))
- */
-
- if (tcp->parent &&
- (tcp->parent->flags & TCB_SUSPENDED) &&
- (tcp->parent->waitpid <= 0 || tcp->parent->waitpid == tcp->pid)) {
- error = resume(tcp->parent);
- ++resumed;
- }
-#ifdef TCB_CLONE_THREAD
- if (tcp->parent && tcp->parent->nclone_waiting > 0) {
- /* Some other threads of our parent are waiting too. */
- unsigned int i;
-
- /* Resume all the threads that were waiting for this PID. */
- for (i = 0; i < tcbtabsize; i++) {
- struct tcb *t = tcbtab[i];
- if (t->parent == tcp->parent && t != tcp
- && ((t->flags & (TCB_CLONE_THREAD|TCB_SUSPENDED))
- == (TCB_CLONE_THREAD|TCB_SUSPENDED))
- && t->waitpid == tcp->pid) {
- error |= resume (t);
- ++resumed;
- }
- }
- if (resumed == 0)
- /* Noone was waiting for this PID in particular,
- so now we might need to resume some wildcarders. */
- for (i = 0; i < tcbtabsize; i++) {
- struct tcb *t = tcbtab[i];
- if (t->parent == tcp->parent && t != tcp
- && ((t->flags
- & (TCB_CLONE_THREAD|TCB_SUSPENDED))
- == (TCB_CLONE_THREAD|TCB_SUSPENDED))
- && t->waitpid <= 0
- ) {
- error |= resume (t);
- break;
- }
- }
- }
-#endif
-
- return error;
+ memset(tcp, 0, sizeof(*tcp));
}
-#endif /* !USE_PROCFS */
-
/* detach traced process; continue with sig
Never call DETACH twice on the same process as both unattached and
attached-unstopped processes give the same ESRCH. For unattached process we
would SIGSTOP it and wait for its SIGSTOP notification forever. */
static int
-detach(tcp, sig)
-struct tcb *tcp;
-int sig;
+detach(struct tcb *tcp, int sig)
{
int error = 0;
#ifdef LINUX
int status, catch_sigstop;
- struct tcb *zombie = NULL;
-
- /* If the group leader is lingering only because of this other
- thread now dying, then detach the leader as well. */
- if ((tcp->flags & TCB_CLONE_THREAD) &&
- tcp->parent->nclone_threads == 1 &&
- (tcp->parent->flags & TCB_EXITING))
- zombie = tcp->parent;
#endif
if (tcp->flags & TCB_BPTSET)
- sig = SIGKILL;
+ clearbpt(tcp);
#ifdef LINUX
/*
* detached process would be left stopped (process state T).
*/
catch_sigstop = (tcp->flags & TCB_STARTUP);
- if ((error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig)) == 0) {
+ error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig);
+ if (error == 0) {
/* On a clear day, you can see forever. */
}
else if (errno != ESRCH) {
/* Shouldn't happen. */
perror("detach: ptrace(PTRACE_DETACH, ...)");
}
- else if (my_tgkill((tcp->flags & TCB_CLONE_THREAD ? tcp->parent->pid
- : tcp->pid),
- tcp->pid, 0) < 0) {
+ else if (my_tkill(tcp->pid, 0) < 0) {
if (errno != ESRCH)
perror("detach: checking sanity");
}
- else if (!catch_sigstop && my_tgkill((tcp->flags & TCB_CLONE_THREAD
- ? tcp->parent->pid : tcp->pid),
- tcp->pid, SIGSTOP) < 0) {
+ else if (!catch_sigstop && my_tkill(tcp->pid, SIGSTOP) < 0) {
if (errno != ESRCH)
perror("detach: stopping child");
}
break;
}
error = ptrace_restart(PTRACE_CONT, tcp,
- WSTOPSIG(status) == SIGTRAP ? 0
+ WSTOPSIG(status) == syscall_trap_sig ? 0
: WSTOPSIG(status));
if (error < 0)
break;
error = ptrace_restart(PTRACE_DETACH, tcp, sig);
#endif /* SUNOS4 */
-#ifndef USE_PROCFS
- error |= resume_from_tcp (tcp);
-#endif
-
if (!qflag)
fprintf(stderr, "Process %u detached\n", tcp->pid);
droptcb(tcp);
-#ifdef LINUX
- if (zombie != NULL) {
- /* TCP no longer exists therefore you must not detach () it. */
- droptcb(zombie);
- }
-#endif
-
return error;
}
#ifdef USE_PROCFS
-static void
-reaper(sig)
-int sig;
+static void reaper(int sig)
{
int pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
-#if 0
- struct tcb *tcp;
-
- tcp = pid2tcb(pid);
- if (tcp)
- droptcb(tcp);
-#endif
}
}
#endif /* USE_PROCFS */
static void
-cleanup()
+cleanup(void)
{
int i;
struct tcb *tcp;
}
static void
-interrupt(sig)
-int sig;
+interrupt(int sig)
{
interrupted = 1;
}
#endif /* HAVE_DECL_SYS_ERRLIST */
const char *
-strerror(errno)
-int errno;
+strerror(int err_no)
{
static char buf[64];
- if (errno < 1 || errno >= sys_nerr) {
- sprintf(buf, "Unknown error %d", errno);
+ if (err_no < 1 || err_no >= sys_nerr) {
+ sprintf(buf, "Unknown error %d", err_no);
return buf;
}
- return sys_errlist[errno];
+ return sys_errlist[err_no];
}
#endif /* HAVE_STERRROR */
#endif
const char *
-strsignal(sig)
-int sig;
+strsignal(int sig)
{
static char buf[64];
#ifdef USE_PROCFS
static void
-rebuild_pollv()
+rebuild_pollv(void)
{
int i, j;
- if (pollv != NULL)
- free (pollv);
- pollv = (struct pollfd *) malloc(nprocs * sizeof pollv[0]);
- if (pollv == NULL) {
- fprintf(stderr, "%s: out of memory\n", progname);
- exit(1);
- }
+ free(pollv);
+ pollv = malloc(nprocs * sizeof(pollv[0]));
+ if (!pollv)
+ die_out_of_memory();
for (i = j = 0; i < tcbtabsize; i++) {
struct tcb *tcp = tcbtab[i];
j++;
}
if (j != nprocs) {
- fprintf(stderr, "strace: proc miscount\n");
- exit(1);
+ error_msg_and_die("proc miscount");
}
}
#ifndef HAVE_POLLABLE_PROCFS
static void
-proc_poll_open()
+proc_poll_open(void)
{
int i;
if (pipe(proc_poll_pipe) < 0) {
- perror("pipe");
- exit(1);
+ perror_msg_and_die("pipe");
}
for (i = 0; i < 2; i++) {
- if (set_cloexec_flag(proc_poll_pipe[i]) < 0) {
- exit(1);
- }
+ set_cloexec_flag(proc_poll_pipe[i]);
}
}
static int
-proc_poll(pollv, nfds, timeout)
-struct pollfd *pollv;
-int nfds;
-int timeout;
+proc_poll(struct pollfd *pollv, int nfds, int timeout)
{
int i;
int n;
struct proc_pollfd pollinfo;
- if ((n = read(proc_poll_pipe[0], &pollinfo, sizeof(pollinfo))) < 0)
+ n = read(proc_poll_pipe[0], &pollinfo, sizeof(pollinfo));
+ if (n < 0)
return n;
if (n != sizeof(struct proc_pollfd)) {
- fprintf(stderr, "panic: short read: %d\n", n);
- exit(1);
+ error_msg_and_die("panic: short read: %d", n);
}
for (i = 0; i < nprocs; i++) {
if (pollv[i].fd == pollinfo.fd)
}
static void
-wakeup_handler(sig)
-int sig;
+wakeup_handler(int sig)
{
}
static void
-proc_poller(pfd)
-int pfd;
+proc_poller(int pfd)
{
struct proc_pollfd pollinfo;
struct sigaction sa;
switch (fork()) {
case -1:
- perror("fork");
- _exit(1);
+ perror_msg_and_die("fork");
case 0:
break;
default:
sigemptyset(&empty_set);
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
- perror("getrlimit(RLIMIT_NOFILE, ...)");
- _exit(1);
+ perror_msg_and_die("getrlimit(RLIMIT_NOFILE, ...)");
}
n = rl.rlim_cur;
for (i = 0; i < n; i++) {
* heuristic improves the readability of the trace.
*/
tcp = pfd2tcb(pollv[last].fd);
- if (tcp && (tcp->flags & TCB_INSYSCALL))
+ if (tcp && exiting(tcp))
return pollv[last].fd;
}
if (pollv[j].revents & (POLLHUP | POLLERR)) {
tcp = pfd2tcb(pollv[j].fd);
if (!tcp) {
- fprintf(stderr, "strace: lost proc\n");
- exit(1);
+ error_msg_and_die("lost proc");
}
droptcb(tcp);
return -1;
return pollv[j].fd;
}
}
- fprintf(stderr, "strace: nothing ready\n");
- exit(1);
+ error_msg_and_die("nothing ready");
}
static int
-trace()
+trace(void)
{
#ifdef POLL_HACK
struct tcb *in_syscall = NULL;
#ifndef HAVE_POLLABLE_PROCFS
if (proc_poll_pipe[0] == -1) {
#endif
- tcp = pid2tcb(0);
+ tcp = first_used_tcb();
if (!tcp)
continue;
pfd = tcp->pfd;
in_syscall = NULL;
pv.fd = tcp->pfd;
pv.events = POLLWANT;
- if ((what = poll (&pv, 1, 1)) < 0) {
+ what = poll(&pv, 1, 1);
+ if (what < 0) {
if (interrupted)
return 0;
continue;
}
/* Look up `pfd' in our table. */
- if ((tcp = pfd2tcb(pfd)) == NULL) {
- fprintf(stderr, "unknown pfd: %u\n", pfd);
- exit(1);
+ tcp = pfd2tcb(pfd);
+ if (tcp == NULL) {
+ error_msg_and_die("unknown pfd: %u", pfd);
}
#ifdef POLL_HACK
FOUND:
/* Get the status of the process. */
if (!interrupted) {
#ifndef FREEBSD
- ioctl_result = IOCTL_WSTOP (tcp);
+ ioctl_result = IOCTL_WSTOP(tcp);
#else /* FREEBSD */
/* Thanks to some scheduling mystery, the first poller
sometimes waits for the already processed end of fork
event. Doing a non blocking poll here solves the problem. */
if (proc_poll_pipe[0] != -1)
- ioctl_result = IOCTL_STATUS (tcp);
+ ioctl_result = IOCTL_STATUS(tcp);
else
- ioctl_result = IOCTL_WSTOP (tcp);
+ ioctl_result = IOCTL_WSTOP(tcp);
#endif /* FREEBSD */
ioctl_errno = errno;
#ifndef HAVE_POLLABLE_PROCFS
droptcb(tcp);
continue;
default:
- perror("PIOCWSTOP");
- exit(1);
+ perror_msg_and_die("PIOCWSTOP");
}
}
#ifdef FREEBSD
if ((tcp->flags & TCB_STARTUP) && (tcp->status.PR_WHY == PR_SYSEXIT)) {
/* discard first event for a syscall we never entered */
- IOCTL (tcp->pfd, PIOCRUN, 0);
+ IOCTL(tcp->pfd, PIOCRUN, 0);
continue;
}
#endif
/* set current output file */
outf = tcp->outf;
+ curcol = tcp->curcol;
if (cflag) {
struct timeval stime;
char buf[1024];
int len;
- if ((len = pread(tcp->pfd_status, buf, sizeof(buf) - 1, 0)) > 0) {
+ len = pread(tcp->pfd_status, buf, sizeof(buf) - 1, 0);
+ if (len > 0) {
buf[len] = '\0';
sscanf(buf,
"%*s %*d %*d %*d %*d %*d,%*d %*s %*d,%*d %*d,%*d %ld,%ld",
if (tcp->status.PR_FLAGS & PR_ASLEEP) {
tcp->status.PR_WHY = PR_SYSENTRY;
if (trace_syscall(tcp) < 0) {
- fprintf(stderr, "syscall trouble\n");
- exit(1);
+ error_msg_and_die("syscall trouble");
}
}
break;
#endif
case PR_SYSEXIT:
if (trace_syscall(tcp) < 0) {
- fprintf(stderr, "syscall trouble\n");
- exit(1);
+ error_msg_and_die("syscall trouble");
}
break;
case PR_SIGNALLED:
- if (!cflag && (qual_flags[what] & QUAL_SIGNAL)) {
+ if (cflag != CFLAG_ONLY_STATS
+ && (qual_flags[what] & QUAL_SIGNAL)) {
printleader(tcp);
tprintf("--- %s (%s) ---",
signame(what), strsignal(what));
}
break;
case PR_FAULTED:
- if (!cflag && (qual_flags[what] & QUAL_FAULT)) {
+ if (cflag != CFLAGS_ONLY_STATS
+ && (qual_flags[what] & QUAL_FAULT)) {
printleader(tcp);
tprintf("=== FAULT %d ===", what);
printtrailer();
continue;
#endif
default:
- fprintf(stderr, "odd stop %d\n", tcp->status.PR_WHY);
- exit(1);
+ error_msg_and_die("odd stop %d", tcp->status.PR_WHY);
break;
}
+ /* Remember current print column before continuing. */
+ tcp->curcol = curcol;
arg = 0;
#ifndef FREEBSD
- if (IOCTL (tcp->pfd, PIOCRUN, &arg) < 0) {
+ if (IOCTL(tcp->pfd, PIOCRUN, &arg) < 0)
#else
- if (IOCTL (tcp->pfd, PIOCRUN, 0) < 0) {
+ if (IOCTL(tcp->pfd, PIOCRUN, 0) < 0)
#endif
- perror("PIOCRUN");
- exit(1);
+ {
+ perror_msg_and_die("PIOCRUN");
}
}
return 0;
#else /* !USE_PROCFS */
-#ifdef TCB_GROUP_EXITING
-/* Handle an exit detach or death signal that is taking all the
- related clone threads with it. This is called in three circumstances:
- SIG == -1 TCP has already died (TCB_ATTACHED is clear, strace is parent).
- SIG == 0 Continuing TCP will perform an exit_group syscall.
- SIG == other Continuing TCP with SIG will kill the process.
-*/
-static int
-handle_group_exit(struct tcb *tcp, int sig)
-{
- /* We need to locate our records of all the clone threads
- related to TCP, either its children or siblings. */
- struct tcb *leader = NULL;
-
- if (tcp->flags & TCB_CLONE_THREAD)
- leader = tcp->parent;
- else if (tcp->nclone_detached > 0)
- leader = tcp;
-
- if (sig < 0) {
- if (leader != NULL && leader != tcp
- && !(leader->flags & TCB_GROUP_EXITING)
- && !(tcp->flags & TCB_STARTUP)
- ) {
- fprintf(stderr,
- "PANIC: handle_group_exit: %d leader %d\n",
- tcp->pid, leader ? leader->pid : -1);
- }
- /* TCP no longer exists therefore you must not detach() it. */
-#ifndef USE_PROCFS
- resume_from_tcp(tcp);
-#endif
- droptcb(tcp); /* Already died. */
- }
- else {
- /* Mark that we are taking the process down. */
- tcp->flags |= TCB_EXITING | TCB_GROUP_EXITING;
- if (tcp->flags & TCB_ATTACHED) {
- detach(tcp, sig);
- if (leader != NULL && leader != tcp)
- leader->flags |= TCB_GROUP_EXITING;
- } else {
- if (ptrace_restart(PTRACE_CONT, tcp, sig) < 0) {
- cleanup();
- return -1;
- }
- if (leader != NULL) {
- leader->flags |= TCB_GROUP_EXITING;
- if (leader != tcp)
- droptcb(tcp);
- }
- /* The leader will report to us as parent now,
- and then we'll get to the SIG==-1 case. */
- return 0;
- }
- }
-
- return 0;
-}
-#endif
-
static int
trace()
{
struct tcb *tcp;
#ifdef LINUX
struct rusage ru;
-#ifdef __WALL
+ struct rusage *rup = cflag ? &ru : NULL;
+# ifdef __WALL
static int wait4_options = __WALL;
-#endif
+# endif
#endif /* LINUX */
while (nprocs != 0) {
if (interactive)
sigprocmask(SIG_SETMASK, &empty_set, NULL);
#ifdef LINUX
-#ifdef __WALL
- pid = wait4(-1, &status, wait4_options, cflag ? &ru : NULL);
+# ifdef __WALL
+ pid = wait4(-1, &status, wait4_options, rup);
if (pid < 0 && (wait4_options & __WALL) && errno == EINVAL) {
/* this kernel does not support __WALL */
wait4_options &= ~__WALL;
- errno = 0;
- pid = wait4(-1, &status, wait4_options,
- cflag ? &ru : NULL);
+ pid = wait4(-1, &status, wait4_options, rup);
}
if (pid < 0 && !(wait4_options & __WALL) && errno == ECHILD) {
/* most likely a "cloned" process */
- pid = wait4(-1, &status, __WCLONE,
- cflag ? &ru : NULL);
- if (pid == -1) {
- fprintf(stderr, "strace: clone wait4 "
- "failed: %s\n", strerror(errno));
+ pid = wait4(-1, &status, __WCLONE, rup);
+ if (pid < 0) {
+ perror_msg("wait4(__WCLONE) failed");
}
}
-#else
- pid = wait4(-1, &status, 0, cflag ? &ru : NULL);
-#endif /* __WALL */
+# else
+ pid = wait4(-1, &status, 0, rup);
+# endif /* __WALL */
#endif /* LINUX */
#ifdef SUNOS4
pid = wait(&status);
-#endif /* SUNOS4 */
+#endif
wait_errno = errno;
if (interactive)
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
- if (pid == -1) {
+ if (pid < 0) {
switch (wait_errno) {
case EINTR:
continue;
* version of SunOS sometimes reports
* ECHILD before sending us SIGCHILD.
*/
-#if 0
- if (nprocs == 0)
- return 0;
- fprintf(stderr, "strace: proc miscount\n");
- exit(1);
-#endif
return 0;
default:
errno = wait_errno;
}
if (pid == popen_pid) {
if (WIFEXITED(status) || WIFSIGNALED(status))
- popen_pid = -1;
+ popen_pid = 0;
continue;
}
- if (debug)
- fprintf(stderr, " [wait(%#x) = %u]\n", status, pid);
+ if (debug) {
+ char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
+#ifdef LINUX
+ unsigned ev = (unsigned)status >> 16;
+ if (ev) {
+ static const char *const event_names[] = {
+ [PTRACE_EVENT_CLONE] = "CLONE",
+ [PTRACE_EVENT_FORK] = "FORK",
+ [PTRACE_EVENT_VFORK] = "VFORK",
+ [PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE",
+ [PTRACE_EVENT_EXEC] = "EXEC",
+ [PTRACE_EVENT_EXIT] = "EXIT",
+ };
+ const char *e;
+ if (ev < ARRAY_SIZE(event_names))
+ e = event_names[ev];
+ else {
+ sprintf(buf, "?? (%u)", ev);
+ e = buf;
+ }
+ fprintf(stderr, " PTRACE_EVENT_%s", e);
+ }
+#endif
+ strcpy(buf, "???");
+ if (WIFSIGNALED(status))
+#ifdef WCOREDUMP
+ sprintf(buf, "WIFSIGNALED,%ssig=%s",
+ WCOREDUMP(status) ? "core," : "",
+ signame(WTERMSIG(status)));
+#else
+ sprintf(buf, "WIFSIGNALED,sig=%s",
+ signame(WTERMSIG(status)));
+#endif
+ if (WIFEXITED(status))
+ sprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
+ if (WIFSTOPPED(status))
+ sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
+#ifdef WIFCONTINUED
+ if (WIFCONTINUED(status))
+ strcpy(buf, "WIFCONTINUED");
+#endif
+ fprintf(stderr, " [wait(0x%04x) = %u] %s\n", status, pid, buf);
+ }
/* Look up `pid' in our table. */
- if ((tcp = pid2tcb(pid)) == NULL) {
+ tcp = pid2tcb(pid);
+ if (tcp == NULL) {
#ifdef LINUX
if (followfork) {
/* This is needed to go with the CLONE_PTRACE
child so that we know how to do clearbpt
in the child. */
tcp = alloctcb(pid);
- tcp->flags |= TCB_ATTACHED | TCB_SUSPENDED;
+ tcp->flags |= TCB_ATTACHED;
if (!qflag)
- fprintf(stderr, "\
-Process %d attached (waiting for parent)\n",
+ fprintf(stderr, "Process %d attached\n",
pid);
}
else
CLONE_PTRACE itself. */
#endif
{
- fprintf(stderr, "unknown pid: %u\n", pid);
if (WIFSTOPPED(status))
ptrace(PTRACE_CONT, pid, (char *) 1, 0);
- exit(1);
+ error_msg_and_die("Unknown pid: %u", pid);
}
}
/* set current output file */
outf = tcp->outf;
- if (cflag) {
+ curcol = tcp->curcol;
#ifdef LINUX
+ if (cflag) {
tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
tcp->stime = ru.ru_stime;
-#endif /* !LINUX */
}
+#endif
- if (tcp->flags & TCB_SUSPENDED) {
- /*
- * Apparently, doing any ptrace() call on a stopped
- * process, provokes the kernel to report the process
- * status again on a subsequent wait(), even if the
- * process has not been actually restarted.
- * Since we have inspected the arguments of suspended
- * processes we end up here testing for this case.
- */
- continue;
- }
if (WIFSIGNALED(status)) {
if (pid == strace_child)
exit_code = 0x100 | WTERMSIG(status);
- if (!cflag
+ if (cflag != CFLAG_ONLY_STATS
&& (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) {
printleader(tcp);
+#ifdef WCOREDUMP
tprintf("+++ killed by %s %s+++",
signame(WTERMSIG(status)),
-#ifdef WCOREDUMP
- WCOREDUMP(status) ? "(core dumped) " :
+ WCOREDUMP(status) ? "(core dumped) " : "");
+#else
+ tprintf("+++ killed by %s +++",
+ signame(WTERMSIG(status)));
#endif
- "");
printtrailer();
}
-#ifdef TCB_GROUP_EXITING
- handle_group_exit(tcp, -1);
-#else
droptcb(tcp);
-#endif
continue;
}
if (WIFEXITED(status)) {
if (pid == strace_child)
exit_code = WEXITSTATUS(status);
- if (debug)
- fprintf(stderr, "pid %u exited with %d\n", pid, WEXITSTATUS(status));
- if ((tcp->flags & (TCB_ATTACHED|TCB_STARTUP)) == TCB_ATTACHED
-#ifdef TCB_GROUP_EXITING
- && !(tcp->parent && (tcp->parent->flags & TCB_GROUP_EXITING))
- && !(tcp->flags & TCB_GROUP_EXITING)
-#endif
- ) {
- fprintf(stderr,
- "PANIC: attached pid %u exited with %d\n",
- pid, WEXITSTATUS(status));
- }
if (tcp == tcp_last) {
if ((tcp->flags & (TCB_INSYSCALL|TCB_REPRINT)) == TCB_INSYSCALL)
tprintf(" <unfinished ... exit status %d>\n",
WEXITSTATUS(status));
tcp_last = NULL;
}
-#ifdef TCB_GROUP_EXITING
- handle_group_exit(tcp, -1);
-#else
+ if (!cflag /* && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) */ ) {
+ printleader(tcp);
+ tprintf("+++ exited with %d +++", WEXITSTATUS(status));
+ printtrailer();
+ }
droptcb(tcp);
-#endif
continue;
}
if (!WIFSTOPPED(status)) {
droptcb(tcp);
continue;
}
- if (debug)
- fprintf(stderr, "pid %u stopped, [%s]\n",
- pid, signame(WSTOPSIG(status)));
+
+ if (status >> 16) {
+ /* Ptrace event (we ignore all of them for now) */
+ goto tracing;
+ }
/*
* Interestingly, the process may stop
if (tcp->flags & TCB_BPTSET) {
/*
* One example is a breakpoint inherited from
- * parent through fork ().
+ * parent through fork().
*/
if (clearbpt(tcp) < 0) /* Pretty fatal */ {
droptcb(tcp);
return -1;
}
}
+#ifdef LINUX
+ if (ptrace_setoptions) {
+ if (debug)
+ fprintf(stderr, "setting opts %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 */
+ perror_msg_and_die("PTRACE_SETOPTIONS");
+ }
+ }
+ }
+#endif
goto tracing;
}
- if (WSTOPSIG(status) != SIGTRAP) {
+ if (WSTOPSIG(status) != syscall_trap_sig) {
if (WSTOPSIG(status) == SIGSTOP &&
(tcp->flags & TCB_SIGTRAPPED)) {
/*
}
continue;
}
- if (!cflag
+ if (cflag != CFLAG_ONLY_STATS
&& (qual_flags[WSTOPSIG(status)] & QUAL_SIGNAL)) {
- unsigned long addr = 0;
+ siginfo_t si;
+#if defined(PT_CR_IPSR) && defined(PT_CR_IIP)
long pc = 0;
-#if defined(PT_CR_IPSR) && defined(PT_CR_IIP) && defined(PT_GETSIGINFO)
-# define PSR_RI 41
- struct siginfo si;
- long psr;
+ long psr = 0;
upeek(tcp, PT_CR_IPSR, &psr);
upeek(tcp, PT_CR_IIP, &pc);
+# define PSR_RI 41
pc += (psr >> PSR_RI) & 0x3;
- ptrace(PT_GETSIGINFO, pid, 0, (long) &si);
- addr = (unsigned long) si.si_addr;
-#elif defined PTRACE_GETSIGINFO
- if (WSTOPSIG(status) == SIGSEGV ||
- WSTOPSIG(status) == SIGBUS) {
- siginfo_t si;
- if (ptrace(PTRACE_GETSIGINFO, pid,
- 0, &si) == 0)
- addr = (unsigned long)
- si.si_addr;
- }
+# define PC_FORMAT_STR " @ %lx"
+# define PC_FORMAT_ARG , pc
+#else
+# define PC_FORMAT_STR ""
+# define PC_FORMAT_ARG /* nothing */
#endif
printleader(tcp);
- tprintf("--- %s (%s) @ %lx (%lx) ---",
- signame(WSTOPSIG(status)),
- strsignal(WSTOPSIG(status)), pc, addr);
+ if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) {
+ tprintf("--- ");
+ printsiginfo(&si, verbose(tcp));
+ tprintf(" (%s)" PC_FORMAT_STR " ---",
+ strsignal(WSTOPSIG(status))
+ PC_FORMAT_ARG);
+ } else
+ tprintf("--- %s by %s" PC_FORMAT_STR " ---",
+ strsignal(WSTOPSIG(status)),
+ signame(WSTOPSIG(status))
+ PC_FORMAT_ARG);
printtrailer();
}
- if (((tcp->flags & TCB_ATTACHED) ||
- tcp->nclone_threads > 0) &&
- !sigishandled(tcp, WSTOPSIG(status))) {
-#ifdef TCB_GROUP_EXITING
- handle_group_exit(tcp, WSTOPSIG(status));
-#else
- detach(tcp, WSTOPSIG(status));
-#endif
- continue;
- }
if (ptrace_restart(PTRACE_SYSCALL, tcp, WSTOPSIG(status)) < 0) {
cleanup();
return -1;
}
- tcp->flags &= ~TCB_SUSPENDED;
continue;
}
- /* we handled the STATUS, we are permitted to interrupt now. */
+
+ /* We handled quick cases, we are permitted to interrupt now. */
if (interrupted)
return 0;
+
+ /* This should be syscall entry or exit.
+ * (Or it still can be that pesky post-execve SIGTRAP!)
+ * Handle it.
+ */
if (trace_syscall(tcp) < 0 && !tcp->ptrace_errno) {
/* ptrace() failed in trace_syscall() with ESRCH.
* Likely a result of process disappearing mid-flight.
if (tcp->flags & TCB_ATTACHED) {
if (tcp_last) {
/* Do we have dangling line "syscall(param, param"?
- * Finish the line then. We cannot
+ * Finish the line then.
*/
tcp_last->flags |= TCB_REPRINT;
tprintf(" <unfinished ...>");
}
continue;
}
- if (tcp->flags & TCB_EXITING) {
-#ifdef TCB_GROUP_EXITING
- if (tcp->flags & TCB_GROUP_EXITING) {
- if (handle_group_exit(tcp, 0) < 0)
- return -1;
- continue;
- }
-#endif
- if (tcp->flags & TCB_ATTACHED)
- detach(tcp, 0);
- else if (ptrace_restart(PTRACE_CONT, tcp, 0) < 0) {
- cleanup();
- return -1;
- }
- continue;
- }
- if (tcp->flags & TCB_SUSPENDED) {
- if (!qflag)
- fprintf(stderr, "Process %u suspended\n", pid);
- continue;
- }
tracing:
+ /* Remember current print column before continuing. */
+ tcp->curcol = curcol;
if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0) {
cleanup();
return -1;
#endif /* !USE_PROCFS */
-static int curcol;
-
-#ifdef __STDC__
-#include <stdarg.h>
-#define VA_START(a, b) va_start(a, b)
-#else
-#include <varargs.h>
-#define VA_START(a, b) va_start(a)
-#endif
-
void
-#ifdef __STDC__
tprintf(const char *fmt, ...)
-#else
-tprintf(fmt, va_alist)
-char *fmt;
-va_dcl
-#endif
{
va_list args;
- VA_START(args, fmt);
+ va_start(args, fmt);
if (outf) {
int n = vfprintf(outf, fmt, args);
- if (n < 0 && outf != stderr)
- perror(outfname == NULL
- ? "<writing to pipe>" : outfname);
- else
+ if (n < 0) {
+ if (outf != stderr)
+ perror(outfname == NULL
+ ? "<writing to pipe>" : outfname);
+ } else
curcol += n;
}
va_end(args);
- return;
}
void
-printleader(tcp)
-struct tcb *tcp;
+tprints(const char *str)
+{
+ if (outf) {
+ int n = fputs(str, outf);
+ if (n >= 0) {
+ curcol += strlen(str);
+ return;
+ }
+ if (outf != stderr)
+ perror(outfname == NULL
+ ? "<writing to pipe>" : outfname);
+ }
+}
+
+void
+printleader(struct tcb *tcp)
{
if (tcp_last) {
if (tcp_last->ptrace_errno) {
if (tcp_last->flags & TCB_INSYSCALL) {
- tprintf(" <unavailable>)");
- tabto(acolumn);
+ tprintf(" <unavailable>) ");
+ tabto();
}
tprintf("= ? <unavailable>\n");
tcp_last->ptrace_errno = 0;
}
void
-tabto(col)
-int col;
+tabto(void)
{
- if (curcol < col)
- tprintf("%*s", col - curcol, "");
+ if (curcol < acolumn)
+ tprints(acolumn_spaces + curcol);
}
void