* [PATCH bpf-next v4] selftests/bpf: add BPF program dump in veristat
@ 2025-08-29 13:28 Mykyta Yatsenko
2025-08-29 20:41 ` Andrii Nakryiko
0 siblings, 1 reply; 2+ messages in thread
From: Mykyta Yatsenko @ 2025-08-29 13:28 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87; +Cc: Mykyta Yatsenko
From: Mykyta Yatsenko <yatsenko@meta.com>
Add the ability to dump BPF program instructions directly from veristat.
Previously, inspecting a program required separate bpftool invocations:
one to load and another to dump it, which meant running multiple
commands.
During active development, it's common for developers to use veristat
for testing verification. Integrating instruction dumping into veristat
reduces the need to switch tools and simplifies the workflow.
By making this information more readily accessible, this change aims
to streamline the BPF development cycle and improve usability for
developers.
This implementation leverages bpftool, by running it directly via popen
to avoid any code duplication and keep veristat simple.
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
tools/testing/selftests/bpf/veristat.c | 75 +++++++++++++++++++++++++-
1 file changed, 74 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
index d532dd82a3a8..c824f6e72e2f 100644
--- a/tools/testing/selftests/bpf/veristat.c
+++ b/tools/testing/selftests/bpf/veristat.c
@@ -181,6 +181,12 @@ struct var_preset {
bool applied;
};
+enum dump_mode {
+ DUMP_NONE = 0,
+ DUMP_XLATED = 1,
+ DUMP_JITED = 2,
+};
+
static struct env {
char **filenames;
int filename_cnt;
@@ -227,6 +233,7 @@ static struct env {
char orig_cgroup[PATH_MAX];
char stat_cgroup[PATH_MAX];
int memory_peak_fd;
+ __u32 dump_mode;
} env;
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
@@ -271,6 +278,7 @@ const char argp_program_doc[] =
enum {
OPT_LOG_FIXED = 1000,
OPT_LOG_SIZE = 1001,
+ OPT_DUMP = 1002,
};
static const struct argp_option opts[] = {
@@ -295,10 +303,12 @@ static const struct argp_option opts[] = {
"Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" },
{ "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" },
{ "set-global-vars", 'G', "GLOBAL", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" },
+ { "dump", OPT_DUMP, "DUMP_MODE", OPTION_ARG_OPTIONAL, "Print BPF program dump (xlated, jited)" },
{},
};
static int parse_stats(const char *stats_str, struct stat_specs *specs);
+static int parse_dump_mode(char *mode_str, __u32 *dump_mode);
static int append_filter(struct filter **filters, int *cnt, const char *str);
static int append_filter_file(const char *path);
static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr);
@@ -427,6 +437,11 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
return err;
}
break;
+ case OPT_DUMP:
+ err = parse_dump_mode(arg, &env.dump_mode);
+ if (err)
+ return err;
+ break;
default:
return ARGP_ERR_UNKNOWN;
}
@@ -956,6 +971,32 @@ static int parse_stats(const char *stats_str, struct stat_specs *specs)
return 0;
}
+static int parse_dump_mode(char *mode_str, __u32 *dump_mode)
+{
+ char *state = NULL, *cur;
+ int cnt = 0;
+
+ if (!mode_str) {
+ env.dump_mode = DUMP_XLATED;
+ return 0;
+ }
+
+ for (cur = mode_str; *cur; ++cur)
+ *cur = tolower(*cur);
+
+ while ((cur = strtok_r(cnt++ ? NULL : mode_str, ",", &state))) {
+ if (strcmp(cur, "jited") == 0) {
+ env.dump_mode |= DUMP_JITED;
+ } else if (strcmp(cur, "xlated") == 0) {
+ env.dump_mode |= DUMP_XLATED;
+ } else {
+ fprintf(stderr, "Unrecognized dump mode '%s'\n", cur);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt)
{
int i;
@@ -1554,6 +1595,35 @@ static int parse_rvalue(const char *val, struct rvalue *rvalue)
return 0;
}
+static void dump(__u32 prog_id, const char *file_name, const char *prog_name)
+{
+ char command[64], buf[1024];
+ enum dump_mode modes[2] = { DUMP_XLATED, DUMP_JITED };
+ const char *mode_lower[2] = { "xlated", "jited" };
+ const char *mode_upper[2] = { "XLATED", "JITED" };
+ FILE *fp;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(modes); ++i) {
+ if (!(env.dump_mode & modes[i]))
+ continue;
+ snprintf(command, sizeof(command), "bpftool prog dump %s id %u",
+ mode_lower[i], prog_id);
+
+ fp = popen(command, "r");
+ if (!fp) {
+ fprintf(stderr, "Can't run bpftool\n");
+ return;
+ }
+
+ printf("%s/%s DUMP %s:\n", file_name, prog_name, mode_upper[i]);
+ while (fgets(buf, sizeof(buf), fp))
+ printf("%s", buf);
+ printf("\n");
+ pclose(fp);
+ }
+}
+
static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
{
const char *base_filename = basename(strdupa(filename));
@@ -1630,8 +1700,11 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
memset(&info, 0, info_len);
fd = bpf_program__fd(prog);
- if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0)
+ if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0) {
stats->stats[JITED_SIZE] = info.jited_prog_len;
+ if (env.dump_mode != DUMP_NONE)
+ dump(info.id, base_filename, prog_name);
+ }
parse_verif_log(buf, buf_sz, stats);
--
2.51.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH bpf-next v4] selftests/bpf: add BPF program dump in veristat
2025-08-29 13:28 [PATCH bpf-next v4] selftests/bpf: add BPF program dump in veristat Mykyta Yatsenko
@ 2025-08-29 20:41 ` Andrii Nakryiko
0 siblings, 0 replies; 2+ messages in thread
From: Andrii Nakryiko @ 2025-08-29 20:41 UTC (permalink / raw)
To: Mykyta Yatsenko
Cc: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87,
Mykyta Yatsenko
On Fri, Aug 29, 2025 at 6:28 AM Mykyta Yatsenko
<mykyta.yatsenko5@gmail.com> wrote:
>
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> Add the ability to dump BPF program instructions directly from veristat.
> Previously, inspecting a program required separate bpftool invocations:
> one to load and another to dump it, which meant running multiple
> commands.
> During active development, it's common for developers to use veristat
> for testing verification. Integrating instruction dumping into veristat
> reduces the need to switch tools and simplifies the workflow.
> By making this information more readily accessible, this change aims
> to streamline the BPF development cycle and improve usability for
> developers.
> This implementation leverages bpftool, by running it directly via popen
> to avoid any code duplication and keep veristat simple.
>
> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
> ---
> tools/testing/selftests/bpf/veristat.c | 75 +++++++++++++++++++++++++-
> 1 file changed, 74 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
> index d532dd82a3a8..c824f6e72e2f 100644
> --- a/tools/testing/selftests/bpf/veristat.c
> +++ b/tools/testing/selftests/bpf/veristat.c
> @@ -181,6 +181,12 @@ struct var_preset {
> bool applied;
> };
>
> +enum dump_mode {
> + DUMP_NONE = 0,
> + DUMP_XLATED = 1,
> + DUMP_JITED = 2,
> +};
> +
> static struct env {
> char **filenames;
> int filename_cnt;
> @@ -227,6 +233,7 @@ static struct env {
> char orig_cgroup[PATH_MAX];
> char stat_cgroup[PATH_MAX];
> int memory_peak_fd;
> + __u32 dump_mode;
> } env;
>
> static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
> @@ -271,6 +278,7 @@ const char argp_program_doc[] =
> enum {
> OPT_LOG_FIXED = 1000,
> OPT_LOG_SIZE = 1001,
> + OPT_DUMP = 1002,
> };
>
> static const struct argp_option opts[] = {
> @@ -295,10 +303,12 @@ static const struct argp_option opts[] = {
> "Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" },
> { "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" },
> { "set-global-vars", 'G', "GLOBAL", 0, "Set global variables provided in the expression, for example \"var1 = 1\"" },
> + { "dump", OPT_DUMP, "DUMP_MODE", OPTION_ARG_OPTIONAL, "Print BPF program dump (xlated, jited)" },
> {},
> };
>
> static int parse_stats(const char *stats_str, struct stat_specs *specs);
> +static int parse_dump_mode(char *mode_str, __u32 *dump_mode);
> static int append_filter(struct filter **filters, int *cnt, const char *str);
> static int append_filter_file(const char *path);
> static int append_var_preset(struct var_preset **presets, int *cnt, const char *expr);
> @@ -427,6 +437,11 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
> return err;
> }
> break;
> + case OPT_DUMP:
> + err = parse_dump_mode(arg, &env.dump_mode);
> + if (err)
> + return err;
> + break;
> default:
> return ARGP_ERR_UNKNOWN;
> }
> @@ -956,6 +971,32 @@ static int parse_stats(const char *stats_str, struct stat_specs *specs)
> return 0;
> }
>
> +static int parse_dump_mode(char *mode_str, __u32 *dump_mode)
> +{
> + char *state = NULL, *cur;
> + int cnt = 0;
> +
> + if (!mode_str) {
> + env.dump_mode = DUMP_XLATED;
> + return 0;
> + }
> +
> + for (cur = mode_str; *cur; ++cur)
> + *cur = tolower(*cur);
> +
> + while ((cur = strtok_r(cnt++ ? NULL : mode_str, ",", &state))) {
there is no need to parse comma-separated list, it is allowed to
specify the same CLI flag multiple times, so we can use that to
accumulate multiple modes:
--dump=xlated --dump=jited
this will do the trick, so keep it simple.
And then you don't need to do tolower(), you can just strcasecmp() against arg
pw-bot: cr
> + if (strcmp(cur, "jited") == 0) {
> + env.dump_mode |= DUMP_JITED;
> + } else if (strcmp(cur, "xlated") == 0) {
> + env.dump_mode |= DUMP_XLATED;
> + } else {
> + fprintf(stderr, "Unrecognized dump mode '%s'\n", cur);
> + return -EINVAL;
> + }
> + }
> + return 0;
> +}
> +
> static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt)
> {
> int i;
> @@ -1554,6 +1595,35 @@ static int parse_rvalue(const char *val, struct rvalue *rvalue)
> return 0;
> }
>
> +static void dump(__u32 prog_id, const char *file_name, const char *prog_name)
> +{
> + char command[64], buf[1024];
> + enum dump_mode modes[2] = { DUMP_XLATED, DUMP_JITED };
> + const char *mode_lower[2] = { "xlated", "jited" };
> + const char *mode_upper[2] = { "XLATED", "JITED" };
Instead of this, why don't we just pass desired mode as an argument,
and then just call dump(DUMP_XLATED) if env.dump_mode & DUMP_XLATED,
and similarly for DUMP_JITED. It's not like we'll have tons of
different dump modes, right?
> + FILE *fp;
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(modes); ++i) {
> + if (!(env.dump_mode & modes[i]))
> + continue;
> + snprintf(command, sizeof(command), "bpftool prog dump %s id %u",
> + mode_lower[i], prog_id);
should we check if bpftool is installed first? I.e., run `which
bpftool` and check if that's successful? And if not, print some
user-friendly warning or error?
> +
> + fp = popen(command, "r");
> + if (!fp) {
> + fprintf(stderr, "Can't run bpftool\n");
> + return;
> + }
> +
> + printf("%s/%s DUMP %s:\n", file_name, prog_name, mode_upper[i]);
> + while (fgets(buf, sizeof(buf), fp))
> + printf("%s", buf);
nit: is there any advantage to using fgets+printf vs just binary-based
read/write? (and I'd probably make buf page sized)
> + printf("\n");
> + pclose(fp);
> + }
> +}
> +
> static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
> {
> const char *base_filename = basename(strdupa(filename));
> @@ -1630,8 +1700,11 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
>
> memset(&info, 0, info_len);
> fd = bpf_program__fd(prog);
> - if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0)
> + if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0) {
> stats->stats[JITED_SIZE] = info.jited_prog_len;
> + if (env.dump_mode != DUMP_NONE)
> + dump(info.id, base_filename, prog_name);
> + }
>
> parse_verif_log(buf, buf_sz, stats);
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2025-08-29 20:42 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-29 13:28 [PATCH bpf-next v4] selftests/bpf: add BPF program dump in veristat Mykyta Yatsenko
2025-08-29 20:41 ` Andrii Nakryiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).