[PATCH v3] strace.c: refactor trace into two functions
Victor Krapivensky
krapivenskiy.va at phystech.edu
Fri Jun 2 14:05:56 UTC 2017
This change decouples waiting for next event from reacting to it. This
makes the control flow easier to understand, and serves as a preparation
for implementing a pull-style API for LuaJIT.
* strace.c (enum trace_event): New enum.
(trace): Split into...
(next_event, dispatch_event): ...new functions.
(main): Use them.
---
strace.c | 351 ++++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 211 insertions(+), 140 deletions(-)
diff --git a/strace.c b/strace.c
index 42650ab..0d3c8e9 100644
--- a/strace.c
+++ b/strace.c
@@ -2252,21 +2252,76 @@ 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_NEXT,
+
+ /* Restart the tracee with signal 0 and call next_event() again. */
+ TE_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.
+ */
+
+ /*
+ * Syscall entry or exit.
+ * Restart the tracee with signal 0, or with an injected signal number.
+ */
+ TE_SYSCALL_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,
+
+ /*
+ * Tracee was killed by a signal with number WTERMSIG(*pstatus).
+ */
+ TE_SIGNALLED,
+
+ /*
+ * Tracee was stopped by a signal with number WSTOPSIG(*pstatus).
+ * Restart the tracee with that signal number.
+ */
+ TE_GROUP_STOP,
+
+ /*
+ * Tracee exited with status WEXITSTATUS(*pstatus).
+ */
+ TE_EXITED,
+
+ /*
+ * 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,
+};
+
+static enum trace_event
+next_event(int *pstatus, siginfo_t *si)
{
int pid;
int wait_errno;
int status;
- bool stopped;
unsigned int sig;
unsigned int event;
struct tcb *tcp;
struct rusage ru;
if (interrupted)
- return false;
+ return TE_BREAK;
/*
* Used to exit simply when nprocs hits zero, but in this testcase:
@@ -2286,21 +2341,21 @@ trace(void)
* on exit. Oh well...
*/
if (nprocs == 0)
- return false;
+ return TE_BREAK;
}
if (interactive)
sigprocmask(SIG_SETMASK, &start_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_SETMASK, &blocked_set, NULL);
if (pid < 0) {
if (wait_errno == EINTR)
- return true;
+ return TE_NEXT;
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:
@@ -2309,10 +2364,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_NEXT;
}
if (debug_flag)
@@ -2324,61 +2381,29 @@ trace(void)
if (!tcp) {
tcp = maybe_allocate_tcb(pid, status);
if (!tcp)
- return true;
+ return TE_NEXT;
}
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)) {
/*
@@ -2387,128 +2412,172 @@ trace(void)
*/
error_msg("pid %u not stopped!", pid);
droptcb(tcp);
- return true;
+ return TE_NEXT;
}
/* 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:
- break;
- case PTRACE_EVENT_EXIT:
- print_event_exit(tcp);
- goto restart_tracee_with_sig_0;
-#if USE_SEIZE
- case PTRACE_EVENT_STOP:
+ 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_RESTART;
+ } else if (sig == syscall_trap_sig) {
+ return TE_SYSCALL_STOP;
+ } else {
+ *si = (siginfo_t) {};
/*
- * PTRACE_INTERRUPT-stop or group-stop.
- * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+ * 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...
*/
- switch (sig) {
- case SIGSTOP:
- case SIGTSTP:
- case SIGTTIN:
- case SIGTTOU:
- stopped = true;
- goto show_stopsig;
- }
- /* fall through */
+ bool stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, si) < 0;
+ return stopped ? TE_GROUP_STOP : TE_SIGNAL_DELIVERY_STOP;
+ }
+ break;
+#if USE_SEIZE
+ case PTRACE_EVENT_STOP:
+ /*
+ * PTRACE_INTERRUPT-stop or group-stop.
+ * PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
+ */
+ switch (sig) {
+ case SIGSTOP:
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ return TE_GROUP_STOP;
+ }
+ return TE_RESTART;
#endif
- default:
- goto restart_tracee_with_sig_0;
+ case PTRACE_EVENT_EXIT:
+ return TE_STOP_BEFORE_EXIT;
+ default:
+ return TE_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;
- }
+/* 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;
- if (sig != syscall_trap_sig) {
- siginfo_t si = {};
+ switch (ret) {
+ case TE_BREAK:
+ return false;
- /*
- * 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);
+ case TE_NEXT:
+ return true;
- if (!stopped)
- /* It's signal-delivery-stop. Inject the signal */
- goto restart_tracee;
+ case TE_RESTART:
+ break;
- /* It's group-stop */
+ 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;
+ }
+ break;
+
+ case TE_SIGNAL_DELIVERY_STOP:
+ restart_sig = WSTOPSIG(*pstatus);
+ print_stopped(current_tcp, si, restart_sig);
+ break;
+
+ case TE_SIGNALLED:
+ print_signalled(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).
+ * This makes stopping signals work properly on straced
+ * process (that is, process really stops. It used to
+ * continue to run).
*/
- if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
- /* Note: ptrace_restart emitted error message */
- exit_code = 1;
- return false;
- }
- return true;
+ restart_op = PTRACE_LISTEN;
+ restart_sig = 0;
}
- /* We don't have PTRACE_LISTEN support... */
- goto restart_tracee;
- }
+ break;
- /* We handled quick cases, we are permitted to interrupt now. */
- if (interrupted)
- return false;
+ case TE_EXITED:
+ print_exited(current_tcp, current_tcp->pid, *pstatus);
+ droptcb(current_tcp);
+ return true;
- /*
- * This should be syscall entry or exit.
- * Handle it.
- */
- sig = 0;
- if (trace_syscall(tcp, &sig) < 0) {
+ case TE_STOP_BEFORE_EXECVE:
/*
- * 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.
+ * 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.
*/
- return true;
+ 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;
+ }
+ }
+ break;
+
+ case TE_STOP_BEFORE_EXIT:
+ print_event_exit(current_tcp);
+ break;
}
- goto restart_tracee;
-restart_tracee_with_sig_0:
- sig = 0;
+ /* We handled quick cases, we are permitted to interrupt now. */
+ if (interrupted)
+ return false;
-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;
}
@@ -2523,7 +2592,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