[PATCH] strace.c: split trace() into next_event()/dispatch_event()

Victor Krapivensky krapivenskiy.va at phystech.edu
Sun May 14 22:42:40 UTC 2017


This is a preparation needed to implement a pull-style API for LuaJIT.
---
 strace.c | 336 +++++++++++++++++++++++++++++++++++++--------------------------
 1 file changed, 198 insertions(+), 138 deletions(-)

diff --git a/strace.c b/strace.c
index aee2097..10fa1bf 100644
--- a/strace.c
+++ b/strace.c
@@ -2250,9 +2250,65 @@ print_event_exit(struct tcb *tcp)
 	line_ended();
 }
 
-/* Returns true iff the main trace loop has to continue. */
-static bool
-trace(void)
+enum trace_event {
+	/* Break the main loop. */
+	TE_BREAK,
+
+	/* Call next_event() again. */
+	TE_AGAIN,
+
+	/* Restart the tracee with signal 0 and call next_event() again. */
+	TE_AGAIN_RESTART,
+
+	/*
+	 * For all the events below, current_tcp is set to current tracee's tcb.
+	 * All the suggested actions imply that you want to continue the tracing of the current
+	 * tracee; alternatively, you can detach it.
+	 */
+
+	/*
+	 * Tracee is going to perform execve().
+	 * Restart the tracee with signal 0.
+	 */
+	TE_STOP_BEFORE_EXECVE,
+
+	/*
+	 * Tracee is going to terminate.
+	 * Restart the tracee with signal 0.
+	 */
+	TE_STOP_BEFORE_EXIT,
+
+	/*
+	 * Tracee was killed by a signal with number WTERMSIG(*pstatus).
+	 */
+	TE_SIGNALLED,
+
+	/*
+	 * Tracee exited with status WEXITSTATUS(*pstatus).
+	 */
+	TE_EXITED,
+
+	/*
+	 * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
+	 * Restart the tracee with that signal number.
+	 */
+	TE_GROUP_STOP,
+
+	/*
+	 * Tracee received signal with number WSTOPSIG(*pstatus); signal info is written to *si.
+	 * Restart the tracee (with that signal number if you want to deliver it).
+	 */
+	TE_SIGNAL_DELIVERY_STOP,
+
+	/*
+	 * Syscall entry or exit.
+	 * Restart the tracee with signal 0, or with an injected signal number.
+	 */
+	TE_SYSCALL_STOP,
+};
+
+static enum trace_event
+next_event(int *pstatus, siginfo_t *si)
 {
 	int pid;
 	int wait_errno;
@@ -2260,11 +2316,11 @@ trace(void)
 	bool stopped;
 	unsigned int sig;
 	unsigned int event;
-	struct tcb *tcp;
 	struct rusage ru;
+	struct tcb *tcp;
 
 	if (interrupted)
-		return false;
+		return TE_BREAK;
 
 	/*
 	 * Used to exit simply when nprocs hits zero, but in this testcase:
@@ -2284,21 +2340,21 @@ trace(void)
 		 * on exit. Oh well...
 		 */
 		if (nprocs == 0)
-			return false;
+			return TE_BREAK;
 	}
 
 	if (interactive)
 		sigprocmask(SIG_SETMASK, &empty_set, NULL);
-	pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
+	pid = wait4(-1, pstatus, __WALL, (cflag ? &ru : NULL));
 	wait_errno = errno;
 	if (interactive)
-		sigprocmask(SIG_BLOCK, &blocked_set, NULL);
+		sigprocmask(SIG_SETMASK, &blocked_set, NULL);
 
 	if (pid < 0) {
 		if (wait_errno == EINTR)
-			return true;
+			return TE_AGAIN;
 		if (nprocs == 0 && wait_errno == ECHILD)
-			return false;
+			return TE_BREAK;
 		/*
 		 * If nprocs > 0, ECHILD is not expected,
 		 * treat it as any other error here:
@@ -2307,10 +2363,12 @@ trace(void)
 		perror_msg_and_die("wait4(__WALL)");
 	}
 
+	status = *pstatus;
+
 	if (pid == popen_pid) {
 		if (!WIFSTOPPED(status))
 			popen_pid = 0;
-		return true;
+		return TE_AGAIN;
 	}
 
 	if (debug_flag)
@@ -2322,61 +2380,28 @@ trace(void)
 	if (!tcp) {
 		tcp = maybe_allocate_tcb(pid, status);
 		if (!tcp)
-			return true;
+			return TE_AGAIN;
 	}
 
 	clear_regs();
 
 	event = (unsigned int) status >> 16;
 
-	if (event == PTRACE_EVENT_EXEC) {
-		/*
-		 * Under Linux, execve changes pid to thread leader's pid,
-		 * and we see this changed pid on EVENT_EXEC and later,
-		 * execve sysexit. Leader "disappears" without exit
-		 * notification. Let user know that, drop leader's tcb,
-		 * and fix up pid in execve thread's tcb.
-		 * Effectively, execve thread's tcb replaces leader's tcb.
-		 *
-		 * BTW, leader is 'stuck undead' (doesn't report WIFEXITED
-		 * on exit syscall) in multithreaded programs exactly
-		 * in order to handle this case.
-		 *
-		 * PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
-		 * On 2.6 and earlier, it can return garbage.
-		 */
-		if (os_release >= KERNEL_VERSION(3,0,0))
-			tcp = maybe_switch_tcbs(tcp, pid);
-
-		if (detach_on_execve) {
-			if (tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
-				tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
-			} else {
-				detach(tcp); /* do "-b execve" thingy */
-				return true;
-			}
-		}
-	}
-
-	/* Set current output file */
 	current_tcp = tcp;
 
+	if (event == PTRACE_EVENT_EXEC)
+		return TE_STOP_BEFORE_EXECVE;
+
 	if (cflag) {
 		tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
 		tcp->stime = ru.ru_stime;
 	}
 
-	if (WIFSIGNALED(status)) {
-		print_signalled(tcp, pid, status);
-		droptcb(tcp);
-		return true;
-	}
+	if (WIFSIGNALED(status))
+		return TE_SIGNALLED;
 
-	if (WIFEXITED(status)) {
-		print_exited(tcp, pid, status);
-		droptcb(tcp);
-		return true;
-	}
+	if (WIFEXITED(status))
+		return TE_EXITED;
 
 	if (!WIFSTOPPED(status)) {
 		/*
@@ -2385,22 +2410,43 @@ trace(void)
 		 */
 		error_msg("pid %u not stopped!", pid);
 		droptcb(tcp);
-		return true;
+		return TE_AGAIN;
 	}
 
 	/* Is this the very first time we see this tracee stopped? */
-	if (tcp->flags & TCB_STARTUP) {
+	if (tcp->flags & TCB_STARTUP)
 		startup_tcb(tcp);
-	}
 
 	sig = WSTOPSIG(status);
 
 	switch (event) {
 		case 0:
+			/*
+			 * Is this post-attach SIGSTOP?
+			 * Interestingly, the process may stop
+			 * with STOPSIG equal to some other signal
+			 * than SIGSTOP if we happend to attach
+			 * just before the process takes a signal.
+			 */
+			if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
+				if (debug_flag)
+					error_msg("ignored SIGSTOP on pid %d", tcp->pid);
+				tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
+				return TE_AGAIN_RESTART;
+			} else if (sig == syscall_trap_sig) {
+				return TE_SYSCALL_STOP;
+			} else {
+				*si = (siginfo_t) {};
+				/*
+				 * True if tracee is stopped by signal
+				 * (as opposed to "tracee received signal").
+				 * TODO: shouldn't we check for errno == EINVAL too?
+				 * We can get ESRCH instead, you know...
+				 */
+				stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, si) < 0;
+				return stopped ? TE_GROUP_STOP : TE_SIGNAL_DELIVERY_STOP;
+			}
 			break;
-		case PTRACE_EVENT_EXIT:
-			print_event_exit(tcp);
-			goto restart_tracee_with_sig_0;
 #if USE_SEIZE
 		case PTRACE_EVENT_STOP:
 			/*
@@ -2412,101 +2458,113 @@ trace(void)
 				case SIGTSTP:
 				case SIGTTIN:
 				case SIGTTOU:
-					stopped = true;
-					goto show_stopsig;
-			}
-			/* fall through */
+					return TE_GROUP_STOP;
+				}
+			return TE_AGAIN_RESTART;
 #endif
+		case PTRACE_EVENT_EXIT:
+			return TE_STOP_BEFORE_EXIT;
 		default:
-			goto restart_tracee_with_sig_0;
+			return TE_AGAIN_RESTART;
 	}
+}
 
-	/*
-	 * Is this post-attach SIGSTOP?
-	 * Interestingly, the process may stop
-	 * with STOPSIG equal to some other signal
-	 * than SIGSTOP if we happend to attach
-	 * just before the process takes a signal.
-	 */
-	if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
-		if (debug_flag)
-			error_msg("ignored SIGSTOP on pid %d", tcp->pid);
-		tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
-		goto restart_tracee_with_sig_0;
-	}
-
-	if (sig != syscall_trap_sig) {
-		siginfo_t si = {};
-
-		/*
-		 * True if tracee is stopped by signal
-		 * (as opposed to "tracee received signal").
-		 * TODO: shouldn't we check for errno == EINVAL too?
-		 * We can get ESRCH instead, you know...
-		 */
-		stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, &si) < 0;
-#if USE_SEIZE
-show_stopsig:
-#endif
-		print_stopped(tcp, stopped ? NULL : &si, sig);
-
-		if (!stopped)
-			/* It's signal-delivery-stop. Inject the signal */
-			goto restart_tracee;
-
-		/* It's group-stop */
-		if (use_seize) {
+/* Returns true iff the main trace loop has to continue. */
+static bool
+dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
+{
+	unsigned int restart_op = PTRACE_SYSCALL;
+	unsigned int restart_sig = 0;
+	switch (ret) {
+		case TE_BREAK:
+			return false;
+		case TE_AGAIN:
+			return true;
+		case TE_AGAIN_RESTART:
+			goto restart;
+		case TE_STOP_BEFORE_EXECVE:
 			/*
-			 * This ends ptrace-stop, but does *not* end group-stop.
-			 * This makes stopping signals work properly on straced process
-			 * (that is, process really stops. It used to continue to run).
+			 * Under Linux, execve changes pid to thread leader's pid,
+			 * and we see this changed pid on EVENT_EXEC and later,
+			 * execve sysexit. Leader "disappears" without exit
+			 * notification. Let user know that, drop leader's tcb,
+			 * and fix up pid in execve thread's tcb.
+			 * Effectively, execve thread's tcb replaces leader's tcb.
+			 *
+			 * BTW, leader is 'stuck undead' (doesn't report WIFEXITED
+			 * on exit syscall) in multithreaded programs exactly
+			 * in order to handle this case.
+			 *
+			 * PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
+			 * On 2.6 and earlier, it can return garbage.
 			 */
-			if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
-				/* Note: ptrace_restart emitted error message */
-				exit_code = 1;
-				return false;
+			if (os_release >= KERNEL_VERSION(3,0,0))
+				current_tcp = maybe_switch_tcbs(current_tcp, current_tcp->pid);
+
+			if (detach_on_execve) {
+				if (current_tcp->flags & TCB_SKIP_DETACH_ON_FIRST_EXEC) {
+					current_tcp->flags &= ~TCB_SKIP_DETACH_ON_FIRST_EXEC;
+				} else {
+					detach(current_tcp); /* do "-b execve" thingy */
+					return true;
+				}
 			}
+			goto restart;
+		case TE_SIGNALLED:
+			print_signalled(current_tcp, current_tcp->pid, *pstatus);
+			droptcb(current_tcp);
 			return true;
-		}
-		/* We don't have PTRACE_LISTEN support... */
-		goto restart_tracee;
+		case TE_EXITED:
+			print_exited(current_tcp, current_tcp->pid, *pstatus);
+			droptcb(current_tcp);
+			return true;
+		case TE_GROUP_STOP:
+			restart_sig = WSTOPSIG(*pstatus);
+			print_stopped(current_tcp, NULL, restart_sig);
+			if (use_seize) {
+				/*
+				 * This ends ptrace-stop, but does *not* end group-stop.
+				 * This makes stopping signals work properly on straced
+				 * process (that is, process really stops. It used to
+				 * continue to run).
+				 */
+				restart_op = PTRACE_LISTEN;
+				restart_sig = 0;
+			}
+			goto restart;
+		case TE_SIGNAL_DELIVERY_STOP:
+			restart_sig = WSTOPSIG(*pstatus);
+			print_stopped(current_tcp, si, restart_sig);
+			goto restart;
+		case TE_SYSCALL_STOP:
+			if (trace_syscall(current_tcp, &restart_sig) < 0) {
+				/*
+				 * ptrace() failed in trace_syscall().
+				 * Likely a result of process disappearing mid-flight.
+				 * Observed case: exit_group() or SIGKILL terminating
+				 * all processes in thread group.
+				 * We assume that ptrace error was caused by process death.
+				 * We used to detach(current_tcp) here, but since we no longer
+				 * implement "detach before death" policy/hack,
+				 * we can let this process to report its death to us
+				 * normally, via WIFEXITED or WIFSIGNALED wait status.
+				 */
+				return true;
+			}
+			goto restart;
+		case TE_STOP_BEFORE_EXIT:
+			print_event_exit(current_tcp);
+			goto restart;
 	}
-
-	/* We handled quick cases, we are permitted to interrupt now. */
+restart:
 	if (interrupted)
 		return false;
 
-	/*
-	 * This should be syscall entry or exit.
-	 * Handle it.
-	 */
-	sig = 0;
-	if (trace_syscall(tcp, &sig) < 0) {
-		/*
-		 * ptrace() failed in trace_syscall().
-		 * Likely a result of process disappearing mid-flight.
-		 * Observed case: exit_group() or SIGKILL terminating
-		 * all processes in thread group.
-		 * We assume that ptrace error was caused by process death.
-		 * We used to detach(tcp) here, but since we no longer
-		 * implement "detach before death" policy/hack,
-		 * we can let this process to report its death to us
-		 * normally, via WIFEXITED or WIFSIGNALED wait status.
-		 */
-		return true;
-	}
-	goto restart_tracee;
-
-restart_tracee_with_sig_0:
-	sig = 0;
-
-restart_tracee:
-	if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
+	if (ptrace_restart(restart_op, current_tcp, restart_sig) < 0) {
 		/* Note: ptrace_restart emitted error message */
 		exit_code = 1;
 		return false;
 	}
-
 	return true;
 }
 
@@ -2517,7 +2575,9 @@ main(int argc, char *argv[])
 
 	exit_code = !nprocs;
 
-	while (trace())
+	int status;
+	siginfo_t si;
+	while (dispatch_event(next_event(&status, &si), &status, &si))
 		;
 
 	cleanup();
-- 
2.10.2





More information about the Strace-devel mailing list