From 970bd2dced35632ce1c9e38943354d5389d80ca0 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Tue, 17 Mar 2026 10:39:21 -0700 Subject: [PATCH 1/3] libbpf: Introduce bpf_program__clone() Add bpf_program__clone() API that loads a single BPF program from a prepared BPF object into the kernel, returning a file descriptor owned by the caller. After bpf_object__prepare(), callers can use bpf_program__clone() to load individual programs with custom bpf_prog_load_opts, instead of loading all programs at once via bpf_object__load(). Non-zero fields in opts override the defaults derived from the program and object internals; passing NULL opts populates everything automatically. Signed-off-by: Mykyta Yatsenko Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260317-veristat_prepare-v4-1-74193d4cc9d9@meta.com Signed-off-by: Alexei Starovoitov --- tools/lib/bpf/libbpf.c | 78 ++++++++++++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.h | 17 +++++++++ tools/lib/bpf/libbpf.map | 1 + 3 files changed, 96 insertions(+) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 0662d72bad20..1eaa7527d4da 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -9802,6 +9802,84 @@ __u32 bpf_program__line_info_cnt(const struct bpf_program *prog) return prog->line_info_cnt; } +int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts) +{ + LIBBPF_OPTS(bpf_prog_load_opts, attr); + struct bpf_object *obj; + int err, fd; + + if (!prog) + return libbpf_err(-EINVAL); + + if (!OPTS_VALID(opts, bpf_prog_load_opts)) + return libbpf_err(-EINVAL); + + obj = prog->obj; + if (obj->state < OBJ_PREPARED) + return libbpf_err(-EINVAL); + + /* + * Caller-provided opts take priority; fall back to + * prog/object defaults when the caller leaves them zero. + */ + attr.attach_prog_fd = OPTS_GET(opts, attach_prog_fd, 0) ?: prog->attach_prog_fd; + attr.prog_flags = OPTS_GET(opts, prog_flags, 0) ?: prog->prog_flags; + attr.prog_ifindex = OPTS_GET(opts, prog_ifindex, 0) ?: prog->prog_ifindex; + attr.kern_version = OPTS_GET(opts, kern_version, 0) ?: obj->kern_version; + attr.fd_array = OPTS_GET(opts, fd_array, NULL) ?: obj->fd_array; + attr.fd_array_cnt = OPTS_GET(opts, fd_array_cnt, 0) ?: obj->fd_array_cnt; + attr.token_fd = OPTS_GET(opts, token_fd, 0) ?: obj->token_fd; + if (attr.token_fd) + attr.prog_flags |= BPF_F_TOKEN_FD; + + /* BTF func/line info */ + if (obj->btf && btf__fd(obj->btf) >= 0) { + attr.prog_btf_fd = OPTS_GET(opts, prog_btf_fd, 0) ?: btf__fd(obj->btf); + attr.func_info = OPTS_GET(opts, func_info, NULL) ?: prog->func_info; + attr.func_info_cnt = OPTS_GET(opts, func_info_cnt, 0) ?: prog->func_info_cnt; + attr.func_info_rec_size = + OPTS_GET(opts, func_info_rec_size, 0) ?: prog->func_info_rec_size; + attr.line_info = OPTS_GET(opts, line_info, NULL) ?: prog->line_info; + attr.line_info_cnt = OPTS_GET(opts, line_info_cnt, 0) ?: prog->line_info_cnt; + attr.line_info_rec_size = + OPTS_GET(opts, line_info_rec_size, 0) ?: prog->line_info_rec_size; + } + + attr.log_buf = OPTS_GET(opts, log_buf, NULL); + attr.log_size = OPTS_GET(opts, log_size, 0); + attr.log_level = OPTS_GET(opts, log_level, 0); + + /* + * Fields below may be mutated by prog_prepare_load_fn: + * Seed them from prog/obj defaults here; + * Later override with caller-provided opts. + */ + attr.expected_attach_type = prog->expected_attach_type; + attr.attach_btf_id = prog->attach_btf_id; + attr.attach_btf_obj_fd = prog->attach_btf_obj_fd; + + if (prog->sec_def && prog->sec_def->prog_prepare_load_fn) { + err = prog->sec_def->prog_prepare_load_fn(prog, &attr, prog->sec_def->cookie); + if (err) + return libbpf_err(err); + } + + /* Re-apply caller overrides for output fields */ + if (OPTS_GET(opts, expected_attach_type, 0)) + attr.expected_attach_type = + OPTS_GET(opts, expected_attach_type, 0); + if (OPTS_GET(opts, attach_btf_id, 0)) + attr.attach_btf_id = OPTS_GET(opts, attach_btf_id, 0); + if (OPTS_GET(opts, attach_btf_obj_fd, 0)) + attr.attach_btf_obj_fd = + OPTS_GET(opts, attach_btf_obj_fd, 0); + + fd = bpf_prog_load(prog->type, prog->name, obj->license, prog->insns, prog->insns_cnt, + &attr); + + return libbpf_err(fd); +} + #define SEC_DEF(sec_pfx, ptype, atype, flags, ...) { \ .sec = (char *)sec_pfx, \ .prog_type = BPF_PROG_TYPE_##ptype, \ diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index dfc37a615578..0be34852350f 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -2021,6 +2021,23 @@ LIBBPF_API int libbpf_register_prog_handler(const char *sec, */ LIBBPF_API int libbpf_unregister_prog_handler(int handler_id); +/** + * @brief **bpf_program__clone()** loads a single BPF program from a prepared + * BPF object into the kernel, returning its file descriptor. + * + * The BPF object must have been previously prepared with + * **bpf_object__prepare()**. If @opts is provided, any non-zero field + * overrides the defaults derived from the program/object internals. + * If @opts is NULL, all fields are populated automatically. + * + * The returned FD is owned by the caller and must be closed with close(). + * + * @param prog BPF program from a prepared object + * @param opts Optional load options; non-zero fields override defaults + * @return program FD (>= 0) on success; negative error code on failure + */ +LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index 3526c937bdbe..5828040f178a 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -452,6 +452,7 @@ LIBBPF_1.7.0 { bpf_map__set_exclusive_program; bpf_map__exclusive_program; bpf_prog_assoc_struct_ops; + bpf_program__clone; bpf_program__assoc_struct_ops; btf__permute; } LIBBPF_1.6.0; From 3be706b937f3d4cf1b0236561eb23d064e833253 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Tue, 17 Mar 2026 10:39:22 -0700 Subject: [PATCH 2/3] selftests/bpf: Use bpf_program__clone() in veristat Replace veristat's per-program object re-opening with bpf_program__clone(). Previously, veristat opened a separate bpf_object for every program in a multi-program object file, iterated all programs to enable only the target one, and then loaded the entire object. Use bpf_object__prepare() once, then call bpf_program__clone() for each program individually. This lets veristat load programs one at a time from a single prepared object. The caller now owns the returned fd and closes it after collecting stats. Remove the special single-program fast path and the per-file early exit in handle_verif_mode() so all files are always processed. Split fixup_obj() into fixup_obj_maps() for object-wide map fixups that must run before bpf_object__prepare(), and fixup_obj() for per-program fixups (struct_ops masking, freplace type guessing) that run before each bpf_program__clone() call. Reviewed-by: Emil Tsalapatis Signed-off-by: Mykyta Yatsenko Link: https://lore.kernel.org/r/20260317-veristat_prepare-v4-2-74193d4cc9d9@meta.com Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/veristat.c | 102 ++++++++++--------------- 1 file changed, 42 insertions(+), 60 deletions(-) diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c index 75f85e0362f5..9652649171ce 100644 --- a/tools/testing/selftests/bpf/veristat.c +++ b/tools/testing/selftests/bpf/veristat.c @@ -1236,7 +1236,7 @@ static void mask_unrelated_struct_ops_progs(struct bpf_object *obj, } } -static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) +static void fixup_obj_maps(struct bpf_object *obj) { struct bpf_map *map; @@ -1251,15 +1251,23 @@ static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const ch case BPF_MAP_TYPE_INODE_STORAGE: case BPF_MAP_TYPE_CGROUP_STORAGE: case BPF_MAP_TYPE_CGRP_STORAGE: - break; case BPF_MAP_TYPE_STRUCT_OPS: - mask_unrelated_struct_ops_progs(obj, map, prog); break; default: if (bpf_map__max_entries(map) == 0) bpf_map__set_max_entries(map, 1); } } +} + +static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) +{ + struct bpf_map *map; + + bpf_object__for_each_map(map, obj) { + if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS) + mask_unrelated_struct_ops_progs(obj, map, prog); + } /* SEC(freplace) programs can't be loaded with veristat as is, * but we can try guessing their target program's expected type by @@ -1608,6 +1616,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf const char *base_filename = basename(strdupa(filename)); const char *prog_name = bpf_program__name(prog); long mem_peak_a, mem_peak_b, mem_peak = -1; + LIBBPF_OPTS(bpf_prog_load_opts, opts); char *buf; int buf_sz, log_level; struct verif_stats *stats; @@ -1647,9 +1656,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf } verif_log_buf[0] = '\0'; - bpf_program__set_log_buf(prog, buf, buf_sz); - bpf_program__set_log_level(prog, log_level); - /* increase chances of successful BPF object loading */ fixup_obj(obj, prog, base_filename); @@ -1658,15 +1664,21 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf if (env.force_reg_invariants) bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_REG_INVARIANTS); - err = bpf_object__prepare(obj); - if (!err) { - cgroup_err = reset_stat_cgroup(); - mem_peak_a = cgroup_memory_peak(); - err = bpf_object__load(obj); - mem_peak_b = cgroup_memory_peak(); - if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0) - mem_peak = mem_peak_b - mem_peak_a; + opts.log_buf = buf; + opts.log_size = buf_sz; + opts.log_level = log_level; + + cgroup_err = reset_stat_cgroup(); + mem_peak_a = cgroup_memory_peak(); + fd = bpf_program__clone(prog, &opts); + if (fd < 0) { + err = fd; + fprintf(stderr, "Failed to load program %s %d\n", prog_name, err); } + mem_peak_b = cgroup_memory_peak(); + if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0) + mem_peak = mem_peak_b - mem_peak_a; + env.progs_processed++; stats->file_name = strdup(base_filename); @@ -1678,7 +1690,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf stats->stats[MEMORY_PEAK] = mem_peak < 0 ? -1 : mem_peak / (1024 * 1024); memset(&info, 0, info_len); - fd = bpf_program__fd(prog); 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_JITED) @@ -1699,7 +1710,8 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf if (verif_log_buf != buf) free(buf); - + if (fd > 0) + close(fd); return 0; } @@ -2182,8 +2194,8 @@ static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, i static int process_obj(const char *filename) { const char *base_filename = basename(strdupa(filename)); - struct bpf_object *obj = NULL, *tobj; - struct bpf_program *prog, *tprog, *lprog; + struct bpf_object *obj = NULL; + struct bpf_program *prog; libbpf_print_fn_t old_libbpf_print_fn; LIBBPF_OPTS(bpf_object_open_opts, opts); int err = 0, prog_cnt = 0; @@ -2222,51 +2234,24 @@ static int process_obj(const char *filename) env.files_processed++; bpf_object__for_each_program(prog, obj) { + bpf_program__set_autoload(prog, true); prog_cnt++; } - if (prog_cnt == 1) { - prog = bpf_object__next_program(obj, NULL); - bpf_program__set_autoload(prog, true); - err = set_global_vars(obj, env.presets, env.npresets); - if (err) { - fprintf(stderr, "Failed to set global variables %d\n", err); - goto cleanup; - } - process_prog(filename, obj, prog); + fixup_obj_maps(obj); + + err = set_global_vars(obj, env.presets, env.npresets); + if (err) { + fprintf(stderr, "Failed to set global variables %d\n", err); goto cleanup; } + err = bpf_object__prepare(obj); + if (err) /* run process_prog() anyway to output per program failures */ + fprintf(stderr, "Failed to prepare BPF object for loading %d\n", err); + bpf_object__for_each_program(prog, obj) { - const char *prog_name = bpf_program__name(prog); - - tobj = bpf_object__open_file(filename, &opts); - if (!tobj) { - err = -errno; - fprintf(stderr, "Failed to open '%s': %d\n", filename, err); - goto cleanup; - } - - err = set_global_vars(tobj, env.presets, env.npresets); - if (err) { - fprintf(stderr, "Failed to set global variables %d\n", err); - goto cleanup; - } - - lprog = NULL; - bpf_object__for_each_program(tprog, tobj) { - const char *tprog_name = bpf_program__name(tprog); - - if (strcmp(prog_name, tprog_name) == 0) { - bpf_program__set_autoload(tprog, true); - lprog = tprog; - } else { - bpf_program__set_autoload(tprog, false); - } - } - - process_prog(filename, tobj, lprog); - bpf_object__close(tobj); + process_prog(filename, obj, prog); } cleanup: @@ -3264,17 +3249,14 @@ static int handle_verif_mode(void) create_stat_cgroup(); for (i = 0; i < env.filename_cnt; i++) { err = process_obj(env.filenames[i]); - if (err) { + if (err) fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); - goto out; - } } qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); output_prog_stats(); -out: destroy_stat_cgroup(); return err; } From ceebdeec6e8c6c879260a7293ac66c08eb185d43 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Tue, 17 Mar 2026 10:39:23 -0700 Subject: [PATCH 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override Add a test that verifies bpf_program__clone() respects caller-provided attach_btf_id in bpf_prog_load_opts. The BPF program has SEC("fentry/bpf_fentry_test1"). It is cloned twice from the same prepared object: first with no opts, verifying the callback resolves attach_btf_id from sec_name to bpf_fentry_test1; then with attach_btf_id overridden to bpf_fentry_test2, verifying the loaded program is actually attached to bpf_fentry_test2. Both results are checked via bpf_prog_get_info_by_fd(). Signed-off-by: Mykyta Yatsenko Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260317-veristat_prepare-v4-3-74193d4cc9d9@meta.com Signed-off-by: Alexei Starovoitov --- .../bpf/prog_tests/clone_attach_btf_id.c | 78 +++++++++++++++++++ .../selftests/bpf/progs/clone_attach_btf_id.c | 13 ++++ 2 files changed, 91 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/clone_attach_btf_id.c create mode 100644 tools/testing/selftests/bpf/progs/clone_attach_btf_id.c diff --git a/tools/testing/selftests/bpf/prog_tests/clone_attach_btf_id.c b/tools/testing/selftests/bpf/prog_tests/clone_attach_btf_id.c new file mode 100644 index 000000000000..1c3e28e74606 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/clone_attach_btf_id.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta */ +#include +#include "clone_attach_btf_id.skel.h" + +/* + * Test that bpf_program__clone() respects caller-provided attach_btf_id + * override via bpf_prog_load_opts. + * + * The BPF program has SEC("fentry/bpf_fentry_test1"). Clone it twice + * from the same prepared object: first with no opts (callback resolves + * attach_btf_id from sec_name), then with attach_btf_id overridden to + * bpf_fentry_test2. Verify each loaded program's attach_btf_id via + * bpf_prog_get_info_by_fd(). + */ + +static int get_prog_attach_btf_id(int prog_fd) +{ + struct bpf_prog_info info = {}; + __u32 info_len = sizeof(info); + int err; + + err = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len); + if (err) + return err; + return info.attach_btf_id; +} + +void test_clone_attach_btf_id(void) +{ + struct clone_attach_btf_id *skel; + int fd1 = -1, fd2 = -1, err; + int btf_id_test1, btf_id_test2; + + btf_id_test1 = libbpf_find_vmlinux_btf_id("bpf_fentry_test1", BPF_TRACE_FENTRY); + if (!ASSERT_GT(btf_id_test1, 0, "find_btf_id_test1")) + return; + + btf_id_test2 = libbpf_find_vmlinux_btf_id("bpf_fentry_test2", BPF_TRACE_FENTRY); + if (!ASSERT_GT(btf_id_test2, 0, "find_btf_id_test2")) + return; + + skel = clone_attach_btf_id__open(); + if (!ASSERT_OK_PTR(skel, "skel_open")) + return; + + err = bpf_object__prepare(skel->obj); + if (!ASSERT_OK(err, "obj_prepare")) + goto out; + + /* Clone with no opts — callback resolves BTF from sec_name */ + fd1 = bpf_program__clone(skel->progs.fentry_handler, NULL); + if (!ASSERT_GE(fd1, 0, "clone_default")) + goto out; + ASSERT_EQ(get_prog_attach_btf_id(fd1), btf_id_test1, + "attach_btf_id_default"); + + /* + * Clone with attach_btf_id override pointing to a different + * function. The BPF program never accesses arguments, so the + * load succeeds regardless of signature mismatch. + */ + LIBBPF_OPTS(bpf_prog_load_opts, opts, + .attach_btf_id = btf_id_test2, + ); + fd2 = bpf_program__clone(skel->progs.fentry_handler, &opts); + if (!ASSERT_GE(fd2, 0, "clone_override")) + goto out; + ASSERT_EQ(get_prog_attach_btf_id(fd2), btf_id_test2, + "attach_btf_id_override"); + +out: + if (fd1 >= 0) + close(fd1); + if (fd2 >= 0) + close(fd2); + clone_attach_btf_id__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c b/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c new file mode 100644 index 000000000000..0ffa3ec3e1a0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta */ +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +SEC("fentry/bpf_fentry_test1") +int BPF_PROG(fentry_handler, int a) +{ + return 0; +}