#include "defs.h"
#include <sys/types.h>
+#include <stdarg.h>
#include <signal.h>
#include <errno.h>
#include <sys/param.h>
int debug = 0, followfork = 0;
-unsigned int ptrace_setoptions = 0;
+unsigned int ptrace_setoptions_followfork = 0;
+static unsigned int ptrace_setoptions_for_all = 0;
+/* Which WSTOPSIG(status) value marks syscall traps? */
+static unsigned int SYSCALLTRAP = SIGTRAP;
int dtime = 0, xflag = 0, qflag = 0;
cflag_t cflag = CFLAG_NONE;
static int iflag = 0, interactive = 0, pflag_seen = 0, rflag = 0, tflag = 0;
exit(exitval);
}
+static void error_msg_and_die(const char *fmt, ...)
+#if defined __GNUC__
+ __attribute__ ((noreturn, format(printf, 1, 2)))
+#endif
+;
+static void error_msg_and_die(const char *fmt, ...)
+{
+ char *msg;
+ va_list p;
+
+ va_start(p, fmt);
+ msg = NULL;
+ vasprintf(&msg, fmt, p);
+ if (msg) {
+ fprintf(stderr, "%s: %s\n", progname, msg);
+ free(msg);
+ }
+ va_end(p);
+
+ /* TODO? cflag = 0; -- or else cleanup() may print summary */
+ cleanup();
+ fflush(NULL);
+ _exit(1);
+}
+
#ifdef SVR4
#ifdef MIPS
void
* and then see which options are supported by the kernel.
*/
static int
-test_ptrace_setoptions(void)
+test_ptrace_setoptions_followfork(void)
{
int pid, expected_grandchild = 0, found_grandchild = 0;
const unsigned int test_options = PTRACE_O_TRACECLONE |
continue;
else if (errno == ECHILD)
break;
- perror("test_ptrace_setoptions");
+ perror("test_ptrace_setoptions_followfork");
return -1;
}
if (tracee_pid != pid) {
}
}
if (expected_grandchild && expected_grandchild == found_grandchild)
- ptrace_setoptions |= test_options;
+ ptrace_setoptions_followfork |= test_options;
return 0;
}
+
+/*
+ * Test whether the kernel support PTRACE_O_TRACESYSGOOD.
+ * First fork a new child, call ptrace(PTRACE_SETOPTIONS) on it,
+ * and then see whether it will stop with (SIGTRAP | 0x80).
+ *
+ * Use of this option enables correct handling of user-generated SIGTRAPs,
+ * and SIGTRAPs generated by special instructions such as int3 on x86:
+ * _start: .globl _start
+ * int3
+ * movl $42, %ebx
+ * movl $1, %eax
+ * int $0x80
+ * (compile with: "gcc -nostartfiles -nostdlib -o int3 int3.S")
+ */
+static void
+test_ptrace_setoptions_for_all(void)
+{
+ const unsigned int test_options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
+ int pid;
+ int it_worked = 0;
+
+ pid = fork();
+ if (pid < 0)
+ error_msg_and_die("fork failed");
+
+ if (pid == 0) {
+ pid = getpid();
+ if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0)
+ /* "parent, something is deeply wrong!" */
+ kill(pid, SIGKILL);
+ 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(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected wait result %d", __func__, tracee_pid);
+ }
+ if (WIFEXITED(status))
+ break;
+ 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.
+ */
+ ptrace(PTRACE_SETOPTIONS, pid, 0L, test_options);
+ }
+ if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
+ it_worked = 1;
+ }
+ if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
+ kill(pid, SIGKILL);
+ error_msg_and_die("PTRACE_SYSCALL doesn't work");
+ }
+ }
+
+ if (it_worked) {
+ SYSCALLTRAP = (SIGTRAP | 0x80);
+ ptrace_setoptions_for_all = test_options;
+ if (debug)
+ fprintf(stderr, "ptrace_setoptions_for_all = %#x\n",
+ ptrace_setoptions_for_all);
+ return;
+ }
+
+ fprintf(stderr,
+ "Test for PTRACE_O_TRACESYSGOOD failed, giving up using this feature.\n");
+}
#endif
int
#ifdef LINUX
if (followfork) {
- if (test_ptrace_setoptions() < 0) {
+ if (test_ptrace_setoptions_followfork() < 0) {
fprintf(stderr,
"Test for options supported by PTRACE_SETOPTIONS "
"failed, giving up using this feature.\n");
- ptrace_setoptions = 0;
+ ptrace_setoptions_followfork = 0;
}
if (debug)
- fprintf(stderr, "ptrace_setoptions = %#x\n",
- ptrace_setoptions);
+ fprintf(stderr, "ptrace_setoptions_followfork = %#x\n",
+ ptrace_setoptions_followfork);
}
+ test_ptrace_setoptions_for_all();
#endif
/* Check if they want to redirect the output. */
break;
}
error = ptrace_restart(PTRACE_CONT, tcp,
- WSTOPSIG(status) == SIGTRAP ? 0
+ WSTOPSIG(status) == SYSCALLTRAP ? 0
: WSTOPSIG(status));
if (error < 0)
break;
}
return handle_new_child(tcp, childpid, 0);
}
+ if (status >> 16 == PTRACE_EVENT_EXEC) {
+ if (debug)
+ fprintf(stderr, "PTRACE_EVENT_EXEC on pid %d (ignored)\n", tcp->pid);
+ return 0;
+ }
return 1;
}
#endif
fprintf(stderr, "pid %u stopped, [%s]\n",
pid, signame(WSTOPSIG(status)));
- if (ptrace_setoptions && (status >> 16)) {
+ if (status >> 16) {
if (handle_ptrace_event(status, tcp) != 1)
goto tracing;
}
}
}
#ifdef LINUX
- if (followfork && (tcp->parent == NULL) && ptrace_setoptions)
- if (ptrace(PTRACE_SETOPTIONS, tcp->pid,
- NULL, ptrace_setoptions) < 0 &&
- errno != ESRCH)
- ptrace_setoptions = 0;
+ int options = ptrace_setoptions_for_all;
+ if (followfork && (tcp->parent == NULL))
+ options |= ptrace_setoptions_followfork;
+ if (options) {
+ if (debug)
+ fprintf(stderr, "setting opts %x on pid %d\n", options, tcp->pid);
+ if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, options) < 0) {
+ if (errno != ESRCH) {
+ /* Should never happen, really */
+ error_msg_and_die("PTRACE_SETOPTIONS");
+ }
+ }
+ }
#endif
goto tracing;
}
- if (WSTOPSIG(status) != SIGTRAP) {
+ if (WSTOPSIG(status) != SYSCALLTRAP) {
if (WSTOPSIG(status) == SIGSTOP &&
(tcp->flags & TCB_SIGTRAPPED)) {
/*