#include <stdarg.h>
#include <sys/param.h>
#include <fcntl.h>
+#include <signal.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/stat.h>
#ifdef HAVE_PRCTL
# include <sys/prctl.h>
#endif
-#if defined(IA64)
-# include <asm/ptrace_offsets.h>
-#endif
+
+#include "ptrace.h"
+#include "printsiginfo.h"
+
/* In some libc, these aren't declared. Do it ourself: */
extern char **environ;
extern int optind;
# define fork() vfork()
#endif
+const unsigned int syscall_trap_sig = SIGTRAP | 0x80;
+
cflag_t cflag = CFLAG_NONE;
unsigned int followfork = 0;
-unsigned int ptrace_setoptions = 0;
+unsigned int ptrace_setoptions = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
unsigned int xflag = 0;
-bool need_fork_exec_workarounds = 0;
bool debug_flag = 0;
bool Tflag = 0;
bool iflag = 0;
bool count_wallclock = 0;
unsigned int qflag = 0;
-/* Which WSTOPSIG(status) value marks syscall traps? */
-static unsigned int syscall_trap_sig = SIGTRAP;
static unsigned int tflag = 0;
static bool rflag = 0;
static bool print_pid_pfx = 0;
bool not_failing_only = 0;
/* Show path associated with fd arguments */
-bool show_fd_path = 0;
+unsigned int show_fd_path = 0;
static bool detach_on_execve = 0;
/* Are we "strace PROG" and need to skip detach on first execve? */
#endif /* HAVE_STERRROR */
static void
-usage(FILE *ofp, int exitval)
+usage()
{
- fprintf(ofp, "\
-usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\n\
+ printf("\
+usage: strace [-CdffhiqrtttTvVwxxy] [-I n] [-e expr]...\n\
[-a column] [-o file] [-s strsize] [-P path]...\n\
-p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\
- or: strace -c[df] [-I n] [-e expr]... [-O overhead] [-S sortby]\n\
+ or: strace -c[dfw] [-I n] [-e expr]... [-O overhead] [-S sortby]\n\
-p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\
--c -- count time, calls, and errors for each syscall and report summary\n\
--C -- like -c but also print regular output\n\
--w -- summarise syscall latency (default is system time)\n\
--d -- enable debug output to stderr\n\
--D -- run tracer process as a detached grandchild, not as parent\n\
--f -- follow forks, -ff -- with output into separate files\n\
--i -- print instruction pointer at time of syscall\n\
--q -- suppress messages about attaching, detaching, etc.\n\
--r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs\n\
--T -- print time spent in each syscall\n\
--v -- verbose mode: print unabbreviated argv, stat, termios, etc. args\n\
--x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\
--y -- print paths associated with file descriptor arguments\n\
--h -- print help message, -V -- print version\n\
--a column -- alignment COLUMN for printing syscall results (default %d)\n\
--b execve -- detach on this syscall\n\
--e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
- options: trace, abbrev, verbose, raw, signal, read, write\n\
--I interruptible --\n\
- 1: no signals are blocked\n\
- 2: fatal signals are blocked while decoding syscall (default)\n\
- 3: fatal signals are always blocked (default if '-o FILE PROG')\n\
- 4: fatal signals and SIGTSTP (^Z) are always blocked\n\
- (useful to make 'strace -o FILE PROG' not stop on ^Z)\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\
--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\
--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\
+\n\
+Output format:\n\
+ -a column alignment COLUMN for printing syscall results (default %d)\n\
+ -i print instruction pointer at time of syscall\n\
+ -o file send trace output to FILE instead of stderr\n\
+ -q suppress messages about attaching, detaching, etc.\n\
+ -r print relative timestamp\n\
+ -s strsize limit length of print strings to STRSIZE chars (default %d)\n\
+ -t print absolute timestamp\n\
+ -tt print absolute timestamp with usecs\n\
+ -T print time spent in each syscall\n\
+ -x print non-ascii strings in hex\n\
+ -xx print all strings in hex\n\
+ -y print paths associated with file descriptor arguments\n\
+ -yy print ip:port pairs associated with socket file descriptors\n\
+\n\
+Statistics:\n\
+ -c count time, calls, and errors for each syscall and report summary\n\
+ -C like -c but also print regular output\n\
+ -O overhead set overhead for tracing syscalls to OVERHEAD usecs\n\
+ -S sortby sort syscall counts by: time, calls, name, nothing (default %s)\n\
+ -w summarise syscall latency (default is system time)\n\
+\n\
+Filtering:\n\
+ -e expr a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
+ options: trace, abbrev, verbose, raw, signal, read, write\n\
+ -P path trace accesses to path\n\
+\n\
+Tracing:\n\
+ -b execve detach on execve syscall\n\
+ -D run tracer process as a detached grandchild, not as parent\n\
+ -f follow forks\n\
+ -ff follow forks with output into separate files\n\
+ -I interruptible\n\
+ 1: no signals are blocked\n\
+ 2: fatal signals are blocked while decoding syscall (default)\n\
+ 3: fatal signals are always blocked (default if '-o FILE PROG')\n\
+ 4: fatal signals and SIGTSTP (^Z) are always blocked\n\
+ (useful to make 'strace -o FILE PROG' not stop on ^Z)\n\
+\n\
+Startup:\n\
+ -E var remove var from the environment for command\n\
+ -E var=val put var=val in the environment for command\n\
+ -p pid trace process with process id PID, may be repeated\n\
+ -u username run command as username handling setuid and/or setgid\n\
+\n\
+Miscellaneous:\n\
+ -d enable debug output to stderr\n\
+ -v verbose mode: print unabbreviated argv, stat, termios, etc. args\n\
+ -h print help message\n\
+ -V print version\n\
"
#ifdef USE_LIBUNWIND
-"-k obtain stack trace between each syscall\n\
+" -k obtain stack trace between each syscall (experimental)\n\
"
#endif
/* ancient, no one should use it
-z -- print only succeeding syscalls\n\
*/
, DEFAULT_ACOLUMN, DEFAULT_STRLEN, DEFAULT_SORTBY);
- exit(exitval);
+ exit(0);
}
-static void die(void) __attribute__ ((noreturn));
-static void die(void)
+static void ATTRIBUTE_NORETURN
+die(void)
{
if (strace_tracer_pid == getpid()) {
cflag = 0;
die();
}
+void error_msg_and_help(const char *fmt, ...)
+{
+ if (fmt != NULL) {
+ va_list p;
+ va_start(p, fmt);
+ verror_msg(0, fmt, p);
+ }
+ fprintf(stderr, "Try '%s -h' for more information.\n", progname);
+ die();
+}
+
void perror_msg(const char *fmt, ...)
{
va_list 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");
-}
-
static void
error_opt_arg(int opt, const char *arg)
{
- error_msg_and_die("Invalid -%c argument: '%s'", opt, arg);
+ error_msg_and_help("invalid -%c argument: '%s'", opt, arg);
}
#if USE_SEIZE
int r;
if (!use_seize)
return ptrace(PTRACE_ATTACH, pid, 0L, 0L);
- r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long)ptrace_setoptions);
+ r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long) ptrace_setoptions);
if (r)
return r;
r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L);
callers have pointers and it would be a pain.
So tcbtab is a table of pointers. Since we never
free the TCBs, we allocate a single chunk of many. */
- int i = tcbtabsize;
- 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();
+ unsigned int i = tcbtabsize;
+ struct tcb *newtcbs = xcalloc(tcbtabsize, sizeof(newtcbs[0]));
+ struct tcb **newtab = xreallocarray(tcbtab, tcbtabsize * 2,
+ sizeof(tcbtab[0]));
tcbtabsize *= 2;
tcbtab = newtab;
while (i < tcbtabsize)
static struct tcb *
alloctcb(int pid)
{
- int i;
+ unsigned int i;
struct tcb *tcp;
if (nprocs == tcbtabsize)
nprocs++;
if (debug_flag)
- fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs);
+ error_msg("new tcb for pid %d, active tcbs:%d",
+ tcp->pid, nprocs);
return tcp;
}
}
nprocs--;
if (debug_flag)
- fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs);
+ error_msg("dropped tcb for pid %d, %d remain",
+ tcp->pid, nprocs);
if (tcp->outf) {
if (followfork >= 2) {
int error;
int status;
- if (tcp->flags & TCB_BPTSET)
- clearbpt(tcp);
-
/*
* Linux wrongly insists the child be stopped
* before detaching. Arghh. We go through hoops
* 3. Attach SIGSTOP was already pending (TCB_IGNORE_ONE_SIGSTOP set)
*/
for (;;) {
- int sig;
+ unsigned int sig;
if (waitpid(tcp->pid, &status, __WALL) < 0) {
if (errno == EINTR)
continue;
}
sig = WSTOPSIG(status);
if (debug_flag)
- fprintf(stderr, "detach wait: event:%d sig:%d\n",
- (unsigned)status >> 16, sig);
+ error_msg("detach wait: event:%d sig:%d",
+ (unsigned)status >> 16, sig);
if (use_seize) {
unsigned event = (unsigned)status >> 16;
if (event == PTRACE_EVENT_STOP /*&& sig == SIGTRAP*/) {
drop:
if (!qflag && (tcp->flags & TCB_ATTACHED))
- fprintf(stderr, "Process %u detached\n", tcp->pid);
+ error_msg("Process %u detached", tcp->pid);
droptcb(tcp);
}
static void
startup_attach(void)
{
- int tcbi;
+ unsigned int tcbi;
struct tcb *tcp;
/*
if (ptrace_attach_or_seize(tid) < 0) {
++nerr;
if (debug_flag)
- fprintf(stderr, "attach to pid %d failed\n", tid);
+ error_msg("attach to pid %d failed", tid);
continue;
}
if (debug_flag)
- fprintf(stderr, "attach to pid %d succeeded\n", tid);
+ error_msg("attach to pid %d succeeded", tid);
cur_tcp = tcp;
if (tid != tcp->pid)
cur_tcp = alloctcb(tid);
continue;
}
if (!qflag) {
- fprintf(stderr, ntid > 1
-? "Process %u attached with %u threads\n"
-: "Process %u attached\n",
- tcp->pid, ntid);
+ if (ntid > 1)
+ error_msg("Process %u attached"
+ " with %u threads",
+ tcp->pid, ntid);
+ else
+ error_msg("Process %u attached",
+ tcp->pid);
}
if (!(tcp->flags & TCB_ATTACHED)) {
/* -p PID, we failed to attach to PID itself
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
newoutf(tcp);
if (debug_flag)
- fprintf(stderr, "attach to pid %d (main) succeeded\n", tcp->pid);
+ error_msg("attach to pid %d (main) succeeded", tcp->pid);
if (daemonized_tracer) {
/*
}
if (!qflag)
- fprintf(stderr,
- "Process %u attached\n",
- tcp->pid);
+ error_msg("Process %u attached", tcp->pid);
} /* for each tcbtab[] */
ret:
char *pathname;
};
static struct exec_params params_for_tracee;
-static void __attribute__ ((noinline, noreturn))
+
+static void ATTRIBUTE_NOINLINE ATTRIBUTE_NORETURN
exec_or_die(void)
{
struct exec_params *params = ¶ms_for_tracee;
{
struct_stat statbuf;
const char *filename;
- char pathname[MAXPATHLEN];
+ size_t filename_len;
+ char pathname[PATH_MAX];
int pid;
struct tcb *tcp;
filename = argv[0];
+ filename_len = strlen(filename);
+
+ if (filename_len > sizeof(pathname) - 1) {
+ errno = ENAMETOOLONG;
+ perror_msg_and_die("exec");
+ }
if (strchr(filename, '/')) {
- if (strlen(filename) > sizeof pathname - 1) {
- errno = ENAMETOOLONG;
- perror_msg_and_die("exec");
- }
strcpy(pathname, filename);
}
#ifdef USE_DEBUGGING_EXEC
#endif /* USE_DEBUGGING_EXEC */
else {
const char *path;
- int m, n, len;
+ size_t m, n, len;
for (path = getenv("PATH"); path && *path; path += m) {
const char *colon = strchr(path, ':');
else
m = n = strlen(path);
if (n == 0) {
- if (!getcwd(pathname, MAXPATHLEN))
+ if (!getcwd(pathname, PATH_MAX))
continue;
len = strlen(pathname);
}
}
if (len && pathname[len - 1] != '/')
pathname[len++] = '/';
+ if (filename_len + len > sizeof(pathname) - 1)
+ continue;
strcpy(pathname + len, filename);
if (stat_file(pathname, &statbuf) == 0 &&
/* Accept only regular files
(statbuf.st_mode & 0111))
break;
}
+ if (!path || !*path)
+ pathname[0] = '\0';
}
if (stat_file(pathname, &statbuf) < 0) {
perror_msg_and_die("Can't stat '%s'", filename);
* On NOMMU, can be safely freed only after execve in tracee.
* It's hard to know when that happens, so we just leak it.
*/
- params_for_tracee.pathname = NOMMU_SYSTEM ? strdup(pathname) : pathname;
+ params_for_tracee.pathname = NOMMU_SYSTEM ? xstrdup(pathname) : pathname;
#if defined HAVE_PRCTL && defined PR_SET_PTRACER && defined PR_SET_PTRACER_ANY
if (daemonized_tracer)
* instead of call (won't push anything to stack),
* (2) by trying very hard in exec_or_die()
* to not use any stack,
- * (3) having a really big (MAXPATHLEN) stack object
+ * (3) having a really big (PATH_MAX) stack object
* in this function, which creates a "buffer" between
* child's and parent's stack pointers.
* This may save us if (1) and (2) failed
}
}
-/*
- * 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_followfork(void)
-{
- int pid, expected_grandchild = 0, found_grandchild = 0;
- const unsigned int test_options = PTRACE_O_TRACECLONE |
- PTRACE_O_TRACEFORK |
- PTRACE_O_TRACEVFORK;
-
- /* Need fork for test. NOMMU has no forks */
- if (NOMMU_SYSTEM)
- goto worked; /* be bold, and pretend that test succeeded */
-
- pid = fork();
- if (pid < 0)
- perror_msg_and_die("fork");
- if (pid == 0) {
- pid = getpid();
- if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0)
- perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
- __func__);
- kill_save_errno(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;
- 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_save_errno(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) {
- worked:
- ptrace_setoptions |= test_options;
- if (debug_flag)
- fprintf(stderr, "ptrace_setoptions = %#x\n",
- ptrace_setoptions);
- return 0;
- }
- error_msg("Test for PTRACE_O_TRACECLONE failed, "
- "giving up using this feature.");
- return 1;
-}
-
-/*
- * 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:
-
-# compile with: gcc -nostartfiles -nostdlib -o int3 int3.S
-_start: .globl _start
- int3
- movl $42, %ebx
- movl $1, %eax
- int $0x80
- */
-static int
-test_ptrace_setoptions_for_all(void)
-{
- const unsigned int test_options = PTRACE_O_TRACESYSGOOD |
- PTRACE_O_TRACEEXEC;
- int pid;
- int it_worked = 0;
-
- /* Need fork for test. NOMMU has no forks */
- if (NOMMU_SYSTEM)
- goto worked; /* be bold, and pretend that test succeeded */
-
- 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) {
- worked:
- syscall_trap_sig = (SIGTRAP | 0x80);
- ptrace_setoptions |= test_options;
- if (debug_flag)
- fprintf(stderr, "ptrace_setoptions = %#x\n",
- ptrace_setoptions);
- return 0;
- }
-
- error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
- "giving up using this feature.");
- return 1;
-}
-
#if USE_SEIZE
static void
test_ptrace_seize(void)
if (ptrace(PTRACE_SEIZE, pid, 0, 0) == 0) {
post_attach_sigstop = 0; /* this sets use_seize to 1 */
} else if (debug_flag) {
- fprintf(stderr, "PTRACE_SEIZE doesn't work\n");
+ error_msg("PTRACE_SEIZE doesn't work");
}
kill(pid, SIGKILL);
* Don't want main() to inline us and defeat the reason
* we have a separate function.
*/
-static void __attribute__ ((noinline))
+static void ATTRIBUTE_NOINLINE
init(int argc, char *argv[])
{
struct tcb *tcp;
int c, i;
int optF = 0;
+ unsigned int tcbi;
struct sigaction sa;
progname = argv[0] ? argv[0] : "strace";
/* Allocate the initial tcbtab. */
tcbtabsize = argc; /* Surely enough for all -p args. */
- 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++;
+ tcbtab = xcalloc(tcbtabsize, sizeof(tcbtab[0]));
+ tcp = xcalloc(tcbtabsize, sizeof(*tcp));
+ for (tcbi = 0; tcbi < tcbtabsize; tcbi++)
+ tcbtab[tcbi] = tcp++;
shared_log = stderr;
set_sortby(DEFAULT_SORTBY);
break;
case 'c':
if (cflag == CFLAG_BOTH) {
- error_msg_and_die("-c and -C are mutually exclusive");
+ error_msg_and_help("-c and -C are mutually exclusive");
}
cflag = CFLAG_ONLY_STATS;
break;
case 'C':
if (cflag == CFLAG_ONLY_STATS) {
- error_msg_and_die("-c and -C are mutually exclusive");
+ error_msg_and_help("-c and -C are mutually exclusive");
}
cflag = CFLAG_BOTH;
break;
followfork++;
break;
case 'h':
- usage(stdout, 0);
+ usage();
break;
case 'i':
iflag = 1;
xflag++;
break;
case 'y':
- show_fd_path = 1;
+ show_fd_path++;
break;
case 'v':
qualify("abbrev=none");
qualify(optarg);
break;
case 'o':
- outfname = strdup(optarg);
+ outfname = xstrdup(optarg);
break;
case 'O':
i = string_to_uint(optarg);
set_sortby(optarg);
break;
case 'u':
- username = strdup(optarg);
+ username = xstrdup(optarg);
break;
#ifdef USE_LIBUNWIND
case 'k':
error_opt_arg(c, optarg);
break;
default:
- usage(stderr, 1);
+ error_msg_and_help(NULL);
break;
}
}
argv += optind;
/* argc -= optind; - no need, argc is not used below */
- acolumn_spaces = malloc(acolumn + 1);
- if (!acolumn_spaces)
- die_out_of_memory();
+ acolumn_spaces = xmalloc(acolumn + 1);
memset(acolumn_spaces, ' ', acolumn);
acolumn_spaces[acolumn] = '\0';
/* Must have PROG [ARGS], or -p PID. Not both. */
- if (!argv[0] == !nprocs)
- usage(stderr, 1);
+ if (!argv[0] == !nprocs) {
+ error_msg_and_help("must have PROG [ARGS] or -p PID");
+ }
if (nprocs != 0 && daemonized_tracer) {
- error_msg_and_die("-D and -p are mutually exclusive");
+ error_msg_and_help("-D and -p are mutually exclusive");
}
if (!followfork)
followfork = optF;
if (followfork >= 2 && cflag) {
- error_msg_and_die("(-c or -C) and -ff are mutually exclusive");
+ error_msg_and_help("(-c or -C) and -ff are mutually exclusive");
}
if (count_wallclock && !cflag) {
- error_msg_and_die("-w must be given with (-c or -C)");
+ error_msg_and_help("-w must be given with (-c or -C)");
}
if (cflag == CFLAG_ONLY_STATS) {
run_gid = getgid();
}
- /*
- * On any reasonably recent Linux kernel (circa about 2.5.46)
- * need_fork_exec_workarounds should stay 0 after these tests:
- */
- /*need_fork_exec_workarounds = 0; - already is */
if (followfork)
- need_fork_exec_workarounds = test_ptrace_setoptions_followfork();
- need_fork_exec_workarounds |= test_ptrace_setoptions_for_all();
+ ptrace_setoptions |= PTRACE_O_TRACECLONE |
+ PTRACE_O_TRACEFORK |
+ PTRACE_O_TRACEVFORK;
+ if (debug_flag)
+ error_msg("ptrace_setoptions = %#x", ptrace_setoptions);
test_ptrace_seize();
/* Check if they want to redirect the output. */
* when using popen, so prohibit it.
*/
if (followfork >= 2)
- error_msg_and_die("Piping the output and -ff are mutually exclusive");
+ error_msg_and_help("piping the output and -ff are mutually exclusive");
shared_log = strace_popen(outfname + 1);
}
else if (followfork < 2)
}
if (!outfname || outfname[0] == '|' || outfname[0] == '!') {
- char *buf = malloc(BUFSIZ);
- if (!buf)
- die_out_of_memory();
+ char *buf = xmalloc(BUFSIZ);
setvbuf(shared_log, buf, _IOLBF, BUFSIZ);
}
if (outfname && argv[0]) {
if (!opt_intr)
opt_intr = INTR_NEVER;
- qflag = 1;
+ if (!qflag)
+ qflag = 1;
}
if (!opt_intr)
opt_intr = INTR_WHILE_WAIT;
static struct tcb *
pid2tcb(int pid)
{
- int i;
+ unsigned int i;
if (pid <= 0)
return NULL;
static void
cleanup(void)
{
- int i;
+ unsigned int i;
struct tcb *tcp;
int fatal_sig;
if (!tcp->pid)
continue;
if (debug_flag)
- fprintf(stderr,
- "cleanup: looking at pid %u\n", tcp->pid);
+ error_msg("cleanup: looking at pid %u", tcp->pid);
if (tcp->pid == strace_child) {
kill(tcp->pid, SIGCONT);
kill(tcp->pid, fatal_sig);
}
static void
+print_debug_info(const int pid, int status)
+{
+ const unsigned int event = (unsigned int) status >> 16;
+ char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
+ char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
+
+ strcpy(buf, "???");
+ if (WIFSIGNALED(status))
+#ifdef WCOREDUMP
+ 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
+ /* Should never be seen */
+ if (WIFCONTINUED(status))
+ strcpy(buf, "WIFCONTINUED");
+#endif
+ evbuf[0] = '\0';
+ if (event != 0) {
+ 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",
+ /* [PTRACE_EVENT_STOP (=128)] would make biggish array */
+ };
+ const char *e = "??";
+ if (event < ARRAY_SIZE(event_names))
+ e = event_names[event];
+ else if (event == PTRACE_EVENT_STOP)
+ e = "STOP";
+ sprintf(evbuf, ",EVENT_%s (%u)", e, event);
+ }
+ error_msg("[wait(0x%06x) = %u] %s%s", status, pid, buf, evbuf);
+}
+
+static struct tcb *
+maybe_allocate_tcb(const int pid, int status)
+{
+ if (!WIFSTOPPED(status)) {
+ if (detach_on_execve && pid == strace_child) {
+ /* example: strace -bexecve sh -c 'exec true' */
+ strace_child = 0;
+ return NULL;
+ }
+ /*
+ * This can happen if we inherited an unknown child.
+ * Example: (sleep 1 & exec strace true)
+ */
+ error_msg("Exit of unknown pid %u ignored", pid);
+ return NULL;
+ }
+ if (followfork) {
+ /* We assume it's a fork/vfork/clone child */
+ struct tcb *tcp = alloctcb(pid);
+ tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
+ newoutf(tcp);
+ if (!qflag)
+ error_msg("Process %d attached", pid);
+ return tcp;
+ } else {
+ /* This can happen if a clone call used
+ * CLONE_PTRACE itself.
+ */
+ ptrace(PTRACE_CONT, pid, (char *) 0, 0);
+ error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
+ return NULL;
+ }
+}
+
+static struct tcb *
+maybe_switch_tcbs(struct tcb *tcp, const int pid)
+{
+ FILE *fp;
+ struct tcb *execve_thread;
+ long old_pid = 0;
+
+ if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
+ return tcp;
+ /* Avoid truncation in pid2tcb() param passing */
+ if (old_pid <= 0 || old_pid == pid)
+ return tcp;
+ if ((unsigned long) old_pid > UINT_MAX)
+ return tcp;
+ execve_thread = pid2tcb(old_pid);
+ /* It should be !NULL, but I feel paranoid */
+ if (!execve_thread)
+ return tcp;
+
+ if (execve_thread->curcol != 0) {
+ /*
+ * One case we are here is -ff:
+ * try "strace -oLOG -ff test/threaded_execve"
+ */
+ fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
+ /*execve_thread->curcol = 0; - no need, see code below */
+ }
+ /* Swap output FILEs (needed for -ff) */
+ fp = execve_thread->outf;
+ execve_thread->outf = tcp->outf;
+ tcp->outf = fp;
+ /* And their column positions */
+ execve_thread->curcol = tcp->curcol;
+ tcp->curcol = 0;
+ /* Drop leader, but close execve'd thread outfile (if -ff) */
+ droptcb(tcp);
+ /* Switch to the thread, reusing leader's outfile and pid */
+ tcp = execve_thread;
+ tcp->pid = pid;
+ if (cflag != CFLAG_ONLY_STATS) {
+ printleader(tcp);
+ tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
+ line_ended();
+ tcp->flags |= TCB_REPRINT;
+ }
+
+ return tcp;
+}
+
+static void
+print_signalled(struct tcb *tcp, const int pid, int status)
+{
+ if (pid == strace_child) {
+ exit_code = 0x100 | WTERMSIG(status);
+ strace_child = 0;
+ }
+
+ if (cflag != CFLAG_ONLY_STATS
+ && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
+ ) {
+ printleader(tcp);
+#ifdef WCOREDUMP
+ tprintf("+++ killed by %s %s+++\n",
+ signame(WTERMSIG(status)),
+ WCOREDUMP(status) ? "(core dumped) " : "");
+#else
+ tprintf("+++ killed by %s +++\n",
+ signame(WTERMSIG(status)));
+#endif
+ line_ended();
+ }
+}
+
+static void
+print_exited(struct tcb *tcp, const int pid, int status)
+{
+ if (pid == strace_child) {
+ exit_code = WEXITSTATUS(status);
+ strace_child = 0;
+ }
+
+ if (cflag != CFLAG_ONLY_STATS &&
+ qflag < 2) {
+ printleader(tcp);
+ tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
+ line_ended();
+ }
+}
+
+static void
+print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
+{
+ if (cflag != CFLAG_ONLY_STATS
+ && !hide_log_until_execve
+ && (qual_flags[sig] & QUAL_SIGNAL)
+ ) {
+ printleader(tcp);
+ if (si) {
+ tprintf("--- %s ", signame(sig));
+ printsiginfo(si, verbose(tcp));
+ tprints(" ---\n");
+ } else
+ tprintf("--- stopped by %s ---\n", signame(sig));
+ line_ended();
+ }
+}
+
+static void
+startup_tcb(struct tcb *tcp)
+{
+ if (debug_flag)
+ error_msg("pid %d has TCB_STARTUP, initializing it", tcp->pid);
+
+ tcp->flags &= ~TCB_STARTUP;
+
+ if (!use_seize) {
+ if (debug_flag)
+ error_msg("setting opts 0x%x on pid %d",
+ 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");
+ }
+ }
+ }
+}
+
+/* Returns true iff the main trace loop has to continue. */
+static bool
trace(void)
{
+ int pid;
+ int wait_errno;
+ int status;
+ bool stopped;
+ unsigned int sig;
+ unsigned int event;
+ struct tcb *tcp;
struct rusage ru;
- /* Used to be "while (nprocs != 0)", but in this testcase:
+ if (interrupted)
+ return false;
+
+ /*
+ * Used to exit simply when nprocs hits zero, but in this testcase:
* int main() { _exit(!!fork()); }
* under strace -f, parent sometimes (rarely) manages
* to exit before we see the first stop of the child,
* 19923 clone(...) = 19924
* 19923 exit_group(1) = ?
* 19923 +++ exited with 1 +++
- * Waiting for ECHILD works better.
- * (However, if -o|logger is in use, we can't do that.
- * Can work around that by double-forking the logger,
- * but that loses the ability to wait for its completion on exit.
- * Oh well...)
+ * Exiting only when wait() returns ECHILD works better.
*/
- while (1) {
- int pid;
- int wait_errno;
- int status, sig;
- int stopped;
- struct tcb *tcp;
- unsigned event;
-
- if (interrupted)
- return;
-
- if (popen_pid != 0 && nprocs == 0)
- return;
+ if (popen_pid != 0) {
+ /* However, if -o|logger is in use, we can't do that.
+ * Can work around that by double-forking the logger,
+ * but that loses the ability to wait for its completion
+ * on exit. Oh well...
+ */
+ if (nprocs == 0)
+ return false;
+ }
- if (interactive)
- sigprocmask(SIG_SETMASK, &empty_set, NULL);
- pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
- wait_errno = errno;
- if (interactive)
- sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+ if (interactive)
+ sigprocmask(SIG_SETMASK, &empty_set, NULL);
+ pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
+ wait_errno = errno;
+ if (interactive)
+ sigprocmask(SIG_BLOCK, &blocked_set, NULL);
- if (pid < 0) {
- if (wait_errno == EINTR)
- continue;
- if (nprocs == 0 && wait_errno == ECHILD)
- return;
- /* If nprocs > 0, ECHILD is not expected,
- * treat it as any other error here:
- */
- errno = wait_errno;
- perror_msg_and_die("wait4(__WALL)");
- }
+ if (pid < 0) {
+ if (wait_errno == EINTR)
+ return true;
+ if (nprocs == 0 && wait_errno == ECHILD)
+ return false;
+ /*
+ * If nprocs > 0, ECHILD is not expected,
+ * treat it as any other error here:
+ */
+ errno = wait_errno;
+ perror_msg_and_die("wait4(__WALL)");
+ }
- if (pid == popen_pid) {
- if (!WIFSTOPPED(status))
- popen_pid = 0;
- continue;
- }
+ if (pid == popen_pid) {
+ if (!WIFSTOPPED(status))
+ popen_pid = 0;
+ return true;
+ }
- event = ((unsigned)status >> 16);
- if (debug_flag) {
- char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
- char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
- strcpy(buf, "???");
- if (WIFSIGNALED(status))
-#ifdef WCOREDUMP
- 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
- /* Should never be seen */
- if (WIFCONTINUED(status))
- strcpy(buf, "WIFCONTINUED");
-#endif
- evbuf[0] = '\0';
- if (event != 0) {
- 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",
- /* [PTRACE_EVENT_STOP (=128)] would make biggish array */
- };
- const char *e = "??";
- if (event < ARRAY_SIZE(event_names))
- e = event_names[event];
- else if (event == PTRACE_EVENT_STOP)
- e = "STOP";
- sprintf(evbuf, ",EVENT_%s (%u)", e, event);
- }
- fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
- }
+ if (debug_flag)
+ print_debug_info(pid, status);
- /* Look up 'pid' in our table. */
- tcp = pid2tcb(pid);
+ /* Look up 'pid' in our table. */
+ tcp = pid2tcb(pid);
- if (!tcp) {
- if (!WIFSTOPPED(status)) {
- /* This can happen if we inherited
- * an unknown child. Example:
- * (sleep 1 & exec strace sleep 2)
- */
- error_msg("Exit of unknown pid %u seen", pid);
- continue;
- }
- if (followfork) {
- /* We assume it's a fork/vfork/clone child */
- tcp = alloctcb(pid);
- tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
- newoutf(tcp);
- if (!qflag)
- fprintf(stderr, "Process %d attached\n",
- pid);
- } else {
- /* This can happen if a clone call used
- * CLONE_PTRACE itself.
- */
- ptrace(PTRACE_CONT, pid, (char *) 0, 0);
- error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
- continue;
- }
- }
+ if (!tcp) {
+ tcp = maybe_allocate_tcb(pid, status);
+ if (!tcp)
+ return true;
+ }
+ if (WIFSTOPPED(status))
+ get_regs(pid);
+ else
clear_regs();
- if (WIFSTOPPED(status))
- get_regs(pid);
- /* Under Linux, execve changes pid to thread leader's pid,
+ event = (unsigned int) status >> 16;
+
+ if (event == PTRACE_EVENT_EXEC) {
+ /*
+ * Under Linux, execve changes pid to thread leader's pid,
* and we see this changed pid on EVENT_EXEC and later,
* execve sysexit. Leader "disappears" without exit
* notification. Let user know that, drop leader's tcb,
* PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
* On 2.6 and earlier, it can return garbage.
*/
- if (event == PTRACE_EVENT_EXEC && os_release >= KERNEL_VERSION(3,0,0)) {
- FILE *fp;
- struct tcb *execve_thread;
- long old_pid = 0;
-
- if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
- goto dont_switch_tcbs;
- /* Avoid truncation in pid2tcb() param passing */
- if (old_pid > UINT_MAX)
- goto dont_switch_tcbs;
- if (old_pid <= 0 || old_pid == pid)
- goto dont_switch_tcbs;
- execve_thread = pid2tcb(old_pid);
- /* It should be !NULL, but I feel paranoid */
- if (!execve_thread)
- goto dont_switch_tcbs;
-
- if (execve_thread->curcol != 0) {
- /*
- * One case we are here is -ff:
- * try "strace -oLOG -ff test/threaded_execve"
- */
- fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
- /*execve_thread->curcol = 0; - no need, see code below */
- }
- /* Swap output FILEs (needed for -ff) */
- fp = execve_thread->outf;
- execve_thread->outf = tcp->outf;
- tcp->outf = fp;
- /* And their column positions */
- execve_thread->curcol = tcp->curcol;
- tcp->curcol = 0;
- /* Drop leader, but close execve'd thread outfile (if -ff) */
- droptcb(tcp);
- /* Switch to the thread, reusing leader's outfile and pid */
- tcp = execve_thread;
- tcp->pid = pid;
- if (cflag != CFLAG_ONLY_STATS) {
- printleader(tcp);
- tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
- line_ended();
- tcp->flags |= TCB_REPRINT;
- }
- }
- dont_switch_tcbs:
+ if (os_release >= KERNEL_VERSION(3,0,0))
+ tcp = maybe_switch_tcbs(tcp, pid);
- if (event == PTRACE_EVENT_EXEC) {
- if (detach_on_execve && !skip_one_b_execve)
- detach(tcp); /* do "-b execve" thingy */
- skip_one_b_execve = 0;
+ if (detach_on_execve && !skip_one_b_execve) {
+ detach(tcp); /* do "-b execve" thingy */
+ return true;
}
+ skip_one_b_execve = 0;
+ }
- /* Set current output file */
- current_tcp = tcp;
+ /* Set current output file */
+ current_tcp = tcp;
- if (cflag) {
- tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
- tcp->stime = ru.ru_stime;
- }
+ if (cflag) {
+ tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
+ tcp->stime = ru.ru_stime;
+ }
- if (WIFSIGNALED(status)) {
- if (pid == strace_child)
- exit_code = 0x100 | WTERMSIG(status);
- if (cflag != CFLAG_ONLY_STATS
- && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
- ) {
- printleader(tcp);
-#ifdef WCOREDUMP
- tprintf("+++ killed by %s %s+++\n",
- signame(WTERMSIG(status)),
- WCOREDUMP(status) ? "(core dumped) " : "");
-#else
- tprintf("+++ killed by %s +++\n",
- signame(WTERMSIG(status)));
-#endif
- line_ended();
- }
- droptcb(tcp);
- continue;
- }
- if (WIFEXITED(status)) {
- if (pid == strace_child)
- exit_code = WEXITSTATUS(status);
- if (cflag != CFLAG_ONLY_STATS &&
- qflag < 2) {
- printleader(tcp);
- tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
- line_ended();
- }
- droptcb(tcp);
- continue;
- }
- if (!WIFSTOPPED(status)) {
- fprintf(stderr, "PANIC: pid %u not stopped\n", pid);
- droptcb(tcp);
- continue;
- }
+ if (WIFSIGNALED(status)) {
+ print_signalled(tcp, pid, status);
+ droptcb(tcp);
+ return true;
+ }
- /* Is this the very first time we see this tracee stopped? */
- if (tcp->flags & TCB_STARTUP) {
- if (debug_flag)
- fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n", tcp->pid);
- tcp->flags &= ~TCB_STARTUP;
- if (tcp->flags & TCB_BPTSET) {
- /*
- * One example is a breakpoint inherited from
- * parent through fork().
- */
- if (clearbpt(tcp) < 0) {
- /* Pretty fatal */
- droptcb(tcp);
- exit_code = 1;
- return;
- }
- }
- if (!use_seize && ptrace_setoptions) {
- if (debug_flag)
- fprintf(stderr, "setting opts 0x%x on pid %d\n", ptrace_setoptions, tcp->pid);
- if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) {
- if (errno != ESRCH) {
- /* Should never happen, really */
- perror_msg_and_die("PTRACE_SETOPTIONS");
- }
- }
- }
- }
+ if (WIFEXITED(status)) {
+ print_exited(tcp, pid, status);
+ droptcb(tcp);
+ return true;
+ }
- sig = WSTOPSIG(status);
+ if (!WIFSTOPPED(status)) {
+ /*
+ * Neither signalled, exited or stopped.
+ * How could that be?
+ */
+ error_msg("pid %u not stopped!", pid);
+ droptcb(tcp);
+ return true;
+ }
- if (event != 0) {
- /* Ptrace event */
+ /* Is this the very first time we see this tracee stopped? */
+ if (tcp->flags & TCB_STARTUP) {
+ startup_tcb(tcp);
+ if (get_scno(tcp) == 1)
+ tcp->s_prev_ent = tcp->s_ent;
+ }
+
+ sig = WSTOPSIG(status);
+
+ if (event != 0) {
+ /* Ptrace event */
#if USE_SEIZE
- if (event == PTRACE_EVENT_STOP) {
- /*
- * PTRACE_INTERRUPT-stop or group-stop.
- * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
- */
- if (sig == SIGSTOP
- || sig == SIGTSTP
- || sig == SIGTTIN
- || sig == SIGTTOU
- ) {
- stopped = 1;
+ if (event == PTRACE_EVENT_STOP) {
+ /*
+ * PTRACE_INTERRUPT-stop or group-stop.
+ * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+ */
+ switch (sig) {
+ case SIGSTOP:
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ stopped = true;
goto show_stopsig;
- }
}
-#endif
- goto restart_tracee_with_sig_0;
}
+#endif
+ goto restart_tracee_with_sig_0;
+ }
- /* Is this post-attach SIGSTOP?
- * 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.
- */
- if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
- if (debug_flag)
- fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
- tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
- goto restart_tracee_with_sig_0;
- }
+ /*
+ * Is this post-attach SIGSTOP?
+ * 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.
+ */
+ if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
+ if (debug_flag)
+ error_msg("ignored SIGSTOP on pid %d", tcp->pid);
+ tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
+ goto restart_tracee_with_sig_0;
+ }
- if (sig != syscall_trap_sig) {
- siginfo_t si;
+ if (sig != syscall_trap_sig) {
+ siginfo_t si = {};
- /* Nonzero (true) if tracee is stopped by signal
- * (as opposed to "tracee received signal").
- * TODO: shouldn't we check for errno == EINVAL too?
- * We can get ESRCH instead, you know...
- */
- stopped = (ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0);
+ /*
+ * True if tracee is stopped by signal
+ * (as opposed to "tracee received signal").
+ * TODO: shouldn't we check for errno == EINVAL too?
+ * We can get ESRCH instead, you know...
+ */
+ stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0;
#if USE_SEIZE
- show_stopsig:
+show_stopsig:
#endif
- if (cflag != CFLAG_ONLY_STATS
- && !hide_log_until_execve
- && (qual_flags[sig] & QUAL_SIGNAL)
- ) {
- printleader(tcp);
- if (!stopped) {
- tprintf("--- %s ", signame(sig));
- printsiginfo(&si, verbose(tcp));
- tprints(" ---\n");
- } else
- tprintf("--- stopped by %s ---\n",
- signame(sig));
- line_ended();
- }
+ print_stopped(tcp, stopped ? NULL : &si, sig);
- if (!stopped)
- /* It's signal-delivery-stop. Inject the signal */
- goto restart_tracee;
+ if (!stopped)
+ /* It's signal-delivery-stop. Inject the signal */
+ goto restart_tracee;
- /* It's group-stop */
- if (use_seize) {
- /*
- * This ends ptrace-stop, but does *not* end group-stop.
- * This makes stopping signals work properly on straced process
- * (that is, process really stops. It used to continue to run).
- */
- if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
- /* Note: ptrace_restart emitted error message */
- exit_code = 1;
- return;
- }
- continue;
+ /* It's group-stop */
+ if (use_seize) {
+ /*
+ * This ends ptrace-stop, but does *not* end group-stop.
+ * This makes stopping signals work properly on straced process
+ * (that is, process really stops. It used to continue to run).
+ */
+ if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
+ /* Note: ptrace_restart emitted error message */
+ exit_code = 1;
+ return false;
}
- /* We don't have PTRACE_LISTEN support... */
- goto restart_tracee;
+ return true;
}
+ /* We don't have PTRACE_LISTEN support... */
+ goto restart_tracee;
+ }
- /* We handled quick cases, we are permitted to interrupt now. */
- if (interrupted)
- return;
+ /* We handled quick cases, we are permitted to interrupt now. */
+ if (interrupted)
+ return false;
- /* This should be syscall entry or exit.
- * (Or it still can be that pesky post-execve SIGTRAP!)
- * Handle it.
+ /*
+ * This should be syscall entry or exit.
+ * Handle it.
+ */
+ if (trace_syscall(tcp) < 0) {
+ /*
+ * ptrace() failed in trace_syscall().
+ * Likely a result of process disappearing mid-flight.
+ * Observed case: exit_group() or SIGKILL terminating
+ * all processes in thread group.
+ * We assume that ptrace error was caused by process death.
+ * We used to detach(tcp) here, but since we no longer
+ * implement "detach before death" policy/hack,
+ * we can let this process to report its death to us
+ * normally, via WIFEXITED or WIFSIGNALED wait status.
*/
- if (trace_syscall(tcp) < 0) {
- /* ptrace() failed in trace_syscall().
- * Likely a result of process disappearing mid-flight.
- * Observed case: exit_group() or SIGKILL terminating
- * all processes in thread group.
- * We assume that ptrace error was caused by process death.
- * We used to detach(tcp) here, but since we no longer
- * implement "detach before death" policy/hack,
- * we can let this process to report its death to us
- * normally, via WIFEXITED or WIFSIGNALED wait status.
- */
- continue;
- }
- restart_tracee_with_sig_0:
- sig = 0;
- restart_tracee:
- if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
- /* Note: ptrace_restart emitted error message */
- exit_code = 1;
- return;
- }
- } /* while (1) */
+ return true;
+ }
+
+restart_tracee_with_sig_0:
+ sig = 0;
+
+restart_tracee:
+ if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
+ /* Note: ptrace_restart emitted error message */
+ exit_code = 1;
+ return false;
+ }
+
+ return true;
}
int
{
init(argc, argv);
- /* Run main tracing loop */
- trace();
+ while (trace())
+ ;
cleanup();
fflush(NULL);