[PATCH v4 3/4] Implement -e status=set option

Paul Chaignon paul.chaignon at gmail.com
Sat Jun 15 17:32:45 UTC 2019


The status qualifier enables filtering based on the return status of
syscalls.  -z and -Z become aliases for -e status=successful and -e
status=failed.  Staged output is only enabled when at least one status is
filtered, that is, when the set is incomplete.

Signed-off-by: Paul Chaignon <paul.chaignon at gmail.com>
---
 defs.h           |  2 --
 filter_qualify.c | 33 +++++++++++++++++++++++++++++++++
 number_set.c     | 32 ++++++++++++++++++++++++++++++++
 number_set.h     | 13 +++++++++++++
 strace.1.in      | 35 +++++++++++++++++++++++++++++++++++
 strace.c         | 31 +++++++++++++++++++------------
 syscall.c        | 23 +++++++++++++----------
 7 files changed, 145 insertions(+), 24 deletions(-)

diff --git a/defs.h b/defs.h
index 213be9e6..848e925d 100644
--- a/defs.h
+++ b/defs.h
@@ -420,8 +420,6 @@ extern bool Tflag;
 extern bool iflag;
 extern bool count_wallclock;
 extern unsigned int qflag;
-extern bool not_failing_only;
-extern bool failing_only;
 extern unsigned int show_fd_path;
 /* are we filtering traces based on paths? */
 extern struct path_set {
diff --git a/filter_qualify.c b/filter_qualify.c
index 4a05f1b2..3f40b741 100644
--- a/filter_qualify.c
+++ b/filter_qualify.c
@@ -12,10 +12,12 @@
 #include "filter.h"
 #include "delay.h"
 #include "retval.h"
+#include "static_assert.h"
 
 struct number_set *read_set;
 struct number_set *write_set;
 struct number_set *signal_set;
+struct number_set *status_set;
 
 static struct number_set *abbrev_set;
 static struct number_set *inject_set;
@@ -57,6 +59,28 @@ sigstr_to_uint(const char *s)
 	return -1;
 }
 
+static const char *statuses[] = {
+	"successful",
+	"failed",
+	"unfinished",
+	"unavailable",
+	"detached",
+};
+static_assert(sizeof(statuses) / sizeof(char *) == __MAX_STATUS,
+	      "statuses array and status_t enum mismatch");
+
+static int
+statusstr_to_uint(const char *str)
+{
+	unsigned int i;
+
+	for (i = 0; i < __MAX_STATUS; ++i)
+		if (strcasecmp(str, statuses[i]) == 0)
+			return i;
+
+	return -1;
+}
+
 static int
 find_errno_by_name(const char *name)
 {
@@ -275,6 +299,14 @@ qualify_signals(const char *const str)
 	qualify_tokens(str, signal_set, sigstr_to_uint, "signal");
 }
 
+static void
+qualify_status(const char *const str)
+{
+	if (!status_set)
+		status_set = alloc_number_set_array(1);
+	qualify_tokens(str, status_set, statusstr_to_uint, "status");
+}
+
 static void
 qualify_trace(const char *const str)
 {
@@ -421,6 +453,7 @@ static const struct qual_options {
 	{ "x",		qualify_raw	},
 	{ "signal",	qualify_signals	},
 	{ "signals",	qualify_signals	},
+	{ "status",	qualify_status },
 	{ "s",		qualify_signals	},
 	{ "read",	qualify_read	},
 	{ "reads",	qualify_read	},
diff --git a/number_set.c b/number_set.c
index 4092ffda..878fad48 100644
--- a/number_set.c
+++ b/number_set.c
@@ -47,6 +47,31 @@ reallocate_number_set(struct number_set *const set, const unsigned int new_nslot
 	set->nslots = new_nslots;
 }
 
+static unsigned int
+popcount(const unsigned int x)
+{
+	unsigned int count = 0;
+
+#ifdef HAVE___BUILTIN_POPCOUNT
+	count = __builtin_popcount(x);
+#else
+	for (; x; ++count)
+		x &= x - 1;
+#endif
+	return count;
+}
+
+static unsigned int
+get_number_setbit(const struct number_set *const set)
+{
+	unsigned int i;
+	unsigned int count = 0;
+
+	for (i = 0; i < set->nslots; ++i)
+		count += popcount(set->vec[i]);
+	return count;
+}
+
 bool
 number_set_array_is_empty(const struct number_set *const set,
 			  const unsigned int idx)
@@ -69,6 +94,13 @@ is_number_in_set_array(const unsigned int number, const struct number_set *const
 		&& number_isset(number, set[idx].vec)) ^ set[idx].not;
 }
 
+bool
+is_complete_set(const struct number_set *const set, const unsigned int max_numbers)
+{
+	return set && ((set->not && !set->nslots) ||
+		       (get_number_setbit(set) == max_numbers));
+}
+
 void
 add_number_to_set(const unsigned int number, struct number_set *const set)
 {
diff --git a/number_set.h b/number_set.h
index 77dc3a9c..2e2025e4 100644
--- a/number_set.h
+++ b/number_set.h
@@ -21,6 +21,9 @@ is_number_in_set(unsigned int number, const struct number_set *);
 extern bool
 is_number_in_set_array(unsigned int number, const struct number_set *, unsigned int idx);
 
+extern bool
+is_complete_set(const struct number_set *, unsigned int max_numbers);
+
 extern void
 add_number_to_set(unsigned int number, struct number_set *);
 
@@ -39,8 +42,18 @@ alloc_number_set_array(unsigned int nmemb) ATTRIBUTE_MALLOC;
 extern void
 free_number_set_array(struct number_set *, unsigned int nmemb);
 
+enum status_t {
+	STATUS_SUCCESSFUL,
+	STATUS_FAILED,
+	STATUS_UNFINISHED,
+	STATUS_UNAVAILABLE,
+	STATUS_DETACHED,
+	__MAX_STATUS,
+};
+
 extern struct number_set *read_set;
 extern struct number_set *write_set;
 extern struct number_set *signal_set;
+extern struct number_set *status_set;
 
 #endif /* !STRACE_NUMBER_SET_H */
diff --git a/strace.1.in b/strace.1.in
index e1090a0f..4bd406df 100644
--- a/strace.1.in
+++ b/strace.1.in
@@ -408,6 +408,7 @@ is one of
 .BR read ,
 .BR write ,
 .BR fault ,
+.BR status ,
 .BR inject ,
 or
 .B kvm
@@ -613,6 +614,40 @@ Note that this is independent from the normal tracing of the
 system call which is controlled by the option
 .BR -e "\ " trace = write .
 .TP
+\fB\-e\ status\fR=\,\fIset\fR
+Trace only system calls with the specified return status.  The default is
+.BR status = all .
+When using the
+.B status qualifier, because
+.B strace waits for system calls to return before deciding if they should be
+printed, the order of events may not be preserved anymore.  For example, if a
+first system call is being executed and another is called by a different
+thread,
+.B strace will print the first system call after the second.  The first system
+call will still be marked as
+.IR unfinished and
+.IR resumed , to signal to the user that it was interrupted.
+.TP
+.BR "\-e\ status" = successful
+Trace system calls that returned without an error code.  The
+.B -z option has the effect of
+.BR status=successful .
+.TP
+.BR "\-e\ status" = failed
+Trace system calls that returned with an error code.  The
+.B -Z option has the effect of
+.BR status=failed .
+.TP
+.BR "\-e\ status" = unfinished
+Trace system calls that did not return.  This might happen, for example, due to
+an execve call in a neighbour thread.
+.TP
+.BR "\-e\ status" = unavailable
+Trace system calls that returned but strace failed to fetch the error status.
+.TP
+.BR "\-e\ status" = detached
+Trace system calls for which strace detached before the return.
+.TP
 \fB\-e\ inject\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR|:\fBretval\fR=\,\fIvalue\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBsyscall\fR=\fIsyscall\fR][:\fBdelay_enter\fR=\,\fIusecs\/\fR][:\fBdelay_exit\fR=\,\fIusecs\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
 Perform syscall tampering for the specified set of syscalls.
 
diff --git a/strace.c b/strace.c
index 6c3d68c0..baaa771c 100644
--- a/strace.c
+++ b/strace.c
@@ -109,10 +109,6 @@ static bool daemonized_tracer;
 static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
 #define use_seize (post_attach_sigstop == 0)
 
-/* Sometimes we want to print succeeding/failing syscalls only. */
-bool not_failing_only;
-bool failing_only;
-
 /* Show path associated with fd arguments */
 unsigned int show_fd_path;
 
@@ -273,7 +269,7 @@ Statistics:\n\
 \n\
 Filtering:\n\
   -e expr        a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
-     options:    trace, abbrev, verbose, raw, signal, read, write, fault, inject, kvm\n\
+     options:    trace, abbrev, verbose, raw, signal, read, write, fault, status, inject, kvm\n\
   -P path        trace accesses to path\n\
   -z             print only syscalls that returned without an error code\n\
   -Z             print only syscalls that returned with an error code\n\
@@ -812,12 +808,18 @@ droptcb(struct tcb *tcp)
 	debug_msg("dropped tcb for pid %d, %d remain", tcp->pid, nprocs);
 
 	if (tcp->outf) {
+		bool publish = true;
+		if (!is_complete_set(status_set, __MAX_STATUS)) {
+			publish = is_number_in_set(STATUS_DETACHED, status_set);
+			strace_close_memstream(tcp, publish);
+		}
+
 		if (followfork >= 2) {
-			if (tcp->curcol != 0)
+			if (tcp->curcol != 0 && publish)
 				fprintf(tcp->outf, " <detached ...>\n");
 			fclose(tcp->outf);
 		} else {
-			if (printing_tcp == tcp && tcp->curcol != 0)
+			if (printing_tcp == tcp && tcp->curcol != 0 && publish)
 				fprintf(tcp->outf, " <detached ...>\n");
 			flush_tcp_output(tcp);
 		}
@@ -1577,6 +1579,7 @@ init(int argc, char *argv[])
 	qualify("trace=all");
 	qualify("abbrev=all");
 	qualify("verbose=all");
+	qualify("status=all");
 #if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
 # error Bug in DEFAULT_QUAL_FLAGS
 #endif
@@ -1714,10 +1717,10 @@ init(int argc, char *argv[])
 			show_fd_path++;
 			break;
 		case 'z':
-			not_failing_only = 1;
+			qualify("status=successful");
 			break;
 		case 'Z':
-			failing_only = 1;
+			qualify("status=failed");
 			break;
 		default:
 			error_msg_and_help(NULL);
@@ -1770,8 +1773,8 @@ init(int argc, char *argv[])
 	}
 
 #ifndef HAVE_OPEN_MEMSTREAM
-	if (not_failing_only || failing_only)
-		error_msg_and_help("open_memstream is required to use -z or -Z");
+	if (!is_complete_set(status_set, __MAX_STATUS))
+		error_msg_and_help("open_memstream is required to use -z, -Z, or -e status");
 #endif
 
 	acolumn_spaces = xmalloc(acolumn + 1);
@@ -2100,7 +2103,7 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
 		line_ended();
 		/* Need to reopen memstream for thread
 		 * as we closed it in droptcb. */
-		if (failing_only || not_failing_only)
+		if (!is_complete_set(status_set, __MAX_STATUS))
 			strace_open_memstream(tcp);
 		tcp->flags |= TCB_REPRINT;
 	}
@@ -2217,6 +2220,10 @@ print_event_exit(struct tcb *tcp)
 	tprints(") ");
 	tabto();
 	tprints("= ?\n");
+	if (!is_complete_set(status_set, __MAX_STATUS)) {
+		bool publish = is_number_in_set(STATUS_UNFINISHED, status_set);
+		strace_close_memstream(tcp, publish);
+	}
 	line_ended();
 }
 
diff --git a/syscall.c b/syscall.c
index 2a471711..f868eec2 100644
--- a/syscall.c
+++ b/syscall.c
@@ -656,7 +656,7 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
 	}
 #endif
 
-	if (not_failing_only || failing_only)
+	if (!is_complete_set(status_set, __MAX_STATUS))
 		strace_open_memstream(tcp);
 
 	printleader(tcp);
@@ -751,6 +751,11 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
 		tprints(") ");
 		tabto();
 		tprints("= ? <unavailable>\n");
+		if (!is_complete_set(status_set, __MAX_STATUS)) {
+			bool publish = is_number_in_set(STATUS_UNAVAILABLE,
+							status_set);
+			strace_close_memstream(tcp, publish);
+		}
 		line_ended();
 		return res;
 	}
@@ -760,13 +765,6 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
 	if (raw(tcp)) {
 		/* sys_res = printargs(tcp); - but it's nop on sysexit */
 	} else {
-		if ((not_failing_only && syserror(tcp)) ||
-		    (failing_only && !syserror(tcp))) {
-			strace_close_memstream(tcp, false);
-			line_ended();
-			return 0;	/* ignore failed/successful
-					 * syscalls */
-		}
 		if (tcp->sys_func_rval & RVAL_DECODED)
 			sys_res = tcp->sys_func_rval;
 		else
@@ -902,8 +900,13 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
 	}
 	tprints("\n");
 	dumpio(tcp);
-	if (not_failing_only || failing_only)
-		strace_close_memstream(tcp, true);
+	if (!is_complete_set(status_set, __MAX_STATUS)) {
+		bool publish = syserror(tcp)
+			       && is_number_in_set(STATUS_FAILED, status_set);
+		publish |= !syserror(tcp)
+			   && is_number_in_set(STATUS_SUCCESSFUL, status_set);
+		strace_close_memstream(tcp, publish);
+	}
 	line_ended();
 
 #ifdef ENABLE_STACKTRACE
-- 
2.17.1



More information about the Strace-devel mailing list