]> granicus.if.org Git - strace/commitdiff
Properly handle real SIGTRAPs.
authorDenys Vlasenko <dvlasenk@redhat.com>
Mon, 23 May 2011 19:29:03 +0000 (21:29 +0200)
committerDenys Vlasenko <dvlasenk@redhat.com>
Mon, 23 May 2011 19:29:03 +0000 (21:29 +0200)
* defs.h (ptrace_setoptions): Variable renamed to ptrace_setoptions_followfork.
* process.c (internal_fork): Ditto.
* strace.c (ptrace_setoptions_for_all): New variable.
(SYSCALLTRAP): New variable.
(error_msg_and_die): New function.
(test_ptrace_setoptions_for_all): New function.
(main): Call test_ptrace_setoptions_for_all() at init.
(handle_ptrace_event): Handle PTRACE_EVENT_EXEC (by ignoring it).
(trace): Check events and set ptrace options without -f too.
Check WSTOPSIG(status) not for SIGTRAP, but for SYSCALLTRAP.

defs.h
process.c
strace.c

diff --git a/defs.h b/defs.h
index 0b960f9243eb67cac39ebd25aa6553ae0964b30e..ef4ac4755afa209ed493d6c0804ea1241e5ba2e1 100644 (file)
--- a/defs.h
+++ b/defs.h
@@ -504,7 +504,7 @@ typedef enum {
 extern struct tcb **tcbtab;
 extern int *qual_flags;
 extern int debug, followfork;
-extern unsigned int ptrace_setoptions;
+extern unsigned int ptrace_setoptions_followfork;
 extern int dtime, xflag, qflag;
 extern cflag_t cflag;
 extern int acolumn;
index 6c1aa6b6e7822173f1f5ed0b0caf54dbdf38afed..36ac5ab8326a59b0b7de6007fc9ecafdf750062e 100644 (file)
--- a/process.c
+++ b/process.c
@@ -915,7 +915,7 @@ Process %u resumed (parent %d ready)\n",
 int
 internal_fork(struct tcb *tcp)
 {
-       if ((ptrace_setoptions
+       if ((ptrace_setoptions_followfork
            & (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK))
           == (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK))
                return 0;
index 7874d19ed97f87b0fd14df48c3c7e2da041f2e04..472805482e02b2b9d422d4b21fc2855960e9e2ba 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -33,6 +33,7 @@
 #include "defs.h"
 
 #include <sys/types.h>
+#include <stdarg.h>
 #include <signal.h>
 #include <errno.h>
 #include <sys/param.h>
@@ -83,7 +84,10 @@ extern char *optarg;
 
 
 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;
@@ -211,6 +215,31 @@ usage: strace [-CdDffhiqrtttTvVxxy] [-a column] [-e expr] ... [-o file]\n\
        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
@@ -702,7 +731,7 @@ startup_child (char **argv)
  * 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 |
@@ -727,7 +756,7 @@ test_ptrace_setoptions(void)
                                continue;
                        else if (errno == ECHILD)
                                break;
-                       perror("test_ptrace_setoptions");
+                       perror("test_ptrace_setoptions_followfork");
                        return -1;
                }
                if (tracee_pid != pid) {
@@ -761,9 +790,91 @@ test_ptrace_setoptions(void)
                }
        }
        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
@@ -977,16 +1088,17 @@ main(int argc, char *argv[])
 
 #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. */
@@ -1751,7 +1863,7 @@ int sig;
                                break;
                        }
                        error = ptrace_restart(PTRACE_CONT, tcp,
-                                       WSTOPSIG(status) == SIGTRAP ? 0
+                                       WSTOPSIG(status) == SYSCALLTRAP ? 0
                                        : WSTOPSIG(status));
                        if (error < 0)
                                break;
@@ -2408,6 +2520,11 @@ handle_ptrace_event(int status, struct tcb *tcp)
                }
                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
@@ -2597,7 +2714,7 @@ Process %d attached (waiting for parent)\n",
                        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;
                }
@@ -2630,16 +2747,24 @@ Process %d attached (waiting for parent)\n",
                                }
                        }
 #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)) {
                                /*