Browse Source

Implement -e status=set option

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>
Paul Chaignon 4 months ago
parent
commit
e45a594cb0
9 changed files with 185 additions and 43 deletions
  1. 3
    1
      NEWS
  2. 22
    2
      defs.h
  3. 33
    0
      filter_qualify.c
  4. 17
    0
      number_set.c
  5. 13
    0
      number_set.h
  6. 0
    19
      signal.c
  7. 48
    0
      strace.1.in
  8. 33
    12
      strace.c
  9. 16
    9
      syscall.c

+ 3
- 1
NEWS View File

@@ -6,7 +6,9 @@ Noteworthy changes in release ?.? (????-??-??)
6 6
     PTRACE_GET_SYSCALL_INFO is in use.
7 7
 
8 8
 * Improvements
9
-  * Implemented syscall return status filtering options: -z, -Z.
9
+  * Implemented syscall return status filtering with -e status=set option
10
+    and its aliases: -z (limit syscall printing to successful syscalls only)
11
+    and -Z (limit syscall printing to failed syscalls only).
10 12
   * Implemented decoding of open_tree, move_mount, fsopen, fsconfig, fsmount,
11 13
     and fspick syscalls.
12 14
   * Enhanced decoding of bpf, clone, mbind, and set_mempolicy syscalls.

+ 22
- 2
defs.h View File

@@ -420,8 +420,6 @@ extern bool Tflag;
420 420
 extern bool iflag;
421 421
 extern bool count_wallclock;
422 422
 extern unsigned int qflag;
423
-extern bool not_failing_only;
424
-extern bool failing_only;
425 423
 extern unsigned int show_fd_path;
426 424
 /* are we filtering traces based on paths? */
427 425
 extern struct path_set {
@@ -1451,6 +1449,28 @@ truncate_kulong_to_current_wordsize(const kernel_ulong_t v)
1451 1449
 	 sizeof(v) == sizeof(long) ? (long long) (long) (v) : \
1452 1450
 	 (long long) (v))
1453 1451
 
1452
+/*
1453
+ * Computes the popcount of a vector of 32-bit values.
1454
+ */
1455
+static inline unsigned int
1456
+popcount32(const uint32_t *a, unsigned int size)
1457
+{
1458
+	unsigned int count = 0;
1459
+
1460
+	for (; size; ++a, --size) {
1461
+		uint32_t x = *a;
1462
+
1463
+#ifdef HAVE___BUILTIN_POPCOUNT
1464
+		count += __builtin_popcount(x);
1465
+#else
1466
+		for (; x; ++count)
1467
+			x &= x - 1;
1468
+#endif
1469
+	}
1470
+
1471
+	return count;
1472
+}
1473
+
1454 1474
 extern const char *const errnoent[];
1455 1475
 extern const char *const signalent[];
1456 1476
 extern const unsigned int nerrnos;

+ 33
- 0
filter_qualify.c View File

@@ -12,10 +12,12 @@
12 12
 #include "filter.h"
13 13
 #include "delay.h"
14 14
 #include "retval.h"
15
+#include "static_assert.h"
15 16
 
16 17
 struct number_set *read_set;
17 18
 struct number_set *write_set;
18 19
 struct number_set *signal_set;
20
+struct number_set *status_set;
19 21
 
20 22
 static struct number_set *abbrev_set;
21 23
 static struct number_set *inject_set;
@@ -57,6 +59,28 @@ sigstr_to_uint(const char *s)
57 59
 	return -1;
58 60
 }
59 61
 
62
+static const char *statuses[] = {
63
+	"successful",
64
+	"failed",
65
+	"unfinished",
66
+	"unavailable",
67
+	"detached",
68
+};
69
+static_assert(ARRAY_SIZE(statuses) == NUMBER_OF_STATUSES,
70
+	      "statuses array and status_t enum mismatch");
71
+
72
+static int
73
+statusstr_to_uint(const char *str)
74
+{
75
+	unsigned int i;
76
+
77
+	for (i = 0; i < NUMBER_OF_STATUSES; ++i)
78
+		if (strcasecmp(str, statuses[i]) == 0)
79
+			return i;
80
+
81
+	return -1;
82
+}
83
+
60 84
 static int
61 85
 find_errno_by_name(const char *name)
62 86
 {
@@ -275,6 +299,14 @@ qualify_signals(const char *const str)
275 299
 	qualify_tokens(str, signal_set, sigstr_to_uint, "signal");
276 300
 }
277 301
 
302
+static void
303
+qualify_status(const char *const str)
304
+{
305
+	if (!status_set)
306
+		status_set = alloc_number_set_array(1);
307
+	qualify_tokens(str, status_set, statusstr_to_uint, "status");
308
+}
309
+
278 310
 static void
279 311
 qualify_trace(const char *const str)
280 312
 {
@@ -421,6 +453,7 @@ static const struct qual_options {
421 453
 	{ "x",		qualify_raw	},
422 454
 	{ "signal",	qualify_signals	},
423 455
 	{ "signals",	qualify_signals	},
456
+	{ "status",	qualify_status	},
424 457
 	{ "s",		qualify_signals	},
425 458
 	{ "read",	qualify_read	},
426 459
 	{ "reads",	qualify_read	},

+ 17
- 0
number_set.c View File

@@ -12,7 +12,9 @@
12 12
 #include <stdbool.h>
13 13
 #include <stdlib.h>
14 14
 #include <string.h>
15
+#include "defs.h"
15 16
 #include "number_set.h"
17
+#include "static_assert.h"
16 18
 #include "xmalloc.h"
17 19
 
18 20
 typedef unsigned int number_slot_t;
@@ -47,6 +49,14 @@ reallocate_number_set(struct number_set *const set, const unsigned int new_nslot
47 49
 	set->nslots = new_nslots;
48 50
 }
49 51
 
52
+static unsigned int
53
+get_number_setbit(const struct number_set *const set)
54
+{
55
+	static_assert(sizeof(number_slot_t) == sizeof(uint32_t),
56
+		      "number_slot_t is not 32-bit long");
57
+	return popcount32(set->vec, set->nslots);
58
+}
59
+
50 60
 bool
51 61
 number_set_array_is_empty(const struct number_set *const set,
52 62
 			  const unsigned int idx)
@@ -69,6 +79,13 @@ is_number_in_set_array(const unsigned int number, const struct number_set *const
69 79
 		&& number_isset(number, set[idx].vec)) ^ set[idx].not;
70 80
 }
71 81
 
82
+bool
83
+is_complete_set(const struct number_set *const set, const unsigned int max_numbers)
84
+{
85
+	return set && ((set->not && !set->nslots) ||
86
+		       (get_number_setbit(set) == max_numbers));
87
+}
88
+
72 89
 void
73 90
 add_number_to_set(const unsigned int number, struct number_set *const set)
74 91
 {

+ 13
- 0
number_set.h View File

@@ -21,6 +21,9 @@ is_number_in_set(unsigned int number, const struct number_set *);
21 21
 extern bool
22 22
 is_number_in_set_array(unsigned int number, const struct number_set *, unsigned int idx);
23 23
 
24
+extern bool
25
+is_complete_set(const struct number_set *, unsigned int max_numbers);
26
+
24 27
 extern void
25 28
 add_number_to_set(unsigned int number, struct number_set *);
26 29
 
@@ -39,8 +42,18 @@ alloc_number_set_array(unsigned int nmemb) ATTRIBUTE_MALLOC;
39 42
 extern void
40 43
 free_number_set_array(struct number_set *, unsigned int nmemb);
41 44
 
45
+enum status_t {
46
+	STATUS_SUCCESSFUL,
47
+	STATUS_FAILED,
48
+	STATUS_UNFINISHED,
49
+	STATUS_UNAVAILABLE,
50
+	STATUS_DETACHED,
51
+	NUMBER_OF_STATUSES
52
+};
53
+
42 54
 extern struct number_set *read_set;
43 55
 extern struct number_set *write_set;
44 56
 extern struct number_set *signal_set;
57
+extern struct number_set *status_set;
45 58
 
46 59
 #endif /* !STRACE_NUMBER_SET_H */

+ 0
- 19
signal.c View File

@@ -138,25 +138,6 @@ sprintsigname(const int sig)
138 138
 	return buf;
139 139
 }
140 140
 
141
-static unsigned int
142
-popcount32(const uint32_t *a, unsigned int size)
143
-{
144
-	unsigned int count = 0;
145
-
146
-	for (; size; ++a, --size) {
147
-		uint32_t x = *a;
148
-
149
-#ifdef HAVE___BUILTIN_POPCOUNT
150
-		count += __builtin_popcount(x);
151
-#else
152
-		for (; x; ++count)
153
-			x &= x - 1;
154
-#endif
155
-	}
156
-
157
-	return count;
158
-}
159
-
160 141
 const char *
161 142
 sprintsigmask_n(const char *prefix, const void *sig_mask, unsigned int bytes)
162 143
 {

+ 48
- 0
strace.1.in View File

@@ -409,6 +409,7 @@ is one of
409 409
 .BR write ,
410 410
 .BR fault ,
411 411
 .BR inject ,
412
+.BR status ,
412 413
 or
413 414
 .B kvm
414 415
 and
@@ -613,6 +614,53 @@ Note that this is independent from the normal tracing of the
613 614
 system call which is controlled by the option
614 615
 .BR -e "\ " trace = write .
615 616
 .TP
617
+\fB\-e\ status\fR=\,\fIset\fR
618
+Print only system calls with the specified return status.  The default is
619
+.BR status = all .
620
+When using the
621
+.B status
622
+qualifier, because
623
+.B strace
624
+waits for system calls to return before deciding whether they should be printed
625
+or not, the traditional order of events may not be preserved anymore.  If two
626
+system calls are executed by concurrent threads,
627
+.B strace
628
+will first print both the entry and exit of the first system call to exit,
629
+regardless of their respective entry time.  The entry and exit of the second
630
+system call to exit will be printed afterwards.  Here is an example when
631
+.BR select (2)
632
+is called, but a different thread calls
633
+.BR clock_gettime (2)
634
+before
635
+.BR select (2)
636
+finishes:
637
+.CW
638
+[pid 28779] 1130322148.939977 clock_gettime(CLOCK_REALTIME, {1130322148, 939977000}) = 0
639
+[pid 28772] 1130322148.438139 select(4, [3], NULL, NULL, NULL) = 1 (in [3])
640
+.CE
641
+.TP
642
+.BR "\-e\ status" = successful
643
+Trace system calls that returned without an error code.  The
644
+.B -z
645
+option has the effect of
646
+.BR status=successful .
647
+.TP
648
+.BR "\-e\ status" = failed
649
+Trace system calls that returned with an error code.  The
650
+.B -Z
651
+option has the effect of
652
+.BR status=failed .
653
+.TP
654
+.BR "\-e\ status" = unfinished
655
+Trace system calls that did not return.  This might happen, for example, due to
656
+an execve call in a neighbour thread.
657
+.TP
658
+.BR "\-e\ status" = unavailable
659
+Trace system calls that returned but strace failed to fetch the error status.
660
+.TP
661
+.BR "\-e\ status" = detached
662
+Trace system calls for which strace detached before the return.
663
+.TP
616 664
 \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]
617 665
 Perform syscall tampering for the specified set of syscalls.
618 666
 

+ 33
- 12
strace.c View File

@@ -109,10 +109,6 @@ static bool daemonized_tracer;
109 109
 static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP;
110 110
 #define use_seize (post_attach_sigstop == 0)
111 111
 
112
-/* Sometimes we want to print succeeding/failing syscalls only. */
113
-bool not_failing_only;
114
-bool failing_only;
115
-
116 112
 /* Show path associated with fd arguments */
117 113
 unsigned int show_fd_path;
118 114
 
@@ -273,7 +269,7 @@ Statistics:\n\
273 269
 \n\
274 270
 Filtering:\n\
275 271
   -e expr        a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\
276
-     options:    trace, abbrev, verbose, raw, signal, read, write, fault, inject, kvm\n\
272
+     options:    trace, abbrev, verbose, raw, signal, read, write, fault, inject, status, kvm\n\
277 273
   -P path        trace accesses to path\n\
278 274
   -z             print only syscalls that returned without an error code\n\
279 275
   -Z             print only syscalls that returned with an error code\n\
@@ -813,12 +809,18 @@ droptcb(struct tcb *tcp)
813 809
 	debug_msg("dropped tcb for pid %d, %d remain", tcp->pid, nprocs);
814 810
 
815 811
 	if (tcp->outf) {
812
+		bool publish = true;
813
+		if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
814
+			publish = is_number_in_set(STATUS_DETACHED, status_set);
815
+			strace_close_memstream(tcp, publish);
816
+		}
817
+
816 818
 		if (followfork >= 2) {
817
-			if (tcp->curcol != 0)
819
+			if (tcp->curcol != 0 && publish)
818 820
 				fprintf(tcp->outf, " <detached ...>\n");
819 821
 			fclose(tcp->outf);
820 822
 		} else {
821
-			if (printing_tcp == tcp && tcp->curcol != 0)
823
+			if (printing_tcp == tcp && tcp->curcol != 0 && publish)
822 824
 				fprintf(tcp->outf, " <detached ...>\n");
823 825
 			flush_tcp_output(tcp);
824 826
 		}
@@ -1554,7 +1556,7 @@ static void ATTRIBUTE_NOINLINE
1554 1556
 init(int argc, char *argv[])
1555 1557
 {
1556 1558
 	int c, i;
1557
-	int optF = 0;
1559
+	int optF = 0, zflags = 0;
1558 1560
 
1559 1561
 	if (!program_invocation_name || !*program_invocation_name) {
1560 1562
 		static char name[] = "strace";
@@ -1572,6 +1574,7 @@ init(int argc, char *argv[])
1572 1574
 	qualify("trace=all");
1573 1575
 	qualify("abbrev=all");
1574 1576
 	qualify("verbose=all");
1577
+	qualify("status=all");
1575 1578
 #if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
1576 1579
 # error Bug in DEFAULT_QUAL_FLAGS
1577 1580
 #endif
@@ -1709,10 +1712,14 @@ init(int argc, char *argv[])
1709 1712
 			show_fd_path++;
1710 1713
 			break;
1711 1714
 		case 'z':
1712
-			not_failing_only = 1;
1715
+			clear_number_set_array(status_set, 1);
1716
+			add_number_to_set(STATUS_SUCCESSFUL, status_set);
1717
+			zflags++;
1713 1718
 			break;
1714 1719
 		case 'Z':
1715
-			failing_only = 1;
1720
+			clear_number_set_array(status_set, 1);
1721
+			add_number_to_set(STATUS_FAILED, status_set);
1722
+			zflags++;
1716 1723
 			break;
1717 1724
 		default:
1718 1725
 			error_msg_and_help(NULL);
@@ -1765,10 +1772,14 @@ init(int argc, char *argv[])
1765 1772
 	}
1766 1773
 
1767 1774
 #ifndef HAVE_OPEN_MEMSTREAM
1768
-	if (not_failing_only || failing_only)
1769
-		error_msg_and_help("open_memstream is required to use -z or -Z");
1775
+	if (!is_complete_set(status_set, NUMBER_OF_STATUSES))
1776
+		error_msg_and_help("open_memstream is required to use -z, -Z, or -e status");
1770 1777
 #endif
1771 1778
 
1779
+	if (zflags > 1)
1780
+		error_msg("Only the last of -z/-Z options will take effect. "
1781
+			  "See status qualifier for more complex filters.");
1782
+
1772 1783
 	acolumn_spaces = xmalloc(acolumn + 1);
1773 1784
 	memset(acolumn_spaces, ' ', acolumn);
1774 1785
 	acolumn_spaces[acolumn] = '\0';
@@ -2111,6 +2122,12 @@ maybe_switch_tcbs(struct tcb *tcp, const int pid)
2111 2122
 		printleader(tcp);
2112 2123
 		tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
2113 2124
 		line_ended();
2125
+		/*
2126
+		 * Need to reopen memstream for thread
2127
+		 * as we closed it in droptcb.
2128
+		 */
2129
+		if (!is_complete_set(status_set, NUMBER_OF_STATUSES))
2130
+			strace_open_memstream(tcp);
2114 2131
 		tcp->flags |= TCB_REPRINT;
2115 2132
 	}
2116 2133
 
@@ -2237,6 +2254,10 @@ print_event_exit(struct tcb *tcp)
2237 2254
 	tprints(") ");
2238 2255
 	tabto();
2239 2256
 	tprints("= ?\n");
2257
+	if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
2258
+		bool publish = is_number_in_set(STATUS_UNFINISHED, status_set);
2259
+		strace_close_memstream(tcp, publish);
2260
+	}
2240 2261
 	line_ended();
2241 2262
 }
2242 2263
 

+ 16
- 9
syscall.c View File

@@ -650,7 +650,7 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
650 650
 	}
651 651
 #endif
652 652
 
653
-	if (not_failing_only || failing_only)
653
+	if (!is_complete_set(status_set, NUMBER_OF_STATUSES))
654 654
 		strace_open_memstream(tcp);
655 655
 
656 656
 	printleader(tcp);
@@ -745,6 +745,11 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
745 745
 		tprints(") ");
746 746
 		tabto();
747 747
 		tprints("= ? <unavailable>\n");
748
+		if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
749
+			bool publish = is_number_in_set(STATUS_UNAVAILABLE,
750
+							status_set);
751
+			strace_close_memstream(tcp, publish);
752
+		}
748 753
 		line_ended();
749 754
 		return res;
750 755
 	}
@@ -760,14 +765,16 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
760 765
 			sys_res = tcp_sysent(tcp)->sys_func(tcp);
761 766
 	}
762 767
 
763
-	if ((not_failing_only && syserror(tcp)) ||
764
-	    (failing_only && !syserror(tcp))) {
765
-		strace_close_memstream(tcp, false);
766
-		line_ended();
767
-		return 0;	/* ignore failed/successful
768
-				 * syscalls */
769
-	} else if (not_failing_only || failing_only) {
770
-		strace_close_memstream(tcp, true);
768
+	if (!is_complete_set(status_set, NUMBER_OF_STATUSES)) {
769
+		bool publish = syserror(tcp)
770
+			       && is_number_in_set(STATUS_FAILED, status_set);
771
+		publish |= !syserror(tcp)
772
+			   && is_number_in_set(STATUS_SUCCESSFUL, status_set);
773
+		strace_close_memstream(tcp, publish);
774
+		if (!publish) {
775
+			line_ended();
776
+			return 0;
777
+		}
771 778
 	}
772 779
 
773 780
 	tprints(") ");

Loading…
Cancel
Save