[PATCH] new -D option: move strace process into background. Preserves parent<->child link

Denys Vlasenko dvlasenk at redhat.com
Fri Sep 26 13:24:46 UTC 2008


Hi,

Consider the case where parent process supervises child and
sends signals to it. For the sake of simplicity, in this example
parent wants to ensure that child doesn't run for more than one second:

# timeout1 squeak N

and "squeak N" sleeps for N seconds, then prints "Squeak!" and exits.
If I run

# timeout1 squeak 2

timeout1 parent kills child before it prints "Squeak!".

Imagine that I want to strace squeak.

# timeout1 strace -olog squeak 2

Now timeout1 kills *strace*! squeak continues and prints "Squeak!".
Not good.

This patch introduces -D option.

With this option, strace forks twice.
Unlike normal case, with -D *grandparent* process exec's,
becoming a traced process. Child exits (this prevents straced process
from having children it doesn't expect to have), and grandchild
attaches to grandparent similarly to strace -p PID.

With patched strace:

# timeout1 strace -D -olog squeak 2

strace does not get killed, "Squeak!" does not appear, and log
looks like this:

wait4(-1, NULL, 0, NULL)                = 3573
--- SIGCHLD (Child exited) @ 0 (0) ---
rt_sigaction(SIGCHLD, {SIG_DFL, [], SA_RESTORER, 0x38248322a0}, NULL, 8) = 0
execve("./squeak", ["./squeak", "2"], [/* 54 vars */]) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], SA_RESTORER|SA_INTERRUPT|0x8322a0, (nil)}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({2, 0}, {1, 3839594})         = ? ERESTART_RESTARTBLOCK (To be restarted)
--- SIGUSR1 (User defined signal 1) @ 0 (0) ---
+++ killed by SIGUSR1 +++

Killing by SIGUSR1 is just the way timeout1 program works (see below).
First two syscalls (those before execve) are artifacts of the method
used to attach to the parent.

Example programs:

timeout1.c

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char **argv)
{
        int pid = fork();
        if (!pid) {
                execvp(argv[1], argv + 1);
                return 1;
        }
        sleep(1);
        kill(pid, SIGUSR1) || puts("Sent SIGUSR1");
        usleep(150*1000);
        kill(pid, SIGTERM) || puts("Sent SIGTERM");
        usleep(150*1000);
        kill(pid, 0) || puts("Process is still alive!");
        return 0;
}

squeak.c

int main(int argc, char **argv)
{
        sleep(atoi(argv[1]));
        write(1, "Squeak!\n", sizeof("Squeak!\n")-1);
        return 0;
}

Patch is below.
--
vda


diff -d -urpN strace.0/strace.c strace.2/strace.c
--- strace.0/strace.c	2008-08-06 23:38:52.000000000 +0200
+++ strace.2/strace.c	2008-09-26 14:59:02.000000000 +0200
@@ -81,6 +81,18 @@
 int debug = 0, followfork = 0;
 int dtime = 0, cflag = 0, xflag = 0, qflag = 0;
 static int iflag = 0, interactive = 0, pflag_seen = 0, rflag = 0, tflag = 0;
+/* daemonized_tracer supports -D option.
+ * With this option, strace forks twice.
+ * Unlike normal case, with -D *grandparent* process exec's,
+ * becoming a traced process. Child exits (this prevents traced process
+ * from having children it doesn't expect to have), and grandchild
+ * attaches to grandparent similarly to strace -p PID.
+ * This allows for more transparent interaction in cases
+ * when process and its parent are communicating via signals,
+ * wait() etc. Without -D, strace process gets lodged in between,
+ * disrupting parent<->child link.
+ */
+static int daemonized_tracer = 0;
 
 /* Sometimes we want to print only succeeding syscalls. */
 int not_failing_only = 0;
@@ -144,6 +156,37 @@ static int proc_poll_pipe[2] = { -1, -1 
 #endif
 #endif /* USE_PROCFS */
 
+/* SIGCHLD must be first */
+static const int sigs_we_touch[] = {
+	SIGCHLD, SIGHUP, SIGINT, SIGPIPE, SIGQUIT,
+	SIGTERM, SIGTTIN, SIGTTOU, SIGUSR1
+};
+#define NUM_SIGS_WE_TOUCH (sizeof(sigs_we_touch) / sizeof(sigs_we_touch[0]))
+static sigset_t initial_sigmask;
+struct sigaction initial_sa[NUM_SIGS_WE_TOUCH];
+
+static void save_sigs(void)
+{
+	int i;
+	sigprocmask(SIG_SETMASK, NULL, &initial_sigmask);
+	for (i = 0; i < NUM_SIGS_WE_TOUCH; i++)
+		sigaction(sigs_we_touch[i], NULL, &initial_sa[i]);
+}
+
+static void restore_sigs(void)
+{
+	int i;
+	sigprocmask(SIG_SETMASK, &initial_sigmask, NULL);
+	for (i = 0; i < NUM_SIGS_WE_TOUCH; i++)
+		sigaction(sigs_we_touch[i], &initial_sa[i], NULL);
+}
+
+static void restore_SIGCHLD(void)
+{
+	sigaction(SIGCHLD, &initial_sa[0], NULL);
+}
+
+
 static void
 usage(ofp, exitval)
 FILE *ofp;
@@ -346,6 +389,7 @@ startup_attach(void)
 {
 	int tcbi;
 	struct tcb *tcp;
+	pid_t pid = 0; /* for compiler only */
 
 	/*
 	 * Block user interruptions as we would leave the traced
@@ -356,6 +400,21 @@ startup_attach(void)
 	if (interactive)
 		sigprocmask(SIG_BLOCK, &blocked_set, NULL);
 
+	if (daemonized_tracer) {
+		pid = fork();
+		if (pid < 0)
+			_exit(1);
+		if (pid) { /* parent */
+			/* Wait for child to attach to straced process
+			   (our parent). Child will SIGKILL us then.
+			   Parent proceeds to exec the straced program. */
+			pause();
+			_exit(0); /* paranoia */
+		}
+		/* child: continues */
+		pid = getppid();
+	}
+
 	for (tcbi = 0; tcbi < tcbtabsize; tcbi++) {
 		tcp = tcbtab[tcbi];
 		if (!(tcp->flags & TCB_INUSE) || !(tcp->flags & TCB_ATTACHED))
@@ -446,6 +505,16 @@ Process %u attached - interrupt to quit\
 			continue;
 		}
 		/* INTERRUPTED is going to be checked at the top of TRACE.  */
+
+		if (daemonized_tracer) {
+			/* It is our grandparent we trace, not a -p PID.
+			   Don't want to just detact on exit, so...  */
+			tcp->flags &= ~TCB_ATTACHED;
+			/* Make parent go away.
+			   Also makes grandparent's wait() unblock.  */
+			kill(pid, SIGKILL);
+		}
+
 #endif /* !USE_PROCFS */
 		if (!qflag)
 			fprintf(stderr,
@@ -523,14 +592,18 @@ startup_child (char **argv)
 			progname, filename);
 		exit(1);
 	}
-	switch (pid = fork()) {
-	case -1:
+	pid = fork();
+	if (pid < 0) {
 		perror("strace: fork");
 		cleanup();
 		exit(1);
-		break;
-	case 0: {
-#ifdef USE_PROCFS
+	}
+
+	if ((pid != 0 && daemonized_tracer) /* parent: to become a traced process */
+	 || (pid == 0 && !daemonized_tracer) /* child: to become a traced process */
+	) {
+		pid = getpid();
+#ifdef USE_PROCFS /* NB: for these daemonized_tracer is not supported yet... */
 		if (outf != stderr) close (fileno (outf));
 #ifdef MIPS
 		/* Kludge for SGI, see proc_open for details. */
@@ -542,18 +615,20 @@ startup_child (char **argv)
 #ifndef FREEBSD
 		pause();
 #else /* FREEBSD */
-		kill(getpid(), SIGSTOP); /* stop HERE */
+		kill(pid, SIGSTOP); /* stop HERE */
 #endif /* FREEBSD */
 #else /* !USE_PROCFS */
 		if (outf!=stderr)
 			close(fileno (outf));
 
-		if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) {
-			perror("strace: ptrace(PTRACE_TRACEME, ...)");
-			exit(1);
+		if (!daemonized_tracer) {
+			if (ptrace(PTRACE_TRACEME, 0, (char *) 1, 0) < 0) {
+				perror("strace: ptrace(PTRACE_TRACEME, ...)");
+				exit(1);
+			}
+			if (debug)
+				kill(pid, SIGSTOP);
 		}
-		if (debug)
-			kill(getpid(), SIGSTOP);
 
 		if (username != NULL || geteuid() == 0) {
 			uid_t run_euid = run_uid;
@@ -586,33 +661,46 @@ startup_child (char **argv)
 		else
 			setreuid(run_uid, run_uid);
 
-		/*
-		 * Induce an immediate stop so that the parent
-		 * will resume us with PTRACE_SYSCALL and display
-		 * this execve call normally.
-		 */
-		kill(getpid(), SIGSTOP);
+		if (!daemonized_tracer) {
+			/*
+			 * Induce an immediate stop so that the parent
+			 * will resume us with PTRACE_SYSCALL and display
+			 * this execve call normally.
+			 */
+			restore_sigs();
+			kill(getpid(), SIGSTOP);
+		} else {
+			/* Wait for strace to attach to us.  */
+			restore_sigs();
+			/* Make sure it is not SIG_IGN */
+			signal(SIGCHLD, SIG_DFL);
+			wait(NULL);
+			restore_SIGCHLD();
+		}
 #endif /* !USE_PROCFS */
 
 		execv(pathname, argv);
 		perror("strace: exec");
 		_exit(1);
-		break;
 	}
-	default:
-		if ((tcp = alloctcb(pid)) == NULL) {
-			cleanup();
-			exit(1);
-		}
+
+	/* We are the tracer.  */
+	tcp = alloctcb(daemonized_tracer ? getppid() /* parent */ : pid /* child */);
+	if (tcp == NULL) {
+		cleanup();
+		exit(1);
+	}
+	if (daemonized_tracer) {
+		/* We want subsequent startup_attach() to attach to it.  */
+		tcp->flags |= TCB_ATTACHED;
+	}
 #ifdef USE_PROCFS
-		if (proc_open(tcp, 0) < 0) {
-			fprintf(stderr, "trouble opening proc file\n");
-			cleanup();
-			exit(1);
-		}
-#endif /* USE_PROCFS */
-		break;
+	if (proc_open(tcp, 0) < 0) {
+		fprintf(stderr, "trouble opening proc file\n");
+		cleanup();
+		exit(1);
 	}
+#endif /* USE_PROCFS */
 }
 
 int
@@ -642,6 +730,8 @@ main(int argc, char *argv[])
 	for (tcp = tcbtab[0]; tcp < &tcbtab[0][tcbtabsize]; ++tcp)
 		tcbtab[tcp - tcbtab[0]] = &tcbtab[0][tcp - tcbtab[0]];
 
+	save_sigs();
+
 	outf = stderr;
 	interactive = 1;
 	set_sortby(DEFAULT_SORTBY);
@@ -651,7 +741,11 @@ main(int argc, char *argv[])
 	qualify("verbose=all");
 	qualify("signal=all");
 	while ((c = getopt(argc, argv,
-		"+cdfFhiqrtTvVxza:e:o:O:p:s:S:u:E:")) != EOF) {
+		"+cdfFhiqrtTvVxz"
+#ifndef USE_PROCFS
+		"D"
+#endif
+		"a:e:o:O:p:s:S:u:E:")) != EOF) {
 		switch (c) {
 		case 'c':
 			cflag++;
@@ -660,6 +754,11 @@ main(int argc, char *argv[])
 		case 'd':
 			debug++;
 			break;
+#ifndef USE_PROCFS
+		case 'D':
+			daemonized_tracer++;
+			break;
+#endif
 		case 'F':
 			optF = 1;
 			break;
@@ -873,7 +972,7 @@ main(int argc, char *argv[])
 	sigaction(SIGCHLD, &sa, NULL);
 #endif /* USE_PROCFS */
 
-	if (pflag_seen)
+	if (pflag_seen || daemonized_tracer)
 		startup_attach();
 
 	if (trace() < 0)






More information about the Strace-devel mailing list