From: "Emil Tsalapatis" <emil@etsalapatis.com>
To: "Eduard Zingerman" <eddyz87@gmail.com>, <bpf@vger.kernel.org>,
<ast@kernel.org>
Cc: <andrii@kernel.org>, <daniel@iogearbox.net>,
<martin.lau@linux.dev>, <kernel-team@fb.com>,
<yonghong.song@linux.dev>
Subject: Re: [PATCH RFC bpf-next v3 4/6] bpf: report hot simulated callchains when 1M instructions limit is met
Date: Mon, 01 Jun 2026 15:50:45 -0400 [thread overview]
Message-ID: <DIXZ2VPVZP94.ACM1OJMPGF4V@etsalapatis.com> (raw)
In-Reply-To: <20260527-better-1m-reporting-v3-4-b3ede0588a75@gmail.com>
On Wed May 27, 2026 at 3:29 AM EDT, Eduard Zingerman wrote:
> When the verifier exceeds the 1M instruction budget, the current error
> output shows a random execution trace that happens to be active when
> the limit is hit, which is not very helpful for debugging.
>
> To rectify this, employ a profiler-inspired technique:
> collect and count "callchain" stack traces that the verifier visits
> during program validation and report the top 3 hot traces. Sample
> callchains at instructions marked as force checkpoints and loop header
> entries.
>
> When 1M instructions is met, print a report containing:
> - callchains verifier visited most often;
> - disassembly of the subprograms referenced from the printed
> callchains.
>
> Here is an example of the report:
>
> budget_nested_call_loop():
> ; asm volatile (" \ @ verifier_budget_report.c:44
> 0: (b7) r0 = 0
> 1: (85) call pc+1
> 2: (95) exit
>
> budget_nested_call_loop__inner():
> ; asm volatile (" \ @ verifier_budget_report.c:54
> 3: (b7) r0 = 0
> 4: (07) r0 += 1
> 5: (05) goto pc-2
>
> #1 most visited simulated stacktrace (visited 499999 times):
> budget_nested_call_loop/1 (.../verifier_budget_report.c:44)
> budget_nested_call_loop__inner/4 (.../verifier_budget_report.c:54)
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Minor suggestion: Can we make the N of top-N a #define for clarity and
to make it easy to change during hacking?
> ---
> include/linux/bpf_verifier.h | 14 ++++
> kernel/bpf/verifier.c | 155 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 169 insertions(+)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index d0a1fb135cbf..347a155d8e21 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -9,6 +9,8 @@
> #include <linux/filter.h> /* for MAX_BPF_STACK */
> #include <linux/tnum.h>
> #include <linux/cnum.h>
> +#include <linux/hashtable.h>
> +#include <linux/jhash.h>
>
> /* Maximum variable offset umax_value permitted when resolving memory accesses.
> * In practice this is far bigger than any realistic pointer offset; this limit
> @@ -660,6 +662,17 @@ struct bpf_loop {
> bool irreducible;
> };
>
> +struct bpf_callchain {
> + u32 insn_idx[MAX_CALL_FRAMES];
> + u32 curframe;
> +};
> +
> +struct bpf_callchain_entry {
> + struct hlist_node node;
> + struct bpf_callchain cc;
> + u64 count;
> +};
> +
> struct bpf_insn_aux_data {
> union {
> enum bpf_reg_type ptr_type; /* pointer type for load/store insns */
> @@ -1043,6 +1056,7 @@ struct bpf_verifier_env {
> u32 scc_cnt;
> struct bpf_iarray *succ;
> struct bpf_iarray *gotox_tmp_buf;
> + DECLARE_HASHTABLE(callchain_htab, 8);
> };
>
> static inline struct bpf_func_info_aux *subprog_aux(struct bpf_verifier_env *env, int subprog)
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 2b3584712ad2..54b7ad65b7fc 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17247,6 +17247,152 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
> return INSN_IDX_UPDATED;
> }
>
> +static void compute_callchain(struct bpf_verifier_state *st, struct bpf_callchain *cc)
> +{
> + int i;
> +
> + cc->curframe = st->curframe;
> + for (i = 0; i <= st->curframe; i++)
> + cc->insn_idx[i] = bpf_frame_insn_idx(st, i);
> +}
> +
> +static int update_callchain_profile(struct bpf_verifier_env *env, struct bpf_verifier_state *st)
> +{
> + struct bpf_callchain_entry *entry;
> + struct bpf_callchain cc = {};
> + u32 hash;
> +
> + compute_callchain(st, &cc);
> + hash = jhash2(cc.insn_idx, cc.curframe + 1, cc.curframe + 1);
> + hash_for_each_possible(env->callchain_htab, entry, node, hash) {
> + if (entry->cc.curframe == cc.curframe &&
> + !memcmp(entry->cc.insn_idx, cc.insn_idx, (cc.curframe + 1) * sizeof(u32))) {
> + entry->count++;
> + return 0;
> + }
> + }
> + entry = kzalloc(sizeof(*entry), GFP_KERNEL_ACCOUNT);
> + if (!entry)
> + return -ENOMEM;
> + entry->cc = cc;
> + entry->count = 1;
> + hash_add(env->callchain_htab, &entry->node, hash);
> + return 0;
> +}
> +
> +static void free_callchain_profile(struct bpf_verifier_env *env)
> +{
> + struct bpf_callchain_entry *entry;
> + struct hlist_node *tmp;
> + int bkt;
> +
> + hash_for_each_safe(env->callchain_htab, bkt, tmp, entry, node) {
> + hash_del(&entry->node);
> + kfree(entry);
> + }
> +}
> +
> +static void print_callchain_entry(struct bpf_verifier_env *env,
> + struct bpf_callchain_entry *entry, int idx)
> +{
> + struct bpf_callchain *cc = &entry->cc;
> + const struct bpf_line_info *linfo;
> + struct bpf_subprog_info *sub;
> + int i, insn_idx;
> +
> + verbose(env, "#%d most visited simulated stacktrace (visited %llu times):\n",
> + idx, entry->count);
> + for (i = 0; i <= cc->curframe; i++) {
> + insn_idx = cc->insn_idx[i];
> + sub = bpf_find_containing_subprog(env, insn_idx);
> + linfo = bpf_find_linfo(env->prog, insn_idx);
> + if (sub->name)
> + verbose(env, " %s/%d", sub->name, insn_idx);
> + else
> + verbose(env, " subprog#%td/%d", sub - env->subprog_info, insn_idx);
> + if (linfo)
> + verbose(env, " (%s:%u)",
> + btf_name_by_offset(env->prog->aux->btf, linfo->file_name_off),
> + BPF_LINE_INFO_LINE_NUM(linfo->line_col));
> + verbose(env, "\n");
> + }
> +}
> +
> +static void disasm_subprog(struct bpf_verifier_env *env, struct bpf_subprog_info *sub)
> +{
> + u32 i, end = (sub + 1)->start;
> +
> + if (sub->name)
> + verbose(env, "%s():\n", sub->name);
> + else
> + verbose(env, "subprog#%td:\n", sub - env->subprog_info);
> + env->prev_linfo = NULL;
> + for (i = sub->start; i < end; i++) {
> + verbose_linfo(env, i, " ; ");
> + verbose(env, " %d: ", i);
> + bpf_verbose_insn(env, &env->prog->insnsi[i]);
> + if (bpf_is_ldimm64(&env->prog->insnsi[i]))
> + i++;
> + }
> +}
> +
> +/*
> + * Print several most visited simulated stack traces,
> + * and a disasembly of related subprograms.
> + */
> +static void print_hotspots(struct bpf_verifier_env *env)
> +{
> + DECLARE_BITMAP(printed_subs, BPF_MAX_SUBPROGS + 2) = {};
> + struct bpf_callchain_entry *top[3] = {};
> + struct bpf_callchain_entry *entry;
> + struct bpf_subprog_info *sub;
> + int i, j, bkt, nr_top = 0;
> +
> + if (!(env->log.level & BPF_LOG_LEVEL))
> + return;
> +
> + /* Collect the hottest callchains */
> + hash_for_each(env->callchain_htab, bkt, entry, node) {
> + for (i = 0; i < 3; i++) {
> + if (!top[i] || entry->count > top[i]->count) {
> + memmove(&top[i + 1], &top[i], (2 - i) * sizeof(top[0]));
> + top[i] = entry;
> + break;
> + }
> + }
> + }
> +
> + for (i = 0; i < 3 && top[i]; i++)
> + nr_top++;
> +
> + if (!nr_top)
> + return;
> +
> + if (!(env->log.level & BPF_LOG_LEVEL2))
> + bpf_vlog_reset(&env->log, 0);
> +
> + /* Identify callchain subprograms and print disasm of those */
> + for (i = 0; i < nr_top; i++) {
> + struct bpf_callchain *cc = &top[i]->cc;
> +
> + for (j = 0; j <= cc->curframe; j++) {
> + sub = bpf_find_containing_subprog(env, cc->insn_idx[j]);
> + __set_bit(sub - env->subprog_info, printed_subs);
> + }
> + }
> +
> + for_each_set_bit(i, printed_subs, env->subprog_cnt) {
> + disasm_subprog(env, &env->subprog_info[i]);
> + verbose(env, "\n");
> + }
> +
> + /* Print the hot callchains */
> + for (i = 0; i < nr_top; i++) {
> + print_callchain_entry(env, top[i], i + 1);
> + verbose(env, "\n");
> + }
> +}
> +
> static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
> {
> int err;
> @@ -17381,6 +17527,7 @@ static int do_check(struct bpf_verifier_env *env)
> insn_aux = &env->insn_aux_data[env->insn_idx];
>
> if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
> + print_hotspots(env);
> verbose(env,
> "BPF program is too large. Processed %d insn\n",
> env->insn_processed);
> @@ -17390,6 +17537,12 @@ static int do_check(struct bpf_verifier_env *env)
> state->last_insn_idx = env->prev_insn_idx;
> state->insn_idx = env->insn_idx;
>
> + if (bpf_is_force_checkpoint(env, env->insn_idx) || insn_aux->loop) {
> + err = update_callchain_profile(env, state);
> + if (err)
> + return err;
> + }
> +
> if (bpf_is_prune_point(env, env->insn_idx)) {
> err = bpf_is_state_visited(env, env->insn_idx);
> if (err < 0)
> @@ -19673,6 +19826,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> return -ENOMEM;
>
> env->bt.env = env;
> + hash_init(env->callchain_htab);
>
> len = (*prog)->len;
> env->insn_aux_data =
> @@ -19945,6 +20099,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> bpf_clear_insn_aux_data(env, 0, env->prog->len);
> vfree(env->insn_aux_data);
> err_free_env:
> + free_callchain_profile(env);
> bpf_stack_liveness_free(env);
> kvfree(env->cfg.insn_postorder);
> kvfree(env->scc_info);
next prev parent reply other threads:[~2026-06-01 19:50 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-27 7:29 [PATCH RFC bpf-next v3 0/6] bpf: better error reporting when verifier hits 1M instructions limit Eduard Zingerman
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 1/6] bpf: move live registers and scc printout to a standalone function Eduard Zingerman
2026-06-01 5:50 ` Emil Tsalapatis
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 2/6] bpf: compute loops hierarchy Eduard Zingerman
2026-06-01 19:12 ` Emil Tsalapatis
2026-06-01 19:22 ` Eduard Zingerman
2026-06-01 19:29 ` Emil Tsalapatis
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 3/6] selftests/bpf: test cases for loop hierarchy computation Eduard Zingerman
2026-05-27 8:50 ` sashiko-bot
2026-06-01 19:37 ` Emil Tsalapatis
2026-06-01 19:44 ` Eduard Zingerman
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 4/6] bpf: report hot simulated callchains when 1M instructions limit is met Eduard Zingerman
2026-05-29 10:23 ` Jiri Olsa
2026-05-29 18:44 ` Eduard Zingerman
2026-05-30 9:34 ` Jiri Olsa
2026-06-01 19:50 ` Emil Tsalapatis [this message]
2026-06-01 19:55 ` Eduard Zingerman
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 5/6] bpf: report register diff summary for hot callchains Eduard Zingerman
2026-06-01 21:29 ` Emil Tsalapatis
2026-06-01 21:38 ` Eduard Zingerman
2026-05-27 7:29 ` [PATCH RFC bpf-next v3 6/6] selftests/bpf: test budget exhaustion profiling report Eduard Zingerman
2026-06-01 19:55 ` Emil Tsalapatis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DIXZ2VPVZP94.ACM1OJMPGF4V@etsalapatis.com \
--to=emil@etsalapatis.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox