All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading
@ 2026-03-17 17:39 Mykyta Yatsenko
  2026-03-17 17:39 ` [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-03-17 17:39 UTC (permalink / raw)
  To: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87
  Cc: Mykyta Yatsenko, Emil Tsalapatis

This series adds bpf_program__clone() to libbpf and converts veristat to
use it, replacing the costly per-program object re-opening pattern.

veristat needs to load each BPF program in isolation to collect
per-program verification statistics. Previously it achieved this by
opening a fresh bpf_object for every program, disabling autoload on all
but the target, and loading the whole object. For object files with many
programs this meant repeating ELF parsing and BTF processing N times.

Patch 1 introduces bpf_program__clone(), which loads a single program
from a prepared object into the kernel and returns an fd owned by the
caller. It populates load parameters from the prepared object and lets
callers override any field via bpf_prog_load_opts. Fields written by the
prog_prepare_load_fn callback (expected_attach_type, attach_btf_id,
attach_btf_obj_fd) are seeded from prog/obj defaults before the
callback, then overridden with caller opts after, so explicit values
always win.

Patch 2 converts veristat to prepare the object once and clone each
program individually, eliminating redundant work.

Patch 3 adds a selftest verifying that caller-provided attach_btf_id
overrides are respected by bpf_program__clone().

Performance
Tested on selftests: 918 objects, ~4270 programs:
 - Wall time:   36.88s -> 23.18s (37% faster)
 - User time:   20.80s -> 16.07s (23% faster)
 - Kernel time: 12.07s ->  6.06s (50% faster)

Per-program loading also improves coverage: 83 programs that previously
failed now succeed.

Known regression:
 - Program-containing maps (PROG_ARRAY, DEVMAP, CPUMAP) track owner
   program type. Programs with incompatible attributes loaded against
   a shared map will be rejected. This is expected kernel behavior.

Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
Changes in v5:
- Fix overriding of the attach_btf_id, attach_btf_fd, etc: the override
provided by the caller is applied after prog_prepare_load_fn().
- Added selftest to verify attach_btf_id override works as expected.
- Link to v4: https://lore.kernel.org/all/20260316-veristat_prepare-v3-0-94e5691e0494@meta.com/

Changes in v4:
- Replace OPTS_SET() with direct struct assignment for local
  bpf_prog_load_opts in bpf_program__clone() (libbpf.c)
- Remove unnecessary pattr pointer indirection (libbpf.c)
- Separate input and output fields in bpf_program__clone(): input
  fields (prog_flags, fd_array, etc.) are merged from caller opts
  before the callback; output fields (expected_attach_type,
  attach_btf_id, attach_btf_obj_fd) are initialized from prog/obj
  defaults for the callback, then overridden with caller opts after,
  so explicit caller values always win (libbpf.c)
- Add selftest for attach_btf_id override
- Link to v3: https://lore.kernel.org/r/20260206-veristat_prepare-a4a041873c53-v3@meta.com

Changes in v3:
- Clone fd_array_cnt in bpf_object__clone()
- In veristat do not fail if bpf_object__prepare() fails,
continue per-program processing to produce per program output
- Link to v2: https://lore.kernel.org/r/20260220-veristat_prepare-v2-0-15bff49022a7@meta.com

Changes in v2:
 - Removed map cloning entirely (libbpf.c)
 - Renamed bpf_prog_clone() -> bpf_program__clone()
 - Removed unnecessary obj NULL check (libbpf.c)
 - Fixed opts handling — no longer mutates caller's opts (libbpf.c)
 - Link to v1: https://lore.kernel.org/all/20260212-veristat_prepare-v1-0-c351023fb0db@meta.com/

---
Mykyta Yatsenko (3):
      libbpf: Introduce bpf_program__clone()
      selftests/bpf: Use bpf_program__clone() in veristat
      selftests/bpf: Test bpf_program__clone() attach_btf_id override

 tools/lib/bpf/libbpf.c                             |  78 ++++++++++++++++
 tools/lib/bpf/libbpf.h                             |  17 ++++
 tools/lib/bpf/libbpf.map                           |   1 +
 .../selftests/bpf/prog_tests/clone_attach_btf_id.c |  78 ++++++++++++++++
 .../selftests/bpf/progs/clone_attach_btf_id.c      |  13 +++
 tools/testing/selftests/bpf/veristat.c             | 102 +++++++++------------
 6 files changed, 229 insertions(+), 60 deletions(-)
---
base-commit: 6c8e1a9eee0fec802b542dadf768c30c2a183b3c
change-id: 20260206-veristat_prepare-a4a041873c53

Best regards,
-- 
Mykyta Yatsenko <yatsenko@meta.com>


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone()
  2026-03-17 17:39 [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
@ 2026-03-17 17:39 ` Mykyta Yatsenko
  2026-03-17 19:40   ` Emil Tsalapatis
  2026-03-17 17:39 ` [PATCH bpf-next v4 2/3] selftests/bpf: Use bpf_program__clone() in veristat Mykyta Yatsenko
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-03-17 17:39 UTC (permalink / raw)
  To: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87; +Cc: Mykyta Yatsenko

From: Mykyta Yatsenko <yatsenko@meta.com>

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 <yatsenko@meta.com>
---
 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 d18fbcea7578..e727a54e373a 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;

-- 
2.52.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v4 2/3] selftests/bpf: Use bpf_program__clone() in veristat
  2026-03-17 17:39 [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
  2026-03-17 17:39 ` [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
@ 2026-03-17 17:39 ` Mykyta Yatsenko
  2026-03-17 17:39 ` [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override Mykyta Yatsenko
  2026-03-21 20:20 ` [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading patchwork-bot+netdevbpf
  3 siblings, 0 replies; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-03-17 17:39 UTC (permalink / raw)
  To: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87
  Cc: Mykyta Yatsenko, Emil Tsalapatis

From: Mykyta Yatsenko <yatsenko@meta.com>

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 <emil@etsalapatis.com>
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
 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;
 	}
 
-	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);
-			}
-		}
+	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);
 
-		process_prog(filename, tobj, lprog);
-		bpf_object__close(tobj);
+	bpf_object__for_each_program(prog, obj) {
+		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;
 }

-- 
2.52.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override
  2026-03-17 17:39 [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
  2026-03-17 17:39 ` [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
  2026-03-17 17:39 ` [PATCH bpf-next v4 2/3] selftests/bpf: Use bpf_program__clone() in veristat Mykyta Yatsenko
@ 2026-03-17 17:39 ` Mykyta Yatsenko
  2026-03-17 19:50   ` Emil Tsalapatis
  2026-03-21 20:20 ` [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading patchwork-bot+netdevbpf
  3 siblings, 1 reply; 7+ messages in thread
From: Mykyta Yatsenko @ 2026-03-17 17:39 UTC (permalink / raw)
  To: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87; +Cc: Mykyta Yatsenko

From: Mykyta Yatsenko <yatsenko@meta.com>

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 <yatsenko@meta.com>
---
 .../selftests/bpf/prog_tests/clone_attach_btf_id.c | 78 ++++++++++++++++++++++
 .../selftests/bpf/progs/clone_attach_btf_id.c      | 13 ++++
 2 files changed, 91 insertions(+)

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 <test_progs.h>
+#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 <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(fentry_handler, int a)
+{
+	return 0;
+}

-- 
2.52.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone()
  2026-03-17 17:39 ` [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
@ 2026-03-17 19:40   ` Emil Tsalapatis
  0 siblings, 0 replies; 7+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 19:40 UTC (permalink / raw)
  To: Mykyta Yatsenko, bpf, ast, andrii, daniel, kafai, kernel-team,
	eddyz87
  Cc: Mykyta Yatsenko

On Tue Mar 17, 2026 at 1:39 PM EDT, Mykyta Yatsenko wrote:
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> 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 <yatsenko@meta.com>

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>

Overriding the prepare_load_fn() callback's results with user-provided
ones is a valid choice design-wise.

> ---
>  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 d18fbcea7578..e727a54e373a 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;


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override
  2026-03-17 17:39 ` [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override Mykyta Yatsenko
@ 2026-03-17 19:50   ` Emil Tsalapatis
  0 siblings, 0 replies; 7+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 19:50 UTC (permalink / raw)
  To: Mykyta Yatsenko, bpf, ast, andrii, daniel, kafai, kernel-team,
	eddyz87
  Cc: Mykyta Yatsenko

On Tue Mar 17, 2026 at 1:39 PM EDT, Mykyta Yatsenko wrote:
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> 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 <yatsenko@meta.com>

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>

> ---
>  .../selftests/bpf/prog_tests/clone_attach_btf_id.c | 78 ++++++++++++++++++++++
>  .../selftests/bpf/progs/clone_attach_btf_id.c      | 13 ++++
>  2 files changed, 91 insertions(+)
>
> 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 <test_progs.h>
> +#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 = {};
Nit: I think this is not necessary because the fields are populated in
the kernel in bpf_prog_get_info_by_fd().
> +	__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 <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +
> +char _license[] SEC("license") = "GPL";
> +
> +SEC("fentry/bpf_fentry_test1")
> +int BPF_PROG(fentry_handler, int a)
> +{
> +	return 0;
> +}


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading
  2026-03-17 17:39 [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
                   ` (2 preceding siblings ...)
  2026-03-17 17:39 ` [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override Mykyta Yatsenko
@ 2026-03-21 20:20 ` patchwork-bot+netdevbpf
  3 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-03-21 20:20 UTC (permalink / raw)
  To: Mykyta Yatsenko
  Cc: bpf, ast, andrii, daniel, kafai, kernel-team, eddyz87, yatsenko,
	emil

Hello:

This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Tue, 17 Mar 2026 10:39:20 -0700 you wrote:
> This series adds bpf_program__clone() to libbpf and converts veristat to
> use it, replacing the costly per-program object re-opening pattern.
> 
> veristat needs to load each BPF program in isolation to collect
> per-program verification statistics. Previously it achieved this by
> opening a fresh bpf_object for every program, disabling autoload on all
> but the target, and loading the whole object. For object files with many
> programs this meant repeating ELF parsing and BTF processing N times.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v4,1/3] libbpf: Introduce bpf_program__clone()
    https://git.kernel.org/bpf/bpf-next/c/970bd2dced35
  - [bpf-next,v4,2/3] selftests/bpf: Use bpf_program__clone() in veristat
    https://git.kernel.org/bpf/bpf-next/c/3be706b937f3
  - [bpf-next,v4,3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override
    https://git.kernel.org/bpf/bpf-next/c/ceebdeec6e8c

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-03-21 20:20 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-17 17:39 [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
2026-03-17 17:39 ` [PATCH bpf-next v4 1/3] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
2026-03-17 19:40   ` Emil Tsalapatis
2026-03-17 17:39 ` [PATCH bpf-next v4 2/3] selftests/bpf: Use bpf_program__clone() in veristat Mykyta Yatsenko
2026-03-17 17:39 ` [PATCH bpf-next v4 3/3] selftests/bpf: Test bpf_program__clone() attach_btf_id override Mykyta Yatsenko
2026-03-17 19:50   ` Emil Tsalapatis
2026-03-21 20:20 ` [PATCH bpf-next v4 0/3] libbpf: Add bpf_program__clone() for individual program loading patchwork-bot+netdevbpf

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.