]> granicus.if.org Git - strace/commitdiff
Implement -e status=set option
authorPaul Chaignon <paul.chaignon@gmail.com>
Sat, 15 Jun 2019 05:32:03 +0000 (07:32 +0200)
committerDmitry V. Levin <ldv@altlinux.org>
Wed, 10 Jul 2019 16:12:44 +0000 (16:12 +0000)
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.

* signal.c (popcount32): Move ...
* defs.h (popcount32): ... here.
(not_failing_only, failing_only): Remove.
* filter_qualify.c (status_set): New number_set variable.
(statuses): New variable for names of statuses.
(statusstr_to_uint, qualify_status): New functions.
(qual_options): Handle status qualifier.
* number_set.c (get_number_setbit, is_complete_set): New functions.
* number_set.h (is_complete_set): New prototype.
(status_t): New enumeration for statuses.
(status_set): New prototype.
* strace.1.in: Document new status qualifier.
* strace.c (not_failing_only, failing_only): Remove.
(droptcb): Handle status=detached option.
(init): Handle new status qualifier, set status_set variable on -z and -Z
options, warn on -zZ and -Zz, use is_complete_set.
(maybe_switch_tcbs): Reopen memstream after tcb switch.
(print_event_exit): Handle status=unfinished option.
* syscall.c (syscall_entering_trace): Use is_complete_set.
(syscall_exiting_trace): Use is_complete_set, handle status=unavailable
option.
* NEWS: Mention this change.

Signed-off-by: Paul Chaignon <paul.chaignon@gmail.com>
NEWS
defs.h
filter_qualify.c
number_set.c
number_set.h
signal.c
strace.1.in
strace.c
syscall.c

diff --git a/NEWS b/NEWS
index 67656ea4d98e82c28934bdfd47f1de8f36e66f3a..8241dc40e34c306616f10bd75771ae7a98782310 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,9 @@ Noteworthy changes in release ?.? (????-??-??)
     PTRACE_GET_SYSCALL_INFO is in use.
 
 * Improvements
-  * Implemented syscall return status filtering options: -z, -Z.
+  * Implemented syscall return status filtering with -e status=set option
+    and its aliases: -z (limit syscall printing to successful syscalls only)
+    and -Z (limit syscall printing to failed syscalls only).
   * Implemented decoding of open_tree, move_mount, fsopen, fsconfig, fsmount,
     and fspick syscalls.
   * Enhanced decoding of bpf, clone, mbind, and set_mempolicy syscalls.
diff --git a/defs.h b/defs.h
index b9b394ec8a030727e2335b26d58df455a1b2daea..b6140fe0b08dbc4da32481401e40e622ae1fa0ca 100644 (file)
--- 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 {
@@ -1451,6 +1449,28 @@ truncate_kulong_to_current_wordsize(const kernel_ulong_t v)
         sizeof(v) == sizeof(long) ? (long long) (long) (v) : \
         (long long) (v))
 
+/*
+ * Computes the popcount of a vector of 32-bit values.
+ */
+static inline unsigned int
+popcount32(const uint32_t *a, unsigned int size)
+{
+       unsigned int count = 0;
+
+       for (; size; ++a, --size) {
+               uint32_t x = *a;
+
+#ifdef HAVE___BUILTIN_POPCOUNT
+               count += __builtin_popcount(x);
+#else
+               for (; x; ++count)
+                       x &= x - 1;
+#endif
+       }
+
+       return count;
+}
+
 extern const char *const errnoent[];
 extern const char *const signalent[];
 extern const unsigned int nerrnos;
index 4a05f1b277cb340f98e81ee75c199d09735d8b7a..e5e3728a5811627473724bbf6f78bfd0e81e2bdf 100644 (file)
 #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(ARRAY_SIZE(statuses) == NUMBER_OF_STATUSES,
+             "statuses array and status_t enum mismatch");
+
+static int
+statusstr_to_uint(const char *str)
+{
+       unsigned int i;
+
+       for (i = 0; i < NUMBER_OF_STATUSES; ++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    },
index 4092ffdac91cf62d4011426dd1a155666dc9bd1d..70727eee2eff1c6b99b92250ced7e97c73c46970 100644 (file)
@@ -12,7 +12,9 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
+#include "defs.h"
 #include "number_set.h"
+#include "static_assert.h"
 #include "xmalloc.h"
 
 typedef unsigned int number_slot_t;
@@ -47,6 +49,14 @@ reallocate_number_set(struct number_set *const set, const unsigned int new_nslot
        set->nslots = new_nslots;
 }
 
+static unsigned int
+get_number_setbit(const struct number_set *const set)
+{
+       static_assert(sizeof(number_slot_t) == sizeof(uint32_t),
+                     "number_slot_t is not 32-bit long");
+       return popcount32(set->vec, set->nslots);
+}
+
 bool
 number_set_array_is_empty(const struct number_set *const set,
                          const unsigned int idx)
@@ -69,6 +79,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)
 {
index 77dc3a9c02609f067c9709cb57e937609b2277e2..b6399cc8adb05ad19f0a4badf454dab477525901 100644 (file)
@@ -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,
+       NUMBER_OF_STATUSES
+};
+
 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 */
index fcaf9d4ffaf2c58ee4d7348d77ac877a644e69cf..3cb54bb3edda37cfacfdc11beae650486c613086 100644 (file)
--- a/signal.c
+++ b/signal.c
@@ -138,25 +138,6 @@ sprintsigname(const int sig)
        return buf;
 }
 
-static unsigned int
-popcount32(const uint32_t *a, unsigned int size)
-{
-       unsigned int count = 0;
-
-       for (; size; ++a, --size) {
-               uint32_t x = *a;
-
-#ifdef HAVE___BUILTIN_POPCOUNT
-               count += __builtin_popcount(x);
-#else
-               for (; x; ++count)
-                       x &= x - 1;
-#endif
-       }
-
-       return count;
-}
-
 const char *
 sprintsigmask_n(const char *prefix, const void *sig_mask, unsigned int bytes)
 {
index e1090a0f1de3a966924e84ac84055e1cffa68e08..567283719e98e1ede62c3a6ece27f1325ba3f6f4 100644 (file)
@@ -409,6 +409,7 @@ is one of
 .BR write ,
 .BR fault ,
 .BR inject ,
+.BR status ,
 or
 .B kvm
 and
@@ -613,6 +614,53 @@ 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
+Print 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 whether they should be printed
+or not, the traditional order of events may not be preserved anymore.  If two
+system calls are executed by concurrent threads,
+.B strace
+will first print both the entry and exit of the first system call to exit,
+regardless of their respective entry time.  The entry and exit of the second
+system call to exit will be printed afterwards.  Here is an example when
+.BR select (2)
+is called, but a different thread calls
+.BR clock_gettime (2)
+before
+.BR select (2)
+finishes:
+.CW
+[pid 28779] 1130322148.939977 clock_gettime(CLOCK_REALTIME, {1130322148, 939977000}) = 0
+[pid 28772] 1130322148.438139 select(4, [3], NULL, NULL, NULL) = 1 (in [3])
+.CE
+.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.
 
index 22f30684adf30fe51da43c7ab00edecb2343a2e6..4f03a4c9805a9c8402fe57826e70dfd20b696302 100644 (file)
--- 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, inject, status, 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\
@@ -813,12 +809,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, NUMBER_OF_STATUSES)) {
+                       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);
                }
@@ -1554,7 +1556,7 @@ static void ATTRIBUTE_NOINLINE
 init(int argc, char *argv[])
 {
        int c, i;
-       int optF = 0;
+       int optF = 0, zflags = 0;
 
        if (!program_invocation_name || !*program_invocation_name) {
                static char name[] = "strace";
@@ -1572,6 +1574,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
@@ -1709,10 +1712,14 @@ init(int argc, char *argv[])
                        show_fd_path++;
                        break;
                case 'z':
-                       not_failing_only = 1;
+                       clear_number_set_array(status_set, 1);
+                       add_number_to_set(STATUS_SUCCESSFUL, status_set);
+                       zflags++;
                        break;
                case 'Z':
-                       failing_only = 1;
+                       clear_number_set_array(status_set, 1);
+                       add_number_to_set(STATUS_FAILED, status_set);
+                       zflags++;
                        break;
                default:
                        error_msg_and_help(NULL);
@@ -1765,10 +1772,14 @@ 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, NUMBER_OF_STATUSES))
+               error_msg_and_help("open_memstream is required to use -z, -Z, or -e status");
 #endif
 
+       if (zflags > 1)
+               error_msg("Only the last of -z/-Z options will take effect. "
+                         "See status qualifier for more complex filters.");
+
        acolumn_spaces = xmalloc(acolumn + 1);
        memset(acolumn_spaces, ' ', acolumn);
        acolumn_spaces[acolumn] = '\0';
@@ -2111,6 +2122,12 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
                printleader(tcp);
                tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
                line_ended();
+               /*
+                * Need to reopen memstream for thread
+                * as we closed it in droptcb.
+                */
+               if (!is_complete_set(status_set, NUMBER_OF_STATUSES))
+                       strace_open_memstream(tcp);
                tcp->flags |= TCB_REPRINT;
        }
 
@@ -2237,6 +2254,10 @@ print_event_exit(struct tcb *tcp)
        tprints(") ");
        tabto();
        tprints("= ?\n");
+       if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
+               bool publish = is_number_in_set(STATUS_UNFINISHED, status_set);
+               strace_close_memstream(tcp, publish);
+       }
        line_ended();
 }
 
index 65f00d2d7547d47a474c66558c56b4fc7da3da03..3a31c3cb5ecddf1356416a56b0103482bc8693df 100644 (file)
--- a/syscall.c
+++ b/syscall.c
@@ -650,7 +650,7 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
        }
 #endif
 
-       if (not_failing_only || failing_only)
+       if (!is_complete_set(status_set, NUMBER_OF_STATUSES))
                strace_open_memstream(tcp);
 
        printleader(tcp);
@@ -745,6 +745,11 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
                tprints(") ");
                tabto();
                tprints("= ? <unavailable>\n");
+               if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
+                       bool publish = is_number_in_set(STATUS_UNAVAILABLE,
+                                                       status_set);
+                       strace_close_memstream(tcp, publish);
+               }
                line_ended();
                return res;
        }
@@ -760,14 +765,16 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
                        sys_res = tcp_sysent(tcp)->sys_func(tcp);
        }
 
-       if ((not_failing_only && syserror(tcp)) ||
-           (failing_only && !syserror(tcp))) {
-               strace_close_memstream(tcp, false);
-               line_ended();
-               return 0;       /* ignore failed/successful
-                                * syscalls */
-       } else if (not_failing_only || failing_only) {
-               strace_close_memstream(tcp, true);
+       if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
+               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);
+               if (!publish) {
+                       line_ended();
+                       return 0;
+               }
        }
 
        tprints(") ");