* [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading
@ 2026-09-23 23:29 Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum Andrey Grodzovsky
` (7 more replies)
0 siblings, 8 replies; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
This series was originally posted in January 2025 [1] as a two-patch RFC
introducing a per-program tri-state load strategy (disabled/auto/dynamic)
to let large BPF applications load a subset of their programs lazily,
after the initial bpf_object load. This v5 addresses v4's review
feedback and converts veristat's per-program verification from
bpf_program__clone() to this series' own manual-load API, closing the
loop on the open item from v2/v4 (see changelog below).
Motivation:
Security tools built on top of libbpf commonly ship as a single large
BPF object containing many programs, only a subset of which are needed
on any given system -- the rest are gated on optional features or on
kernel/runtime capabilities that vary across deployments. For this class
of application, per-program manual loading helps in two ways:
- it shortens the initial load, since only the programs actually
needed for the running configuration are loaded and attached up
front instead of the whole object; and
- it lets a single failing program be unloaded and possibly reloaded
(or a feature toggled at runtime) without tearing down and reloading
the entire bpf_object.
Both reduce the time window during which the tool is partially loaded or
inactive -- for a security tool specifically, that is also the time
window during which the system it protects is unprotected.
We have been running this with our internal fork of libbpf in production
for about a year. Depending on kernel and configuration support, around
250 of our programs are potentially loadable on any given system; with
manual loading, only around 90 of those are actually loaded by default,
growing to the full 250 only when every optional feature is enabled.
Cutting the default set of loaded programs from 250 down to 90 measurably
reduces load time (we observed ~60% reduction in our own measurements),
and since unload time scales with the number of loaded programs, it
correspondingly shortens unload time as well -- which matters, since
slow unload can delay system shutdown. We believe this functionality
would benefit other libbpf consumers with similarly large, modular BPF
applications.
Patch overview:
1/8: replace bpf_program's boolean autoload field with an enum
(bpf_prog_load_strategy: DISABLED/AUTO), so a third state can be
added later without an ABI break
2/8: add BPF_PROG_LOAD_STRATEGY_MANUAL and bpf_program__load()/
bpf_program__unload(), letting a program be loaded and attached
independently of the bulk bpf_object load/attach pass, and
reloaded/reattached multiple times
3/8: let a program mark itself MANUAL from its own section name via a
SEC("!...") prefix, instead of requiring an imperative
bpf_program__set_load_strategy() call, mirroring the existing
SEC("?...") convention
4/8: reject bpf_object__gen_loader() for an object with a MANUAL
program, which would otherwise silently corrupt every later
program's generated prog_fd slot
5/8: version bpf_program__set_autoattach()'s void->int return-type and
behavior change via ELF symbol versioning (COMPAT_VERSION()/
DEFAULT_VERSION()), modeled on the xsk_umem__create and
bpf_prog_load precedents, so binaries already linked against the
old void-returning ABI keep that behavior instead of silently
hitting the new MANUAL-rejection logic underneath them
6/8: selftest covering every load-strategy transition
(DISABLED/AUTO/MANUAL), bpf_program__set_autoload()'s bool
compatibility, and autoattach restoration
7/8: selftest covering the manual load/attach/detach/reload cycle, the
declarative SEC("!...") marker, prepare()-only load sufficiency,
a module BTF deferred load, and gen_loader rejection
8/8: convert veristat's process_prog() from bpf_program__clone() to
BPF_PROG_LOAD_STRATEGY_MANUAL + bpf_program__load()/
bpf_program__unload(), the open item from the v2 and v4 threads
Changes since v4 [2]:
- Fix a stray comment in bpf_object__init_prog() (bot+bpf-ci, sashiko-bot)
- Check the previously-ignored return value of
bpf_program__set_load_strategy() for prog1/prog2 in load_type.c,
consistent with every other call to the same setter later in the
same file (sashiko-bot)
- New: convert veristat's process_prog() to use
BPF_PROG_LOAD_STRATEGY_MANUAL/bpf_program__load()/
bpf_program__unload() instead of bpf_program__clone(), RODATA maps get
bound on each per-program load the way clone() deliberately avoids, but
since each program is unloaded again immediately after verification, the
binding is dropped along with it (Andrii, Alexei)
[1] https://lore.kernel.org/bpf/20250122215206.59859-1-slava.imameev@crowdstrike.com/
[2] https://lore.kernel.org/bpf/20260921223937.3203093-1-andrey.grodzovsky@crowdstrike.com/
Andrey Grodzovsky (5):
libbpf: Support declarative manual load via SEC("!...") prefix
libbpf: Reject gen_loader for objects with already-manual programs
libbpf: Version bpf_program__set_autoattach() ABI change
selftests/bpf: Cover BPF program manual loading
selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL
Slava Imameev (3):
libbpf: BPF program load strategy enum
libbpf: BPF programs manual loading and attaching
selftests/bpf: Cover BPF program load strategy transitions
tools/lib/bpf/libbpf.c | 281 +++++++++++---
tools/lib/bpf/libbpf.h | 69 +++-
tools/lib/bpf/libbpf.map | 5 +
tools/lib/bpf/libbpf_common.h | 6 +
.../selftests/bpf/prog_tests/dynamicload.c | 365 ++++++++++++++++++
.../selftests/bpf/prog_tests/load_type.c | 190 +++++++++
.../selftests/bpf/prog_tests/signed_loader.c | 28 ++
.../selftests/bpf/progs/test_dynamicload.c | 54 +++
.../selftests/bpf/progs/test_load_type.c | 31 ++
tools/testing/selftests/bpf/veristat.c | 20 +-
10 files changed, 990 insertions(+), 59 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/dynamicload.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/load_type.c
create mode 100644 tools/testing/selftests/bpf/progs/test_dynamicload.c
create mode 100644 tools/testing/selftests/bpf/progs/test_load_type.c
--
2.34.1
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 0:18 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
` (6 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
From: Slava Imameev <slava.imameev@crowdstrike.com>
Replacing the boolean field with an enum simplifies the addition
of new load strategies. Currently, the bpf_program structure defines
the autoload behavior using a boolean field. This field is now
replaced with an enum, allowing new BPF program loading strategies
to be introduced by extending the enum value range.
Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/lib/bpf/libbpf.c | 50 +++++++++++++++++++++++++---------------
tools/lib/bpf/libbpf.h | 33 ++++++++++++++++++++++++++
tools/lib/bpf/libbpf.map | 2 ++
3 files changed, 66 insertions(+), 19 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index cd1ea1bb53cb..a3085847cb97 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -494,7 +494,7 @@ struct bpf_program {
struct bpf_object *obj;
int fd;
- bool autoload;
+ enum bpf_prog_load_strategy load_strategy;
bool autoattach;
bool sym_global;
bool mark_btf_static;
@@ -874,11 +874,11 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
* autoload set to false.
*/
if (sec_name[0] == '?') {
- prog->autoload = false;
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
/* from now on forget there was ? in section name */
sec_name++;
} else {
- prog->autoload = true;
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_AUTO;
}
prog->autoattach = true;
@@ -1165,7 +1165,8 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
}
}
if (use_cnt)
- prog->autoload = should_load;
+ prog->load_strategy = should_load ? BPF_PROG_LOAD_STRATEGY_AUTO
+ : BPF_PROG_LOAD_STRATEGY_DISABLED;
}
return 0;
@@ -1256,7 +1257,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
* then bpf_object_adjust_struct_ops_autoload() will update its
* autoload accordingly.
*/
- st_ops->progs[i]->autoload = false;
+ st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
st_ops->progs[i] = NULL;
}
@@ -1294,7 +1295,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
* if user replaced it with another program or NULL
*/
if (st_ops->progs[i] && st_ops->progs[i] != prog)
- st_ops->progs[i]->autoload = false;
+ st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
/* Update the value from the shadow type */
st_ops->progs[i] = prog;
@@ -3594,7 +3595,7 @@ static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
}
bpf_object__for_each_program(prog, obj) {
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
if (prog_needs_vmlinux_btf(prog))
return true;
@@ -6214,7 +6215,7 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
/* no need to apply CO-RE relocation if the program is
* not going to be loaded
*/
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
/* adjust insn_idx from section frame of reference to the local
@@ -7558,7 +7559,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
*/
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
err = bpf_object__relocate_calls(obj, prog);
@@ -7594,7 +7595,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
prog = &obj->programs[i];
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
/* Process data relos for main programs */
@@ -8433,8 +8434,8 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)
prog = &obj->programs[i];
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload) {
- pr_debug("prog '%s': skipped loading\n", prog->name);
+ if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) {
+ pr_debug("prog '%s': skipped auto-loading\n", prog->name);
continue;
}
prog->log_level |= log_level;
@@ -9877,16 +9878,13 @@ const char *bpf_program__section_name(const struct bpf_program *prog)
bool bpf_program__autoload(const struct bpf_program *prog)
{
- return prog->autoload;
+ return prog->load_strategy == BPF_PROG_LOAD_STRATEGY_AUTO;
}
int bpf_program__set_autoload(struct bpf_program *prog, bool autoload)
{
- if (prog->obj->state >= OBJ_LOADED)
- return libbpf_err(-EINVAL);
-
- prog->autoload = autoload;
- return 0;
+ return bpf_program__set_load_strategy(prog,
+ autoload ? BPF_PROG_LOAD_STRATEGY_AUTO : BPF_PROG_LOAD_STRATEGY_DISABLED);
}
bool bpf_program__autoattach(const struct bpf_program *prog)
@@ -15264,7 +15262,7 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)
struct bpf_program *prog = *prog_skel->prog;
struct bpf_link **link = prog_skel->link;
- if (!prog->autoload || !prog->autoattach)
+ if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO || !prog->autoattach)
continue;
/* auto-attaching not supported for this program */
@@ -15374,3 +15372,17 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
free(s->progs);
free(s);
}
+
+int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
+{
+ if (prog->obj->state >= OBJ_LOADED)
+ return libbpf_err(-EINVAL);
+
+ prog->load_strategy = strategy;
+ return 0;
+}
+
+enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog)
+{
+ return prog->load_strategy;
+}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 03f794f4adb0..e7255bd4247f 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -2117,6 +2117,39 @@ LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);
*/
LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts);
+/**
+ * The program load strategy:
+ *
+ * - BPF_PROG_LOAD_STRATEGY_DISABLED: the program is not loaded.
+ * - BPF_PROG_LOAD_STRATEGY_AUTO: the program is autoloaded when the bpf_object is loaded.
+ */
+enum bpf_prog_load_strategy {
+ BPF_PROG_LOAD_STRATEGY_DISABLED = 0,
+ BPF_PROG_LOAD_STRATEGY_AUTO,
+};
+
+/**
+ * @brief **bpf_program__set_load_strategy()** sets the load strategy of a
+ * BPF program, controlling whether and when it gets loaded into the kernel.
+ *
+ * Can only be called before the enclosing bpf_object is loaded.
+ *
+ * @param prog BPF program to update
+ * @param strategy new load strategy for the program
+ * @return 0 on success; negative error code if the object was already loaded
+ */
+LIBBPF_API int bpf_program__set_load_strategy(struct bpf_program *prog,
+ enum bpf_prog_load_strategy strategy);
+
+/**
+ * @brief **bpf_program__load_strategy()** returns the current load strategy
+ * of a BPF program.
+ *
+ * @param prog BPF program to query
+ * @return current load strategy of the program
+ */
+LIBBPF_API enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 7a84dd00ce95..03c3d2bd15bf 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -463,6 +463,8 @@ LIBBPF_1.8.0 {
bpf_program__attach_tracing_multi;
bpf_program__clear_flags;
bpf_program__clone;
+ bpf_program__load_strategy;
+ bpf_program__set_load_strategy;
btf__find_by_name_kind_own;
btf__new_empty_opts;
} LIBBPF_1.7.0;
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-23 23:43 ` sashiko-bot
` (2 more replies)
2026-09-23 23:29 ` [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
` (5 subsequent siblings)
7 siblings, 3 replies; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
From: Slava Imameev <slava.imameev@crowdstrike.com>
BPF programs designated as manually loaded can be loaded and
attached independently after the initial bpf_object loading and
attaching.
These programs can also be reloaded and reattached multiple times,
enabling more flexible management of a resident BPF program set.
A key motivation for this feature is to reduce load times for
utilities that include hundreds of BPF programs. When the selection
of a resident BPF program set cannot be determined at the time of
bpf_object loading and attaching, all BPF programs would otherwise
need to be marked as autoload, leading to unnecessary overhead.
This patch addresses that inefficiency.
A manual-strategy program is loaded via bpf_program__load_manually()
and unloaded via bpf_program__unload_manually(), gated on
bpf_object__prepare() having already run (BTF loaded, maps created,
relocations applied) rather than requiring a full bpf_object__load().
Manual programs are skipped by the object's own autoload pass.
Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/lib/bpf/libbpf.c | 200 +++++++++++++++++++++++++++++++++------
tools/lib/bpf/libbpf.h | 32 ++++++-
tools/lib/bpf/libbpf.map | 1 +
3 files changed, 204 insertions(+), 29 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index a3085847cb97..939f0d6378e3 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -496,6 +496,7 @@ struct bpf_program {
int fd;
enum bpf_prog_load_strategy load_strategy;
bool autoattach;
+ bool saved_autoattach;
bool sym_global;
bool mark_btf_static;
enum bpf_prog_type type;
@@ -725,6 +726,7 @@ struct bpf_object {
bool has_subcalls;
bool has_rodata;
+ bool has_manual_progs;
struct bpf_gen *gen_loader;
@@ -797,7 +799,7 @@ static Elf_Data *elf_sec_data(const struct bpf_object *obj, Elf_Scn *scn);
static Elf64_Sym *elf_sym_by_idx(const struct bpf_object *obj, size_t idx);
static Elf64_Rel *elf_rel_by_idx(Elf_Data *data, size_t idx);
-void bpf_program__unload(struct bpf_program *prog)
+static void bpf_program_unload_full(struct bpf_program *prog)
{
if (!prog)
return;
@@ -809,12 +811,30 @@ void bpf_program__unload(struct bpf_program *prog)
zfree(&prog->subprogs);
}
+void bpf_program__unload(struct bpf_program *prog)
+{
+ if (!prog)
+ return;
+
+ /*
+ * MANUAL programs retain their data here so bpf_program__load()
+ * can reload them later; object teardown paths call
+ * bpf_program_unload_full() instead to always release it fully.
+ */
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ zclose(prog->fd);
+ return;
+ }
+
+ bpf_program_unload_full(prog);
+}
+
static void bpf_program__exit(struct bpf_program *prog)
{
if (!prog)
return;
- bpf_program__unload(prog);
+ bpf_program_unload_full(prog);
zfree(&prog->name);
zfree(&prog->sec_name);
zfree(&prog->insns);
@@ -8438,6 +8458,7 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)
pr_debug("prog '%s': skipped auto-loading\n", prog->name);
continue;
}
+
prog->log_level |= log_level;
if (obj->gen_loader)
@@ -8463,6 +8484,8 @@ static int bpf_object_prepare_progs(struct bpf_object *obj)
for (i = 0; i < obj->nr_programs; i++) {
prog = &obj->programs[i];
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
+ obj->has_manual_progs = true;
err = bpf_object__sanitize_prog(obj, prog);
if (err)
return err;
@@ -8489,6 +8512,19 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object
prog->type = prog->sec_def->prog_type;
prog->expected_attach_type = prog->sec_def->expected_attach_type;
+ /*
+ * struct_ops programs are incompatible with manual loading
+ * (see bpf_program__set_load_strategy()); reject SEC("!...")
+ * here too, at declarative parse time, since that check only
+ * runs on the imperative bpf_program__set_load_strategy() path.
+ */
+ if (prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+ prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ pr_warn("prog '%s': struct_ops programs do not support manual loading\n",
+ prog->name);
+ return -EINVAL;
+ }
+
/* sec_def can have custom callback which should be called
* after bpf_program is initialized to adjust its properties
*/
@@ -8689,7 +8725,7 @@ static int bpf_object_unload(struct bpf_object *obj)
}
for (i = 0; i < obj->nr_programs; i++)
- bpf_program__unload(&obj->programs[i]);
+ bpf_program_unload_full(&obj->programs[i]);
return 0;
}
@@ -9132,33 +9168,50 @@ static void bpf_object_unpin(struct bpf_object *obj)
bpf_map__unpin(&obj->maps[i], NULL);
}
-static void bpf_object_cleanup_btf(struct bpf_object *obj)
+static void bpf_object_cleanup_btf(struct bpf_object *obj, bool force)
{
int i;
- /* clean up module BTFs */
- for (i = 0; i < obj->btf_module_cnt; i++) {
- close(obj->btf_modules[i].fd);
- btf__free(obj->btf_modules[i].btf);
- free(obj->btf_modules[i].name);
+ /*
+ * Module BTF fds may still be borrowed (via fd_array,
+ * attach_btf_obj_fd, or baked into relocated instructions) by
+ * programs that have not been manually loaded yet, so defer
+ * closing them in that case to the end of the object lifetime,
+ * unless the caller forces immediate cleanup.
+ */
+ if (force || !obj->has_manual_progs) {
+ for (i = 0; i < obj->btf_module_cnt; i++) {
+ close(obj->btf_modules[i].fd);
+ btf__free(obj->btf_modules[i].btf);
+ free(obj->btf_modules[i].name);
+ }
+ obj->btf_module_cnt = 0;
+ obj->btf_module_cap = 0;
+ obj->btf_modules_loaded = false;
+ zfree(&obj->btf_modules);
}
- obj->btf_module_cnt = 0;
- obj->btf_module_cap = 0;
- obj->btf_modules_loaded = false;
- zfree(&obj->btf_modules);
- /* clean up vmlinux BTF */
- btf__free(obj->btf_vmlinux);
- obj->btf_vmlinux = NULL;
+ /*
+ * The btf_vmlinux data is needed for manually loaded programs,
+ * so defer freeing it in that case to the end of the object lifetime.
+ */
+ if (force || !obj->has_manual_progs) {
+ btf__free(obj->btf_vmlinux);
+ obj->btf_vmlinux = NULL;
+ }
}
-static void bpf_object_post_load_cleanup(struct bpf_object *obj)
+static void bpf_object_post_load_cleanup(struct bpf_object *obj, bool force)
{
- /* clean up fd_array */
- zfree(&obj->fd_array);
+ /*
+ * The fd array is needed for manually loaded programs,
+ * so defer freeing it in that case to the end of the object lifetime.
+ */
+ if (force || !obj->has_manual_progs)
+ zfree(&obj->fd_array);
/* clean up BTF */
- bpf_object_cleanup_btf(obj);
+ bpf_object_cleanup_btf(obj, force);
}
static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_path)
@@ -9233,7 +9286,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch
err = bpf_gen__finish(obj->gen_loader, obj->nr_programs, obj->nr_maps);
}
- bpf_object_post_load_cleanup(obj);
+ bpf_object_post_load_cleanup(obj, false);
obj->state = OBJ_LOADED; /* doesn't matter if successfully or not */
if (err) {
@@ -9701,7 +9754,7 @@ void bpf_object__close(struct bpf_object *obj)
* bpf_object__load(), we need to clean up stuff that is normally
* cleaned up at the end of loading step
*/
- bpf_object_post_load_cleanup(obj);
+ bpf_object_post_load_cleanup(obj, true);
usdt_manager_free(obj->usdt_man);
obj->usdt_man = NULL;
@@ -9710,7 +9763,6 @@ void bpf_object__close(struct bpf_object *obj)
bpf_object__elf_finish(obj);
bpf_object_unload(obj);
btf__free(obj->btf);
- btf__free(obj->btf_vmlinux);
btf_ext__free(obj->btf_ext);
for (i = 0; i < obj->nr_maps; i++)
@@ -9892,9 +9944,13 @@ bool bpf_program__autoattach(const struct bpf_program *prog)
return prog->autoattach;
}
-void bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
+int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
{
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
+ return libbpf_err(-EINVAL);
+
prog->autoattach = autoattach;
+ return 0;
}
const struct bpf_insn *bpf_program__insns(const struct bpf_program *prog)
@@ -12818,7 +12874,7 @@ static int collect_func_ids_by_glob(const struct bpf_program *prog, const char *
err = collect_btf_func_ids_by_glob(btf, pattern, ids);
cleanup:
- bpf_object_cleanup_btf(obj);
+ bpf_object_cleanup_btf(obj, false);
return err;
}
@@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
{
- if (prog->obj->state >= OBJ_LOADED)
+ struct bpf_object *obj = prog->obj;
+
+ /*
+ * has_manual_progs is snapshotted once in bpf_object_prepare_progs()
+ * and never recomputed; once the object is prepared, no transition
+ * into or out of MANUAL may change which programs are MANUAL,
+ * regardless of direction. AUTO<->DISABLED transitions never touch
+ * MANUAL and keep the looser, pre-existing OBJ_LOADED gate.
+ */
+ if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
+ prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ if (obj->state >= OBJ_PREPARED)
+ return libbpf_err(-EINVAL);
+ } else if (obj->state >= OBJ_LOADED) {
+ return libbpf_err(-EINVAL);
+ }
+
+ if (strategy == prog->load_strategy)
+ return 0;
+
+ switch (strategy) {
+ case BPF_PROG_LOAD_STRATEGY_DISABLED:
+ case BPF_PROG_LOAD_STRATEGY_AUTO:
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
+ prog->autoattach = prog->saved_autoattach;
+ prog->load_strategy = strategy;
+ break;
+ case BPF_PROG_LOAD_STRATEGY_MANUAL:
+ /*
+ * Manually-loaded programs are not supported for gen_loader.
+ * This is because bpf_object_load_prog is not called for
+ * manually-loaded programs, so such programs are not visible
+ * to gen_loader. For this reason, prevent calling
+ * bpf_program__set_load_strategy(MANUAL) when gen_loader was
+ * used to generate a BPF object loader.
+ * A gen_loader implementation is being called for autoloaded
+ * programs and defines its own model for loading BPF programs.
+ * To pass a BPF program to gen_loader, set the program's load strategy
+ * to LD_AUTOLOAD.
+ */
+ if (obj->gen_loader)
+ return libbpf_err(-EOPNOTSUPP);
+
+ /*
+ * struct_ops programs are incompatible with manual loading:
+ * bpf_map_prepare_vdata() bakes each member's fd into kern_vdata
+ * automatically during bpf_object__load(), before a MANUAL member
+ * could ever be loaded, and nothing re-bakes it afterwards.
+ */
+ if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
+ return libbpf_err(-EINVAL);
+
+ if (prog_is_subprog(obj, prog))
+ return libbpf_err(-EINVAL);
+
+ prog->saved_autoattach = prog->autoattach;
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
+ prog->autoattach = false;
+ break;
+ default:
return libbpf_err(-EINVAL);
+ }
- prog->load_strategy = strategy;
return 0;
}
@@ -15386,3 +15501,34 @@ enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program
{
return prog->load_strategy;
}
+
+/*
+ * This function must be called after bpf_object__prepare (or
+ * bpf_object__load, which calls bpf_object__prepare internally).
+ * Manually-loaded program data is initialized on object prepare.
+ * Post-prepare initialization is not supported.
+ */
+int
+bpf_program__load(struct bpf_program *prog)
+{
+ int err;
+ struct bpf_object *obj = prog->obj;
+
+ if (obj->state < OBJ_PREPARED)
+ return libbpf_err(-EINVAL);
+
+ if (prog_is_subprog(obj, prog) || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
+ return libbpf_err(-EINVAL);
+
+ if (prog->fd >= 0)
+ return libbpf_err(-EBUSY);
+
+ err = bpf_object_load_prog(obj, prog, prog->insns, prog->insns_cnt,
+ obj->license, obj->kern_version, &prog->fd);
+ if (err) {
+ pr_warn("prog '%s': failed to load: %s\n", prog->name, errstr(err));
+ return libbpf_err(err);
+ }
+
+ return 0;
+}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index e7255bd4247f..faea42f75847 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -392,7 +392,7 @@ LIBBPF_API const char *bpf_program__section_name(const struct bpf_program *prog)
LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
-LIBBPF_API void bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
+LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
struct bpf_insn;
@@ -473,6 +473,16 @@ LIBBPF_API int bpf_program__pin(struct bpf_program *prog, const char *path);
* @return 0, on success; negative error code, otherwise
*/
LIBBPF_API int bpf_program__unpin(struct bpf_program *prog, const char *path);
+/**
+ * @brief **bpf_program__unload()** unloads a BPF program, closing its fd.
+ *
+ * If the program's load strategy is BPF_PROG_LOAD_STRATEGY_MANUAL, only the
+ * fd is closed and the program's data is retained so it can be reloaded
+ * later via bpf_program__load(). For any other load strategy, the program's
+ * data is also freed and it cannot be reloaded.
+ *
+ * @param prog BPF program to unload
+ */
LIBBPF_API void bpf_program__unload(struct bpf_program *prog);
struct bpf_link;
@@ -2122,21 +2132,28 @@ LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_pro
*
* - BPF_PROG_LOAD_STRATEGY_DISABLED: the program is not loaded.
* - BPF_PROG_LOAD_STRATEGY_AUTO: the program is autoloaded when the bpf_object is loaded.
+ * - BPF_PROG_LOAD_STRATEGY_MANUAL: the program is loaded and attached manually.
*/
enum bpf_prog_load_strategy {
BPF_PROG_LOAD_STRATEGY_DISABLED = 0,
BPF_PROG_LOAD_STRATEGY_AUTO,
+ BPF_PROG_LOAD_STRATEGY_MANUAL,
};
/**
* @brief **bpf_program__set_load_strategy()** sets the load strategy of a
* BPF program, controlling whether and when it gets loaded into the kernel.
*
- * Can only be called before the enclosing bpf_object is loaded.
+ * Can only be called before the enclosing bpf_object is loaded, except when
+ * the program's current or new strategy is BPF_PROG_LOAD_STRATEGY_MANUAL, in
+ * which case it can only be called before the enclosing bpf_object is
+ * prepared.
*
* @param prog BPF program to update
* @param strategy new load strategy for the program
* @return 0 on success; negative error code if the object was already loaded
+ * (or, if the program's current or new strategy is
+ * BPF_PROG_LOAD_STRATEGY_MANUAL, already prepared)
*/
LIBBPF_API int bpf_program__set_load_strategy(struct bpf_program *prog,
enum bpf_prog_load_strategy strategy);
@@ -2150,6 +2167,17 @@ LIBBPF_API int bpf_program__set_load_strategy(struct bpf_program *prog,
*/
LIBBPF_API enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog);
+/**
+ * @brief **bpf_program__load()** loads a BPF program whose load strategy is
+ * BPF_PROG_LOAD_STRATEGY_MANUAL. The enclosing bpf_object must already be
+ * prepared.
+ *
+ * @param prog BPF program to load; must not be a subprogram, must have load
+ * strategy BPF_PROG_LOAD_STRATEGY_MANUAL, and must not already be loaded
+ * @return 0 on success; negative error code otherwise
+ */
+LIBBPF_API int bpf_program__load(struct bpf_program *prog);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 03c3d2bd15bf..8def5474885a 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -463,6 +463,7 @@ LIBBPF_1.8.0 {
bpf_program__attach_tracing_multi;
bpf_program__clear_flags;
bpf_program__clone;
+ bpf_program__load;
bpf_program__load_strategy;
bpf_program__set_load_strategy;
btf__find_by_name_kind_own;
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 23:18 ` Andrii Nakryiko
2026-09-23 23:29 ` [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs Andrey Grodzovsky
` (4 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
Add a SEC("!...") section-name prefix, letting a program declare
itself manually-loaded in its source instead of requiring an
imperative bpf_program__set_load_strategy() call, following the exact
convention already used for SEC("?...").
The prefix is recognized and stripped in bpf_object__init_prog(),
before find_sec_def() ever runs, directly setting load_strategy to
MANUAL and initializing autoattach/saved_autoattach - the same place
and same style '?' already uses, and before BTF or sec_def exist.
Assisted-by: Claude:claude-sonnet-5
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/lib/bpf/libbpf.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 939f0d6378e3..d815e53e8295 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -889,19 +889,29 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
prog->fd = -1;
prog->exception_cb_idx = -1;
- /* libbpf's convention for SEC("?abc...") is that it's just like
+ /*
+ * libbpf's convention for SEC("?abc...") is that it's just like
* SEC("abc...") but the corresponding bpf_program starts out with
* autoload set to false.
+ *
+ * Similarly, SEC("!abc...") marks the program for manual loading:
+ * it is skipped by the bulk auto-load pass and must be explicitly
+ * loaded later via bpf_program__load().
*/
if (sec_name[0] == '?') {
prog->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
/* from now on forget there was ? in section name */
sec_name++;
+ } else if (sec_name[0] == '!') {
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
+ /* from now on forget there was ! in section name */
+ sec_name++;
} else {
prog->load_strategy = BPF_PROG_LOAD_STRATEGY_AUTO;
}
- prog->autoattach = true;
+ prog->saved_autoattach = true;
+ prog->autoattach = prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL;
/* inherit object's log_level */
prog->log_level = obj->log_level;
@@ -15469,7 +15479,7 @@ int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_
* A gen_loader implementation is being called for autoloaded
* programs and defines its own model for loading BPF programs.
* To pass a BPF program to gen_loader, set the program's load strategy
- * to LD_AUTOLOAD.
+ * to BPF_PROG_LOAD_STRATEGY_AUTO.
*/
if (obj->gen_loader)
return libbpf_err(-EOPNOTSUPP);
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
` (2 preceding siblings ...)
2026-09-23 23:29 ` [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 23:18 ` Andrii Nakryiko
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
` (3 subsequent siblings)
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
bpf_program__set_load_strategy()'s MANUAL case rejects setting MANUAL
strategy while a gen_loader is already attached, but a program marked
MANUAL declaratively (SEC("!...")) gets that strategy during
bpf_object__open(), before bpf_object__gen_loader() can ever be
called, so the existing guard can never observe it.
Left unchecked, bpf_object_load_progs() skips such programs, so
gen->nr_progs undercounts relative to the object's real program
count. bpf_gen__finish() only rejects the opposite mismatch direction
(nr_progs < gen->nr_progs), so this passes silently, and every
generated skeleton program slot after the manual one ends up wired to
the wrong prog_fd.
Catch it at the one point guaranteed to run after any MANUAL marking
has already happened: reject in bpf_object__gen_loader() itself if any
program already has load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL.
Assisted-by: Claude:claude-sonnet-5
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/lib/bpf/libbpf.c | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index d815e53e8295..85e1d9c775ab 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -9859,11 +9859,31 @@ int bpf_object__set_kversion(struct bpf_object *obj, __u32 kern_version)
int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts)
{
struct bpf_gen *gen;
+ size_t i;
if (!opts)
return libbpf_err(-EFAULT);
if (!OPTS_VALID(opts, gen_loader_opts))
return libbpf_err(-EINVAL);
+
+ /*
+ * Manually-loaded programs are not visible to gen_loader (see
+ * bpf_program__set_load_strategy()'s MANUAL case), and marking a
+ * program MANUAL happens during bpf_object__open(), before this
+ * function can ever run, so that guard can never catch it here.
+ * Reject any pre-existing MANUAL program now, since this is the
+ * earliest point where both are known.
+ */
+ for (i = 0; i < obj->nr_programs; i++) {
+ struct bpf_program *prog = &obj->programs[i];
+
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ pr_warn("prog '%s': gen_loader does not support manually-loaded programs\n",
+ prog->name);
+ return libbpf_err(-EOPNOTSUPP);
+ }
+ }
+
gen = calloc(1, sizeof(*gen));
if (!gen)
return libbpf_err(-ENOMEM);
@@ -15470,16 +15490,10 @@ int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_
break;
case BPF_PROG_LOAD_STRATEGY_MANUAL:
/*
- * Manually-loaded programs are not supported for gen_loader.
- * This is because bpf_object_load_prog is not called for
- * manually-loaded programs, so such programs are not visible
- * to gen_loader. For this reason, prevent calling
- * bpf_program__set_load_strategy(MANUAL) when gen_loader was
- * used to generate a BPF object loader.
- * A gen_loader implementation is being called for autoloaded
- * programs and defines its own model for loading BPF programs.
- * To pass a BPF program to gen_loader, set the program's load strategy
- * to BPF_PROG_LOAD_STRATEGY_AUTO.
+ * Manually-loaded programs are not visible to gen_loader,
+ * since bpf_object__load_progs() skips them during the bulk
+ * load pass; see bpf_object__gen_loader()'s own guard for
+ * the full explanation.
*/
if (obj->gen_loader)
return libbpf_err(-EOPNOTSUPP);
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
` (3 preceding siblings ...)
2026-09-23 23:29 ` [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-23 23:41 ` sashiko-bot
` (2 more replies)
2026-09-23 23:29 ` [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
` (2 subsequent siblings)
7 siblings, 3 replies; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
bpf_program__set_autoattach()'s return type changed from void to int to
let callers observe the new -EINVAL rejection for MANUAL-strategy
programs, but the symbol stayed under its original LIBBPF_1.0.0 node in
libbpf.map. Binaries already linked against the old void-returning ABI
recorded a request for exactly that symbol@version pair at link time;
without a version bump they would silently start hitting the new
rejection behavior underneath them after a libbpf upgrade.
Split the symbol via ELF symbol versioning:
- bpf_program__set_autoattach_deprecated() keeps the original
unconditional behavior, bound via COMPAT_VERSION() to the existing
LIBBPF_1.0.0 node.
- bpf_program__set_autoattach_v1_8_0() carries the new MANUAL-rejecting
behavior, bound via DEFAULT_VERSION() to the new LIBBPF_1.8.0 node.
Old binaries keep resolving bpf_program__set_autoattach() to the old
behavior at runtime; anything linked against the current headers/map
gets the new int-returning, MANUAL-rejecting behavior. Also adds the
missing __LIBBPF_MARK_DEPRECATED_1_8 gate to libbpf_common.h (only the
1.0 gate existed) so LIBBPF_DEPRECATED_SINCE(1, 8, ...) can mark the
deprecated variant.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/lib/bpf/libbpf.c | 9 ++++++++-
tools/lib/bpf/libbpf.h | 6 ++++++
tools/lib/bpf/libbpf.map | 2 ++
tools/lib/bpf/libbpf_common.h | 6 ++++++
4 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 85e1d9c775ab..224fb0247a39 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -9974,7 +9974,14 @@ bool bpf_program__autoattach(const struct bpf_program *prog)
return prog->autoattach;
}
-int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
+COMPAT_VERSION(bpf_program__set_autoattach_deprecated, bpf_program__set_autoattach, LIBBPF_1.0.0)
+void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach)
+{
+ prog->autoattach = autoattach;
+}
+
+DEFAULT_VERSION(bpf_program__set_autoattach_v1_8_0, bpf_program__set_autoattach, LIBBPF_1.8.0)
+int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach)
{
if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
return libbpf_err(-EINVAL);
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index faea42f75847..d172c137cc37 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -393,6 +393,12 @@ LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
+/* this "specialization" should go away once the deprecation window for
+ * bpf_program__set_autoattach_deprecated() closes
+ */
+LIBBPF_API int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach);
+LIBBPF_DEPRECATED_SINCE(1, 8, "use int-returning bpf_program__set_autoattach() instead")
+LIBBPF_API void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach);
struct bpf_insn;
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 8def5474885a..999e791d9887 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -465,6 +465,8 @@ LIBBPF_1.8.0 {
bpf_program__clone;
bpf_program__load;
bpf_program__load_strategy;
+ bpf_program__set_autoattach;
+ bpf_program__set_autoattach_deprecated;
bpf_program__set_load_strategy;
btf__find_by_name_kind_own;
btf__new_empty_opts;
diff --git a/tools/lib/bpf/libbpf_common.h b/tools/lib/bpf/libbpf_common.h
index 8fe248e14eb6..12ad4f5f558f 100644
--- a/tools/lib/bpf/libbpf_common.h
+++ b/tools/lib/bpf/libbpf_common.h
@@ -36,6 +36,12 @@
#define __LIBBPF_MARK_DEPRECATED_1_0(X)
#endif
+#if __LIBBPF_CURRENT_VERSION_GEQ(1, 8)
+#define __LIBBPF_MARK_DEPRECATED_1_8(X) X
+#else
+#define __LIBBPF_MARK_DEPRECATED_1_8(X)
+#endif
+
/* This set of internal macros allows to do "function overloading" based on
* number of arguments provided by used in backwards-compatible way during the
* transition to libbpf 1.0
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
` (4 preceding siblings ...)
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 0:18 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL Andrey Grodzovsky
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
From: Slava Imameev <slava.imameev@crowdstrike.com>
Add load_type test covering load strategy transitions
(DISABLED/AUTO/MANUAL), set_autoload() bool/enum compatibility,
autoattach restore on leaving MANUAL, and the manual load/attach
cycle after the object is loaded.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
.../selftests/bpf/prog_tests/load_type.c | 190 ++++++++++++++++++
.../selftests/bpf/progs/test_load_type.c | 31 +++
2 files changed, 221 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/load_type.c
create mode 100644 tools/testing/selftests/bpf/progs/test_load_type.c
diff --git a/tools/testing/selftests/bpf/prog_tests/load_type.c b/tools/testing/selftests/bpf/prog_tests/load_type.c
new file mode 100644
index 000000000000..67813a8c7646
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/load_type.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <time.h>
+#include "test_load_type.skel.h"
+
+void test_load_type(void)
+{
+ struct bpf_link *link = NULL;
+ struct test_load_type *skel;
+ int err;
+
+ skel = test_load_type__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ /* don't load prog1 */
+ err = bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled_prog1"))
+ goto cleanup;
+
+ /* load and attach prog2 */
+ err = bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto_prog2"))
+ goto cleanup;
+ if (!ASSERT_TRUE(bpf_program__autoload(skel->progs.prog2), "prog2_autoload"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy"))
+ goto cleanup;
+
+ /*
+ * bpf_program__set_autoload() is a thin forwarder to
+ * set_load_strategy(), restricted to AUTO/DISABLED to preserve its
+ * original bool on/off meaning; it does change the load strategy of
+ * a program that isn't currently BPF_PROG_LOAD_STRATEGY_AUTO.
+ */
+ err = bpf_program__set_autoload(skel->progs.prog3, false);
+ if (!ASSERT_OK(err, "set_autoload_false"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3),
+ BPF_PROG_LOAD_STRATEGY_DISABLED, "prog3_load_strategy_after_false"))
+ goto cleanup;
+
+ err = bpf_program__set_autoload(skel->progs.prog3, true);
+ if (!ASSERT_OK(err, "set_autoload_true"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,
+ "prog3_load_strategy_after_true"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_enum"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy_after_manual_enum"))
+ goto cleanup;
+
+ /*
+ * leaving MANUAL for AUTO must restore autoattach (regression test for
+ * the autoattach residue bug: set_load_strategy(MANUAL) clears autoattach, and
+ * nothing used to restore it on exit)
+ */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,
+ "prog3_load_strategy_auto"))
+ goto cleanup;
+
+ if (!ASSERT_TRUE(bpf_program__autoattach(skel->progs.prog3), "prog3_autoattach_restored"))
+ goto cleanup;
+
+ /*
+ * confirm the restore also holds across a MANUAL -> DISABLED -> AUTO
+ * round-trip: AUTO and DISABLED share one guard keyed off the source
+ * strategy being MANUAL, so autoattach is already restored at the
+ * MANUAL -> DISABLED step, not by a separate DISABLED -> AUTO one
+ */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_again"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto_via_disabled"))
+ goto cleanup;
+
+ if (!ASSERT_TRUE(bpf_program__autoattach(skel->progs.prog3),
+ "prog3_autoattach_restored_via_disabled"))
+ goto cleanup;
+
+ /*
+ * discriminate the restore from a hard-coded `true`: force autoattach
+ * to false before entering MANUAL, then confirm AUTO restores it back
+ * to false rather than unconditionally re-enabling it
+ */
+ err = bpf_program__set_autoattach(skel->progs.prog3, false);
+ if (!ASSERT_OK(err, "set_autoattach_false"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_for_false_restore"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto_for_false_restore"))
+ goto cleanup;
+
+ if (!ASSERT_FALSE(bpf_program__autoattach(skel->progs.prog3),
+ "prog3_autoattach_restored_false"))
+ goto cleanup;
+
+ /* restore autoattach to true for the rest of the test */
+ err = bpf_program__set_autoattach(skel->progs.prog3, true);
+ if (!ASSERT_OK(err, "set_autoattach_true_again"))
+ goto cleanup;
+
+ /* an out-of-range load strategy is rejected */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, (enum bpf_prog_load_strategy)999);
+ if (!ASSERT_ERR(err, "set_load_strategy_invalid"))
+ goto cleanup;
+
+ /* change the strategy back to BPF_PROG_LOAD_STRATEGY_MANUAL for the rest of the test */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_final"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy_final"))
+ goto cleanup;
+
+ err = test_load_type__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ if (!ASSERT_TRUE(bpf_program__autoattach(skel->progs.prog2), "prog2_autoattach"))
+ goto cleanup;
+ if (!ASSERT_FALSE(bpf_program__autoattach(skel->progs.prog3), "prog3_autoattach"))
+ goto cleanup;
+
+ /* loaded program strategy cannot be changed */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ ASSERT_ERR(err, "set_load_strategy_after_load");
+
+ err = test_load_type__attach(skel);
+ if (!ASSERT_OK(err, "skel_attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load_manually"))
+ goto cleanup;
+
+ /* attach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called_again"))
+ goto cleanup;
+
+ /* detach prog3 as test_load_type__destroy doesn't detach manually loaded programs */
+ err = bpf_link__destroy(link);
+ ASSERT_OK(err, "link_destroy");
+ link = NULL;
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_load_type__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_load_type.c b/tools/testing/selftests/bpf/progs/test_load_type.c
new file mode 100644
index 000000000000..3d9b81691d7a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_load_type.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+bool prog1_called = false;
+bool prog2_called = false;
+bool prog3_called = false;
+
+SEC("raw_tp/sys_enter")
+int prog1(const void *ctx)
+{
+ prog1_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog2(const void *ctx)
+{
+ prog2_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog3(const void *ctx)
+{
+ prog3_called = true;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
` (5 preceding siblings ...)
2026-09-23 23:29 ` [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 0:32 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL Andrey Grodzovsky
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
Add dynamicload test covering the manual load/attach/detach/reload
cycle, declarative MANUAL via SEC("!...") and its imperative
override, bpf_object__prepare() alone being sufficient for manual
load, and a deferred load of a module BTF attach target. Also add
a signed_loader test verifying bpf_object__gen_loader() rejects
objects with a MANUAL program.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
.../selftests/bpf/prog_tests/dynamicload.c | 365 ++++++++++++++++++
.../selftests/bpf/prog_tests/signed_loader.c | 28 ++
.../selftests/bpf/progs/test_dynamicload.c | 54 +++
3 files changed, 447 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/dynamicload.c
create mode 100644 tools/testing/selftests/bpf/progs/test_dynamicload.c
diff --git a/tools/testing/selftests/bpf/prog_tests/dynamicload.c b/tools/testing/selftests/bpf/prog_tests/dynamicload.c
new file mode 100644
index 000000000000..eaaa3e8bd54a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/dynamicload.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <time.h>
+#include "test_dynamicload.skel.h"
+
+#define READ_SZ 456
+
+/*
+ * prog4 is marked SEC("!...") in the source instead of being set
+ * imperatively; verify that an explicit bpf_program__set_load_strategy() call
+ * before load overrides the declarative default.
+ */
+static void dynamicload_verify_override(void)
+{
+ struct test_dynamicload *skel;
+ int err;
+
+ skel = test_dynamicload__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog4, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog4),
+ BPF_PROG_LOAD_STRATEGY_DISABLED, "prog4_load_strategy_overridden"))
+ goto cleanup;
+
+ /*
+ * disable prog1/prog3 (also autoload by default) so this load only
+ * has to succeed for prog2 and the disabled prog4; prog2 loading is
+ * irrelevant to the assertion below and is left alone
+ */
+ err = bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled_prog1"))
+ goto cleanup;
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled_prog3"))
+ goto cleanup;
+
+ err = test_dynamicload__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ /*
+ * prog4 was overridden to DISABLED, so its load_strategy != MANUAL
+ * and load() must reject it
+ */
+ err = bpf_program__load(skel->progs.prog4);
+ ASSERT_ERR(err, "load_after_override");
+
+cleanup:
+ test_dynamicload__destroy(skel);
+}
+
+/*
+ * prog4 is MANUAL via its SEC("!...") marker; verify that
+ * bpf_object__prepare() alone -- without ever calling bpf_object__load() --
+ * is sufficient for bpf_program__load() to succeed, since BTF
+ * loading, map creation, and relocation of MANUAL programs are all
+ * completed by prepare() already.
+ */
+static void dynamicload_verify_prepare_only(void)
+{
+ struct test_dynamicload *skel;
+ struct bpf_link *link = NULL;
+ int err;
+
+ skel = test_dynamicload__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_object__prepare(skel->obj);
+ if (!ASSERT_OK(err, "bpf_object__prepare"))
+ goto cleanup;
+
+ err = bpf_program__load(skel->progs.prog4);
+ if (!ASSERT_OK(err, "load_after_prepare"))
+ goto cleanup;
+
+ if (!ASSERT_GE(bpf_program__fd(skel->progs.prog4), 0, "prog4_fd_after_prepare"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog4);
+ if (!ASSERT_OK_PTR(link, "attach_after_prepare"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog4_called, "prog4_called_after_prepare"))
+ goto cleanup;
+
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy_after_prepare"))
+ goto cleanup;
+
+ /*
+ * bpf_program__unload() is void now: for a MANUAL program it only
+ * closes the fd and retains func_info/line_info/subprogs so the
+ * program can be reloaded later.
+ */
+ bpf_program__unload(skel->progs.prog4);
+ ASSERT_LT(bpf_program__fd(skel->progs.prog4), 0, "prog4_fd_closed_after_unload");
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_dynamicload__destroy(skel);
+}
+
+/*
+ * prog5 is disabled at parse time; resolve its attach target against
+ * module BTF via bpf_program__set_attach_target() before switching it
+ * to MANUAL and deferring its load past the bulk bpf_object__load().
+ * Regression test for the module BTF fd/array lifetime bug: without
+ * deferring the module BTF fd/array close for MANUAL programs, the fd
+ * cached in prog->attach_btf_obj_fd is closed by the bulk load's
+ * cleanup before this deferred load runs, causing a deterministic
+ * -EINVAL.
+ */
+static void dynamicload_verify_module_btf(void)
+{
+ struct test_dynamicload *skel;
+ struct bpf_link *link;
+ int err;
+
+ if (!env.has_testmod) {
+ test__skip();
+ return;
+ }
+
+ skel = test_dynamicload__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_attach_target(skel->progs.prog5, 0,
+ "bpf_testmod:bpf_testmod_test_read");
+ if (!ASSERT_OK(err, "set_attach_target"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog5, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+
+ /* keep the other autoload programs out of the way of this load */
+ bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+
+ /*
+ * bulk load: prog5 itself is skipped (MANUAL), but this is where
+ * module BTF gets torn down if not correctly deferred
+ */
+ err = test_dynamicload__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ /*
+ * deferred load must still succeed: the module BTF fd cached above
+ * by set_attach_target() must still be a valid, open fd here
+ */
+ err = bpf_program__load(skel->progs.prog5);
+ if (!ASSERT_OK(err, "load_module_btf"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog5);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ ASSERT_OK(trigger_module_test_read(READ_SZ), "trigger_read");
+ ASSERT_EQ(skel->bss->prog5_sz, READ_SZ, "prog5_sz");
+
+ bpf_link__destroy(link);
+
+cleanup:
+ test_dynamicload__destroy(skel);
+}
+
+static void dynamicload_verify_main_cycle(void)
+{
+ struct bpf_link *link = NULL;
+ struct test_dynamicload *skel;
+ int err;
+
+ skel = test_dynamicload__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ /*
+ * the SEC("!...") prefix alone, with no imperative call, must set
+ * prog4's load strategy before it is ever touched below
+ */
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog4),
+ BPF_PROG_LOAD_STRATEGY_MANUAL, "prog4_prefix_load_strategy"))
+ goto cleanup;
+ if (!ASSERT_FALSE(bpf_program__autoattach(skel->progs.prog4), "prog4_autoattach"))
+ goto cleanup;
+
+ /* don't load prog1 */
+ bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+
+ /* prog2 is autoload */
+ bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);
+
+ /* prog3 is manually loaded */
+ bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+
+ err = test_dynamicload__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ err = test_dynamicload__attach(skel);
+ if (!ASSERT_OK(err, "skel_attach"))
+ goto cleanup;
+
+ /* trigger the BPF programs */
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+ ASSERT_FALSE(skel->bss->prog4_called, "prog4_called");
+
+ /* prog1 is disabled for load */
+ err = bpf_program__load(skel->progs.prog1);
+ if (!ASSERT_ERR(err, "load_disabled"))
+ goto cleanup;
+
+ /* prog2 is autoload */
+ err = bpf_program__load(skel->progs.prog2);
+ if (!ASSERT_ERR(err, "load_autoload"))
+ goto cleanup;
+
+ /*
+ * bpf_program__unload() no longer rejects based on load strategy:
+ * calling it on prog2 (AUTO, currently loaded and attached) performs
+ * a full, irreversible unload instead of returning an error
+ */
+ bpf_program__unload(skel->progs.prog2);
+ ASSERT_LT(bpf_program__fd(skel->progs.prog2), 0, "prog2_fd_closed_after_unload");
+
+ /* reset the call flags */
+ skel->bss->prog2_called = false;
+ skel->bss->prog3_called = false;
+
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ /* load prog3 */
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load"))
+ goto cleanup;
+
+ /* attach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called"))
+ goto cleanup;
+
+ /* detach prog3 as test_dynamicload__destroy doesn't detach manually loaded programs */
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy"))
+ goto cleanup;
+
+ /* reset the call flags after detach */
+ skel->bss->prog2_called = false;
+ skel->bss->prog3_called = false;
+
+ usleep(1);
+
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ /* unload prog3; MANUAL strategy means its data is retained for reload */
+ bpf_program__unload(skel->progs.prog3);
+
+ /* reload prog3 */
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load_reload"))
+ goto cleanup;
+
+ /* reattach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "reattach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called_reattach"))
+ goto cleanup;
+
+ /* detach prog3 as test_dynamicload__destroy doesn't detach manually loaded programs */
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy_reattach"))
+ goto cleanup;
+
+ /* reset the call flags after detach */
+ skel->bss->prog2_called = false;
+ skel->bss->prog3_called = false;
+
+ usleep(1);
+
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ /*
+ * run prog4 (declaratively marked) through the same manual
+ * load/attach/trigger/detach/unload cycle as prog3
+ */
+ err = bpf_program__load(skel->progs.prog4);
+ if (!ASSERT_OK(err, "prog4_load"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog4);
+ if (!ASSERT_OK_PTR(link, "prog4_attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog4_called, "prog4_called"))
+ goto cleanup;
+
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "prog4_link_destroy"))
+ goto cleanup;
+
+ bpf_program__unload(skel->progs.prog4);
+
+ test_dynamicload__destroy(skel);
+ return;
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_dynamicload__destroy(skel);
+}
+
+void test_dynamicload(void)
+{
+ if (test__start_subtest("main_cycle"))
+ dynamicload_verify_main_cycle();
+
+ if (test__start_subtest("verify_override"))
+ dynamicload_verify_override();
+
+ if (test__start_subtest("verify_prepare_only"))
+ dynamicload_verify_prepare_only();
+
+ if (test__start_subtest("verify_module_btf"))
+ dynamicload_verify_module_btf();
+}
+
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
index a0f93756e717..0648816e3d50 100644
--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -2168,6 +2168,33 @@ static void signed_module_kfunc_rejected(void)
run_setup("cleanup", dir);
}
+/*
+ * a program marked MANUAL is invisible to gen_loader's program count (it is
+ * skipped by bpf_object_load_progs()), so bpf_object__gen_loader() must
+ * reject the whole object up front instead of silently generating a
+ * skeleton whose prog_fd slots no longer line up with the object's programs
+ */
+static void manual_prog_rejected(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ struct test_signed_loader *skel;
+ int err;
+
+ skel = test_signed_loader__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_load_strategy(skel->progs.probe, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+
+ err = bpf_object__gen_loader(skel->obj, &gopts);
+ ASSERT_ERR(err, "gen_loader_rejected");
+
+cleanup:
+ test_signed_loader__destroy(skel);
+}
+
enum subtest_boot {
BOOT_ANY,
BOOT_SEALED,
@@ -2211,6 +2238,7 @@ static const struct {
{ "signed_map_by_fd_rejected", signed_map_by_fd_rejected, BOOT_SEALED },
{ "signed_sparse_fd_array_rejected", signed_sparse_fd_array_rejected, BOOT_SEALED },
{ "bpf_keyring_provisioned", bpf_keyring_provisioned, BOOT_UNSEALED },
+ { "manual_prog_rejected", manual_prog_rejected, BOOT_ANY },
};
void test_signed_loader(void)
diff --git a/tools/testing/selftests/bpf/progs/test_dynamicload.c b/tools/testing/selftests/bpf/progs/test_dynamicload.c
new file mode 100644
index 000000000000..6cdbfc21ca37
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_dynamicload.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+bool prog1_called = false;
+bool prog2_called = false;
+bool prog3_called = false;
+bool prog4_called = false;
+__u32 prog5_sz = 0;
+
+SEC("raw_tp/sys_enter")
+int prog1(const void *ctx)
+{
+ prog1_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog2(const void *ctx)
+{
+ prog2_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog3(const void *ctx)
+{
+ prog3_called = true;
+ return 0;
+}
+
+SEC("!raw_tp/sys_enter")
+int prog4(const void *ctx)
+{
+ prog4_called = true;
+ return 0;
+}
+
+/*
+ * disabled at parse time; its attach target is resolved against module
+ * BTF via bpf_program__set_attach_target() before it is switched to
+ * MANUAL and loaded
+ */
+SEC("?fentry")
+int BPF_PROG(prog5, struct file *file, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
+{
+ prog5_sz = len;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
` (6 preceding siblings ...)
2026-09-23 23:29 ` [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
@ 2026-09-23 23:29 ` Andrey Grodzovsky
2026-09-24 0:32 ` bot+bpf-ci
7 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-23 23:29 UTC (permalink / raw)
To: bpf, andrii, ast; +Cc: martin.kelly, slava.imameev, linux-open-source
veristat used bpf_program__clone() to verify each program in
isolation. Replace it with BPF_PROG_LOAD_STRATEGY_MANUAL plus
bpf_program__load()/bpf_program__unload(): process_obj() marks every
program MANUAL and calls bpf_object__prepare() instead of
bpf_object__load(); process_prog() loads and unloads one program at a
time, using bpf_program__set_log_buf()/set_log_level() for the
per-call verifier log clone()'s opts used to provide.
Unlike clone(), which skips binding RODATA maps to avoid mutating
shared object state, bpf_program__load() does bind them - but each
program is unloaded right after verification, dropping the binding
along with it.
Assisted-by: Claude:claude-sonnet-5
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
---
tools/testing/selftests/bpf/veristat.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
index 9cfc9b4b41c1..0cd19b59037f 100644
--- a/tools/testing/selftests/bpf/veristat.c
+++ b/tools/testing/selftests/bpf/veristat.c
@@ -1678,7 +1678,6 @@ 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;
@@ -1726,18 +1725,15 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
if (env.force_reg_invariants)
bpf_program__add_flags(prog, BPF_F_TEST_REG_INVARIANTS);
- opts.log_buf = buf;
- opts.log_size = buf_sz;
- opts.log_level = log_level;
+ bpf_program__set_log_buf(prog, buf, buf_sz);
+ bpf_program__set_log_level(prog, log_level);
cgroup_err = reset_stat_cgroup();
mem_peak_a = cgroup_memory_peak();
- fd = bpf_program__clone(prog, &opts);
- if (fd < 0) {
- err = fd;
- if (env.verbose)
- fprintf(stderr, "Failed to load program %s %d\n", prog_name, err);
- }
+ err = bpf_program__load(prog);
+ fd = err == 0 ? bpf_program__fd(prog) : -1;
+ if (err && env.verbose)
+ 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;
@@ -1774,7 +1770,7 @@ 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);
+ bpf_program__unload(prog);
return 0;
}
@@ -2297,7 +2293,7 @@ static int process_obj(const char *filename)
env.files_processed++;
bpf_object__for_each_program(prog, obj) {
- bpf_program__set_autoload(prog, true);
+ bpf_program__set_load_strategy(prog, BPF_PROG_LOAD_STRATEGY_MANUAL);
prog_cnt++;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
@ 2026-09-23 23:41 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
2026-09-24 23:19 ` Andrii Nakryiko
2 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-09-23 23:41 UTC (permalink / raw)
To: Andrey Grodzovsky; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] libbpf: API/ABI pollution with internal symbol versioning aliases
--
commit d36af029b0efcdd76647414b7d862ed1b5af5ff2
Author: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
libbpf: Version bpf_program__set_autoattach() ABI change
This commit splits bpf_program__set_autoattach() via ELF symbol versioning to
change its return type from void to int. It introduces
bpf_program__set_autoattach_deprecated for the original behavior and
bpf_program__set_autoattach_v1_8_0 for the new behavior.
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index faea42f758479..d172c137cc376 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
[ ... ]
> @@ -393,6 +393,12 @@ LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> +/* this "specialization" should go away once the deprecation window for
> + * bpf_program__set_autoattach_deprecated() closes
> + */
> +LIBBPF_API int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach);
> +LIBBPF_DEPRECATED_SINCE(1, 8, "use int-returning bpf_program__set_autoattach() instead")
> +LIBBPF_API void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach);
[Severity: High]
Does exposing these internal versioning aliases in the public header pollute
the public API?
Since bpf_program__set_autoattach_v1_8_0 is not in the export map, won't any
direct calls to it by users cause linker errors?
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 8def5474885a8..999e791d98870 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
[ ... ]
> @@ -465,6 +465,8 @@ LIBBPF_1.8.0 {
> bpf_program__clone;
> bpf_program__load;
> bpf_program__load_strategy;
> + bpf_program__set_autoattach;
> + bpf_program__set_autoattach_deprecated;
[Severity: High]
Is it intended to export bpf_program__set_autoattach_deprecated in the
LIBBPF_1.8.0 ABI node?
This seems to make it a newly added, globally-visible ABI symbol instead of
an internal symver target, which permanently pollutes the libbpf.so stable
ABI.
> bpf_program__set_load_strategy;
> btf__find_by_name_kind_own;
> btf__new_empty_opts;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260923232939.1886328-1-andrey.grodzovsky@crowdstrike.com?part=5
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
@ 2026-09-23 23:43 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
2026-09-24 23:18 ` Andrii Nakryiko
2 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-09-23 23:43 UTC (permalink / raw)
To: Andrey Grodzovsky; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] bpf: missing early validation for MANUAL programs in PROG_ARRAY maps
- [Low] bpf: commit message claims do not match the implemented API names
--
commit ffe6da53cfeec9f1860cfd6d78324b171ee2b1d5
Author: Slava Imameev <slava.imameev@crowdstrike.com>
libbpf: BPF programs manual loading and attaching
This commit introduces manual loading and attaching strategies for BPF
programs, allowing programs to be selectively loaded and unloaded multiple
times without requiring all object programs to be auto-loaded.
> A manual-strategy program is loaded via bpf_program__load_manually()
> and unloaded via bpf_program__unload_manually(), gated on
[Severity: Low]
The commit message mentions bpf_program__load_manually() and
bpf_program__unload_manually(), but the code actually implements
bpf_program__load() and bpf_program__unload(). Should the commit message
be updated to match the implemented API names to fix this regression?
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
[ ... ]
> + /*
> + * struct_ops programs are incompatible with manual loading:
> + * bpf_map_prepare_vdata() bakes each member's fd into kern_vdata
> + * automatically during bpf_object__load(), before a MANUAL member
> + * could ever be loaded, and nothing re-bakes it afterwards.
> + */
> + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
> + return libbpf_err(-EINVAL);
[Severity: Low]
A check exists here in bpf_program__set_load_strategy() to reject struct_ops,
but does this code miss validation for prog_array maps?
If a user configures a prog_array map to hold a reference to a BPF program
and sets that program's load strategy to manual, the object load will fail
later with -EBADF during the kernel map update when init_prog_array_slots()
calls:
fd = bpf_program__fd(targ_prog);
err = bpf_map_update_elem(map->fd, &i, &fd, 0);
Can this regression occur because the manual program has not been loaded yet,
meaning it will have an invalid file descriptor? Could we add an early
validation here similar to the struct_ops check to prevent this later
failure?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260923232939.1886328-1-andrey.grodzovsky@crowdstrike.com?part=2
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum
2026-09-23 23:29 ` [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum Andrey Grodzovsky
@ 2026-09-24 0:18 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:18 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1461 bytes --]
> libbpf: BPF program load strategy enum
>
> Replacing the boolean field with an enum simplifies the addition
> of new load strategies. Currently, the bpf_program structure defines
> the autoload behavior using a boolean field. This field is now
> replaced with an enum, allowing new BPF program loading strategies
> to be introduced by extending the enum value range.
>
> Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
> Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
This isn't a bug, but could the changelog mention the new
bpf_program__set_load_strategy()/bpf_program__load_strategy() API and
the libbpf.map additions?
The changelog describes only the internal conversion of the private
bpf_program::load_strategy field, but the patch also adds two new
exported public APIs (bpf_program__set_load_strategy() and
bpf_program__load_strategy()), a new public enum
bpf_prog_load_strategy in libbpf.h, and two new LIBBPF_1.8.0 symbols
in libbpf.map. At this point in the series those APIs expose exactly
the same two states as the existing
bpf_program__autoload()/bpf_program__set_autoload(), so why is new
public ABI added here rather than together with the strategy that
needs it?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions
2026-09-23 23:29 ` [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
@ 2026-09-24 0:18 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:18 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 5130 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/load_type.c b/tools/testing/selftests/bpf/prog_tests/load_type.c
> new file mode 100644
> index 0000000000000..67813a8c76462
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/load_type.c
> @@ -0,0 +1,190 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include <time.h>
> +#include "test_load_type.skel.h"
> +
> +void test_load_type(void)
> +{
> + struct bpf_link *link = NULL;
> + struct test_load_type *skel;
> + int err;
> +
> + skel = test_load_type__open();
> + if (!ASSERT_OK_PTR(skel, "skel_open"))
> + return;
[ ... ]
> + /*
> + * leaving MANUAL for AUTO must restore autoattach (regression test for
> + * the autoattach residue bug: set_load_strategy(MANUAL) clears autoattach, and
> + * nothing used to restore it on exit)
> + */
This isn't a bug, but would it be clearer to say only what is being
checked (e.g. "leaving MANUAL restores the previous autoattach setting")
and drop the reference to a residue bug that isn't in the tree, plus the
notes on how libbpf's switch is structured internally? The comment
describes a bug that only existed in an unmerged revision, so a future
reader cannot find it in the git history. The comments at other points in
the test (describing "AUTO and DISABLED share one guard keyed off the
source strategy being MANUAL" and calling set_autoload "a thin forwarder
to set_load_strategy()") also document how libbpf's implementation is
laid out rather than what behavior is being checked, so they go stale if
libbpf is restructured.
> + err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
> + if (!ASSERT_OK(err, "set_load_strategy_auto"))
> + goto cleanup;
[ ... ]
> + err = test_load_type__load(skel);
> + if (!ASSERT_OK(err, "skel_load"))
> + goto cleanup;
[ ... ]
> + err = test_load_type__attach(skel);
> + if (!ASSERT_OK(err, "skel_attach"))
> + goto cleanup;
> +
> + usleep(1);
> +
> + ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
> + ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
> + ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
> +
> + err = bpf_program__load(skel->progs.prog3);
> + if (!ASSERT_OK(err, "load_manually"))
> + goto cleanup;
> +
> + /* attach prog3 */
> + link = bpf_program__attach(skel->progs.prog3);
> + if (!ASSERT_OK_PTR(link, "attach"))
> + goto cleanup;
> +
> + usleep(1);
> +
> + if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called_again"))
> + goto cleanup;
This isn't a bug, but the manual load/attach tail here and the BPF object
(progs/test_load_type.c with prog1..prog3, all SEC("raw_tp/sys_enter")
setting prog{1,2,3}_called) are close to what the next commit in this
series adds as progs/test_dynamicload.c and the
dynamicload_verify_main_cycle() function. The only part of load_type.c
that dynamicload.c does not also cover is the pre-load strategy
transition and autoattach-restore sequence. Could the transition checks
become a subtest of dynamicload (reusing test_dynamicload.skel.h), so the
series doesn't carry two copies of the same load/attach cycle and BPF
object?
> + /* detach prog3 as test_load_type__destroy doesn't detach manually loaded programs */
This isn't a bug, but could this comment say what is actually going on?
The stated reason doesn't match libbpf: bpf_object__detach_skeleton()
destroys *prog_skel->link for every program whatever its load strategy.
The real reason test_load_type__destroy() won't detach prog3 is that the
link lives in a local variable instead of skel->links.prog3, and the
cleanup label already destroys that local. The comment's current wording
might lead readers to think libbpf leaks attachments for MANUAL programs
on destroy. Alternatively, storing the link in skel->links.prog3 would
let test_load_type__destroy() handle it automatically.
> + err = bpf_link__destroy(link);
> + ASSERT_OK(err, "link_destroy");
> + link = NULL;
> +
> +cleanup:
> + if (link)
> + bpf_link__destroy(link);
> + test_load_type__destroy(skel);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/test_load_type.c b/tools/testing/selftests/bpf/progs/test_load_type.c
> new file mode 100644
> index 0000000000000..3d9b81691d7ad
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_load_type.c
> @@ -0,0 +1,31 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +
> +bool prog1_called = false;
> +bool prog2_called = false;
> +bool prog3_called = false;
> +
> +SEC("raw_tp/sys_enter")
> +int prog1(const void *ctx)
> +{
> + prog1_called = true;
> + return 0;
> +}
> +
> +SEC("raw_tp/sys_enter")
> +int prog2(const void *ctx)
> +{
> + prog2_called = true;
> + return 0;
> +}
> +
> +SEC("raw_tp/sys_enter")
> +int prog3(const void *ctx)
> +{
> + prog3_called = true;
> + return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading
2026-09-23 23:29 ` [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
@ 2026-09-24 0:32 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:32 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3991 bytes --]
> diff --git a/tools/testing/selftests/bpf/prog_tests/dynamicload.c b/tools/testing/selftests/bpf/prog_tests/dynamicload.c
> new file mode 100644
> index 0000000000000..eaaa3e8bd54af
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/dynamicload.c
> @@ -0,0 +1,365 @@
[ ... ]
> +/*
> + * prog4 is marked SEC("!...") in the source instead of being set
> + * imperatively; verify that an explicit bpf_program__set_load_strategy() call
> + * before load overrides the declarative default.
> + */
> +static void dynamicload_verify_override(void)
> +{
> + struct test_dynamicload *skel;
> + int err;
> +
> + skel = test_dynamicload__open();
> + if (!ASSERT_OK_PTR(skel, "skel_open"))
> + return;
> +
> + err = bpf_program__set_load_strategy(skel->progs.prog4, BPF_PROG_LOAD_STRATEGY_DISABLED);
> + if (!ASSERT_OK(err, "set_load_strategy_disabled"))
> + goto cleanup;
> +
> + if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog4),
> + BPF_PROG_LOAD_STRATEGY_DISABLED, "prog4_load_strategy_overridden"))
> + goto cleanup;
> +
> + /*
> + * disable prog1/prog3 (also autoload by default) so this load only
> + * has to succeed for prog2 and the disabled prog4; prog2 loading is
> + * irrelevant to the assertion below and is left alone
> + */
> + err = bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
> + if (!ASSERT_OK(err, "set_load_strategy_disabled_prog1"))
> + goto cleanup;
> + err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
> + if (!ASSERT_OK(err, "set_load_strategy_disabled_prog3"))
> + goto cleanup;
> +
> + err = test_dynamicload__load(skel);
> + if (!ASSERT_OK(err, "skel_load"))
> + goto cleanup;
> +
> + /*
> + * prog4 was overridden to DISABLED, so its load_strategy != MANUAL
> + * and load() must reject it
> + */
> + err = bpf_program__load(skel->progs.prog4);
> + ASSERT_ERR(err, "load_after_override");
Does this negative assertion actually test that bpf_program__load() checks
the load strategy?
prog4 is DISABLED by the time test_dynamicload__load() runs, so
bpf_object__relocate() skips it:
if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
That leaves prog4's ld_imm64 instruction for the .bss variable
prog4_called unrelocated.
Suppose the strategy check in bpf_program__load() were missing:
/* this check is what the test wants to verify */
if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
return -EINVAL;
bpf_object_load_prog() would send the unrelocated instructions to the
kernel. The verifier would reject the store through a scalar pointer
with -EACCES:
; prog4_called = true;
R0 w= 1
(*u32 *)(r0 + 0) = r0; /* r0 has not been relocated to the .bss map */
R0 invalid mem access 'scalar'
ASSERT_ERR(err, ...) would still pass, since -EACCES is an error.
The subtest also never checks that the bulk load skipped prog4. If
bpf_object__load_progs() loaded it anyway, bpf_program__load() would
return -EINVAL from the strategy check, and nothing looks at
bpf_program__fd(prog4).
The other rejection checks in this file have the same issue.
dynamicload_verify_main_cycle's load_disabled check uses a DISABLED,
unrelocated prog1, and load_autoload returns -EBUSY because prog2 is
already loaded rather than because of the strategy check.
Would asserting the specific errno make the test discriminate?
ASSERT_EQ(err, -EINVAL, "load_after_override");
A missing strategy check would then give -EACCES from the verifier
instead of -EINVAL.
Also, could the test add this after test_dynamicload__load():
ASSERT_LT(bpf_program__fd(skel->progs.prog4), 0, ...);
to verify that the bulk load actually skipped prog4?
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
2026-09-23 23:41 ` sashiko-bot
@ 2026-09-24 0:32 ` bot+bpf-ci
2026-09-24 23:19 ` Andrii Nakryiko
2 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:32 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1708 bytes --]
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 85e1d9c775ab1..224fb0247a395 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index faea42f758479..d172c137cc376 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -393,6 +393,12 @@ LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> +/* this "specialization" should go away once the deprecation window for
> + * bpf_program__set_autoattach_deprecated() closes
> + */
This isn't a bug, but should the opening '/*' be on its own line? The BPF
subsystem comment style requires multi-line comments to have the '/*' on a
separate line:
/*
* this "specialization" should go away once the deprecation window for
* bpf_program__set_autoattach_deprecated() closes
*/
> +LIBBPF_API int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach);
> +LIBBPF_DEPRECATED_SINCE(1, 8, "use int-returning bpf_program__set_autoattach() instead")
> +LIBBPF_API void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach);
>
> struct bpf_insn;
>
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL
2026-09-23 23:29 ` [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL Andrey Grodzovsky
@ 2026-09-24 0:32 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:32 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4702 bytes --]
> diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
> index 9cfc9b4b41c12..0cd19b59037f6 100644
> --- a/tools/testing/selftests/bpf/veristat.c
> +++ b/tools/testing/selftests/bpf/veristat.c
[ ... ]
> @@ -1726,18 +1725,15 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
> if (env.force_reg_invariants)
> bpf_program__add_flags(prog, BPF_F_TEST_REG_INVARIANTS);
>
> - opts.log_buf = buf;
> - opts.log_size = buf_sz;
> - opts.log_level = log_level;
> + bpf_program__set_log_buf(prog, buf, buf_sz);
> + bpf_program__set_log_level(prog, log_level);
>
> cgroup_err = reset_stat_cgroup();
> mem_peak_a = cgroup_memory_peak();
> - fd = bpf_program__clone(prog, &opts);
> - if (fd < 0) {
> - err = fd;
> - if (env.verbose)
> - fprintf(stderr, "Failed to load program %s %d\n", prog_name, err);
> - }
> + err = bpf_program__load(prog);
When bpf_object__prepare() fails earlier in process_obj(), does this path
still capture the verifier log and stats?
Both bpf_program__set_log_buf() and bpf_program__set_log_level() refuse
to work when obj->state >= OBJ_LOADED:
/* from libbpf.c */
if (prog->obj->state >= OBJ_LOADED)
return libbpf_err(-EBUSY);
On failure, bpf_object_prepare() sets obj->state = OBJ_LOADED. The setters
return -EBUSY, veristat ignores these return values, and bpf_program__load()
proceeds with prog->log_buf == NULL and prog->log_level == 0.
This means no stats are captured in veristat's buf (TOTAL_INSNS, TOTAL_STATES,
DURATION all report zero), and the -v "VERIFIER LOG:" block stays empty.
The old bpf_program__clone() path took log_buf/log_size/log_level directly
from opts, which do not depend on object state, so this used to work.
[ ... ]
> @@ -2297,7 +2293,7 @@ static int process_obj(const char *filename)
> env.files_processed++;
>
> bpf_object__for_each_program(prog, obj) {
> - bpf_program__set_autoload(prog, true);
> + bpf_program__set_load_strategy(prog, BPF_PROG_LOAD_STRATEGY_MANUAL);
> prog_cnt++;
> }
Can struct_ops programs still be verified by veristat after this change?
bpf_program__set_load_strategy() refuses MANUAL for struct_ops programs:
/* tools/lib/bpf/libbpf.c */
if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
return libbpf_err(-EINVAL);
prog->type is already BPF_PROG_TYPE_STRUCT_OPS at this point because
bpf_object_init_progs() sets it from SEC("struct_ops+") / SEC("struct_ops.s+")
during bpf_object__open_file().
process_obj() ignores the return value, so these programs stay in their AUTO
state (or AUTO/DISABLED after bpf_object_adjust_struct_ops_autoload()).
Then process_prog() calls bpf_program__load(prog), which rejects any
non-MANUAL program:
if (prog_is_subprog(obj, prog) ||
prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
return libbpf_err(-EINVAL);
Every struct_ops program gets VERDICT failure with an empty verifier log and
zero stats. No verification is attempted.
Before this change, bpf_program__clone() loaded these programs using
prog->attach_btf_id/expected_attach_type, which
bpf_object__init_kern_struct_ops_maps() fills in during bpf_object__prepare().
This affects every sched_ext scheduler (the BPF CI veristat-scx job) and the
struct_ops objects in selftests (bpf_cubic, bpf_dctcp, bpf_qdisc_*,
dummy_st_ops_*, and others).
> commit 0fd4c97df1e253ae8d918685a5e167dcf2123865
> Author: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
>
> selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL
>
> veristat used bpf_program__clone() to verify each program in
> isolation. Replace it with BPF_PROG_LOAD_STRATEGY_MANUAL plus
> bpf_program__load()/bpf_program__unload(): process_obj() marks every
> program MANUAL and calls bpf_object__prepare() instead of
> bpf_object__load(); process_prog() loads and unloads one program at a
The commit message says process_obj() "calls bpf_object__prepare() instead of
bpf_object__load()" as though this patch makes that switch. Looking at the
parent commit, process_obj() already calls bpf_object__prepare() (veristat.c
line 2312 in HEAD~1), and this diff does not touch that call. The only change
in process_obj() is set_autoload(true) -> set_load_strategy(MANUAL).
Also, "marks every program MANUAL" is not accurate for struct_ops programs,
which bpf_program__set_load_strategy() rejects with -EINVAL (see the question
above about struct_ops verification).
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
2026-09-23 23:43 ` sashiko-bot
@ 2026-09-24 0:32 ` bot+bpf-ci
2026-09-28 1:40 ` Andrey Grodzovsky
2026-09-24 23:18 ` Andrii Nakryiko
2 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-09-24 0:32 UTC (permalink / raw)
To: andrey.grodzovsky, bpf, andrii, ast
Cc: martin.kelly, slava.imameev, linux-open-source, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 8648 bytes --]
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index a3085847cb97f..939f0d6378e38 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
>
> int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
> {
> - if (prog->obj->state >= OBJ_LOADED)
> + struct bpf_object *obj = prog->obj;
> +
> + if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
> + prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> + if (obj->state >= OBJ_PREPARED)
> + return libbpf_err(-EINVAL);
> + } else if (obj->state >= OBJ_LOADED) {
> return libbpf_err(-EINVAL);
> + }
> +
> + if (strategy == prog->load_strategy)
> + return 0;
> +
> + switch (strategy) {
> + case BPF_PROG_LOAD_STRATEGY_DISABLED:
> + case BPF_PROG_LOAD_STRATEGY_AUTO:
> + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> + prog->autoattach = prog->saved_autoattach;
> + prog->load_strategy = strategy;
> + break;
> + case BPF_PROG_LOAD_STRATEGY_MANUAL:
> + if (obj->gen_loader)
> + return libbpf_err(-EOPNOTSUPP);
Can this ordering be reversed? When bpf_program__set_load_strategy(MANUAL)
is called before bpf_object__gen_loader(), this check passes because
obj->gen_loader is still NULL. Then when bpf_object__gen_loader() runs
later, it doesn't check whether any MANUAL programs already exist.
Looking at bpf_object__load_progs():
for (i = 0; i < obj->nr_programs; i++) {
prog = &obj->programs[i];
if (!prog->autoload || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) {
pr_debug("prog '%s': skipped auto-loading\n", prog->name);
continue;
}
MANUAL programs are skipped, so gen->nr_progs ends up below
obj->nr_programs. Then bpf_gen__finish() only rejects nr_progs <
gen->nr_progs, so the generated loader silently omits these programs.
If someone later calls bpf_program__load() on the MANUAL program, it goes
through bpf_object_load_prog()'s gen_loader path:
if (obj->gen_loader) {
bpf_gen__prog_load(obj->gen_loader, ...);
*prog_fd = -1;
return 0;
}
which returns success with prog->fd = -1 and appends to gen buffers after
bpf_gen__finish() has already published opts->insns/opts->data. Since
gen_loader grows buffers with realloc(), this can leave those pointers
stale.
Note: This is fixed later in the series by 549383b4eb184 ("libbpf: Reject
gen_loader for objects with already-manual programs").
> + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
> + return libbpf_err(-EINVAL);
> +
> + if (prog_is_subprog(obj, prog))
> + return libbpf_err(-EINVAL);
> +
> + prog->saved_autoattach = prog->autoattach;
> + prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
> + prog->autoattach = false;
> + break;
> + default:
> + return libbpf_err(-EINVAL);
> + }
> +
> return 0;
> }
>
> @@ -15386,3 +15501,34 @@ enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program
> {
> return prog->load_strategy;
> }
> +
> +int
> +bpf_program__load(struct bpf_program *prog)
> +{
> + int err;
> + struct bpf_object *obj = prog->obj;
> +
> + if (obj->state < OBF_PREPARED)
> + return libbpf_err(-EINVAL);
> +
> + if (prog_is_subprog(obj, prog) || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
> + return libbpf_err(-EINVAL);
> +
> + if (prog->fd >= 0)
> + return libbpf_err(-EBUSY);
> +
> + err = bpf_object_load_prog(obj, prog, prog->insns, prog->insns_cnt,
> + obj->license, obj->kern_version, &prog->fd);
> + if (err) {
> + pr_warn("prog '%s': failed to load: %s\n", prog->name, errstr(err));
> + return libbpf_err(err);
> + }
> +
> + return 0;
> +}
The commit message says MANUAL programs "can also be reloaded and
reattached multiple times", and the bpf_program__unload() doc now says
MANUAL programs keep their data "so it can be reloaded later via
bpf_program__load()". Does this work for programs that use gotox jump
tables?
During bpf_object__prepare(), create_jt_map() creates a
BPF_MAP_TYPE_INSN_ARRAY map per (prog, sym_off) and caches it in
obj->jumptable_maps. RELO_INSN_ARRAY then writes the map fd directly into
prog->insns (insn->imm = map_fd). Each bpf_program__load() call
resubmits the same prog->insns with the same map fd.
On the kernel side, bpf_insn_array_init() runs:
if (atomic_xchg(&insn_array->used, 1))
return -EBUSY;
with the message "There can be only one program using the map". That flag
is cleared only by bpf_insn_array_release(), which the verifier calls on
the verification error path, not when a successfully loaded program is
freed.
So this sequence:
bpf_object__prepare()
bpf_program__load(p) // ok, insn_array->used = 0 -> 1
bpf_program__unload(p) // just zclose(prog->fd)
bpf_program__load(p) // -EBUSY, insn_array->used still 1
fails verification with "Failed to properly initialize insn array". The
map and its 'used' state live on in obj->jumptable_maps until
bpf_object__close().
Would libbpf need to recreate the jump-table maps (and re-patch insn->imm)
before each manual load, or should this limitation be rejected or
documented?
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index e7255bd4247f7..faea42f758479 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -392,7 +392,7 @@ LIBBPF_API const char *bpf_program__section_name(const struct bpf_program *prog)
> LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> -LIBBPF_API void bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> +LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
The return type of the public LIBBPF_API bpf_program__set_autoattach()
changes from void to int, but the symbol stays in its original
libbpf.map node. This is source compatible, but callers that ignore the
result cannot tell that the call was rejected for MANUAL programs, and the
ABI of an already exported symbol changes without a new version node.
Note: This is fixed later in the series by fca8106ae09c7 ("libbpf:
Version bpf_program__set_autoattach() ABI change").
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 03c3d2bd15bf4..8def5474885a8 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -463,6 +463,7 @@ LIBBPF_1.8.0 {
> bpf_program__attach_tracing_multi;
> bpf_program__clear_flags;
> bpf_program__clone;
> + bpf_program__load;
> bpf_program__load_strategy;
> bpf_program__set_load_strategy;
> btf__find_by_name_kind_own;
The commit message refers to bpf_program__load_manually() and
bpf_program__unload_manually():
> A manual-strategy program is loaded via bpf_program__load_manually()
> and unloaded via bpf_program__unload_manually(), gated on
> bpf_object__prepare() having already run (BTF loaded, maps created,
> relocations applied) rather than requiring a full bpf_object__load().
Neither function exists. The patch actually adds bpf_program__load()
(exported in LIBBPF_1.8.0) and changes the existing bpf_program__unload()
so that it only closes the fd for MANUAL programs. The message also leaves
out the user-visible change to bpf_program__set_autoattach(): it now
returns int and rejects MANUAL programs.
In the v1 review thread, Andrii Nakryiko expressed concern about adding
complexity to bpf_object internals:
> I don't want to complicate bpf_object internals with this third
> autoload state, as this creates tons of non-obvious gotchas that have
> to constantly be checked whenever any new feature is added to
> bpf_object (or even during refactorings).
From: https://lore.kernel.org/bpf/CAEf4BzYWe0KCzA4-qwAGp5n_ydJ0_zyLSO=Crr_vewFHzZ0t6Q@mail.gmail.com/
While the patch has been revised to use bpf_object__prepare() as Andrii
suggested, does the implementation still add significant complexity: new
state tracking (has_manual_progs), modified cleanup logic with force
parameter, complex state machine logic for load strategies, and deferred
BTF/fd_array cleanup?
Also, for subsequent submissions, Andrii requested the [PATCH bpf-next]
subject prefix in the v1 review.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/35934836796
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
2026-09-23 23:43 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
@ 2026-09-24 23:18 ` Andrii Nakryiko
2026-09-26 16:56 ` Andrey Grodzovsky
2 siblings, 1 reply; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-24 23:18 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> From: Slava Imameev <slava.imameev@crowdstrike.com>
>
> BPF programs designated as manually loaded can be loaded and
> attached independently after the initial bpf_object loading and
> attaching.
>
> These programs can also be reloaded and reattached multiple times,
> enabling more flexible management of a resident BPF program set.
>
> A key motivation for this feature is to reduce load times for
> utilities that include hundreds of BPF programs. When the selection
> of a resident BPF program set cannot be determined at the time of
> bpf_object loading and attaching, all BPF programs would otherwise
> need to be marked as autoload, leading to unnecessary overhead.
> This patch addresses that inefficiency.
>
> A manual-strategy program is loaded via bpf_program__load_manually()
> and unloaded via bpf_program__unload_manually(), gated on
forgot to update commit message?
> bpf_object__prepare() having already run (BTF loaded, maps created,
> relocations applied) rather than requiring a full bpf_object__load().
> Manual programs are skipped by the object's own autoload pass.
>
> Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
> Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> ---
> tools/lib/bpf/libbpf.c | 200 +++++++++++++++++++++++++++++++++------
> tools/lib/bpf/libbpf.h | 32 ++++++-
> tools/lib/bpf/libbpf.map | 1 +
> 3 files changed, 204 insertions(+), 29 deletions(-)
>
[...]
> - obj->btf_module_cnt = 0;
> - obj->btf_module_cap = 0;
> - obj->btf_modules_loaded = false;
> - zfree(&obj->btf_modules);
>
> - /* clean up vmlinux BTF */
> - btf__free(obj->btf_vmlinux);
> - obj->btf_vmlinux = NULL;
> + /*
> + * The btf_vmlinux data is needed for manually loaded programs,
> + * so defer freeing it in that case to the end of the object lifetime.
> + */
> + if (force || !obj->has_manual_progs) {
same condition as if above, why split into two ifs? and really better
to just exit early if !force || obj->has_manual_progs
> + btf__free(obj->btf_vmlinux);
> + obj->btf_vmlinux = NULL;
> + }
> }
>
[...]
> @@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
>
> int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
> {
> - if (prog->obj->state >= OBJ_LOADED)
> + struct bpf_object *obj = prog->obj;
> +
> + /*
> + * has_manual_progs is snapshotted once in bpf_object_prepare_progs()
> + * and never recomputed; once the object is prepared, no transition
> + * into or out of MANUAL may change which programs are MANUAL,
> + * regardless of direction. AUTO<->DISABLED transitions never touch
> + * MANUAL and keep the looser, pre-existing OBJ_LOADED gate.
> + */
> + if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
> + prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> + if (obj->state >= OBJ_PREPARED)
> + return libbpf_err(-EINVAL);
> + } else if (obj->state >= OBJ_LOADED) {
> + return libbpf_err(-EINVAL);
> + }
> +
> + if (strategy == prog->load_strategy)
> + return 0;
> +
> + switch (strategy) {
> + case BPF_PROG_LOAD_STRATEGY_DISABLED:
> + case BPF_PROG_LOAD_STRATEGY_AUTO:
> + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> + prog->autoattach = prog->saved_autoattach;
> + prog->load_strategy = strategy;
not completely follow the whole saved_autoattach thing, and the commit
message doesn't even mention this aspect... why saving and restoring
autoattach if we can leave it as is and just ignore if load strategy
is manual?
> + break;
> + case BPF_PROG_LOAD_STRATEGY_MANUAL:
> + /*
> + * Manually-loaded programs are not supported for gen_loader.
> + * This is because bpf_object_load_prog is not called for
> + * manually-loaded programs, so such programs are not visible
> + * to gen_loader. For this reason, prevent calling
> + * bpf_program__set_load_strategy(MANUAL) when gen_loader was
> + * used to generate a BPF object loader.
> + * A gen_loader implementation is being called for autoloaded
> + * programs and defines its own model for loading BPF programs.
> + * To pass a BPF program to gen_loader, set the program's load strategy
> + * to LD_AUTOLOAD.
you are fixing this comment in the next commit
and the comment doesn't have to be that verbose
pw-bot: cr
> + */
> + if (obj->gen_loader)
> + return libbpf_err(-EOPNOTSUPP);
> +
> + /*
> + * struct_ops programs are incompatible with manual loading:
> + * bpf_map_prepare_vdata() bakes each member's fd into kern_vdata
> + * automatically during bpf_object__load(), before a MANUAL member
> + * could ever be loaded, and nothing re-bakes it afterwards.
> + */
> + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
> + return libbpf_err(-EINVAL);
> +
> + if (prog_is_subprog(obj, prog))
> + return libbpf_err(-EINVAL);
> +
> + prog->saved_autoattach = prog->autoattach;
> + prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
> + prog->autoattach = false;
keep autoattach as is and orthogonal to load strategy? if MANUAL is
set, there is no auto-attach and user will have to manually attach,
no?
> + break;
> + default:
> return libbpf_err(-EINVAL);
> + }
>
> - prog->load_strategy = strategy;
> return 0;
> }
>
[...]
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix
2026-09-23 23:29 ` [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
@ 2026-09-24 23:18 ` Andrii Nakryiko
0 siblings, 0 replies; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-24 23:18 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> Add a SEC("!...") section-name prefix, letting a program declare
> itself manually-loaded in its source instead of requiring an
> imperative bpf_program__set_load_strategy() call, following the exact
> convention already used for SEC("?...").
>
> The prefix is recognized and stripped in bpf_object__init_prog(),
> before find_sec_def() ever runs, directly setting load_strategy to
> MANUAL and initializing autoattach/saved_autoattach - the same place
> and same style '?' already uses, and before BTF or sec_def exist.
>
> Assisted-by: Claude:claude-sonnet-5
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> ---
> tools/lib/bpf/libbpf.c | 16 +++++++++++++---
> 1 file changed, 13 insertions(+), 3 deletions(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 939f0d6378e3..d815e53e8295 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -889,19 +889,29 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
> prog->fd = -1;
> prog->exception_cb_idx = -1;
>
> - /* libbpf's convention for SEC("?abc...") is that it's just like
> + /*
> + * libbpf's convention for SEC("?abc...") is that it's just like
> * SEC("abc...") but the corresponding bpf_program starts out with
> * autoload set to false.
> + *
> + * Similarly, SEC("!abc...") marks the program for manual loading:
> + * it is skipped by the bulk auto-load pass and must be explicitly
> + * loaded later via bpf_program__load().
> */
> if (sec_name[0] == '?') {
> prog->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
> /* from now on forget there was ? in section name */
> sec_name++;
> + } else if (sec_name[0] == '!') {
> + prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
> + /* from now on forget there was ! in section name */
> + sec_name++;
> } else {
> prog->load_strategy = BPF_PROG_LOAD_STRATEGY_AUTO;
> }
>
> - prog->autoattach = true;
> + prog->saved_autoattach = true;
> + prog->autoattach = prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL;
>
> /* inherit object's log_level */
> prog->log_level = obj->log_level;
> @@ -15469,7 +15479,7 @@ int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_
> * A gen_loader implementation is being called for autoloaded
> * programs and defines its own model for loading BPF programs.
> * To pass a BPF program to gen_loader, set the program's load strategy
> - * to LD_AUTOLOAD.
> + * to BPF_PROG_LOAD_STRATEGY_AUTO.
you want other humans to review your AI slop, but you didn't care to
even skim the diff yourself, nice
> */
> if (obj->gen_loader)
> return libbpf_err(-EOPNOTSUPP);
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs
2026-09-23 23:29 ` [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs Andrey Grodzovsky
@ 2026-09-24 23:18 ` Andrii Nakryiko
2026-09-26 17:49 ` Andrey Grodzovsky
0 siblings, 1 reply; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-24 23:18 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> bpf_program__set_load_strategy()'s MANUAL case rejects setting MANUAL
> strategy while a gen_loader is already attached, but a program marked
> MANUAL declaratively (SEC("!...")) gets that strategy during
> bpf_object__open(), before bpf_object__gen_loader() can ever be
> called, so the existing guard can never observe it.
>
> Left unchecked, bpf_object_load_progs() skips such programs, so
> gen->nr_progs undercounts relative to the object's real program
> count. bpf_gen__finish() only rejects the opposite mismatch direction
> (nr_progs < gen->nr_progs), so this passes silently, and every
> generated skeleton program slot after the manual one ends up wired to
> the wrong prog_fd.
>
> Catch it at the one point guaranteed to run after any MANUAL marking
> has already happened: reject in bpf_object__gen_loader() itself if any
> program already has load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL.
>
> Assisted-by: Claude:claude-sonnet-5
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> ---
> tools/lib/bpf/libbpf.c | 34 ++++++++++++++++++++++++----------
> 1 file changed, 24 insertions(+), 10 deletions(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index d815e53e8295..85e1d9c775ab 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -9859,11 +9859,31 @@ int bpf_object__set_kversion(struct bpf_object *obj, __u32 kern_version)
> int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts)
> {
> struct bpf_gen *gen;
> + size_t i;
>
> if (!opts)
> return libbpf_err(-EFAULT);
> if (!OPTS_VALID(opts, gen_loader_opts))
> return libbpf_err(-EINVAL);
gen_loader shouldn't be set after prepare step, we should just reject
that operation, it's a misuse of the API
> +
> + /*
> + * Manually-loaded programs are not visible to gen_loader (see
> + * bpf_program__set_load_strategy()'s MANUAL case), and marking a
> + * program MANUAL happens during bpf_object__open(), before this
> + * function can ever run, so that guard can never catch it here.
> + * Reject any pre-existing MANUAL program now, since this is the
> + * earliest point where both are known.
> + */
> + for (i = 0; i < obj->nr_programs; i++) {
> + struct bpf_program *prog = &obj->programs[i];
> +
> + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> + pr_warn("prog '%s': gen_loader does not support manually-loaded programs\n",
> + prog->name);
> + return libbpf_err(-EOPNOTSUPP);
> + }
> + }
> +
and there is no need to check this on setting gen_loader, during
bpf_object_prepare all this stuff (gen_loader, MANUAL settings) should
be finalized and never changed again. that's when we check all these
conditions
> gen = calloc(1, sizeof(*gen));
> if (!gen)
> return libbpf_err(-ENOMEM);
> @@ -15470,16 +15490,10 @@ int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_
> break;
> case BPF_PROG_LOAD_STRATEGY_MANUAL:
> /*
> - * Manually-loaded programs are not supported for gen_loader.
> - * This is because bpf_object_load_prog is not called for
> - * manually-loaded programs, so such programs are not visible
> - * to gen_loader. For this reason, prevent calling
> - * bpf_program__set_load_strategy(MANUAL) when gen_loader was
> - * used to generate a BPF object loader.
> - * A gen_loader implementation is being called for autoloaded
> - * programs and defines its own model for loading BPF programs.
> - * To pass a BPF program to gen_loader, set the program's load strategy
> - * to BPF_PROG_LOAD_STRATEGY_AUTO.
> + * Manually-loaded programs are not visible to gen_loader,
> + * since bpf_object__load_progs() skips them during the bulk
> + * load pass; see bpf_object__gen_loader()'s own guard for
> + * the full explanation.
> */
yeah, wow, nice way to score human reviewer's points, more sloppy slop please
> if (obj->gen_loader)
> return libbpf_err(-EOPNOTSUPP);
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
2026-09-23 23:41 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
@ 2026-09-24 23:19 ` Andrii Nakryiko
2026-09-26 18:04 ` Andrey Grodzovsky
2 siblings, 1 reply; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-24 23:19 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> bpf_program__set_autoattach()'s return type changed from void to int to
> let callers observe the new -EINVAL rejection for MANUAL-strategy
> programs, but the symbol stayed under its original LIBBPF_1.0.0 node in
> libbpf.map. Binaries already linked against the old void-returning ABI
> recorded a request for exactly that symbol@version pair at link time;
> without a version bump they would silently start hitting the new
> rejection behavior underneath them after a libbpf upgrade.
>
> Split the symbol via ELF symbol versioning:
>
> - bpf_program__set_autoattach_deprecated() keeps the original
> unconditional behavior, bound via COMPAT_VERSION() to the existing
> LIBBPF_1.0.0 node.
> - bpf_program__set_autoattach_v1_8_0() carries the new MANUAL-rejecting
> behavior, bound via DEFAULT_VERSION() to the new LIBBPF_1.8.0 node.
>
> Old binaries keep resolving bpf_program__set_autoattach() to the old
> behavior at runtime; anything linked against the current headers/map
> gets the new int-returning, MANUAL-rejecting behavior. Also adds the
> missing __LIBBPF_MARK_DEPRECATED_1_8 gate to libbpf_common.h (only the
> 1.0 gate existed) so LIBBPF_DEPRECATED_SINCE(1, 8, ...) can mark the
> deprecated variant.
>
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> ---
> tools/lib/bpf/libbpf.c | 9 ++++++++-
> tools/lib/bpf/libbpf.h | 6 ++++++
> tools/lib/bpf/libbpf.map | 2 ++
> tools/lib/bpf/libbpf_common.h | 6 ++++++
> 4 files changed, 22 insertions(+), 1 deletion(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 85e1d9c775ab..224fb0247a39 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -9974,7 +9974,14 @@ bool bpf_program__autoattach(const struct bpf_program *prog)
> return prog->autoattach;
> }
>
> -int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
> +COMPAT_VERSION(bpf_program__set_autoattach_deprecated, bpf_program__set_autoattach, LIBBPF_1.0.0)
no compat stuff, it should be backwards compatible to change return
from void to int
> +void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach)
> +{
> + prog->autoattach = autoattach;
> +}
> +
> +DEFAULT_VERSION(bpf_program__set_autoattach_v1_8_0, bpf_program__set_autoattach, LIBBPF_1.8.0)
> +int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach)
> {
> if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> return libbpf_err(-EINVAL);
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index faea42f75847..d172c137cc37 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -393,6 +393,12 @@ LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> +/* this "specialization" should go away once the deprecation window for
> + * bpf_program__set_autoattach_deprecated() closes
> + */
> +LIBBPF_API int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach);
> +LIBBPF_DEPRECATED_SINCE(1, 8, "use int-returning bpf_program__set_autoattach() instead")
> +LIBBPF_API void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach);
>
> struct bpf_insn;
>
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 8def5474885a..999e791d9887 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -465,6 +465,8 @@ LIBBPF_1.8.0 {
> bpf_program__clone;
> bpf_program__load;
> bpf_program__load_strategy;
> + bpf_program__set_autoattach;
> + bpf_program__set_autoattach_deprecated;
> bpf_program__set_load_strategy;
> btf__find_by_name_kind_own;
> btf__new_empty_opts;
> diff --git a/tools/lib/bpf/libbpf_common.h b/tools/lib/bpf/libbpf_common.h
> index 8fe248e14eb6..12ad4f5f558f 100644
> --- a/tools/lib/bpf/libbpf_common.h
> +++ b/tools/lib/bpf/libbpf_common.h
> @@ -36,6 +36,12 @@
> #define __LIBBPF_MARK_DEPRECATED_1_0(X)
> #endif
>
> +#if __LIBBPF_CURRENT_VERSION_GEQ(1, 8)
> +#define __LIBBPF_MARK_DEPRECATED_1_8(X) X
> +#else
> +#define __LIBBPF_MARK_DEPRECATED_1_8(X)
> +#endif
> +
> /* This set of internal macros allows to do "function overloading" based on
> * number of arguments provided by used in backwards-compatible way during the
> * transition to libbpf 1.0
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-24 23:18 ` Andrii Nakryiko
@ 2026-09-26 16:56 ` Andrey Grodzovsky
0 siblings, 0 replies; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-26 16:56 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Thu, Sep 24, 2026 at 7:18 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
> <andrey.grodzovsky@crowdstrike.com> wrote:
> >
> > From: Slava Imameev <slava.imameev@crowdstrike.com>
> >
> > BPF programs designated as manually loaded can be loaded and
> > attached independently after the initial bpf_object loading and
> > attaching.
> >
> > These programs can also be reloaded and reattached multiple times,
> > enabling more flexible management of a resident BPF program set.
> >
> > A key motivation for this feature is to reduce load times for
> > utilities that include hundreds of BPF programs. When the selection
> > of a resident BPF program set cannot be determined at the time of
> > bpf_object loading and attaching, all BPF programs would otherwise
> > need to be marked as autoload, leading to unnecessary overhead.
> > This patch addresses that inefficiency.
> >
> > A manual-strategy program is loaded via bpf_program__load_manually()
> > and unloaded via bpf_program__unload_manually(), gated on
>
> forgot to update commit message?
Yes, I will fix it.
>
> > bpf_object__prepare() having already run (BTF loaded, maps created,
> > relocations applied) rather than requiring a full bpf_object__load().
> > Manual programs are skipped by the object's own autoload pass.
> >
> > Signed-off-by: Slava Imameev <slava.imameev@crowdstrike.com>
> > Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> > ---
> > tools/lib/bpf/libbpf.c | 200 +++++++++++++++++++++++++++++++++------
> > tools/lib/bpf/libbpf.h | 32 ++++++-
> > tools/lib/bpf/libbpf.map | 1 +
> > 3 files changed, 204 insertions(+), 29 deletions(-)
> >
>
> [...]
>
> > - obj->btf_module_cnt = 0;
> > - obj->btf_module_cap = 0;
> > - obj->btf_modules_loaded = false;
> > - zfree(&obj->btf_modules);
> >
> > - /* clean up vmlinux BTF */
> > - btf__free(obj->btf_vmlinux);
> > - obj->btf_vmlinux = NULL;
> > + /*
> > + * The btf_vmlinux data is needed for manually loaded programs,
> > + * so defer freeing it in that case to the end of the object lifetime.
> > + */
> > + if (force || !obj->has_manual_progs) {
>
>
> same condition as if above, why split into two ifs? and really better
> to just exit early if !force || obj->has_manual_progs
Will do.
>
>
> > + btf__free(obj->btf_vmlinux);
> > + obj->btf_vmlinux = NULL;
> > + }
> > }
> >
>
> [...]
>
> > @@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
> >
> > int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
> > {
> > - if (prog->obj->state >= OBJ_LOADED)
> > + struct bpf_object *obj = prog->obj;
> > +
> > + /*
> > + * has_manual_progs is snapshotted once in bpf_object_prepare_progs()
> > + * and never recomputed; once the object is prepared, no transition
> > + * into or out of MANUAL may change which programs are MANUAL,
> > + * regardless of direction. AUTO<->DISABLED transitions never touch
> > + * MANUAL and keep the looser, pre-existing OBJ_LOADED gate.
> > + */
> > + if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
> > + prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> > + if (obj->state >= OBJ_PREPARED)
> > + return libbpf_err(-EINVAL);
> > + } else if (obj->state >= OBJ_LOADED) {
> > + return libbpf_err(-EINVAL);
> > + }
> > +
> > + if (strategy == prog->load_strategy)
> > + return 0;
> > +
> > + switch (strategy) {
> > + case BPF_PROG_LOAD_STRATEGY_DISABLED:
> > + case BPF_PROG_LOAD_STRATEGY_AUTO:
> > + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> > + prog->autoattach = prog->saved_autoattach;
> > + prog->load_strategy = strategy;
>
> not completely follow the whole saved_autoattach thing, and the commit
> message doesn't even mention this aspect... why saving and restoring
> autoattach if we can leave it as is and just ignore if load strategy
> is manual?
I will update the commit message. Please see the reasoning behind this
in the next reply below.
> > + break;
> > + case BPF_PROG_LOAD_STRATEGY_MANUAL:
> > + /*
> > + * Manually-loaded programs are not supported for gen_loader.
> > + * This is because bpf_object_load_prog is not called for
> > + * manually-loaded programs, so such programs are not visible
> > + * to gen_loader. For this reason, prevent calling
> > + * bpf_program__set_load_strategy(MANUAL) when gen_loader was
> > + * used to generate a BPF object loader.
> > + * A gen_loader implementation is being called for autoloaded
> > + * programs and defines its own model for loading BPF programs.
> > + * To pass a BPF program to gen_loader, set the program's load strategy
> > + * to LD_AUTOLOAD.
>
> you are fixing this comment in the next commit
>
> and the comment doesn't have to be that verbose
>
> pw-bot: cr
>
> > + */
> > + if (obj->gen_loader)
> > + return libbpf_err(-EOPNOTSUPP);
> > +
> > + /*
> > + * struct_ops programs are incompatible with manual loading:
> > + * bpf_map_prepare_vdata() bakes each member's fd into kern_vdata
> > + * automatically during bpf_object__load(), before a MANUAL member
> > + * could ever be loaded, and nothing re-bakes it afterwards.
> > + */
> > + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
> > + return libbpf_err(-EINVAL);
> > +
> > + if (prog_is_subprog(obj, prog))
> > + return libbpf_err(-EINVAL);
> > +
> > + prog->saved_autoattach = prog->autoattach;
> > + prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
> > + prog->autoattach = false;
>
> keep autoattach as is and orthogonal to load strategy? if MANUAL is
> set, there is no auto-attach and user will have to manually attach,
> no?
The idea for coupling between autoattach and prog->load_strategy here
is to avoid situation where bpf_program__autoattach will report false
information, if we truely decouple those two and make them orthogonal
one to another we can end up wth bpf_program__autoattach() reproting
true for manual programs which is factually wrong since those programs
(and DISBLED too) skip autoattach in bpf_object__attach_skeleton().
Now, since coupled them, the CI BOT review in V2 i belive also flagged
the need to restore orignial values of autoattach when we switch
prog->load_strategy back and forth and that th reason for all the
saved_autoattach dance you asked above.
Having said that, and thinking more, this is the behavior in the
baseline code already: autoload and autoattach are truly orthogonal
and behave the same. You can have autoload == false program (DISABLED
in current terminnology) still reporting bpf_program__autoattach() ==
true Therefore, by decoupling we simlpy adhere to exsiting convetion.
I will drop this messy logic.
Andrey
> > + break;
> > + default:
> > return libbpf_err(-EINVAL);
> > + }
> >
> > - prog->load_strategy = strategy;
> > return 0;
> > }
> >
>
> [...]
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs
2026-09-24 23:18 ` Andrii Nakryiko
@ 2026-09-26 17:49 ` Andrey Grodzovsky
0 siblings, 0 replies; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-26 17:49 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Thu, Sep 24, 2026 at 7:19 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
> <andrey.grodzovsky@crowdstrike.com> wrote:
> >
> > bpf_program__set_load_strategy()'s MANUAL case rejects setting MANUAL
> > strategy while a gen_loader is already attached, but a program marked
> > MANUAL declaratively (SEC("!...")) gets that strategy during
> > bpf_object__open(), before bpf_object__gen_loader() can ever be
> > called, so the existing guard can never observe it.
> >
> > Left unchecked, bpf_object_load_progs() skips such programs, so
> > gen->nr_progs undercounts relative to the object's real program
> > count. bpf_gen__finish() only rejects the opposite mismatch direction
> > (nr_progs < gen->nr_progs), so this passes silently, and every
> > generated skeleton program slot after the manual one ends up wired to
> > the wrong prog_fd.
> >
> > Catch it at the one point guaranteed to run after any MANUAL marking
> > has already happened: reject in bpf_object__gen_loader() itself if any
> > program already has load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL.
> >
> > Assisted-by: Claude:claude-sonnet-5
> > Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> > Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> > ---
> > tools/lib/bpf/libbpf.c | 34 ++++++++++++++++++++++++----------
> > 1 file changed, 24 insertions(+), 10 deletions(-)
> >
> > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > index d815e53e8295..85e1d9c775ab 100644
> > --- a/tools/lib/bpf/libbpf.c
> > +++ b/tools/lib/bpf/libbpf.c
> > @@ -9859,11 +9859,31 @@ int bpf_object__set_kversion(struct bpf_object *obj, __u32 kern_version)
> > int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts)
> > {
> > struct bpf_gen *gen;
> > + size_t i;
> >
> > if (!opts)
> > return libbpf_err(-EFAULT);
> > if (!OPTS_VALID(opts, gen_loader_opts))
> > return libbpf_err(-EINVAL);
>
> gen_loader shouldn't be set after prepare step, we should just reject
> that operation, it's a misuse of the API
I will capture this in a separate commit in the next patchset iteration.
>
> > +
> > + /*
> > + * Manually-loaded programs are not visible to gen_loader (see
> > + * bpf_program__set_load_strategy()'s MANUAL case), and marking a
> > + * program MANUAL happens during bpf_object__open(), before this
> > + * function can ever run, so that guard can never catch it here.
> > + * Reject any pre-existing MANUAL program now, since this is the
> > + * earliest point where both are known.
> > + */
> > + for (i = 0; i < obj->nr_programs; i++) {
> > + struct bpf_program *prog = &obj->programs[i];
> > +
> > + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> > + pr_warn("prog '%s': gen_loader does not support manually-loaded programs\n",
> > + prog->name);
> > + return libbpf_err(-EOPNOTSUPP);
> > + }
> > + }
> > +
>
> and there is no need to check this on setting gen_loader, during
> bpf_object_prepare all this stuff (gen_loader, MANUAL settings) should
> be finalized and never changed again. that's when we check all these
> conditions
Will relocate to bpf_object_prepare and also, I think I can drop the
hunk bellow in bpf_program__set_load_strategy returning EOPNOTSUPP on
gen_loader. It's not needed since we cannot set load strategy after
prepare step and setting it before will encounter the check we will
move to bpf_object_prepare.
>
>
> > gen = calloc(1, sizeof(*gen));
> > if (!gen)
> > return libbpf_err(-ENOMEM);
> > @@ -15470,16 +15490,10 @@ int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_
> > break;
> > case BPF_PROG_LOAD_STRATEGY_MANUAL:
> > /*
> > - * Manually-loaded programs are not supported for gen_loader.
> > - * This is because bpf_object_load_prog is not called for
> > - * manually-loaded programs, so such programs are not visible
> > - * to gen_loader. For this reason, prevent calling
> > - * bpf_program__set_load_strategy(MANUAL) when gen_loader was
> > - * used to generate a BPF object loader.
> > - * A gen_loader implementation is being called for autoloaded
> > - * programs and defines its own model for loading BPF programs.
> > - * To pass a BPF program to gen_loader, set the program's load strategy
> > - * to BPF_PROG_LOAD_STRATEGY_AUTO.
> > + * Manually-loaded programs are not visible to gen_loader,
> > + * since bpf_object__load_progs() skips them during the bulk
> > + * load pass; see bpf_object__gen_loader()'s own guard for
> > + * the full explanation.
> > */
>
> yeah, wow, nice way to score human reviewer's points, more sloppy slop please
I want to apologize here in one place for all the instances of AI
sloppiness you justifiably flagged in this section across patches 2, 3
and 4. I take this seriously and this will not happen again.
Andrey
>
>
> > if (obj->gen_loader)
> > return libbpf_err(-EOPNOTSUPP);
> > --
> > 2.34.1
> >
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-24 23:19 ` Andrii Nakryiko
@ 2026-09-26 18:04 ` Andrey Grodzovsky
2026-09-30 15:50 ` Andrii Nakryiko
0 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-26 18:04 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Thu, Sep 24, 2026 at 7:19 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
> <andrey.grodzovsky@crowdstrike.com> wrote:
> >
> > bpf_program__set_autoattach()'s return type changed from void to int to
> > let callers observe the new -EINVAL rejection for MANUAL-strategy
> > programs, but the symbol stayed under its original LIBBPF_1.0.0 node in
> > libbpf.map. Binaries already linked against the old void-returning ABI
> > recorded a request for exactly that symbol@version pair at link time;
> > without a version bump they would silently start hitting the new
> > rejection behavior underneath them after a libbpf upgrade.
> >
> > Split the symbol via ELF symbol versioning:
> >
> > - bpf_program__set_autoattach_deprecated() keeps the original
> > unconditional behavior, bound via COMPAT_VERSION() to the existing
> > LIBBPF_1.0.0 node.
> > - bpf_program__set_autoattach_v1_8_0() carries the new MANUAL-rejecting
> > behavior, bound via DEFAULT_VERSION() to the new LIBBPF_1.8.0 node.
> >
> > Old binaries keep resolving bpf_program__set_autoattach() to the old
> > behavior at runtime; anything linked against the current headers/map
> > gets the new int-returning, MANUAL-rejecting behavior. Also adds the
> > missing __LIBBPF_MARK_DEPRECATED_1_8 gate to libbpf_common.h (only the
> > 1.0 gate existed) so LIBBPF_DEPRECATED_SINCE(1, 8, ...) can mark the
> > deprecated variant.
> >
> > Assisted-by: Claude:claude-sonnet-5
> > Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> > ---
> > tools/lib/bpf/libbpf.c | 9 ++++++++-
> > tools/lib/bpf/libbpf.h | 6 ++++++
> > tools/lib/bpf/libbpf.map | 2 ++
> > tools/lib/bpf/libbpf_common.h | 6 ++++++
> > 4 files changed, 22 insertions(+), 1 deletion(-)
> >
> > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > index 85e1d9c775ab..224fb0247a39 100644
> > --- a/tools/lib/bpf/libbpf.c
> > +++ b/tools/lib/bpf/libbpf.c
> > @@ -9974,7 +9974,14 @@ bool bpf_program__autoattach(const struct bpf_program *prog)
> > return prog->autoattach;
> > }
> >
> > -int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
> > +COMPAT_VERSION(bpf_program__set_autoattach_deprecated, bpf_program__set_autoattach, LIBBPF_1.0.0)
>
> no compat stuff, it should be backwards compatible to change return
> from void to int
I was more worried about legacy binaries dynamiclly linking against
this risking to somehow getting silently rejected by load_strategy ==
BPF_PROG_LOAD_STRATEGY_MANUAL check. But yea, practically this is
impossible so I will drop this.
Andrey
>
>
> > +void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach)
> > +{
> > + prog->autoattach = autoattach;
> > +}
> > +
> > +DEFAULT_VERSION(bpf_program__set_autoattach_v1_8_0, bpf_program__set_autoattach, LIBBPF_1.8.0)
> > +int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach)
> > {
> > if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> > return libbpf_err(-EINVAL);
> > diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> > index faea42f75847..d172c137cc37 100644
> > --- a/tools/lib/bpf/libbpf.h
> > +++ b/tools/lib/bpf/libbpf.h
> > @@ -393,6 +393,12 @@ LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> > LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> > LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> > LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> > +/* this "specialization" should go away once the deprecation window for
> > + * bpf_program__set_autoattach_deprecated() closes
> > + */
> > +LIBBPF_API int bpf_program__set_autoattach_v1_8_0(struct bpf_program *prog, bool autoattach);
> > +LIBBPF_DEPRECATED_SINCE(1, 8, "use int-returning bpf_program__set_autoattach() instead")
> > +LIBBPF_API void bpf_program__set_autoattach_deprecated(struct bpf_program *prog, bool autoattach);
> >
> > struct bpf_insn;
> >
> > diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> > index 8def5474885a..999e791d9887 100644
> > --- a/tools/lib/bpf/libbpf.map
> > +++ b/tools/lib/bpf/libbpf.map
> > @@ -465,6 +465,8 @@ LIBBPF_1.8.0 {
> > bpf_program__clone;
> > bpf_program__load;
> > bpf_program__load_strategy;
> > + bpf_program__set_autoattach;
> > + bpf_program__set_autoattach_deprecated;
> > bpf_program__set_load_strategy;
> > btf__find_by_name_kind_own;
> > btf__new_empty_opts;
> > diff --git a/tools/lib/bpf/libbpf_common.h b/tools/lib/bpf/libbpf_common.h
> > index 8fe248e14eb6..12ad4f5f558f 100644
> > --- a/tools/lib/bpf/libbpf_common.h
> > +++ b/tools/lib/bpf/libbpf_common.h
> > @@ -36,6 +36,12 @@
> > #define __LIBBPF_MARK_DEPRECATED_1_0(X)
> > #endif
> >
> > +#if __LIBBPF_CURRENT_VERSION_GEQ(1, 8)
> > +#define __LIBBPF_MARK_DEPRECATED_1_8(X) X
> > +#else
> > +#define __LIBBPF_MARK_DEPRECATED_1_8(X)
> > +#endif
> > +
> > /* This set of internal macros allows to do "function overloading" based on
> > * number of arguments provided by used in backwards-compatible way during the
> > * transition to libbpf 1.0
> > --
> > 2.34.1
> >
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-24 0:32 ` bot+bpf-ci
@ 2026-09-28 1:40 ` Andrey Grodzovsky
2026-09-30 15:50 ` Andrii Nakryiko
0 siblings, 1 reply; 27+ messages in thread
From: Andrey Grodzovsky @ 2026-09-28 1:40 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
On Wed, Sep 23, 2026 at 8:32 PM <bot+bpf-ci@kernel.org> wrote:
>
> > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > index a3085847cb97f..939f0d6378e38 100644
> > --- a/tools/lib/bpf/libbpf.c
> > +++ b/tools/lib/bpf/libbpf.c
>
> [ ... ]
>
> > @@ -15375,10 +15431,69 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
> >
> > int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
> > {
> > - if (prog->obj->state >= OBJ_LOADED)
> > + struct bpf_object *obj = prog->obj;
> > +
> > + if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
> > + prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> > + if (obj->state >= OBJ_PREPARED)
> > + return libbpf_err(-EINVAL);
> > + } else if (obj->state >= OBJ_LOADED) {
> > return libbpf_err(-EINVAL);
> > + }
> > +
> > + if (strategy == prog->load_strategy)
> > + return 0;
> > +
> > + switch (strategy) {
> > + case BPF_PROG_LOAD_STRATEGY_DISABLED:
> > + case BPF_PROG_LOAD_STRATEGY_AUTO:
> > + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> > + prog->autoattach = prog->saved_autoattach;
> > + prog->load_strategy = strategy;
> > + break;
> > + case BPF_PROG_LOAD_STRATEGY_MANUAL:
> > + if (obj->gen_loader)
> > + return libbpf_err(-EOPNOTSUPP);
>
> Can this ordering be reversed? When bpf_program__set_load_strategy(MANUAL)
> is called before bpf_object__gen_loader(), this check passes because
> obj->gen_loader is still NULL. Then when bpf_object__gen_loader() runs
> later, it doesn't check whether any MANUAL programs already exist.
>
> Looking at bpf_object__load_progs():
>
> for (i = 0; i < obj->nr_programs; i++) {
> prog = &obj->programs[i];
> if (!prog->autoload || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) {
> pr_debug("prog '%s': skipped auto-loading\n", prog->name);
> continue;
> }
>
> MANUAL programs are skipped, so gen->nr_progs ends up below
> obj->nr_programs. Then bpf_gen__finish() only rejects nr_progs <
> gen->nr_progs, so the generated loader silently omits these programs.
>
> If someone later calls bpf_program__load() on the MANUAL program, it goes
> through bpf_object_load_prog()'s gen_loader path:
>
> if (obj->gen_loader) {
> bpf_gen__prog_load(obj->gen_loader, ...);
> *prog_fd = -1;
> return 0;
> }
>
> which returns success with prog->fd = -1 and appends to gen buffers after
> bpf_gen__finish() has already published opts->insns/opts->data. Since
> gen_loader grows buffers with realloc(), this can leave those pointers
> stale.
>
> Note: This is fixed later in the series by 549383b4eb184 ("libbpf: Reject
> gen_loader for objects with already-manual programs").
>
> > + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS)
> > + return libbpf_err(-EINVAL);
> > +
> > + if (prog_is_subprog(obj, prog))
> > + return libbpf_err(-EINVAL);
> > +
> > + prog->saved_autoattach = prog->autoattach;
> > + prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
> > + prog->autoattach = false;
> > + break;
> > + default:
> > + return libbpf_err(-EINVAL);
> > + }
> > +
> > return 0;
> > }
> >
> > @@ -15386,3 +15501,34 @@ enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program
> > {
> > return prog->load_strategy;
> > }
> > +
> > +int
> > +bpf_program__load(struct bpf_program *prog)
> > +{
> > + int err;
> > + struct bpf_object *obj = prog->obj;
> > +
> > + if (obj->state < OBF_PREPARED)
> > + return libbpf_err(-EINVAL);
> > +
> > + if (prog_is_subprog(obj, prog) || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
> > + return libbpf_err(-EINVAL);
> > +
> > + if (prog->fd >= 0)
> > + return libbpf_err(-EBUSY);
> > +
> > + err = bpf_object_load_prog(obj, prog, prog->insns, prog->insns_cnt,
> > + obj->license, obj->kern_version, &prog->fd);
> > + if (err) {
> > + pr_warn("prog '%s': failed to load: %s\n", prog->name, errstr(err));
> > + return libbpf_err(err);
> > + }
> > +
> > + return 0;
> > +}
>
> The commit message says MANUAL programs "can also be reloaded and
> reattached multiple times", and the bpf_program__unload() doc now says
> MANUAL programs keep their data "so it can be reloaded later via
> bpf_program__load()". Does this work for programs that use gotox jump
> tables?
>
> During bpf_object__prepare(), create_jt_map() creates a
> BPF_MAP_TYPE_INSN_ARRAY map per (prog, sym_off) and caches it in
> obj->jumptable_maps. RELO_INSN_ARRAY then writes the map fd directly into
> prog->insns (insn->imm = map_fd). Each bpf_program__load() call
> resubmits the same prog->insns with the same map fd.
>
> On the kernel side, bpf_insn_array_init() runs:
>
> if (atomic_xchg(&insn_array->used, 1))
> return -EBUSY;
>
> with the message "There can be only one program using the map". That flag
> is cleared only by bpf_insn_array_release(), which the verifier calls on
> the verification error path, not when a successfully loaded program is
> freed.
>
> So this sequence:
>
> bpf_object__prepare()
> bpf_program__load(p) // ok, insn_array->used = 0 -> 1
> bpf_program__unload(p) // just zclose(prog->fd)
> bpf_program__load(p) // -EBUSY, insn_array->used still 1
>
> fails verification with "Failed to properly initialize insn array". The
> map and its 'used' state live on in obj->jumptable_maps until
> bpf_object__close().
>
> Would libbpf need to recreate the jump-table maps (and re-patch insn->imm)
> before each manual load, or should this limitation be rejected or
> documented?
This is indeed an issue, but not specific to our manual loading progs.
I was able to reproduce a pretty similar behavior using the exsiting
bpf_program__clone() interface by writing a test for the recently
added "Indirect call of bpf subprog" feature by Alexei [1] - I
prepared and cloned a program which calls function by a func pointer
from a table, first clone worked fine, while the second generated the
failure + kernel log below. Another test also verified that
subsequent bpf_object__load() after the first clone also failed with
same error.
I wasn't able to run the gotox scenario since my LLVM didn't support gotox.
dynamicload_verify_callx_clone_twice:PASS:bpf_object__prepare 0 nsec
dynamicload_verify_callx_clone_twice:PASS:callx_clone_first 0 nsec
callx_clone_second: clone failed: -16
-- BEGIN KERNEL LOG --
map 'test_dyn.rodata' has addresses of functions of another program
processed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0
peak_states 0 mark_read 0
-- END KERNEL LOG --
So I am not sure we should look for and reject all those types of
cases where the first loaded program captures a map exclusively, given
that the first load will work normally. Perhaps document it....
Andrey
[1] - https://lore.kernel.org/bpf/20260922011323.1298619-11-alexei.starovoitov@gmail.com/
>
> > diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> > index e7255bd4247f7..faea42f758479 100644
> > --- a/tools/lib/bpf/libbpf.h
> > +++ b/tools/lib/bpf/libbpf.h
> > @@ -392,7 +392,7 @@ LIBBPF_API const char *bpf_program__section_name(const struct bpf_program *prog)
> > LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> > LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> > LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> > -LIBBPF_API void bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> > +LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
>
> The return type of the public LIBBPF_API bpf_program__set_autoattach()
> changes from void to int, but the symbol stays in its original
> https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$ node. This is source compatible, but callers that ignore the
> result cannot tell that the call was rejected for MANUAL programs, and the
> ABI of an already exported symbol changes without a new version node.
>
> Note: This is fixed later in the series by fca8106ae09c7 ("libbpf:
> Version bpf_program__set_autoattach() ABI change").
>
> > diff --git a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > index 03c3d2bd15bf4..8def5474885a8 100644
> > --- a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > +++ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > @@ -463,6 +463,7 @@ LIBBPF_1.8.0 {
> > bpf_program__attach_tracing_multi;
> > bpf_program__clear_flags;
> > bpf_program__clone;
> > + bpf_program__load;
> > bpf_program__load_strategy;
> > bpf_program__set_load_strategy;
> > btf__find_by_name_kind_own;
>
> The commit message refers to bpf_program__load_manually() and
> bpf_program__unload_manually():
>
> > A manual-strategy program is loaded via bpf_program__load_manually()
> > and unloaded via bpf_program__unload_manually(), gated on
> > bpf_object__prepare() having already run (BTF loaded, maps created,
> > relocations applied) rather than requiring a full bpf_object__load().
>
> Neither function exists. The patch actually adds bpf_program__load()
> (exported in LIBBPF_1.8.0) and changes the existing bpf_program__unload()
> so that it only closes the fd for MANUAL programs. The message also leaves
> out the user-visible change to bpf_program__set_autoattach(): it now
> returns int and rejects MANUAL programs.
>
> In the v1 review thread, Andrii Nakryiko expressed concern about adding
> complexity to bpf_object internals:
>
> > I don't want to complicate bpf_object internals with this third
> > autoload state, as this creates tons of non-obvious gotchas that have
> > to constantly be checked whenever any new feature is added to
> > bpf_object (or even during refactorings).
>
> From: https://urldefense.com/v3/__https://lore.kernel.org/bpf/CAEf4BzYWe0KCzA4-qwAGp5n_ydJ0_zyLSO=Crr_vewFHzZ0t6Q@mail.gmail.com/__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kHoTp1qY$
>
> While the patch has been revised to use bpf_object__prepare() as Andrii
> suggested, does the implementation still add significant complexity: new
> state tracking (has_manual_progs), modified cleanup logic with force
> parameter, complex state machine logic for load strategies, and deferred
> BTF/fd_array cleanup?
>
> Also, for subsequent submissions, Andrii requested the [PATCH bpf-next]
> subject prefix in the v1 review.
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://urldefense.com/v3/__https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kIlMmsxY$
>
> CI run summary: https://urldefense.com/v3/__https://github.com/kernel-patches/bpf/actions/runs/35934836796__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kMF5-RGT$
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching
2026-09-28 1:40 ` Andrey Grodzovsky
@ 2026-09-30 15:50 ` Andrii Nakryiko
0 siblings, 0 replies; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-30 15:50 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bot+bpf-ci, bpf, andrii, ast, martin.kelly, slava.imameev,
linux-open-source, daniel, martin.lau, eddyz87, yonghong.song,
mason, ihor.solodrai
On Mon, Sep 28, 2026 at 2:41 AM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> On Wed, Sep 23, 2026 at 8:32 PM <bot+bpf-ci@kernel.org> wrote:
> >
> > > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > > index a3085847cb97f..939f0d6378e38 100644
> > > --- a/tools/lib/bpf/libbpf.c
> > > +++ b/tools/lib/bpf/libbpf.c
> >
> > [ ... ]
> >
[...]
> >
> > So this sequence:
> >
> > bpf_object__prepare()
> > bpf_program__load(p) // ok, insn_array->used = 0 -> 1
> > bpf_program__unload(p) // just zclose(prog->fd)
> > bpf_program__load(p) // -EBUSY, insn_array->used still 1
> >
> > fails verification with "Failed to properly initialize insn array". The
> > map and its 'used' state live on in obj->jumptable_maps until
> > bpf_object__close().
> >
> > Would libbpf need to recreate the jump-table maps (and re-patch insn->imm)
> > before each manual load, or should this limitation be rejected or
> > documented?
>
> This is indeed an issue, but not specific to our manual loading progs.
yeah, I don't think you have to address this limitation. that second
load does really violate exclusivity, so...
> I was able to reproduce a pretty similar behavior using the exsiting
> bpf_program__clone() interface by writing a test for the recently
> added "Indirect call of bpf subprog" feature by Alexei [1] - I
> prepared and cloned a program which calls function by a func pointer
> from a table, first clone worked fine, while the second generated the
> failure + kernel log below. Another test also verified that
> subsequent bpf_object__load() after the first clone also failed with
> same error.
> I wasn't able to run the gotox scenario since my LLVM didn't support gotox.
>
> dynamicload_verify_callx_clone_twice:PASS:bpf_object__prepare 0 nsec
> dynamicload_verify_callx_clone_twice:PASS:callx_clone_first 0 nsec
> callx_clone_second: clone failed: -16
> -- BEGIN KERNEL LOG --
> map 'test_dyn.rodata' has addresses of functions of another program
> processed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0
> peak_states 0 mark_read 0
> -- END KERNEL LOG --
>
> So I am not sure we should look for and reject all those types of
> cases where the first loaded program captures a map exclusively, given
> that the first load will work normally. Perhaps document it....
>
> Andrey
> [1] - https://lore.kernel.org/bpf/20260922011323.1298619-11-alexei.starovoitov@gmail.com/
>
> >
> > > diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> > > index e7255bd4247f7..faea42f758479 100644
> > > --- a/tools/lib/bpf/libbpf.h
> > > +++ b/tools/lib/bpf/libbpf.h
> > > @@ -392,7 +392,7 @@ LIBBPF_API const char *bpf_program__section_name(const struct bpf_program *prog)
> > > LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog);
> > > LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload);
> > > LIBBPF_API bool bpf_program__autoattach(const struct bpf_program *prog);
> > > -LIBBPF_API void bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> > > +LIBBPF_API int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach);
> >
> > The return type of the public LIBBPF_API bpf_program__set_autoattach()
> > changes from void to int, but the symbol stays in its original
> > https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$ node. This is source compatible, but callers that ignore the
> > result cannot tell that the call was rejected for MANUAL programs, and the
> > ABI of an already exported symbol changes without a new version node.
> >
> > Note: This is fixed later in the series by fca8106ae09c7 ("libbpf:
> > Version bpf_program__set_autoattach() ABI change").
> >
> > > diff --git a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > > index 03c3d2bd15bf4..8def5474885a8 100644
> > > --- a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > > +++ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kBnoMe4M$
> > > @@ -463,6 +463,7 @@ LIBBPF_1.8.0 {
> > > bpf_program__attach_tracing_multi;
> > > bpf_program__clear_flags;
> > > bpf_program__clone;
> > > + bpf_program__load;
> > > bpf_program__load_strategy;
> > > bpf_program__set_load_strategy;
> > > btf__find_by_name_kind_own;
> >
> > The commit message refers to bpf_program__load_manually() and
> > bpf_program__unload_manually():
> >
> > > A manual-strategy program is loaded via bpf_program__load_manually()
> > > and unloaded via bpf_program__unload_manually(), gated on
> > > bpf_object__prepare() having already run (BTF loaded, maps created,
> > > relocations applied) rather than requiring a full bpf_object__load().
> >
> > Neither function exists. The patch actually adds bpf_program__load()
> > (exported in LIBBPF_1.8.0) and changes the existing bpf_program__unload()
> > so that it only closes the fd for MANUAL programs. The message also leaves
> > out the user-visible change to bpf_program__set_autoattach(): it now
> > returns int and rejects MANUAL programs.
> >
> > In the v1 review thread, Andrii Nakryiko expressed concern about adding
> > complexity to bpf_object internals:
> >
> > > I don't want to complicate bpf_object internals with this third
> > > autoload state, as this creates tons of non-obvious gotchas that have
> > > to constantly be checked whenever any new feature is added to
> > > bpf_object (or even during refactorings).
> >
> > From: https://urldefense.com/v3/__https://lore.kernel.org/bpf/CAEf4BzYWe0KCzA4-qwAGp5n_ydJ0_zyLSO=Crr_vewFHzZ0t6Q@mail.gmail.com/__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kHoTp1qY$
> >
> > While the patch has been revised to use bpf_object__prepare() as Andrii
> > suggested, does the implementation still add significant complexity: new
> > state tracking (has_manual_progs), modified cleanup logic with force
> > parameter, complex state machine logic for load strategies, and deferred
> > BTF/fd_array cleanup?
> >
> > Also, for subsequent submissions, Andrii requested the [PATCH bpf-next]
> > subject prefix in the v1 review.
> >
> >
> > ---
> > AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> > See: https://urldefense.com/v3/__https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kIlMmsxY$
> >
> > CI run summary: https://urldefense.com/v3/__https://github.com/kernel-patches/bpf/actions/runs/35934836796__;!!BmdzS3_lV9HdKG8!zC1ATniphW52dqH2YnKa3C_GBu6XJsWup4Mbx4WwxlDc2udDHlre3KohEAG5kA8e6eh0iQUU_SXCP148DhevtSg6kMF5-RGT$
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change
2026-09-26 18:04 ` Andrey Grodzovsky
@ 2026-09-30 15:50 ` Andrii Nakryiko
0 siblings, 0 replies; 27+ messages in thread
From: Andrii Nakryiko @ 2026-09-30 15:50 UTC (permalink / raw)
To: Andrey Grodzovsky
Cc: bpf, andrii, ast, martin.kelly, slava.imameev, linux-open-source
On Sat, Sep 26, 2026 at 7:04 PM Andrey Grodzovsky
<andrey.grodzovsky@crowdstrike.com> wrote:
>
> On Thu, Sep 24, 2026 at 7:19 PM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> >
> > On Wed, Sep 23, 2026 at 4:29 PM Andrey Grodzovsky
> > <andrey.grodzovsky@crowdstrike.com> wrote:
> > >
> > > bpf_program__set_autoattach()'s return type changed from void to int to
> > > let callers observe the new -EINVAL rejection for MANUAL-strategy
> > > programs, but the symbol stayed under its original LIBBPF_1.0.0 node in
> > > libbpf.map. Binaries already linked against the old void-returning ABI
> > > recorded a request for exactly that symbol@version pair at link time;
> > > without a version bump they would silently start hitting the new
> > > rejection behavior underneath them after a libbpf upgrade.
> > >
> > > Split the symbol via ELF symbol versioning:
> > >
> > > - bpf_program__set_autoattach_deprecated() keeps the original
> > > unconditional behavior, bound via COMPAT_VERSION() to the existing
> > > LIBBPF_1.0.0 node.
> > > - bpf_program__set_autoattach_v1_8_0() carries the new MANUAL-rejecting
> > > behavior, bound via DEFAULT_VERSION() to the new LIBBPF_1.8.0 node.
> > >
> > > Old binaries keep resolving bpf_program__set_autoattach() to the old
> > > behavior at runtime; anything linked against the current headers/map
> > > gets the new int-returning, MANUAL-rejecting behavior. Also adds the
> > > missing __LIBBPF_MARK_DEPRECATED_1_8 gate to libbpf_common.h (only the
> > > 1.0 gate existed) so LIBBPF_DEPRECATED_SINCE(1, 8, ...) can mark the
> > > deprecated variant.
> > >
> > > Assisted-by: Claude:claude-sonnet-5
> > > Signed-off-by: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
> > > ---
> > > tools/lib/bpf/libbpf.c | 9 ++++++++-
> > > tools/lib/bpf/libbpf.h | 6 ++++++
> > > tools/lib/bpf/libbpf.map | 2 ++
> > > tools/lib/bpf/libbpf_common.h | 6 ++++++
> > > 4 files changed, 22 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > > index 85e1d9c775ab..224fb0247a39 100644
> > > --- a/tools/lib/bpf/libbpf.c
> > > +++ b/tools/lib/bpf/libbpf.c
> > > @@ -9974,7 +9974,14 @@ bool bpf_program__autoattach(const struct bpf_program *prog)
> > > return prog->autoattach;
> > > }
> > >
> > > -int bpf_program__set_autoattach(struct bpf_program *prog, bool autoattach)
> > > +COMPAT_VERSION(bpf_program__set_autoattach_deprecated, bpf_program__set_autoattach, LIBBPF_1.0.0)
> >
> > no compat stuff, it should be backwards compatible to change return
> > from void to int
>
> I was more worried about legacy binaries dynamiclly linking against
> this risking to somehow getting silently rejected by load_strategy ==
> BPF_PROG_LOAD_STRATEGY_MANUAL check. But yea, practically this is
> impossible so I will drop this.
from what I understand, changing void -> int return type shouldn't
break even the old binaries compiled against old (void-returning)
definition when dynamically linked against newer libbpf with
int-returning API. so we should be good.
Generally speaking, we try to avoid this COMPAT_VERSION as a plague in
libbpf v1.0+
>
> Andrey
>
[...]
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-09-30 15:50 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-23 23:29 [PATCH bpf-next v5 0/8] libbpf: BPF program manual loading Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 1/8] libbpf: BPF program load strategy enum Andrey Grodzovsky
2026-09-24 0:18 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 2/8] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
2026-09-23 23:43 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
2026-09-28 1:40 ` Andrey Grodzovsky
2026-09-30 15:50 ` Andrii Nakryiko
2026-09-24 23:18 ` Andrii Nakryiko
2026-09-26 16:56 ` Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 3/8] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
2026-09-24 23:18 ` Andrii Nakryiko
2026-09-23 23:29 ` [PATCH bpf-next v5 4/8] libbpf: Reject gen_loader for objects with already-manual programs Andrey Grodzovsky
2026-09-24 23:18 ` Andrii Nakryiko
2026-09-26 17:49 ` Andrey Grodzovsky
2026-09-23 23:29 ` [PATCH bpf-next v5 5/8] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
2026-09-23 23:41 ` sashiko-bot
2026-09-24 0:32 ` bot+bpf-ci
2026-09-24 23:19 ` Andrii Nakryiko
2026-09-26 18:04 ` Andrey Grodzovsky
2026-09-30 15:50 ` Andrii Nakryiko
2026-09-23 23:29 ` [PATCH bpf-next v5 6/8] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
2026-09-24 0:18 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 7/8] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
2026-09-24 0:32 ` bot+bpf-ci
2026-09-23 23:29 ` [PATCH bpf-next v5 8/8] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL Andrey Grodzovsky
2026-09-24 0:32 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox