#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>
#endif
#include "ptrace.h"
+#include "printsiginfo.h"
/* In some libc, these aren't declared. Do it ourself: */
extern char **environ;
# 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;
exit(exitval);
}
-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 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)
{
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);
So tcbtab is a table of pointers. Since we never
free the TCBs, we allocate a single chunk of many. */
unsigned int i = tcbtabsize;
- struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0]));
- struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0]));
- if (!newtab || !newtcbs)
- die_out_of_memory();
+ 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)
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
}
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);
}
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",
+ error_msg(ntid > 1
+? "Process %u attached with %u threads"
+: "Process %u attached",
tcp->pid, ntid);
}
if (!(tcp->flags & TCB_ATTACHED)) {
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;
+ 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
}
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)
}
}
-/*
- * 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;
/* 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();
+ tcbtab = xcalloc(tcbtabsize, sizeof(tcbtab[0]));
+ tcp = xcalloc(tcbtabsize, sizeof(*tcp));
for (tcbi = 0; tcbi < tcbtabsize; tcbi++)
tcbtab[tcbi] = tcp++;
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':
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';
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. */
}
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;
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, const int status)
+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];
e = "STOP";
sprintf(evbuf, ",EVENT_%s (%u)", e, event);
}
- fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
+ error_msg("[wait(0x%06x) = %u] %s%s", status, pid, buf, evbuf);
}
static struct tcb *
-maybe_allocate_tcb(const int pid, const int status)
+maybe_allocate_tcb(const int pid, int status)
{
if (!WIFSTOPPED(status)) {
if (detach_on_execve && pid == strace_child) {
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
newoutf(tcp);
if (!qflag)
- fprintf(stderr, "Process %d attached\n", pid);
+ error_msg("Process %d attached", pid);
return tcp;
} else {
/* This can happen if a clone call used
}
static void
-print_signalled(struct tcb *tcp, const int pid, const int status)
+print_signalled(struct tcb *tcp, const int pid, int status)
{
if (pid == strace_child) {
exit_code = 0x100 | WTERMSIG(status);
}
static void
-print_exited(struct tcb *tcp, const int pid, const int status)
+print_exited(struct tcb *tcp, const int pid, int status)
{
if (pid == strace_child) {
exit_code = WEXITSTATUS(status);
}
}
-static bool
+static void
startup_tcb(struct tcb *tcp)
{
if (debug_flag)
- fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n",
- tcp->pid);
+ error_msg("pid %d has TCB_STARTUP, initializing it", 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 false;
- }
- }
-
- if (!use_seize && ptrace_setoptions) {
+ if (!use_seize) {
if (debug_flag)
- fprintf(stderr, "setting opts 0x%x on pid %d\n",
- ptrace_setoptions, tcp->pid);
+ 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 */
}
}
}
-
- return true;
}
/* Returns true iff the main trace loop has to continue. */
if (interrupted)
return false;
- if (popen_pid != 0 && nprocs == 0)
- 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,
+ * and we are losing track of it:
+ * 19923 clone(...) = 19924
+ * 19923 exit_group(1) = ?
+ * 19923 +++ exited with 1 +++
+ * Exiting only when wait() returns ECHILD works better.
+ */
+ 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);
/* Is this the very first time we see this tracee stopped? */
if (tcp->flags & TCB_STARTUP) {
- if (!startup_tcb(tcp))
- return false;
+ startup_tcb(tcp);
+ if (get_scno(tcp) == 1)
+ tcp->s_prev_ent = tcp->s_ent;
}
sig = WSTOPSIG(status);
*/
if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
if (debug_flag)
- fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
+ error_msg("ignored SIGSTOP on pid %d", tcp->pid);
tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
goto restart_tracee_with_sig_0;
}
/*
* This should be syscall entry or exit.
- * (Or it still can be that pesky post-execve SIGTRAP!)
* Handle it.
*/
if (trace_syscall(tcp) < 0) {
{
init(argc, argv);
- /*
- * Run main tracing loop.
- *
- * Used to be "while (nprocs != 0)", but in this testcase:
- * int main() { _exit(!!fork()); }
- * under strace -f, parent sometimes (rarely) manages
- * to exit before we see the first stop of the child,
- * and we are losing track of it:
- * 19923 clone(...) = 19924
- * 19923 exit_group(1) = ?
- * 19923 +++ exited with 1 +++
- * Waiting for ECHILD works better.
- * (However, if -o|logger is in use, we can't do that.
- * Can work around that by double-forking the logger,
- * but that loses the ability to wait for its completion on exit.
- * Oh well...)
- */
while (trace())
;