aboutsummaryrefslogtreecommitdiffstats
path: root/strace.c
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2012-03-02 16:23:23 -0800
committerJeff Brown <jeffbrown@google.com>2012-03-05 13:45:44 -0800
commitf76f96e20f766e6bb91593885b1e800f8bc14a52 (patch)
treeb7665c50730984824850895eec78e33457486e93 /strace.c
parentc9fd2e5ef7d002e12e7cf2512506c84a9414b0fd (diff)
downloadandroid_external_strace-f76f96e20f766e6bb91593885b1e800f8bc14a52.tar.gz
android_external_strace-f76f96e20f766e6bb91593885b1e800f8bc14a52.tar.bz2
android_external_strace-f76f96e20f766e6bb91593885b1e800f8bc14a52.zip
Update to strace v4.6.jellybean-stablejellybean
This change also fixes the system call numbers for ARM. Previously, the ARM port had been hacked to use the system call numbers for SH, which were only partially correct and often wildly inaccurate. Refer to Android.patch for the Android-specific changes. Change-Id: I5bba77591ef40f78a1b146c8831c05f1d7a3df94
Diffstat (limited to 'strace.c')
-rw-r--r--strace.c1747
1 files changed, 1078 insertions, 669 deletions
diff --git a/strace.c b/strace.c
index a0d0d205..a94a19bc 100644
--- a/strace.c
+++ b/strace.c
@@ -27,7 +27,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
- * $Id: strace.c,v 1.66 2005/06/07 23:21:31 roland Exp $
+ * $Id$
*/
#include "defs.h"
@@ -38,7 +38,10 @@
#include <sys/param.h>
#include <fcntl.h>
#include <sys/resource.h>
+#ifdef HAVE_ANDROID_OS
+#define wait4 __wait4
#include <sys/wait.h>
+#endif
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
@@ -46,6 +49,21 @@
#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))
+# 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))
+# endif
+#endif
+
#if defined(IA64) && defined(LINUX)
# include <asm/ptrace_offsets.h>
#endif
@@ -62,58 +80,73 @@
#endif
#endif
#endif
+extern char **environ;
+extern int optind;
+extern char *optarg;
-#ifdef HAVE_ANDROID_OS
-#define wait4 __wait4
-#endif
-int debug = 0, followfork = 0, followvfork = 0, interactive = 0;
-int rflag = 0, tflag = 0, dtime = 0, cflag = 0;
-int iflag = 0, xflag = 0, qflag = 0;
-int pflag_seen = 0;
+int debug = 0, followfork = 0;
+unsigned int ptrace_setoptions = 0;
+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.
+ * With this option, strace forks twice.
+ * Unlike normal case, with -D *grandparent* process exec's,
+ * becoming a traced process. Child exits (this prevents traced process
+ * from having children it doesn't expect to have), and grandchild
+ * attaches to grandparent similarly to strace -p PID.
+ * This allows for more transparent interaction in cases
+ * when process and its parent are communicating via signals,
+ * wait() etc. Without -D, strace process gets lodged in between,
+ * disrupting parent<->child link.
+ */
+static bool daemonized_tracer = 0;
/* Sometimes we want to print only succeeding syscalls. */
int not_failing_only = 0;
-char *username = NULL;
+static int exit_code = 0;
+static int strace_child = 0;
+
+static char *username = NULL;
uid_t run_uid;
gid_t run_gid;
int acolumn = DEFAULT_ACOLUMN;
int max_strlen = DEFAULT_STRLEN;
-char *outfname = NULL;
+static char *outfname = NULL;
FILE *outf;
+static int curcol;
struct tcb **tcbtab;
unsigned int nprocs, tcbtabsize;
-char *progname;
+const char *progname;
extern char **environ;
-static int trace P((void));
-static void cleanup P((void));
-static void interrupt P((int sig));
+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;
@@ -139,12 +172,13 @@ FILE *ofp;
int exitval;
{
fprintf(ofp, "\
-usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
+usage: strace [-CdDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
[-p pid] ... [-s strsize] [-u username] [-E var=val] ...\n\
[command [arg ...]]\n\
- or: strace -c [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\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\
@@ -159,6 +193,7 @@ usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\
-o file -- send trace output to FILE instead of stderr\n\
-O overhead -- set overhead for tracing syscalls to OVERHEAD usecs\n\
-p pid -- trace process with process id PID, may be repeated\n\
+-D -- run tracer process as a detached grandchild, not as parent\n\
-s strsize -- limit length of print strings to STRSIZE chars (default %d)\n\
-S sortby -- sort syscall counts by: time, calls, name, nothing (default %s)\n\
-u username -- run command as username handling setuid and/or setgid\n\
@@ -180,50 +215,621 @@ foobar()
#endif /* MIPS */
#endif /* SVR4 */
+/* Glue for systems without a MMU that cannot provide fork() */
+#ifdef HAVE_FORK
+# define strace_vforked 0
+#else
+# define strace_vforked 1
+# define fork() vfork()
+#endif
+
+static int
+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;
+ }
+
+ 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 0;
+}
+
+/*
+ * When strace is setuid executable, we have to swap uids
+ * before and after filesystem and process management operations.
+ */
+static void
+swap_uid(void)
+{
+#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);
+ }
+#endif
+}
+
+#if _LFS64_LARGEFILE
+# define fopen_for_output fopen64
+#else
+# define fopen_for_output fopen
+#endif
+
+static FILE *
+strace_fopen(const char *path, const char *mode)
+{
+ 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));
+ swap_uid();
+ if (fp && set_cloexec_flag(fileno(fp)) < 0)
+ {
+ fclose(fp);
+ fp = NULL;
+ }
+ return fp;
+}
+
+static int popen_pid = -1;
+
+#ifndef _PATH_BSHELL
+# define _PATH_BSHELL "/bin/sh"
+#endif
+
+/*
+ * We cannot use standard popen(3) here because we have to distinguish
+ * popen child process from other processes we trace, and standard popen(3)
+ * does not export its child's pid.
+ */
+static FILE *
+strace_popen(const char *command)
+{
+ int fds[2];
+
+ swap_uid();
+ if (pipe(fds) < 0)
+ {
+ fprintf(stderr, "%s: pipe: %s\n",
+ progname, strerror(errno));
+ swap_uid();
+ return NULL;
+ }
+
+ if (set_cloexec_flag(fds[1]) < 0)
+ {
+ close(fds[0]);
+ close(fds[1]);
+ swap_uid();
+ return NULL;
+ }
+
+ if ((popen_pid = fork()) == -1)
+ {
+ fprintf(stderr, "%s: fork: %s\n",
+ progname, strerror(errno));
+ close(fds[0]);
+ close(fds[1]);
+ swap_uid();
+ return NULL;
+ }
+
+ if (popen_pid)
+ {
+ /* parent */
+ close(fds[0]);
+ swap_uid();
+ return fdopen(fds[1], "w");
+ } else
+ {
+ /* 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);
+ }
+ execl(_PATH_BSHELL, "sh", "-c", command, NULL);
+ fprintf(stderr, "%s: execl: %s: %s\n",
+ progname, _PATH_BSHELL, strerror(errno));
+ _exit(1);
+ }
+}
+
+static int
+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;
+ }
+ return 0;
+}
+
+static void
+startup_attach(void)
+{
+ int tcbi;
+ struct tcb *tcp;
+
+ /*
+ * 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.
+ */
+ if (interactive)
+ sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+
+ if (daemonized_tracer) {
+ pid_t pid = fork();
+ if (pid < 0) {
+ _exit(1);
+ }
+ 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,
+ * it proceeds to exec the straced program.
+ */
+ pause();
+ _exit(0); /* paranoia */
+ }
+ }
+
+ 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)
+ continue;
+#endif
+ /* Reinitialize the output since it may have changed. */
+ tcp->outf = outf;
+ if (newoutf(tcp) < 0)
+ exit(1);
+
+#ifdef USE_PROCFS
+ if (proc_open(tcp, 1) < 0) {
+ fprintf(stderr, "trouble opening proc file\n");
+ droptcb(tcp);
+ continue;
+ }
+#else /* !USE_PROCFS */
+# ifdef LINUX
+ if (followfork && !daemonized_tracer) {
+ 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;
+ int tid;
+ while ((de = readdir(dir)) != NULL) {
+ if (de->d_fileno == 0)
+ continue;
+ tid = atoi(de->d_name);
+ if (tid <= 0)
+ continue;
+ ++ntid;
+ 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_FOLLOWFORK;
+ tcbtab[tcbi]->nchildren++;
+ tcbtab[tcbi]->nclone_threads++;
+ tcp->parent = tcbtab[tcbi];
+ }
+ if (interactive) {
+ sigprocmask(SIG_SETMASK, &empty_set, NULL);
+ if (interrupted)
+ return;
+ sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+ }
+ }
+ closedir(dir);
+ ntid -= nerr;
+ if (ntid == 0) {
+ perror("attach: ptrace(PTRACE_ATTACH, ...)");
+ droptcb(tcp);
+ continue;
+ }
+ if (!qflag) {
+ 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);
+ }
+ continue;
+ } /* if (opendir worked) */
+ } /* if (-f) */
+# endif
+ 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 (daemonized_tracer) {
+ /*
+ * It is our grandparent we trace, not a -p PID.
+ * Don't want to just detach on exit, so...
+ */
+ tcp->flags &= ~TCB_ATTACHED;
+ /*
+ * Make parent go away.
+ * Also makes grandparent's wait() unblock.
+ */
+ kill(getppid(), SIGKILL);
+ }
+
+#endif /* !USE_PROCFS */
+ if (!qflag)
+ fprintf(stderr,
+ "Process %u attached - interrupt to quit\n",
+ tcp->pid);
+ }
+
+ if (interactive)
+ sigprocmask(SIG_SETMASK, &empty_set, NULL);
+}
+
+static void
+startup_child (char **argv)
+{
+ struct stat statbuf;
+ const char *filename;
+ char pathname[MAXPATHLEN];
+ int pid = 0;
+ struct tcb *tcp;
+
+ filename = argv[0];
+ if (strchr(filename, '/')) {
+ if (strlen(filename) > sizeof pathname - 1) {
+ errno = ENAMETOOLONG;
+ perror("strace: exec");
+ exit(1);
+ }
+ strcpy(pathname, filename);
+ }
+#ifdef USE_DEBUGGING_EXEC
+ /*
+ * Debuggers customarily check the current directory
+ * first regardless of the path but doing that gives
+ * security geeks a panic attack.
+ */
+ else if (stat(filename, &statbuf) == 0)
+ strcpy(pathname, filename);
+#endif /* USE_DEBUGGING_EXEC */
+ else {
+ const char *path;
+ int m, n, len;
+
+ for (path = getenv("PATH"); path && *path; path += m) {
+ if (strchr(path, ':')) {
+ n = strchr(path, ':') - path;
+ m = n + 1;
+ }
+ else
+ m = n = strlen(path);
+ if (n == 0) {
+ if (!getcwd(pathname, MAXPATHLEN))
+ continue;
+ len = strlen(pathname);
+ }
+ else if (n > sizeof pathname - 1)
+ continue;
+ else {
+ strncpy(pathname, path, n);
+ len = n;
+ }
+ if (len && pathname[len - 1] != '/')
+ pathname[len++] = '/';
+ strcpy(pathname + len, filename);
+ if (stat(pathname, &statbuf) == 0 &&
+ /* Accept only regular files
+ with some execute bits set.
+ XXX not perfect, might still fail */
+ S_ISREG(statbuf.st_mode) &&
+ (statbuf.st_mode & 0111))
+ break;
+ }
+ }
+ if (stat(pathname, &statbuf) < 0) {
+ fprintf(stderr, "%s: %s: command not found\n",
+ progname, filename);
+ exit(1);
+ }
+ strace_child = pid = fork();
+ if (pid < 0) {
+ perror("strace: fork");
+ cleanup();
+ exit(1);
+ }
+ if ((pid != 0 && daemonized_tracer) /* parent: to become a traced process */
+ || (pid == 0 && !daemonized_tracer) /* child: to become a traced process */
+ ) {
+ pid = getpid();
+#ifdef USE_PROCFS
+ if (outf != stderr) close (fileno (outf));
+#ifdef MIPS
+ /* Kludge for SGI, see proc_open for details. */
+ sa.sa_handler = foobar;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGINT, &sa, NULL);
+#endif /* MIPS */
+#ifndef FREEBSD
+ pause();
+#else /* FREEBSD */
+ kill(pid, SIGSTOP); /* stop HERE */
+#endif /* FREEBSD */
+#else /* !USE_PROCFS */
+ 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);
+ }
+ if (debug)
+ kill(pid, SIGSTOP);
+ }
+
+ if (username != NULL || geteuid() == 0) {
+ uid_t run_euid = run_uid;
+ gid_t run_egid = run_gid;
+
+ if (statbuf.st_mode & S_ISUID)
+ run_euid = statbuf.st_uid;
+ if (statbuf.st_mode & S_ISGID)
+ run_egid = statbuf.st_gid;
+
+ /*
+ * It is important to set groups before we
+ * lose privileges on setuid.
+ */
+ if (username != NULL) {
+ if (initgroups(username, run_gid) < 0) {
+ perror("initgroups");
+ exit(1);
+ }
+ if (setregid(run_gid, run_egid) < 0) {
+ perror("setregid");
+ exit(1);
+ }
+ if (setreuid(run_uid, run_euid) < 0) {
+ perror("setreuid");
+ exit(1);
+ }
+ }
+ }
+ else
+ setreuid(run_uid, run_uid);
+
+ if (!daemonized_tracer) {
+ /*
+ * Induce an immediate stop so that the parent
+ * will resume us with PTRACE_SYSCALL and display
+ * this execve call normally.
+ * Unless of course we're on a no-MMU system where
+ * we vfork()-ed, so we cannot stop the child.
+ */
+ if (!strace_vforked)
+ kill(getpid(), SIGSTOP);
+ } else {
+ struct sigaction sv_sigchld;
+ sigaction(SIGCHLD, NULL, &sv_sigchld);
+ /*
+ * Make sure it is not SIG_IGN, otherwise wait
+ * will not block.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ /*
+ * Wait for grandchild to attach to us.
+ * It kills child after that, and wait() unblocks.
+ */
+ alarm(3);
+ wait(NULL);
+ alarm(0);
+ sigaction(SIGCHLD, &sv_sigchld, NULL);
+ }
+#endif /* !USE_PROCFS */
+
+ execv(pathname, argv);
+ perror("strace: exec");
+ _exit(1);
+ }
+
+ /* We are the tracer. */
+ tcp = alloctcb(daemonized_tracer ? getppid() : pid);
+ if (daemonized_tracer) {
+ /* We want subsequent startup_attach() to attach to it. */
+ tcp->flags |= TCB_ATTACHED;
+ }
+#ifdef USE_PROCFS
+ if (proc_open(tcp, 0) < 0) {
+ fprintf(stderr, "trouble opening proc file\n");
+ cleanup();
+ exit(1);
+ }
+#endif /* USE_PROCFS */
+}
+
+#ifdef LINUX
+/*
+ * 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 int
+test_ptrace_setoptions(void)
+{
+ int pid, expected_grandchild = 0, found_grandchild = 0;
+ const unsigned int test_options = PTRACE_O_TRACECLONE |
+ PTRACE_O_TRACEFORK |
+ PTRACE_O_TRACEVFORK;
+
+ if ((pid = fork()) < 0)
+ return -1;
+ else if (pid == 0) {
+ if (ptrace(PTRACE_TRACEME, 0, (char *)1, 0) < 0)
+ _exit(1);
+ kill(getpid(), SIGSTOP);
+ _exit(fork() < 0);
+ }
+
+ while (1) {
+ int status, tracee_pid;
+
+ tracee_pid = wait(&status);
+ if (tracee_pid == -1) {
+ if (errno == EINTR)
+ continue;
+ else if (errno == ECHILD)
+ break;
+ perror("test_ptrace_setoptions");
+ return -1;
+ }
+ if (tracee_pid != pid) {
+ found_grandchild = tracee_pid;
+ if (ptrace(PTRACE_CONT, tracee_pid, 0, 0) < 0 &&
+ errno != ESRCH)
+ kill(tracee_pid, SIGKILL);
+ }
+ else if (WIFSTOPPED(status)) {
+ switch (WSTOPSIG(status)) {
+ case SIGSTOP:
+ if (ptrace(PTRACE_SETOPTIONS, pid,
+ NULL, test_options) < 0) {
+ kill(pid, SIGKILL);
+ return -1;
+ }
+ 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 &&
+ errno != ESRCH)
+ kill(pid, SIGKILL);
+ }
+ }
+ if (expected_grandchild && expected_grandchild == found_grandchild)
+ ptrace_setoptions |= test_options;
+ return 0;
+}
+#endif
+
int
-main(argc, argv)
-int argc;
-char *argv[];
+main(int argc, char *argv[])
{
- extern int optind;
- extern char *optarg;
struct tcb *tcp;
int c, pid = 0;
+ int optF = 0;
struct sigaction sa;
static char buf[BUFSIZ];
+ progname = argv[0] ? argv[0] : "strace";
+
/* Allocate the initial tcbtab. */
tcbtabsize = argc; /* Surely enough for all -p args. */
- tcbtab = (struct tcb **) malloc (tcbtabsize * sizeof tcbtab[0]);
- tcbtab[0] = (struct tcb *) calloc (tcbtabsize, sizeof *tcbtab[0]);
+ 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]];
- progname = argv[0];
outf = stderr;
interactive = 1;
+ set_sortby(DEFAULT_SORTBY);
+ set_personality(DEFAULT_PERSONALITY);
qualify("trace=all");
qualify("abbrev=all");
qualify("verbose=all");
qualify("signal=all");
- set_sortby(DEFAULT_SORTBY);
- set_personality(DEFAULT_PERSONALITY);
while ((c = getopt(argc, argv,
- "+cdfFhiqrtTvVxza:e:o:O:p:s:S:u:E:")) != EOF) {
+ "+cCdfFhiqrtTvVxz"
+#ifndef USE_PROCFS
+ "D"
+#endif
+ "a:e:o:O:p:s:S:u:E:")) != EOF) {
switch (c) {
case 'c':
- cflag++;
- dtime++;
+ if (cflag == CFLAG_BOTH) {
+ fprintf(stderr, "%s: -c and -C are mutually exclusive options\n",
+ progname);
+ exit(1);
+ }
+ cflag = CFLAG_ONLY_STATS;
+ break;
+ case 'C':
+ if (cflag == CFLAG_ONLY_STATS) {
+ fprintf(stderr, "%s: -c and -C are mutually exclusive options\n",
+ progname);
+ exit(1);
+ }
+ cflag = CFLAG_BOTH;
break;
case 'd':
debug++;
break;
- case 'f':
- followfork++;
+#ifndef USE_PROCFS
+ case 'D':
+ daemonized_tracer = 1;
break;
+#endif
case 'F':
- followvfork++;
+ optF = 1;
+ break;
+ case 'f':
+ followfork++;
break;
case 'h':
usage(stdout, 0);
@@ -279,11 +885,7 @@ char *argv[];
fprintf(stderr, "%s: I'm sorry, I can't let you do that, Dave.\n", progname);
break;
}
- if ((tcp = alloctcb(pid)) == NULL) {
- fprintf(stderr, "%s: out of memory\n",
- progname);
- exit(1);
- }
+ tcp = alloc_tcb(pid, 0);
tcp->flags |= TCB_ATTACHED;
pflag_seen++;
break;
@@ -315,9 +917,26 @@ char *argv[];
}
}
- if (optind == argc && !pflag_seen)
+ if ((optind == argc) == !pflag_seen)
usage(stderr, 1);
+ if (pflag_seen && daemonized_tracer) {
+ fprintf(stderr,
+ "%s: -D and -p are mutually exclusive options\n",
+ progname);
+ exit(1);
+ }
+
+ if (!followfork)
+ followfork = optF;
+
+ if (followfork > 1 && cflag) {
+ fprintf(stderr,
+ "%s: (-c or -C) and -ff are mutually exclusive options\n",
+ progname);
+ exit(1);
+ }
+
/* See if they want to run as another user. */
if (username != NULL) {
struct passwd *pent;
@@ -330,7 +949,7 @@ char *argv[];
}
if ((pent = getpwnam(username)) == NULL) {
fprintf(stderr, "%s: cannot find user `%s'\n",
- progname, optarg);
+ progname, username);
exit(1);
}
run_uid = pent->pw_uid;
@@ -341,14 +960,22 @@ char *argv[];
run_gid = getgid();
}
-#ifndef SVR4
- setreuid(geteuid(), getuid());
+#ifdef LINUX
+ if (followfork) {
+ if (test_ptrace_setoptions() < 0) {
+ fprintf(stderr,
+ "Test for options supported by PTRACE_SETOPTIONS "
+ "failed, giving up using this feature.\n");
+ ptrace_setoptions = 0;
+ }
+ if (debug)
+ fprintf(stderr, "ptrace_setoptions = %#x\n",
+ ptrace_setoptions);
+ }
#endif
/* Check if they want to redirect the output. */
if (outfname) {
- long f;
-
/* See if they want to pipe the output. */
if (outfname[0] == '|' || outfname[0] == '!') {
/*
@@ -362,39 +989,14 @@ char *argv[];
exit(1);
}
-#ifdef HAVE_ANDROID_OS
- fprintf(stderr,"output to a pipe not supported on android.\n");
- exit(-1);
-#else
- if ((outf = popen(outfname + 1, "w")) == NULL) {
- fprintf(stderr, "%s: can't popen '%s': %s\n",
- progname, outfname + 1,
- strerror(errno));
+ if ((outf = strace_popen(outfname + 1)) == NULL)
exit(1);
- }
-#endif
}
- else if ((outf = fopen(outfname, "w")) == NULL) {
- fprintf(stderr, "%s: can't fopen '%s': %s\n",
- progname, outfname, strerror(errno));
+ else if (followfork <= 1 &&
+ (outf = strace_fopen(outfname, "w")) == NULL)
exit(1);
- }
-
- if ((f=fcntl(fileno(outf), F_GETFD)) < 0 ) {
- perror("failed to get flags for outputfile");
- exit(1);
- }
-
- if (fcntl(fileno(outf), F_SETFD, f|FD_CLOEXEC) < 0 ) {
- perror("failed to set flags for outputfile");
- exit(1);
- }
}
-#ifndef SVR4
- setreuid(geteuid(), getuid());
-#endif
-
if (!outfname || outfname[0] == '|' || outfname[0] == '!')
setvbuf(outf, buf, _IOLBF, BUFSIZ);
if (outfname && optind < argc) {
@@ -402,248 +1004,20 @@ char *argv[];
qflag = 1;
}
- for (c = 0; c < tcbtabsize; c++) {
- tcp = tcbtab[c];
- /* Reinitialize the output since it may have changed. */
- tcp->outf = outf;
- if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED))
- continue;
-#ifdef USE_PROCFS
- if (proc_open(tcp, 1) < 0) {
- fprintf(stderr, "trouble opening proc file\n");
- droptcb(tcp);
- continue;
- }
-#else /* !USE_PROCFS */
-# ifdef LINUX
- if (tcp->flags & TCB_CLONE_THREAD)
- continue;
- if (followfork) {
- char procdir[MAXPATHLEN];
- DIR *dir;
- sprintf(procdir, "/proc/%d/task", tcp->pid);
- dir = opendir(procdir);
- if (dir != NULL) {
- unsigned int ntid = 0, nerr = 0;
- struct dirent *de;
- int tid;
- while ((de = readdir(dir)) != NULL) {
- if (de->d_fileno == 0 ||
- de->d_name[0] == '.')
- continue;
- tid = atoi(de->d_name);
- if (tid <= 0)
- continue;
- ++ntid;
- if (ptrace(PTRACE_ATTACH, tid,
- (char *) 1, 0) < 0)
- ++nerr;
- else if (tid != tcbtab[c]->pid) {
- if (nprocs == tcbtabsize &&
- expand_tcbtab())
- tcp = NULL;
- else
- tcp = alloctcb(tid);
- if (tcp == NULL) {
- fprintf(stderr, "%s: out of memory\n",
- progname);
- exit(1);
- }
- tcp->flags |= TCB_ATTACHED|TCB_CLONE_THREAD|TCB_CLONE_DETACHED|TCB_FOLLOWFORK;
- tcbtab[c]->nchildren++;
- tcbtab[c]->nclone_threads++;
- tcbtab[c]->nclone_detached++;
- tcp->parent = tcbtab[c];
- }
- }
- closedir(dir);
- if (nerr == ntid) {
- perror("attach: ptrace(PTRACE_ATTACH, ...)");
- droptcb(tcp);
- continue;
- }
- if (!qflag) {
- ntid -= nerr;
- if (ntid > 1)
- fprintf(stderr, "\
-Process %u attached with %u threads - interrupt to quit\n",
- tcp->pid, ntid);
- else
- fprintf(stderr, "\
-Process %u attached - interrupt to quit\n",
- tcp->pid);
- }
- continue;
- }
- }
-# endif
- if (ptrace(PTRACE_ATTACH, tcp->pid, (char *) 1, 0) < 0) {
- perror("attach: ptrace(PTRACE_ATTACH, ...)");
- droptcb(tcp);
- continue;
- }
-#endif /* !USE_PROCFS */
- if (!qflag)
- fprintf(stderr,
- "Process %u attached - interrupt to quit\n",
- tcp->pid);
- }
-
- if (!pflag_seen) {
- struct stat statbuf;
- char *filename;
- char pathname[MAXPATHLEN];
-
- filename = argv[optind];
- if (strchr(filename, '/')) {
- if (strlen(filename) > sizeof pathname - 1) {
- errno = ENAMETOOLONG;
- perror("strace: exec");
- exit(1);
- }
- strcpy(pathname, filename);
- }
-#ifdef USE_DEBUGGING_EXEC
- /*
- * Debuggers customarily check the current directory
- * first regardless of the path but doing that gives
- * security geeks a panic attack.
- */
- else if (stat(filename, &statbuf) == 0)
- strcpy(pathname, filename);
-#endif /* USE_DEBUGGING_EXEC */
- else {
- char *path;
- int m, n, len;
-
- for (path = getenv("PATH"); path && *path; path += m) {
- if (strchr(path, ':')) {
- n = strchr(path, ':') - path;
- m = n + 1;
- }
- else
- m = n = strlen(path);
- if (n == 0) {
- getcwd(pathname, MAXPATHLEN);
- len = strlen(pathname);
- }
- else if (n > sizeof pathname - 1)
- continue;
- else {
- strncpy(pathname, path, n);
- len = n;
- }
- if (len && pathname[len - 1] != '/')
- pathname[len++] = '/';
- strcpy(pathname + len, filename);
- if (stat(pathname, &statbuf) == 0 &&
- /* Accept only regular files
- with some execute bits set.
- XXX not perfect, might still fail */
- S_ISREG(statbuf.st_mode) &&
- (statbuf.st_mode & 0111))
- break;
- }
- }
- if (stat(pathname, &statbuf) < 0) {
- fprintf(stderr, "%s: %s: command not found\n",
- progname, filename);
- exit(1);
- }
- switch (pid = fork()) {
- case -1:
- perror("strace: fork");
- cleanup();
- exit(1);
- break;
- case 0: {
-#ifdef USE_PROCFS
- if (outf != stderr) close (fileno (outf));
-#ifdef MIPS
- /* Kludge for SGI, see proc_open for details. */
- sa.sa_handler = foobar;
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- sigaction(SIGINT, &sa, NULL);
-#endif /* MIPS */
-#ifndef FREEBSD
- pause();
-#else /* FREEBSD */
- kill(getpid(), SIGSTOP); /* stop HERE */
-#endif /* FREEBSD */
-#else /* !USE_PROCFS */
- if (outf!=stderr)
- close(fileno (outf));
-
- if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) {
- perror("strace: ptrace(PTRACE_TRACEME, ...)");
- return -1;
- }
- if (debug)
- kill(getpid(), SIGSTOP);
-
- if (username != NULL || geteuid() == 0) {
- uid_t run_euid = run_uid;
- gid_t run_egid = run_gid;
-
- if (statbuf.st_mode & S_ISUID)
- run_euid = statbuf.st_uid;
- if (statbuf.st_mode & S_ISGID)
- run_egid = statbuf.st_gid;
-
- /*
- * It is important to set groups before we
- * lose privileges on setuid.
- */
- if (username != NULL) {
-#ifndef HAVE_ANDROID_OS
- if (initgroups(username, run_gid) < 0) {
- perror("initgroups");
- exit(1);
- }
-#endif /* HAVE_ANDROID_OS */
- if (setregid(run_gid, run_egid) < 0) {
- perror("setregid");
- exit(1);
- }
- if (setreuid(run_uid, run_euid) < 0) {
- perror("setreuid");
- exit(1);
- }
- }
- }
- else
- setreuid(run_uid, run_uid);
-
- /*
- * Induce an immediate stop so that the parent
- * will resume us with PTRACE_SYSCALL and display
- * this execve call normally.
- */
- kill(getpid(), SIGSTOP);
-#endif /* !USE_PROCFS */
+ /* Valid states here:
+ optind < argc pflag_seen outfname interactive
+ 1 0 0 1
+ 0 1 0 1
+ 1 0 1 0
+ 0 1 1 1
+ */
- execv(pathname, &argv[optind]);
- perror("strace: exec");
- _exit(1);
- break;
- }
- default:
- if ((tcp = alloctcb(pid)) == NULL) {
- fprintf(stderr, "tcb table full\n");
- cleanup();
- exit(1);
- }
-#ifdef USE_PROCFS
- if (proc_open(tcp, 0) < 0) {
- fprintf(stderr, "trouble opening proc file\n");
- cleanup();
- exit(1);
- }
-#endif /* USE_PROCFS */
- break;
- }
- }
+ /* STARTUP_CHILD must be called before the signal handlers get
+ installed below as they are inherited into the spawned process.
+ Also we do not need to be protected by them as during interruption
+ in the STARTUP_CHILD mode we kill the spawned process anyway. */
+ if (!pflag_seen)
+ startup_child(&argv[optind]);
sigemptyset(&empty_set);
sigemptyset(&blocked_set);
@@ -682,39 +1056,27 @@ Process %u attached - interrupt to quit\n",
sigaction(SIGCHLD, &sa, NULL);
#endif /* USE_PROCFS */
+ if (pflag_seen || daemonized_tracer)
+ startup_attach();
+
if (trace() < 0)
exit(1);
cleanup();
- exit(0);
+ fflush(NULL);
+ if (exit_code > 0xff) {
+ /* Child was killed by a signal, mimic that. */
+ exit_code &= 0xff;
+ signal(exit_code, SIG_DFL);
+ raise(exit_code);
+ /* Paranoia - what if this signal is not fatal?
+ Exit with 128 + signo then. */
+ exit_code += 128;
+ }
+ exit(exit_code);
}
void
-newoutf(tcp)
-struct tcb *tcp;
-{
- char name[MAXPATHLEN];
- FILE *fp;
-
- if (outfname && followfork > 1) {
- sprintf(name, "%s.%u", outfname, tcp->pid);
-#ifndef SVR4
- setreuid(geteuid(), getuid());
-#endif
- fp = fopen(name, "w");
-#ifndef SVR4
- setreuid(geteuid(), getuid());
-#endif
- if (fp == NULL) {
- perror("fopen");
- return;
- }
- tcp->outf = fp;
- }
- return;
-}
-
-int
-expand_tcbtab()
+expand_tcbtab(void)
{
/* Allocate some more TCBs and expand the table.
We don't want to relocate the TCBs because our
@@ -727,26 +1089,26 @@ expand_tcbtab()
sizeof *newtcbs);
int i;
if (newtab == NULL || newtcbs == NULL) {
- if (newtab != NULL)
- free(newtab);
- return 1;
+ 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];
tcbtabsize *= 2;
tcbtab = newtab;
-
- return 0;
}
-
struct tcb *
-alloctcb(pid)
-int pid;
+alloc_tcb(int pid, int command_options_parsed)
{
int i;
struct tcb *tcp;
+ if (nprocs == tcbtabsize)
+ expand_tcbtab();
+
for (i = 0; i < tcbtabsize; i++) {
tcp = tcbtab[i];
if ((tcp->flags & TCB_INUSE) == 0) {
@@ -755,26 +1117,29 @@ int pid;
tcp->nchildren = 0;
tcp->nzombies = 0;
#ifdef TCB_CLONE_THREAD
- tcp->nclone_threads = tcp->nclone_detached = 0;
+ tcp->nclone_threads = 0;
tcp->nclone_waiting = 0;
#endif
tcp->flags = TCB_INUSE | TCB_STARTUP;
tcp->outf = outf; /* Initialise to current out file */
+ tcp->curcol = 0;
tcp->stime.tv_sec = 0;
tcp->stime.tv_usec = 0;
tcp->pfd = -1;
nprocs++;
+ if (command_options_parsed)
+ newoutf(tcp);
return tcp;
}
}
- return NULL;
+ fprintf(stderr, "%s: bug in alloc_tcb\n", progname);
+ cleanup();
+ exit(1);
}
#ifdef USE_PROCFS
int
-proc_open(tcp, attaching)
-struct tcb *tcp;
-int attaching;
+proc_open(struct tcb *tcp, int attaching)
{
char proc[32];
long arg;
@@ -795,12 +1160,7 @@ int attaching;
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if ((arg = fcntl(tcp->pfd, F_GETFD)) < 0) {
- perror("F_GETFD");
- return -1;
- }
- if (fcntl(tcp->pfd, F_SETFD, arg|FD_CLOEXEC) < 0) {
- perror("F_SETFD");
+ if (set_cloexec_flag(tcp->pfd) < 0) {
return -1;
}
sprintf(proc, "/proc/%d/status", tcp->pid);
@@ -808,12 +1168,7 @@ int attaching;
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if ((arg = fcntl(tcp->pfd_stat, F_GETFD)) < 0) {
- perror("F_GETFD");
- return -1;
- }
- if (fcntl(tcp->pfd_stat, F_SETFD, arg|FD_CLOEXEC) < 0) {
- perror("F_SETFD");
+ if (set_cloexec_flag(tcp->pfd_stat) < 0) {
return -1;
}
sprintf(proc, "/proc/%d/as", tcp->pid);
@@ -821,32 +1176,23 @@ int attaching;
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if ((arg = fcntl(tcp->pfd_as, F_GETFD)) < 0) {
- perror("F_GETFD");
- return -1;
- }
- if (fcntl(tcp->pfd_as, F_SETFD, arg|FD_CLOEXEC) < 0) {
- perror("F_SETFD");
+ if (set_cloexec_flag(tcp->pfd_as) < 0) {
return -1;
}
#else
/* Open the process pseudo-file in /proc. */
#ifndef FREEBSD
sprintf(proc, "/proc/%d", tcp->pid);
- if ((tcp->pfd = open(proc, O_RDWR|O_EXCL)) < 0) {
+ tcp->pfd = open(proc, O_RDWR|O_EXCL);
#else /* FREEBSD */
sprintf(proc, "/proc/%d/mem", tcp->pid);
- if ((tcp->pfd = open(proc, O_RDWR)) < 0) {
+ tcp->pfd = open(proc, O_RDWR);
#endif /* FREEBSD */
+ if (tcp->pfd < 0) {
perror("strace: open(\"/proc/...\", ...)");
return -1;
}
- if ((arg = fcntl(tcp->pfd, F_GETFD)) < 0) {
- perror("F_GETFD");
- return -1;
- }
- if (fcntl(tcp->pfd, F_SETFD, arg|FD_CLOEXEC) < 0) {
- perror("F_SETFD");
+ if (set_cloexec_flag(tcp->pfd) < 0) {
return -1;
}
#endif
@@ -914,12 +1260,12 @@ int attaching;
/* just unset the PF_LINGER flag for the Run-on-Last-Close. */
if (ioctl(tcp->pfd, PIOCGFL, &arg) < 0) {
perror("PIOCGFL");
- return -1;
+ return -1;
}
arg &= ~PF_LINGER;
if (ioctl(tcp->pfd, PIOCSFL, arg) < 0) {
- perror("PIOCSFL");
- return -1;
+ perror("PIOCSFL");
+ return -1;
}
#endif /* FREEBSD */
#endif /* !PIOCSET */
@@ -1081,25 +1427,38 @@ int attaching;
#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 *
+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(pfd)
int pfd;
{
@@ -1139,15 +1498,20 @@ struct tcb *tcp;
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))
+ 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->nzombies++;
tcp->parent = NULL;
}
@@ -1195,19 +1559,85 @@ struct tcb *tcp;
tcp->parent->nclone_waiting--;
#endif
- if (ptrace(PTRACE_SYSCALL, tcp->pid, (char *) 1, 0) < 0) {
- perror("resume: ptrace(PTRACE_SYSCALL, ...)");
+ 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;
+}
+
#endif /* !USE_PROCFS */
-/* detach traced process; continue with sig */
+/* 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)
@@ -1216,11 +1646,19 @@ int sig;
{
int error = 0;
#ifdef LINUX
- int status, resumed;
+ 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
/*
@@ -1232,6 +1670,12 @@ int sig;
#undef PTRACE_DETACH
#define PTRACE_DETACH PTRACE_SUNDETACH
#endif
+ /*
+ * On TCB_STARTUP we did PTRACE_ATTACH but still did not get the
+ * expected SIGSTOP. We must catch exactly one as otherwise the
+ * 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) {
/* On a clear day, you can see forever. */
}
@@ -1239,15 +1683,21 @@ int sig;
/* Shouldn't happen. */
perror("detach: ptrace(PTRACE_DETACH, ...)");
}
- else if (kill(tcp->pid, 0) < 0) {
+ else if (my_tgkill((tcp->flags & TCB_CLONE_THREAD ? tcp->parent->pid
+ : tcp->pid),
+ tcp->pid, 0) < 0) {
if (errno != ESRCH)
perror("detach: checking sanity");
}
- else if (kill(tcp->pid, SIGSTOP) < 0) {
+ else if (!catch_sigstop && my_tgkill((tcp->flags & TCB_CLONE_THREAD
+ ? tcp->parent->pid : tcp->pid),
+ tcp->pid, SIGSTOP) < 0) {
if (errno != ESRCH)
perror("detach: stopping child");
}
- else {
+ else
+ catch_sigstop = 1;
+ if (catch_sigstop) {
for (;;) {
#ifdef __WALL
if (wait4(tcp->pid, &status, __WALL, NULL) < 0) {
@@ -1282,21 +1732,14 @@ int sig;
break;
}
if (WSTOPSIG(status) == SIGSTOP) {
- if ((error = ptrace(PTRACE_DETACH,
- tcp->pid, (char *) 1, sig)) < 0) {
- if (errno != ESRCH)
- perror("detach: ptrace(PTRACE_DETACH, ...)");
- /* I died trying. */
- }
+ ptrace_restart(PTRACE_DETACH, tcp, sig);
break;
}
- if ((error = ptrace(PTRACE_CONT, tcp->pid, (char *) 1,
- WSTOPSIG(status) == SIGTRAP ?
- 0 : WSTOPSIG(status))) < 0) {
- if (errno != ESRCH)
- perror("detach: ptrace(PTRACE_CONT, ...)");
+ error = ptrace_restart(PTRACE_CONT, tcp,
+ WSTOPSIG(status) == SIGTRAP ? 0
+ : WSTOPSIG(status));
+ if (error < 0)
break;
- }
}
}
#endif /* LINUX */
@@ -1306,95 +1749,36 @@ int sig;
if (sig && kill(tcp->pid, sig) < 0)
perror("detach: kill");
sig = 0;
- if ((error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, sig)) < 0)
- perror("detach: ptrace(PTRACE_DETACH, ...)");
+ error = ptrace_restart(PTRACE_DETACH, tcp, sig);
#endif /* SUNOS4 */
#ifndef USE_PROCFS
- 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;
- }
- }
- }
+ error |= resume_from_tcp (tcp);
#endif
-#endif /* !USE_PROCFS */
-
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
}
}
@@ -1415,8 +1799,8 @@ cleanup()
"cleanup: looking at pid %u\n", tcp->pid);
if (tcp_last &&
(!outfname || followfork < 2 || tcp_last == tcp)) {
- tprintf(" <unfinished ...>\n");
- tcp_last = NULL;
+ tprintf(" <unfinished ...>");
+ printtrailer();
}
if (tcp->flags & TCB_ATTACHED)
detach(tcp, 0);
@@ -1520,7 +1904,6 @@ rebuild_pollv()
static void
proc_poll_open()
{
- int arg;
int i;
if (pipe(proc_poll_pipe) < 0) {
@@ -1528,12 +1911,7 @@ proc_poll_open()
exit(1);
}
for (i = 0; i < 2; i++) {
- if ((arg = fcntl(proc_poll_pipe[i], F_GETFD)) < 0) {
- perror("F_GETFD");
- exit(1);
- }
- if (fcntl(proc_poll_pipe[i], F_SETFD, arg|FD_CLOEXEC) < 0) {
- perror("F_SETFD");
+ if (set_cloexec_flag(proc_poll_pipe[i]) < 0) {
exit(1);
}
}
@@ -1588,7 +1966,7 @@ int pfd;
switch (fork()) {
case -1:
perror("fork");
- _exit(0);
+ _exit(1);
case 0:
break;
default:
@@ -1612,7 +1990,7 @@ int pfd;
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
perror("getrlimit(RLIMIT_NOFILE, ...)");
- _exit(0);
+ _exit(1);
}
n = rl.rlim_cur;
for (i = 0; i < n; i++) {
@@ -1624,10 +2002,10 @@ int pfd;
pollinfo.pid = getpid();
for (;;) {
#ifndef FREEBSD
- if (ioctl(pfd, PIOCWSTOP, NULL) < 0)
-#else /* FREEBSD */
- if (ioctl(pfd, PIOCWSTOP, &pfs) < 0)
-#endif /* FREEBSD */
+ if (ioctl(pfd, PIOCWSTOP, NULL) < 0)
+#else
+ if (ioctl(pfd, PIOCWSTOP, &pfs) < 0)
+#endif
{
switch (errno) {
case EINTR:
@@ -1717,7 +2095,7 @@ trace()
#ifndef HAVE_POLLABLE_PROCFS
if (proc_poll_pipe[0] == -1) {
#endif
- tcp = pid2tcb(0);
+ tcp = first_used_tcb();
if (!tcp)
continue;
pfd = tcp->pfd;
@@ -1789,7 +2167,7 @@ trace()
if (proc_poll_pipe[0] != -1)
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
@@ -1838,6 +2216,7 @@ trace()
/* set current output file */
outf = tcp->outf;
+ curcol = tcp->curcol;
if (cflag) {
struct timeval stime;
@@ -1883,43 +2262,48 @@ trace()
}
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));
- printtrailer(tcp);
+ printtrailer();
#ifdef PR_INFO
if (tcp->status.PR_INFO.si_signo == what) {
printleader(tcp);
tprintf(" siginfo=");
printsiginfo(&tcp->status.PR_INFO, 1);
- printtrailer(tcp);
+ printtrailer();
}
#endif
}
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(tcp);
+ printtrailer();
}
break;
#ifdef FREEBSD
case 0: /* handle case we polled for nothing */
- continue;
+ continue;
#endif
default:
fprintf(stderr, "odd stop %d\n", tcp->status.PR_WHY);
exit(1);
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);
}
@@ -1941,57 +2325,43 @@ 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 = ((tcp->flags & TCB_CLONE_THREAD)
- ? tcp->parent
- : tcp->nclone_detached > 0
- ? tcp : NULL);
+ struct tcb *leader = NULL;
+
+ if (tcp->flags & TCB_CLONE_THREAD)
+ leader = tcp->parent;
if (sig < 0) {
- if (leader != NULL && leader != tcp &&
- !(leader->flags & TCB_GROUP_EXITING))
+ 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) {
- if (leader != NULL && leader != tcp) {
- if (leader->flags & TCB_ATTACHED) {
- /* We need to detach the leader so
- that the process death will be
- reported to its real parent.
- But we kill it first to prevent
- it doing anything before we kill
- the whole process in a moment.
- We can use PTRACE_KILL on a
- thread that's not already
- stopped. Then the value we pass
- in PTRACE_DETACH just sets the
- death signal reported to the
- real parent. */
- ptrace(PTRACE_KILL, leader->pid, 0, 0);
- if (debug)
- fprintf(stderr,
- " [%d exit %d kills %d]\n",
- tcp->pid, sig, leader->pid);
- detach(leader, sig);
- }
- else
- leader->flags |= TCB_GROUP_EXITING;
- }
detach(tcp, sig);
- }
- else if (ptrace(PTRACE_CONT, tcp->pid, (char *) 1, sig) < 0) {
- perror("strace: ptrace(PTRACE_CONT, ...)");
- cleanup();
- return -1;
- }
- else {
- if (leader != NULL)
- leader->flags |= TCB_GROUP_EXITING;
if (leader != NULL && leader != tcp)
- droptcb(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;
@@ -2002,6 +2372,31 @@ handle_group_exit(struct tcb *tcp, int sig)
}
#endif
+#ifdef LINUX
+static int
+handle_ptrace_event(int status, struct tcb *tcp)
+{
+ if (status >> 16 == PTRACE_EVENT_VFORK ||
+ status >> 16 == PTRACE_EVENT_CLONE ||
+ status >> 16 == PTRACE_EVENT_FORK) {
+ long childpid;
+
+ if (do_ptrace(PTRACE_GETEVENTMSG, tcp, NULL, &childpid) < 0) {
+ if (errno != ESRCH) {
+ fprintf(stderr, "\
+%s: handle_ptrace_event: ptrace cannot get new child's pid\n",
+ progname);
+ cleanup();
+ exit(1);
+ }
+ return -1;
+ }
+ return handle_new_child(tcp, childpid, 0);
+ }
+ return 1;
+}
+#endif
+
static int
trace()
{
@@ -2017,6 +2412,8 @@ trace()
#endif /* LINUX */
while (nprocs != 0) {
+ if (interrupted)
+ return 0;
if (interactive)
sigprocmask(SIG_SETMASK, &empty_set, NULL);
#ifdef LINUX
@@ -2049,9 +2446,6 @@ trace()
if (interactive)
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
- if (interrupted)
- return 0;
-
if (pid == -1) {
switch (wait_errno) {
case EINTR:
@@ -2063,12 +2457,6 @@ trace()
* 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;
@@ -2076,13 +2464,18 @@ trace()
return -1;
}
}
+ if (pid == popen_pid) {
+ if (WIFEXITED(status) || WIFSIGNALED(status))
+ popen_pid = -1;
+ continue;
+ }
if (debug)
fprintf(stderr, " [wait(%#x) = %u]\n", status, pid);
/* Look up `pid' in our table. */
if ((tcp = pid2tcb(pid)) == NULL) {
#ifdef LINUX
- if (followfork || followvfork) {
+ if (followfork) {
/* This is needed to go with the CLONE_PTRACE
changes in process.c/util.c: we might see
the child's initial trap before we see the
@@ -2092,13 +2485,8 @@ trace()
will we have the association of parent and
child so that we know how to do clearbpt
in the child. */
- if ((tcp = alloctcb(pid)) == NULL) {
- fprintf(stderr, " [tcb table full]\n");
- kill(pid, SIGKILL); /* XXX */
- return 0;
- }
+ tcp = alloctcb(pid);
tcp->flags |= TCB_ATTACHED | TCB_SUSPENDED;
- newoutf(tcp);
if (!qflag)
fprintf(stderr, "\
Process %d attached (waiting for parent)\n",
@@ -2117,6 +2505,7 @@ Process %d attached (waiting for parent)\n",
}
/* set current output file */
outf = tcp->outf;
+ curcol = tcp->curcol;
if (cflag) {
#ifdef LINUX
tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
@@ -2136,7 +2525,9 @@ Process %d attached (waiting for parent)\n",
continue;
}
if (WIFSIGNALED(status)) {
- if (!cflag
+ if (pid == strace_child)
+ exit_code = 0x100 | WTERMSIG(status);
+ if (cflag != CFLAG_ONLY_STATS
&& (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) {
printleader(tcp);
tprintf("+++ killed by %s %s+++",
@@ -2145,7 +2536,7 @@ Process %d attached (waiting for parent)\n",
WCOREDUMP(status) ? "(core dumped) " :
#endif
"");
- printtrailer(tcp);
+ printtrailer();
}
#ifdef TCB_GROUP_EXITING
handle_group_exit(tcp, -1);
@@ -2155,20 +2546,22 @@ Process %d attached (waiting for parent)\n",
continue;
}
if (WIFEXITED(status)) {
+ if (pid == strace_child)
+ exit_code = WEXITSTATUS(status);
if (debug)
- fprintf(stderr, "pid %u exited\n", pid);
- if ((tcp->flags & TCB_ATTACHED)
+ 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->parent && (tcp->parent->flags & TCB_GROUP_EXITING))
+ && !(tcp->flags & TCB_GROUP_EXITING)
#endif
- )
+ ) {
fprintf(stderr,
- "PANIC: attached pid %u exited\n",
- pid);
+ "PANIC: attached pid %u exited with %d\n",
+ pid, WEXITSTATUS(status));
+ }
if (tcp == tcp_last) {
- if ((tcp->flags & (TCB_INSYSCALL|TCB_REPRINT))
- == TCB_INSYSCALL)
+ if ((tcp->flags & (TCB_INSYSCALL|TCB_REPRINT)) == TCB_INSYSCALL)
tprintf(" <unfinished ... exit status %d>\n",
WEXITSTATUS(status));
tcp_last = NULL;
@@ -2189,41 +2582,45 @@ Process %d attached (waiting for parent)\n",
fprintf(stderr, "pid %u stopped, [%s]\n",
pid, signame(WSTOPSIG(status)));
- if (tcp->flags & TCB_STARTUP) {
+ if (ptrace_setoptions && (status >> 16)) {
+ if (handle_ptrace_event(status, tcp) != 1)
+ goto tracing;
+ }
+
+ /*
+ * Interestingly, the process may stop
+ * with STOPSIG equal to some other signal
+ * than SIGSTOP if we happend to attach
+ * just before the process takes a signal.
+ * A no-MMU vforked child won't send up a signal,
+ * so skip the first (lost) execve notification.
+ */
+ if ((tcp->flags & TCB_STARTUP) &&
+ (WSTOPSIG(status) == SIGSTOP || strace_vforked)) {
/*
* This flag is there to keep us in sync.
* Next time this process stops it should
* really be entering a system call.
*/
tcp->flags &= ~TCB_STARTUP;
- if (tcp->flags & TCB_ATTACHED) {
+ if (tcp->flags & TCB_BPTSET) {
/*
- * Interestingly, the process may stop
- * with STOPSIG equal to some other signal
- * than SIGSTOP if we happend to attach
- * just before the process takes a signal.
+ * One example is a breakpoint inherited from
+ * parent through fork ().
*/
- if (!WIFSTOPPED(status)) {
- fprintf(stderr,
- "pid %u not stopped\n", pid);
- detach(tcp, WSTOPSIG(status));
- continue;
- }
- }
- else {
-#ifdef SUNOS4
- /* A child of us stopped at exec */
- if (WSTOPSIG(status) == SIGTRAP && followvfork)
- fixvfork(tcp);
-#endif /* SUNOS4 */
- }
- if (tcp->flags & TCB_BPTSET) {
if (clearbpt(tcp) < 0) /* Pretty fatal */ {
droptcb(tcp);
cleanup();
return -1;
}
}
+#ifdef LINUX
+ if (followfork && (tcp->parent == NULL) && ptrace_setoptions)
+ if (ptrace(PTRACE_SETOPTIONS, tcp->pid,
+ NULL, ptrace_setoptions) < 0 &&
+ errno != ESRCH)
+ ptrace_setoptions = 0;
+#endif
goto tracing;
}
@@ -2235,43 +2632,43 @@ Process %d attached (waiting for parent)\n",
* Hope we are back in control now.
*/
tcp->flags &= ~(TCB_INSYSCALL | TCB_SIGTRAPPED);
- if (ptrace(PTRACE_SYSCALL,
- pid, (char *) 1, 0) < 0) {
- perror("trace: ptrace(PTRACE_SYSCALL, ...)");
+ if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0) {
cleanup();
return -1;
}
continue;
}
- if (!cflag
+ if (cflag != CFLAG_ONLY_STATS
&& (qual_flags[WSTOPSIG(status)] & QUAL_SIGNAL)) {
- unsigned long addr = 0, pc = 0;
-#ifdef PT_GETSIGINFO
-# define PSR_RI 41
- struct siginfo si;
- unsigned long psr;
+ siginfo_t si;
+#if defined(PT_CR_IPSR) && defined(PT_CR_IIP)
+ long pc = 0;
+ long psr = 0;
- upeek(pid, PT_CR_IPSR, &psr);
- upeek(pid, PT_CR_IIP, &pc);
+ 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 "%s"
+# define PC_FORMAT_ARG ""
#endif
printleader(tcp);
- tprintf("--- %s (%s) @ %lx (%lx) ---",
- signame(WSTOPSIG(status)),
- strsignal(WSTOPSIG(status)), pc, addr);
- printtrailer(tcp);
+ 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) &&
@@ -2283,19 +2680,35 @@ Process %d attached (waiting for parent)\n",
#endif
continue;
}
- if (ptrace(PTRACE_SYSCALL, pid, (char *) 1,
- WSTOPSIG(status)) < 0) {
- perror("trace: ptrace(PTRACE_SYSCALL, ...)");
+ if (ptrace_restart(PTRACE_SYSCALL, tcp, WSTOPSIG(status)) < 0) {
cleanup();
return -1;
}
tcp->flags &= ~TCB_SUSPENDED;
continue;
}
- if (trace_syscall(tcp) < 0) {
- if (tcp->flags & TCB_ATTACHED)
+ /* we handled the STATUS, we are permitted to interrupt now. */
+ if (interrupted)
+ return 0;
+ if (trace_syscall(tcp) < 0 && !tcp->ptrace_errno) {
+ /* ptrace() failed in trace_syscall() with ESRCH.
+ * Likely a result of process disappearing mid-flight.
+ * Observed case: exit_group() terminating
+ * all processes in thread group. In this case, threads
+ * "disappear" in an unpredictable moment without any
+ * notification to strace via wait().
+ */
+ if (tcp->flags & TCB_ATTACHED) {
+ if (tcp_last) {
+ /* Do we have dangling line "syscall(param, param"?
+ * Finish the line then. We cannot
+ */
+ tcp_last->flags |= TCB_REPRINT;
+ tprintf(" <unfinished ...>");
+ printtrailer();
+ }
detach(tcp, 0);
- else {
+ } else {
ptrace(PTRACE_KILL,
tcp->pid, (char *) 1, SIGTERM);
droptcb(tcp);
@@ -2312,8 +2725,7 @@ Process %d attached (waiting for parent)\n",
#endif
if (tcp->flags & TCB_ATTACHED)
detach(tcp, 0);
- else if (ptrace(PTRACE_CONT, pid, (char *) 1, 0) < 0) {
- perror("strace: ptrace(PTRACE_CONT, ...)");
+ else if (ptrace_restart(PTRACE_CONT, tcp, 0) < 0) {
cleanup();
return -1;
}
@@ -2325,8 +2737,9 @@ Process %d attached (waiting for parent)\n",
continue;
}
tracing:
- if (ptrace(PTRACE_SYSCALL, pid, (char *) 1, 0) < 0) {
- perror("trace: ptrace(PTRACE_SYSCALL, ...)");
+ /* Remember current print column before continuing. */
+ tcp->curcol = curcol;
+ if (ptrace_restart(PTRACE_SYSCALL, tcp, 0) < 0) {
cleanup();
return -1;
}
@@ -2336,34 +2749,21 @@ Process %d attached (waiting for parent)\n",
#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);
@@ -2374,9 +2774,18 @@ void
printleader(tcp)
struct tcb *tcp;
{
- if (tcp_last && (!outfname || followfork < 2 || tcp_last == tcp)) {
- tcp_last->flags |= TCB_REPRINT;
- tprintf(" <unfinished ...>\n");
+ if (tcp_last) {
+ if (tcp_last->ptrace_errno) {
+ if (tcp_last->flags & TCB_INSYSCALL) {
+ tprintf(" <unavailable>)");
+ tabto(acolumn);
+ }
+ tprintf("= ? <unavailable>\n");
+ tcp_last->ptrace_errno = 0;
+ } else if (!outfname || followfork < 2 || tcp_last == tcp) {
+ tcp_last->flags |= TCB_REPRINT;
+ tprintf(" <unfinished ...>\n");
+ }
}
curcol = 0;
if ((followfork == 1 || pflag_seen > 1) && outfname)
@@ -2423,8 +2832,7 @@ int col;
}
void
-printtrailer(tcp)
-struct tcb *tcp;
+printtrailer(void)
{
tprintf("\n");
tcp_last = NULL;
@@ -2432,8 +2840,9 @@ struct tcb *tcp;
#ifdef HAVE_MP_PROCFS
-int mp_ioctl (int fd, int cmd, void *arg, int size) {
-
+int
+mp_ioctl(int fd, int cmd, void *arg, int size)
+{
struct iovec iov[2];
int n = 1;
@@ -2445,7 +2854,7 @@ int mp_ioctl (int fd, int cmd, void *arg, int size) {
iov[1].iov_len = size;
}
- return writev (fd, iov, n);
+ return writev(fd, iov, n);
}
#endif