public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
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
>>
>>

  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