From: Paul Chaignon Date: Sat, 15 Jun 2019 05:32:03 +0000 (+0200) Subject: Implement -e status=set option X-Git-Tag: v5.2~6 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e45a594cb08394c96f71105db9bacf08aa4c734d;p=strace 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 --- diff --git a/NEWS b/NEWS index 67656ea4..8241dc40 100644 --- 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 b9b394ec..b6140fe0 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 { @@ -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; diff --git a/filter_qualify.c b/filter_qualify.c index 4a05f1b2..e5e3728a 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(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 }, diff --git a/number_set.c b/number_set.c index 4092ffda..70727eee 100644 --- a/number_set.c +++ b/number_set.c @@ -12,7 +12,9 @@ #include #include #include +#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) { diff --git a/number_set.h b/number_set.h index 77dc3a9c..b6399cc8 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, + 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 */ diff --git a/signal.c b/signal.c index fcaf9d4f..3cb54bb3 100644 --- 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) { diff --git a/strace.1.in b/strace.1.in index e1090a0f..56728371 100644 --- a/strace.1.in +++ b/strace.1.in @@ -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. diff --git a/strace.c b/strace.c index 22f30684..4f03a4c9 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, 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, " \n"); fclose(tcp->outf); } else { - if (printing_tcp == tcp && tcp->curcol != 0) + if (printing_tcp == tcp && tcp->curcol != 0 && publish) fprintf(tcp->outf, " \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(); } diff --git a/syscall.c b/syscall.c index 65f00d2d..3a31c3cb 100644 --- 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("= ? \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(") ");