On 09/09/2026 05:24, ThiƩbaud Weksteen wrote:
> When generating a light skeleton (bpftool gen skeleton -L),
> bpf_object__load() skips loading programs marked as non-autoload (e.g.
> SEC("?...")), so the generated loader program only records and populates
> file descriptors for autoloaded programs.
>
> Previously, bpftool emitted struct bpf_prog_desc fields, link fields,
> and attach/detach/destroy functions for all programs in the BPF object,
> causing the loader program to store subsequent program FDs into
> incorrect skeleton struct fields when non-autoload programs were
> present.
>
> Furthermore, bpf_object__load() can update a program's autoload status
> during preparation (e.g. for struct_ops programs when resolving kernel
> BTF members or adjusting autoload based on map autocreate settings).
> Move bpf_object__gen_loader() and bpf_object__load() out of gen_trace()
> into do_skeleton() before counting programs and emitting struct fields so
> that struct field declarations and attach/detach/destroy functions all
> observe the final post-load autoload state.
>
> Skip programs with !bpf_program__autoload(prog) when counting programs
> and generating progs/links struct fields as well as attach, detach, and
> destroy functions for light skeletons.
>
> Fixes: d510296d331a ("bpftool: Use syscall/loader program in "prog load" and
> "gen skeleton" command.")
> Signed-off-by: ThiƩbaud Weksteen <[email protected]>
> ---
> Changes since v1:
> - Move bpf_object__gen_loader() and bpf_object__load() out of gen_trace()
>
> .../bpf/bpftool/Documentation/bpftool-gen.rst | 4 +-
> tools/bpf/bpftool/gen.c | 64 ++++++++++++-------
> 2 files changed, 44 insertions(+), 24 deletions(-)
>
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> index d0a36f442db7..1cdecf3e4fa5 100644
> --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst
> @@ -184,7 +184,9 @@ OPTIONS
> -L, --use-loader
> For skeletons, generate a "light" skeleton (also known as "loader"
> skeleton). A light skeleton contains a loader eBPF program. It does not
> use
> - the majority of the libbpf infrastructure, and does not need libelf.
> + the majority of the libbpf infrastructure, and does not need libelf. BPF
> + programs marked as non-autoload (e.g., via **SEC("?...")**) are skipped
> and
> + not included in the generated skeleton.
>
> -S, --sign
> For skeletons, generate a signed skeleton. This option must be used with
> diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
> index a50540ef6521..e9a1a018f270 100644
> --- a/tools/bpf/bpftool/gen.c
> +++ b/tools/bpf/bpftool/gen.c
[...]
> @@ -712,19 +721,6 @@ static int gen_trace(struct bpf_object *obj, const char
> *obj_name, const char *h
> char ident[256];
> int err = 0;
>
> - if (sign_progs)
> - opts.gen_hash = true;
> -
> - err = bpf_object__gen_loader(obj, &opts);
> - if (err)
> - return err;
> -
> - err = bpf_object__load(obj);
> - if (err) {
> - p_err("failed to load object file");
> - goto out;
> - }
> -
> /* If there was no error during load then gen_loader_opts
> * are populated with the loader program.
> */
This comment could be updated (or moved to do_skeleton()).
> @@ -752,7 +748,7 @@ static int gen_trace(struct bpf_object *obj, const char
> *obj_name, const char *h
> goto cleanup; \n\
> skel->ctx.sz = (char *)&skel->links - (char *)skel; \n\
> ",
> - obj_name, opts.data_sz);
> + obj_name, opts->data_sz);
> bpf_object__for_each_map(map, obj) {
> const void *mmap_data = NULL;
> size_t mmap_size = 0;
> @@ -795,22 +791,22 @@ static int gen_trace(struct bpf_object *obj, const char
> *obj_name, const char *h
> static const char opts_data[]
> __attribute__((__aligned__(8))) = \"\\\n\
> ",
> obj_name);
> - print_hex(opts.data, opts.data_sz);
> + print_hex(opts->data, opts->data_sz);
> codegen("\
> \n\
> \"; \n\
> static const char opts_insn[]
> __attribute__((__aligned__(8))) = \"\\\n\
> ");
> - print_hex(opts.insns, opts.insns_sz);
> + print_hex(opts->insns, opts->insns_sz);
> codegen("\
> \n\
> \";\n");
>
> if (sign_progs) {
> - sopts.insns = opts.insns;
> - sopts.insns_sz = opts.insns_sz;
> - sopts.data = opts.data;
> - sopts.data_sz = opts.data_sz;
> + sopts.insns = opts->insns;
> + sopts.insns_sz = opts->insns_sz;
> + sopts.data = opts->data;
> + sopts.data_sz = opts->data_sz;
> sopts.excl_prog_hash = prog_sha;
> sopts.excl_prog_hash_sz = sizeof(prog_sha);
> sopts.signature = sig_buf;
> @@ -1250,6 +1246,7 @@ static int do_skeleton(int argc, char **argv)
> char header_guard[MAX_OBJ_NAME_LEN + sizeof("__SKEL_H__")];
> size_t map_cnt = 0, prog_cnt = 0, attach_map_cnt = 0, file_sz, mmap_sz;
> DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
> + DECLARE_LIBBPF_OPTS(gen_loader_opts, gen_opts);
> char obj_name[MAX_OBJ_NAME_LEN] = "", *obj_data;
> struct bpf_object *obj = NULL;
> const char *file;
> @@ -1326,6 +1323,21 @@ static int do_skeleton(int argc, char **argv)
> goto out_obj;
> }
>
> + if (use_loader) {
> + if (sign_progs)
> + gen_opts.gen_hash = true;
> +
> + err = bpf_object__gen_loader(obj, &gen_opts);
> + if (err)
> + goto out;
> +
> + err = bpf_object__load(obj);
> + if (err) {
> + p_err("failed to load object file");
> + goto out;
> + }
> + }
Could you please add a comment above this block to explain why it's at
the current location (to avoid autoload flags update), to avoid people
moving it by accident, please?
Looks good otherwise, thank you!