From 3454e4b463e6c22c7ea8c5461ef5a077f4650a54 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 23 May 2011 21:29:03 +0200 Subject: [PATCH] Properly handle real SIGTRAPs. * 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 | 2 +- process.c | 2 +- strace.c | 157 ++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/defs.h b/defs.h index 0b960f92..ef4ac475 100644 --- 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; diff --git a/process.c b/process.c index 6c1aa6b6..36ac5ab8 100644 --- 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; diff --git a/strace.c b/strace.c index 7874d19e..47280548 100644 --- a/strace.c +++ b/strace.c @@ -33,6 +33,7 @@ #include "defs.h" #include +#include #include #include #include @@ -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)) { /* -- 2.40.0