From: Mykyta Yatsenko <mykyta.yatsenko5@gmail.com>
To: Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com>,
Andrii Nakryiko <andrii@kernel.org>
Cc: bpf@vger.kernel.org, ast@kernel.org, daniel@iogearbox.net,
kernel-team@meta.com,
DL Linux Open Source Team <linux-open-source@crowdstrike.com>
Subject: Re: [External] [PATCH bpf-next v2 1/2] libbpf: Introduce bpf_program__clone()
Date: Tue, 10 Mar 2026 00:08:32 +0000 [thread overview]
Message-ID: <878qc0mtq7.fsf@gmail.com> (raw)
In-Reply-To: <CAOu3gNgW9+C7bOPkt0CGs8d_ad_TNyQ0AKv7_Jfg4=F5PET4ow@mail.gmail.com>
Andrey Grodzovsky <andrey.grodzovsky@crowdstrike.com> writes:
Hi,
Thanks for reaching out, I'm providing my own opinion on this, I did not
discuss this with Andrii in depth.
bpf_object__finalize() - you probably do not need this, the mentioned
example of bpf_program__set_type() actually rejects when object is in
LOADED state (you can't mutate loaded program).
To make your dynamic loading/unloading work, you need to keep your
object in the PREPARED state indefinitely. As clone() uses some of the
fields that are destroyed by the post_load_cleanup(). calling
bpf_object__load() in this setup may be unsafe (leaking fd, etc).
We have a precedent with bpf_map__reuse_fd(), bpf_program__set_fd() does
not seem too extreme to me, but it seems like some lifecycle invariants
are changing, as we'll have loaded program in prepared object, which I'm
not 100% sure is a problem right now, but possibly going to break
something.
Also a small detail: bpf_program__clone() does not support PROG_ARRAY
maps (just in case you need that) (see cover letter for details).
> Mykyta and Andrii Hi!
>
> We're evaluating the bpf_object__prepare() +
> bpf_program__clone() API for use in a production BPF
> application that manages hundreds of BPF programs with
> selective (dynamic) loading — some programs are loaded at
> startup, others loaded/unloaded at runtime based on feature
> configuration.
>
> We have a few questions about the intended usage and
> potential extensions of this API:
>
> 1. Compatibility with bpf_object__load() and object state
>
> After bpf_object__prepare(), the object is in OBJ_PREPARED
> state. Several libbpf APIs (e.g., bpf_program__set_type())
> gate on OBJ_LOADED state.
>
> Is there a recommended way to transition the object to
> OBJ_LOADED after cloning all desired programs? For example,
> would a bpf_object__finalize() or similar API that runs
> post_load_cleanup() and sets OBJ_LOADED be in scope? This
> would allow users to benefit from prepare() + clone() for
> selective loading while keeping the object in a state that
> the rest of libbpf expects. Or, is the new API not intended
> to work with bpf_object in the first place ?
>
> 2. Storing the clone FD back on struct bpf_program
>
> bpf_program__clone() returns a caller-owned FD, but APIs
> like bpf_program__attach() read prog->fd internally.
> Without a way to set the FD back on the program struct, the
> caller must reimplement attach logic (section-type dispatch
> for kprobe, fentry, raw_tp, etc.).
>
> Would a bpf_program__set_fd() setter (similar to the
> existing btf__set_fd()) be acceptable to store the clone FD
> back, making bpf_program__attach() and related APIs usable
> with cloned programs?
>
> 3. Use case: selective program loading from a single BPF
> object
>
> Our use case involves a single large BPF object (skeleton)
> with hundreds of programs where a subset is loaded at
> startup and others are loaded/unloaded dynamically based on
> runtime configuration. The current approach requires either:
> - Loading all programs upfront (wasteful), or
> - Maintaining out-of-tree patches to libbpf for selective
> loading
>
> Last year we made an attempt to upstream our solution to
> this use case to libbpf[1] but Andrii pointed out how our
> approach was problematic for upstream. He then proposed
> splitting bpf_object__load() into two steps:
> bpf_object__prepare() (creates maps, loads BTF, does
> relocations, produces final program instructions) and then
> bpf_object__load(). We are trying to follow up on his
> input and become more upstream compliant.
>
> The prepare() + clone() API seems similiar to this,
> but the questions above about object state and FD ownership
> are the main gaps for production adoption. Are there plans
> to address these in future revisions, or is this
> intentionally scoped to testing/tooling use cases only?
>
> Thanks,
> Andrey
>
> [1] -https://lore.kernel.org/all/20250122215206.59859-1-slava.imameev@crowdstrike.com/t/#m93ec917b3dfe3115be2a4b6439e2c649c791686d
>
> On Fri, Feb 20, 2026 at 2:18 PM Mykyta Yatsenko
> <mykyta.yatsenko5@gmail.com> wrote:
>>
>> From: Mykyta Yatsenko <yatsenko@meta.com>
>>
>> Add bpf_program__clone() API that loads a single BPF program from a
>> prepared BPF object into the kernel, returning a file descriptor owned
>> by the caller.
>>
>> After bpf_object__prepare(), callers can use bpf_program__clone() to
>> load individual programs with custom bpf_prog_load_opts, instead of
>> loading all programs at once via bpf_object__load(). Non-zero fields in
>> opts override the defaults derived from the program and object
>> internals; passing NULL opts populates everything automatically.
>>
>> Internally, bpf_program__clone() resolves BTF-based attach targets
>> (attach_btf_id, attach_btf_obj_fd) and the sleepable flag, fills
>> func/line info, fd_array, license, and kern_version from the
>> prepared object before calling bpf_prog_load().
>>
>> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
>> ---
>> tools/lib/bpf/libbpf.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++
>> tools/lib/bpf/libbpf.h | 17 +++++++++++++
>> tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!3OeUmMPxKbEw0Wl_ZYw9yzRwdxA2X7kuWyzFOxvKuIsQg1fhhJtfaqSd4n0N6UokYqOuQporDXrIKYc3k7dvu4Rel1BiJSjA99yzJZk$ | 1 +
>> 3 files changed, 82 insertions(+)
>>
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index 0c8bf0b5cce4..4b084bda3f47 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -9793,6 +9793,70 @@ __u32 bpf_program__line_info_cnt(const struct bpf_program *prog)
>> return prog->line_info_cnt;
>> }
>>
>> +int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts)
>> +{
>> + LIBBPF_OPTS(bpf_prog_load_opts, attr);
>> + struct bpf_prog_load_opts *pattr = &attr;
>> + struct bpf_object *obj;
>> + int err, fd;
>> +
>> + if (!prog)
>> + return libbpf_err(-EINVAL);
>> +
>> + if (!OPTS_VALID(opts, bpf_prog_load_opts))
>> + return libbpf_err(-EINVAL);
>> +
>> + obj = prog->obj;
>> + if (obj->state < OBJ_PREPARED)
>> + return libbpf_err(-EINVAL);
>> +
>> + /* Copy caller opts, fall back to prog/object defaults */
>> + OPTS_SET(pattr, expected_attach_type,
>> + OPTS_GET(opts, expected_attach_type, 0) ?: prog->expected_attach_type);
>> + OPTS_SET(pattr, attach_btf_id, OPTS_GET(opts, attach_btf_id, 0) ?: prog->attach_btf_id);
>> + OPTS_SET(pattr, attach_btf_obj_fd,
>> + OPTS_GET(opts, attach_btf_obj_fd, 0) ?: prog->attach_btf_obj_fd);
>> + OPTS_SET(pattr, attach_prog_fd, OPTS_GET(opts, attach_prog_fd, 0) ?: prog->attach_prog_fd);
>> + OPTS_SET(pattr, prog_flags, OPTS_GET(opts, prog_flags, 0) ?: prog->prog_flags);
>> + OPTS_SET(pattr, prog_ifindex, OPTS_GET(opts, prog_ifindex, 0) ?: prog->prog_ifindex);
>> + OPTS_SET(pattr, kern_version, OPTS_GET(opts, kern_version, 0) ?: obj->kern_version);
>> + OPTS_SET(pattr, fd_array, OPTS_GET(opts, fd_array, NULL) ?: obj->fd_array);
>> + OPTS_SET(pattr, token_fd, OPTS_GET(opts, token_fd, 0) ?: obj->token_fd);
>> + if (attr.token_fd)
>> + attr.prog_flags |= BPF_F_TOKEN_FD;
>> +
>> + /* BTF func/line info */
>> + if (obj->btf && btf__fd(obj->btf) >= 0) {
>> + OPTS_SET(pattr, prog_btf_fd, OPTS_GET(opts, prog_btf_fd, 0) ?: btf__fd(obj->btf));
>> + OPTS_SET(pattr, func_info, OPTS_GET(opts, func_info, NULL) ?: prog->func_info);
>> + OPTS_SET(pattr, func_info_cnt,
>> + OPTS_GET(opts, func_info_cnt, 0) ?: prog->func_info_cnt);
>> + OPTS_SET(pattr, func_info_rec_size,
>> + OPTS_GET(opts, func_info_rec_size, 0) ?: prog->func_info_rec_size);
>> + OPTS_SET(pattr, line_info, OPTS_GET(opts, line_info, NULL) ?: prog->line_info);
>> + OPTS_SET(pattr, line_info_cnt,
>> + OPTS_GET(opts, line_info_cnt, 0) ?: prog->line_info_cnt);
>> + OPTS_SET(pattr, line_info_rec_size,
>> + OPTS_GET(opts, line_info_rec_size, 0) ?: prog->line_info_rec_size);
>> + }
>> +
>> + OPTS_SET(pattr, log_buf, OPTS_GET(opts, log_buf, NULL));
>> + OPTS_SET(pattr, log_size, OPTS_GET(opts, log_size, 0));
>> + OPTS_SET(pattr, log_level, OPTS_GET(opts, log_level, 0));
>> +
>> + /* Resolve BTF attach targets, set sleepable/XDP flags, etc. */
>> + if (prog->sec_def && prog->sec_def->prog_prepare_load_fn) {
>> + err = prog->sec_def->prog_prepare_load_fn(prog, pattr, prog->sec_def->cookie);
>> + if (err)
>> + return libbpf_err(err);
>> + }
>> +
>> + fd = bpf_prog_load(prog->type, prog->name, obj->license, prog->insns, prog->insns_cnt,
>> + pattr);
>> +
>> + return libbpf_err(fd);
>> +}
>> +
>> #define SEC_DEF(sec_pfx, ptype, atype, flags, ...) { \
>> .sec = (char *)sec_pfx, \
>> .prog_type = BPF_PROG_TYPE_##ptype, \
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index dfc37a615578..0be34852350f 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -2021,6 +2021,23 @@ LIBBPF_API int libbpf_register_prog_handler(const char *sec,
>> */
>> LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);
>>
>> +/**
>> + * @brief **bpf_program__clone()** loads a single BPF program from a prepared
>> + * BPF object into the kernel, returning its file descriptor.
>> + *
>> + * The BPF object must have been previously prepared with
>> + * **bpf_object__prepare()**. If @opts is provided, any non-zero field
>> + * overrides the defaults derived from the program/object internals.
>> + * If @opts is NULL, all fields are populated automatically.
>> + *
>> + * The returned FD is owned by the caller and must be closed with close().
>> + *
>> + * @param prog BPF program from a prepared object
>> + * @param opts Optional load options; non-zero fields override defaults
>> + * @return program FD (>= 0) on success; negative error code on failure
>> + */
>> +LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts);
>> +
>> #ifdef __cplusplus
>> } /* extern "C" */
>> #endif
>> diff --git a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!3OeUmMPxKbEw0Wl_ZYw9yzRwdxA2X7kuWyzFOxvKuIsQg1fhhJtfaqSd4n0N6UokYqOuQporDXrIKYc3k7dvu4Rel1BiJSjA99yzJZk$ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!3OeUmMPxKbEw0Wl_ZYw9yzRwdxA2X7kuWyzFOxvKuIsQg1fhhJtfaqSd4n0N6UokYqOuQporDXrIKYc3k7dvu4Rel1BiJSjA99yzJZk$
>> index d18fbcea7578..e727a54e373a 100644
>> --- a/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!3OeUmMPxKbEw0Wl_ZYw9yzRwdxA2X7kuWyzFOxvKuIsQg1fhhJtfaqSd4n0N6UokYqOuQporDXrIKYc3k7dvu4Rel1BiJSjA99yzJZk$
>> +++ b/tools/lib/bpf/https://urldefense.com/v3/__http://libbpf.map__;!!BmdzS3_lV9HdKG8!3OeUmMPxKbEw0Wl_ZYw9yzRwdxA2X7kuWyzFOxvKuIsQg1fhhJtfaqSd4n0N6UokYqOuQporDXrIKYc3k7dvu4Rel1BiJSjA99yzJZk$
>> @@ -452,6 +452,7 @@ LIBBPF_1.7.0 {
>> bpf_map__set_exclusive_program;
>> bpf_map__exclusive_program;
>> bpf_prog_assoc_struct_ops;
>> + bpf_program__clone;
>> bpf_program__assoc_struct_ops;
>> btf__permute;
>> } LIBBPF_1.6.0;
>>
>> --
>> 2.47.3
>>
>>
next prev parent reply other threads:[~2026-03-10 0:08 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-20 19:18 [PATCH bpf-next v2 0/2] libbpf: Add bpf_program__clone() for individual program loading Mykyta Yatsenko
2026-02-20 19:18 ` [PATCH bpf-next v2 1/2] libbpf: Introduce bpf_program__clone() Mykyta Yatsenko
2026-02-23 17:25 ` Emil Tsalapatis
2026-02-23 17:59 ` Mykyta Yatsenko
2026-02-23 18:04 ` Emil Tsalapatis
2026-02-24 19:28 ` Eduard Zingerman
2026-02-24 19:32 ` Eduard Zingerman
2026-02-24 20:47 ` Mykyta Yatsenko
2026-03-06 17:22 ` [External] " Andrey Grodzovsky
2026-03-10 0:08 ` Mykyta Yatsenko [this message]
2026-03-11 13:35 ` Andrey Grodzovsky
2026-03-11 22:52 ` Andrii Nakryiko
2026-03-16 14:23 ` Andrey Grodzovsky
2026-03-11 23:03 ` Andrii Nakryiko
2026-02-20 19:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: Use bpf_program__clone() in veristat Mykyta Yatsenko
2026-02-23 17:49 ` Emil Tsalapatis
2026-02-23 18:39 ` Mykyta Yatsenko
2026-02-23 18:54 ` Emil Tsalapatis
2026-02-24 2:03 ` Eduard Zingerman
2026-02-24 12:20 ` Mykyta Yatsenko
2026-02-24 19:08 ` Eduard Zingerman
2026-02-24 19:12 ` Mykyta Yatsenko
2026-02-24 19:16 ` Eduard Zingerman
2026-02-20 22:48 ` [PATCH bpf-next v2 0/2] libbpf: Add bpf_program__clone() for individual program loading Alexei Starovoitov
2026-02-23 13:57 ` Mykyta Yatsenko
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=878qc0mtq7.fsf@gmail.com \
--to=mykyta.yatsenko5@gmail.com \
--cc=andrey.grodzovsky@crowdstrike.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@meta.com \
--cc=linux-open-source@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