From fd319b1270c982032ea97371619d6372a2d048ab Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Fri, 26 Jul 2024 16:41:30 -0700 Subject: [PATCH] retsnoop: make func call trace and call stack modes independent Make it possible to have only function trace mode without emitting call stack traces, and vice versa. For function trace-only mode switch the default to allow capturing success stacks, as that's the most typical assumption. For call stack mode still default to emitting erroring stacks only, by default. Allow to force it one way or the other with -Sn or -Sy/-S arguments. Signed-off-by: Andrii Nakryiko --- src/env.c | 30 +++++++++++++++++++++++++++--- src/env.h | 20 ++++++++++++++++---- src/logic.c | 41 +++++++++++++++++++++++------------------ src/retsnoop.bpf.c | 6 +++--- src/retsnoop.c | 13 +++++++++++-- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/env.c b/src/env.c index 497ffc4..ff2f20e 100644 --- a/src/env.c +++ b/src/env.c @@ -83,6 +83,7 @@ static const struct argp_option opts[] = { /* Running mode configuration */ { .flags = OPTION_DOC, "RUNMODE\n=========================" }, + { "call-stack", 'E', NULL, 0, "Capture and emit call stacks (default mode)" }, { "trace", 'T', NULL, 0, "Capture and emit function call traces" }, { "capture-args", 'A', NULL, 0, "Capture and emit function arguments" }, { "lbr", 'B', "SPEC", OPTION_ARG_OPTIONAL, @@ -113,7 +114,8 @@ static const struct argp_option opts[] = { "Skip tracing processes with given name" }, { "longer", 'L', "MS", 0, "Only emit stacks that took at least a given amount of milliseconds" }, - { "success-stacks", 'S', NULL, 0, "Emit any stack, successful or not" }, + { "success-stacks", 'S', "VALUE", OPTION_ARG_OPTIONAL, + "Specify whether emitting non-erroring (successful) call stacks is allowed" }, { "allow-errors", 'x', "ERROR", 0, "Record stacks only with specified errors" }, { "deny-errors", 'X', "ERROR", 0, "Ignore stacks that have specified errors" }, @@ -393,7 +395,7 @@ static enum debug_feat parse_config_arg(const char *arg) static error_t parse_arg(int key, char *arg, struct argp_state *state) { - int i, j, err; + int i, j, err, val; switch (key) { case 'h': @@ -417,6 +419,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) else if (!env.debug_extra) env.debug_extra = true; break; + case 'E': + env.emit_call_stack = true; + break; case 'T': env.emit_func_trace = true; break; @@ -537,6 +542,11 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) return err; break; case 'x': + if (env.emit_success_stacks > 0) { + elog("Can't specify -S/-Sy and -x arguments at the same time!\n"); + return -EINVAL; + } + env.emit_success_stacks = -1; /* force failing stacks only */ err = str_to_err(arg); if (err < 0) return err; @@ -562,7 +572,21 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) err_mask_set(env.deny_error_mask, err); break; case 'S': - env.emit_success_stacks = true; + if (arg && strcasecmp(arg, "y") == 0) { + val = +1; + } else if (arg && strcasecmp(arg, "n") == 0) { + val = -1; + } else if (!arg) { + val = +1; + } else { + elog("Unrecognized -S%s argument, only -S, -Sy, or -Sn are supported!\n", arg); + return -EINVAL; + } + if (env.emit_success_stacks != 0 && env.emit_success_stacks != val) { + elog("Conflicting combination of -S/-Sn/-Sy and -x arguments specified!\n"); + return -EINVAL; + } + env.emit_success_stacks = val; break; case 'M': if (env.attach_mode != ATTACH_DEFAULT) { diff --git a/src/env.h b/src/env.h index 981f12b..363c85c 100644 --- a/src/env.h +++ b/src/env.h @@ -49,20 +49,26 @@ enum debug_feat { }; struct env { + /* two main modes of operation; if only emit_func_trace is specified, + * we default to capturing all (including non-erroring) sessions + */ + bool emit_call_stack; + bool emit_func_trace; + + bool capture_args; + bool use_lbr; + bool show_version; bool show_config_help; bool verbose; bool debug; bool debug_extra; bool dry_run; - bool emit_success_stacks; - bool emit_func_trace; - bool capture_args; enum attach_mode attach_mode; enum debug_feat debug_feats; - bool use_lbr; long lbr_flags; int lbr_max_cnt; + const char *vmlinux_path; int pid; int longer_than_ms; @@ -105,6 +111,12 @@ struct env { int allow_comm_cnt; int deny_comm_cnt; + /* default 0 will depend on allow_call_stack and allow_func_trace settings; + * +1 forces success stacks capture; + * -1 forces unsuccessful stacks only capture; + */ + int emit_success_stacks; + int allow_error_cnt; bool has_error_filter; uint64_t allow_error_mask[(MAX_ERRNO + 1) / 64]; diff --git a/src/logic.c b/src/logic.c index 8418e83..0ee3dda 100644 --- a/src/logic.c +++ b/src/logic.c @@ -1148,23 +1148,6 @@ static int handle_session_end(struct ctx *dctx, struct session *sess, const stru s->depth, s->max_depth, s->saved_depth, s->saved_max_depth); } - fstack_n = filter_fstack(dctx, fstack, s); - if (fstack_n < 0) { - fprintf(stderr, "FAILURE DURING FILTERING FUNCTION STACK!!! %d\n", fstack_n); - ret = -EINVAL; - goto out_purge; - } - kstack_n = filter_kstack(dctx, kstack, s); - if (kstack_n < 0) { - fprintf(stderr, "FAILURE DURING FILTERING KERNEL STACK!!! %d\n", kstack_n); - ret = -EINVAL; - goto out_purge; - } - if (env.debug) { - printf("FSTACK (%d items):\n", fstack_n); - printf("KSTACK (%d items out of original %ld):\n", kstack_n, s->kstack_sz / 8); - } - ts_to_str(ktime_to_ts(sess->start_ts), ts1, sizeof(ts1)); ts_to_str(ktime_to_ts(r->emit_ts), ts2, sizeof(ts2)); printf("%s -> %s TID/PID %d/%d (%s/%s):\n", ts1, ts2, sess->pid, sess->tgid, @@ -1183,6 +1166,26 @@ static int handle_session_end(struct ctx *dctx, struct session *sess, const stru print_ft_items(dctx, &stack_items1); } + if (!env.emit_call_stack && !env.use_lbr) + goto skip_call_stack; + + fstack_n = filter_fstack(dctx, fstack, s); + if (fstack_n < 0) { + fprintf(stderr, "FAILURE DURING FILTERING FUNCTION STACK!!! %d\n", fstack_n); + ret = -EINVAL; + goto out_purge; + } + kstack_n = filter_kstack(dctx, kstack, s); + if (kstack_n < 0) { + fprintf(stderr, "FAILURE DURING FILTERING KERNEL STACK!!! %d\n", kstack_n); + ret = -EINVAL; + goto out_purge; + } + if (env.debug) { + printf("FSTACK (%d items):\n", fstack_n); + printf("KSTACK (%d items out of original %ld):\n", kstack_n, s->kstack_sz / 8); + } + /* Determine address range of deepest nested function */ if (fstack_n > 0) { const struct fstack_item *fitem = &fstack[fstack_n - 1]; @@ -1204,8 +1207,10 @@ static int handle_session_end(struct ctx *dctx, struct session *sess, const stru } /* Emit combined fstack/kstack + errors stack trace */ - output_call_stack(dctx, sess, fstack, fstack_n, kstack, kstack_n); + if (env.emit_call_stack) + output_call_stack(dctx, sess, fstack, fstack_n, kstack, kstack_n); +skip_call_stack: if (r->dropped_records) { printf("WARNING! Sample data incomplete! %d record%s dropped. Consider increasing --ringbuf-map-size.\n", r->dropped_records, r->dropped_records == 1 ? "" : "s"); diff --git a/src/retsnoop.bpf.c b/src/retsnoop.bpf.c index f0d52ea..95a887e 100644 --- a/src/retsnoop.bpf.c +++ b/src/retsnoop.bpf.c @@ -71,12 +71,12 @@ struct { const volatile bool verbose = false; const volatile bool extra_verbose = false; -const volatile bool use_lbr = true; -const volatile int targ_tgid = -1; -const volatile bool emit_success_stacks = false; +const volatile bool emit_call_stack = true; const volatile bool emit_func_trace = true; +const volatile bool emit_success_stacks = false; const volatile bool capture_args = true; const volatile bool capture_raw_ptrs = true; +const volatile bool use_lbr = true; const volatile bool use_kprobes = true; const volatile int args_max_total_args_sz; diff --git a/src/retsnoop.c b/src/retsnoop.c index 9b2ef3d..9e55e8c 100644 --- a/src/retsnoop.c +++ b/src/retsnoop.c @@ -355,6 +355,15 @@ int main(int argc, char **argv, char **envp) goto cleanup_silent; } #endif + if (!env.emit_func_trace) + env.emit_call_stack = true; + /* default setting for success stacks, resolve based on call stack vs func trace modes */ + if (env.emit_success_stacks == 0) { + if (env.emit_call_stack) + env.emit_success_stacks = -1; + else + env.emit_success_stacks = +1; + } /* Open BPF skeleton */ env.ctx.skel = skel = retsnoop_bpf__open(); @@ -384,8 +393,7 @@ int main(int argc, char **argv, char **envp) /* turn on extra bpf_printk()'s on BPF side */ skel->rodata->verbose = env.debug_feats & DEBUG_BPF; skel->rodata->extra_verbose = (env.debug_feats & DEBUG_BPF) && env.debug_extra; - skel->rodata->targ_tgid = env.pid; - skel->rodata->emit_success_stacks = env.emit_success_stacks; + skel->rodata->emit_success_stacks = env.emit_success_stacks > 0; skel->rodata->duration_ns = env.longer_than_ms * 1000000ULL; skel->rodata->use_kprobes = env.attach_mode != ATTACH_FENTRY; memset(skel->rodata->spaces, ' ', sizeof(skel->rodata->spaces) - 1); @@ -424,6 +432,7 @@ int main(int argc, char **argv, char **envp) if (env.use_lbr && env.verbose) printf("LBR capture enabled.\n"); + skel->rodata->emit_call_stack = env.emit_call_stack; skel->rodata->emit_func_trace = env.emit_func_trace; att_opts.verbose = env.verbose;