From: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>
To: <bpf@vger.kernel.org>, <andrii@kernel.org>
Cc: <ast@kernel.org>, <martin.kelly@crowdstrike.com>,
<slava.imameev@crowdstrike.com>,
<linux-open-source@crowdstrike.com>
Subject: [PATCH bpf-next v4 2/7] libbpf: BPF programs manual loading and attaching
Date: Mon, 21 Sep 2026 18:39:32 -0400 [thread overview]
Message-ID: <20260921223937.3203093-3-andrey.grodzovsky@crowdstrike.com> (raw)
In-Reply-To: <20260921223937.3203093-1-andrey.grodzovsky@crowdstrike.com>
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 d88df7e4c86d..61195d16ee0f 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;
@@ -795,7 +797,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;
@@ -807,12 +809,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);
@@ -8410,6 +8430,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)
@@ -8435,6 +8456,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;
@@ -8461,6 +8484,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
*/
@@ -8625,7 +8661,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;
}
@@ -9068,33 +9104,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)
@@ -9169,7 +9222,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) {
@@ -9637,7 +9690,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;
@@ -9646,7 +9699,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++)
@@ -9822,9 +9874,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)
@@ -12748,7 +12804,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;
}
@@ -15305,10 +15361,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;
}
@@ -15316,3 +15431,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 a06259c4dacc..5e44bcfa2ca1 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -378,7 +378,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;
@@ -459,6 +459,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;
@@ -2108,21 +2118,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);
@@ -2136,6 +2153,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
next prev parent reply other threads:[~2026-09-21 22:39 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-21 22:39 [PATCH bpf-next v4 0/7] libbpf: BPF program manual loading Andrey Grodzovsky
2026-09-21 22:39 ` [PATCH bpf-next v4 1/7] libbpf: BPF program load strategy enum Andrey Grodzovsky
2026-09-21 23:21 ` bot+bpf-ci
2026-09-21 22:39 ` Andrey Grodzovsky [this message]
2026-09-21 23:03 ` [PATCH bpf-next v4 2/7] libbpf: BPF programs manual loading and attaching sashiko-bot
2026-09-22 23:51 ` Andrey Grodzovsky
2026-09-21 22:39 ` [PATCH bpf-next v4 3/7] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
2026-09-21 23:12 ` sashiko-bot
2026-09-21 23:21 ` bot+bpf-ci
2026-09-21 22:39 ` [PATCH bpf-next v4 4/7] libbpf: Reject gen_loader for objects with already-manual programs Andrey Grodzovsky
2026-09-21 22:39 ` [PATCH bpf-next v4 5/7] libbpf: Version bpf_program__set_autoattach() ABI change Andrey Grodzovsky
2026-09-21 23:31 ` sashiko-bot
2026-09-21 22:39 ` [PATCH bpf-next v4 6/7] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
2026-09-21 23:37 ` sashiko-bot
2026-09-21 22:39 ` [PATCH bpf-next v4 7/7] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
2026-09-22 1:37 ` [PATCH bpf-next v4 0/7] libbpf: " Alexei Starovoitov
2026-09-22 14:34 ` Andrey Grodzovsky
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260921223937.3203093-3-andrey.grodzovsky@crowdstrike.com \
--to=andrey.grodzovsky@crowdstrike.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=linux-open-source@crowdstrike.com \
--cc=martin.kelly@crowdstrike.com \
--cc=slava.imameev@crowdstrike.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox