* [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons
@ 2026-09-09 4:24 Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 2/3] bpftool: Compute light skeleton ctx.sz without relying on links Thiébaud Weksteen
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Thiébaud Weksteen @ 2026-09-09 4:24 UTC (permalink / raw)
To: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Shuah Khan, Thiébaud Weksteen, KP Singh, Leon Hwang,
Emil Tsalapatis
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Ihor Solodrai, bpf, linux-kernel, linux-kselftest
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 <tweek@google.com>
---
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
@@ -583,6 +583,9 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name)
bpf_object__for_each_program(prog, obj) {
const char *tp_name;
+ if (!bpf_program__autoload(prog))
+ continue;
+
codegen("\
\n\
\n\
@@ -629,6 +632,8 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name)
", obj_name);
bpf_object__for_each_program(prog, obj) {
+ if (!bpf_program__autoload(prog))
+ continue;
codegen("\
\n\
ret = ret < 0 ? ret : %1$s__%2$s__attach(skel); \n\
@@ -646,6 +651,8 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name)
", obj_name);
bpf_object__for_each_program(prog, obj) {
+ if (!bpf_program__autoload(prog))
+ continue;
codegen("\
\n\
skel_closenz(skel->links.%1$s_fd); \n\
@@ -676,6 +683,8 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
obj_name);
bpf_object__for_each_program(prog, obj) {
+ if (!bpf_program__autoload(prog))
+ continue;
codegen("\
\n\
skel_closenz(skel->progs.%1$s.prog_fd); \n\
@@ -701,9 +710,9 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
obj_name);
}
-static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard)
+static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard,
+ const struct gen_loader_opts *opts)
{
- DECLARE_LIBBPF_OPTS(gen_loader_opts, opts);
struct bpf_load_and_run_opts sopts = {};
char sig_buf[MAX_SIG_SIZE];
__u8 prog_sha[SHA256_DIGEST_LENGTH];
@@ -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.
*/
@@ -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;
+ }
+ }
+
bpf_object__for_each_map(map, obj) {
if (!get_map_ident(map, ident, sizeof(ident))) {
p_err("ignoring unrecognized internal map '%s'...",
@@ -1339,6 +1351,8 @@ static int do_skeleton(int argc, char **argv)
map_cnt++;
}
bpf_object__for_each_program(prog, obj) {
+ if (use_loader && !bpf_program__autoload(prog))
+ continue;
prog_cnt++;
}
@@ -1402,6 +1416,8 @@ static int do_skeleton(int argc, char **argv)
if (prog_cnt) {
printf("\tstruct {\n");
bpf_object__for_each_program(prog, obj) {
+ if (use_loader && !bpf_program__autoload(prog))
+ continue;
if (use_loader)
printf("\t\tstruct bpf_prog_desc %s;\n",
bpf_program__name(prog));
@@ -1415,6 +1431,8 @@ static int do_skeleton(int argc, char **argv)
if (prog_cnt + attach_map_cnt) {
printf("\tstruct {\n");
bpf_object__for_each_program(prog, obj) {
+ if (use_loader && !bpf_program__autoload(prog))
+ continue;
if (use_loader)
printf("\t\tint %s_fd;\n",
bpf_program__name(prog));
@@ -1451,7 +1469,7 @@ static int do_skeleton(int argc, char **argv)
goto out;
}
if (use_loader) {
- err = gen_trace(obj, obj_name, header_guard);
+ err = gen_trace(obj, obj_name, header_guard, &gen_opts);
goto out;
}
--
2.55.0.979.g7e5102b832-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v2 2/3] bpftool: Compute light skeleton ctx.sz without relying on links
2026-09-09 4:24 [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons Thiébaud Weksteen
@ 2026-09-09 4:24 ` Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel Thiébaud Weksteen
2026-09-09 5:02 ` [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons bot+bpf-ci
2 siblings, 0 replies; 5+ messages in thread
From: Thiébaud Weksteen @ 2026-09-09 4:24 UTC (permalink / raw)
To: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Shuah Khan, Thiébaud Weksteen, KP Singh, Leon Hwang,
Emil Tsalapatis
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Ihor Solodrai, bpf, linux-kernel, linux-kselftest
When generating a light skeleton, gen_trace() unconditionally sets
skel->ctx.sz using (char *)&skel->links - (char *)skel. However, if a
BPF object has no programs and no struct_ops maps (prog_cnt +
attach_map_cnt == 0), do_skeleton() omits the links struct, causing the
generated skeleton header to fail compilation.
Compute skel->ctx.sz from the end of progs (if prog_cnt > 0), maps (if
map_cnt > 0), or ctx instead of relying on skel->links. Also remove the
unused opts.data_sz argument passed to codegen().
Fixes: d510296d331a ("bpftool: Use syscall/loader program in "prog load" and "gen skeleton" command.")
Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
tools/bpf/bpftool/gen.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index e9a1a018f270..4a93c392857a 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -711,7 +711,7 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
}
static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard,
- const struct gen_loader_opts *opts)
+ const struct gen_loader_opts *opts, size_t prog_cnt, size_t map_cnt)
{
struct bpf_load_and_run_opts sopts = {};
char sig_buf[MAX_SIG_SIZE];
@@ -746,9 +746,16 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
skel = (struct %1$s *)skel_alloc(sizeof(*skel)); \n\
if (!skel) \n\
goto cleanup; \n\
- skel->ctx.sz = (char *)&skel->links - (char *)skel; \n\
",
- obj_name, opts->data_sz);
+ obj_name);
+ if (prog_cnt)
+ printf("\tskel->ctx.sz = (char *)&skel->progs - (char *)skel\n"
+ "\t\t + sizeof(skel->progs);\n");
+ else if (map_cnt)
+ printf("\tskel->ctx.sz = (char *)&skel->maps - (char *)skel\n"
+ "\t\t + sizeof(skel->maps);\n");
+ else
+ printf("\tskel->ctx.sz = sizeof(skel->ctx);\n");
bpf_object__for_each_map(map, obj) {
const void *mmap_data = NULL;
size_t mmap_size = 0;
@@ -1469,7 +1476,7 @@ static int do_skeleton(int argc, char **argv)
goto out;
}
if (use_loader) {
- err = gen_trace(obj, obj_name, header_guard, &gen_opts);
+ err = gen_trace(obj, obj_name, header_guard, &gen_opts, prog_cnt, map_cnt);
goto out;
}
--
2.55.0.979.g7e5102b832-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel
2026-09-09 4:24 [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 2/3] bpftool: Compute light skeleton ctx.sz without relying on links Thiébaud Weksteen
@ 2026-09-09 4:24 ` Thiébaud Weksteen
2026-09-10 5:49 ` Leon Hwang
2026-09-09 5:02 ` [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons bot+bpf-ci
2 siblings, 1 reply; 5+ messages in thread
From: Thiébaud Weksteen @ 2026-09-09 4:24 UTC (permalink / raw)
To: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Shuah Khan, Thiébaud Weksteen, KP Singh, Leon Hwang,
Emil Tsalapatis
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Ihor Solodrai, bpf, linux-kernel, linux-kselftest
In test_global_percpu_data.c, dump_percpu_data is defined after two
non-autoload SEC("?kprobe") programs (verifier_strncmp and
verifier_snprintf). Verify in test_global_percpu_data_lskel() that
lskel->progs.dump_percpu_data.prog_fd is properly populated (> 0) when
loaded via the light skeleton.
Signed-off-by: Thiébaud Weksteen <tweek@google.com>
---
No changes since v1.
tools/testing/selftests/bpf/prog_tests/global_data_init.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/global_data_init.c b/tools/testing/selftests/bpf/prog_tests/global_data_init.c
index 7c539cbcf3a1..ebda60200b71 100644
--- a/tools/testing/selftests/bpf/prog_tests/global_data_init.c
+++ b/tools/testing/selftests/bpf/prog_tests/global_data_init.c
@@ -214,6 +214,8 @@ static void test_global_percpu_data_lskel(void)
lskel = test_global_percpu_data_lskel__open_and_load();
if (!ASSERT_OK_PTR(lskel, "test_global_percpu_data_lskel__open_and_load"))
goto out;
+ if (!ASSERT_GT(lskel->progs.dump_percpu_data.prog_fd, 0, "dump_percpu_data.prog_fd"))
+ goto out;
map_fd = lskel->maps.percpu.map_fd;
prog_fd = lskel->progs.update_percpu_data.prog_fd;
--
2.55.0.979.g7e5102b832-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons
2026-09-09 4:24 [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 2/3] bpftool: Compute light skeleton ctx.sz without relying on links Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel Thiébaud Weksteen
@ 2026-09-09 5:02 ` bot+bpf-ci
2 siblings, 0 replies; 5+ messages in thread
From: bot+bpf-ci @ 2026-09-09 5:02 UTC (permalink / raw)
To: tweek, qmo, ast, daniel, andrii, eddyz87, memxor, shuah, tweek,
kpsingh, leon.hwang, emil
Cc: martin.lau, song, yonghong.song, jolsa, ihor.solodrai, bpf,
linux-kernel, linux-kselftest, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1824 bytes --]
> 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
[ ... ]
> @@ -701,9 +710,9 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
> obj_name);
> }
>
> -static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard)
> +static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *header_guard,
> + const struct gen_loader_opts *opts)
> {
> - DECLARE_LIBBPF_OPTS(gen_loader_opts, opts);
> struct bpf_load_and_run_opts sopts = {};
> char sig_buf[MAX_SIG_SIZE];
> __u8 prog_sha[SHA256_DIGEST_LENGTH];
> @@ -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 isn't a bug, but now that the bpf_object__load() call moved to
do_skeleton(), would this comment be clearer next to the new call site, or
reworded as a precondition on the opts parameter? The comment currently sits
at the top of gen_trace() followed immediately by another unrelated comment,
and a reader has to look at the caller to find the load it refers to.
---
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/34311359322
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel
2026-09-09 4:24 ` [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel Thiébaud Weksteen
@ 2026-09-10 5:49 ` Leon Hwang
0 siblings, 0 replies; 5+ messages in thread
From: Leon Hwang @ 2026-09-10 5:49 UTC (permalink / raw)
To: Thiébaud Weksteen, Quentin Monnet, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Shuah Khan, KP Singh, Emil Tsalapatis
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Ihor Solodrai, bpf, linux-kernel, linux-kselftest
On 9/9/26 12:24, Thiébaud Weksteen wrote:
> In test_global_percpu_data.c, dump_percpu_data is defined after two
> non-autoload SEC("?kprobe") programs (verifier_strncmp and
> verifier_snprintf). Verify in test_global_percpu_data_lskel() that
> lskel->progs.dump_percpu_data.prog_fd is properly populated (> 0) when
> loaded via the light skeleton.
>
> Signed-off-by: Thiébaud Weksteen <tweek@google.com>
> ---
> No changes since v1.
The Fixes tags of v1 are dropped.
>
> tools/testing/selftests/bpf/prog_tests/global_data_init.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/global_data_init.c b/tools/testing/selftests/bpf/prog_tests/global_data_init.c
> index 7c539cbcf3a1..ebda60200b71 100644
> --- a/tools/testing/selftests/bpf/prog_tests/global_data_init.c
> +++ b/tools/testing/selftests/bpf/prog_tests/global_data_init.c
> @@ -214,6 +214,8 @@ static void test_global_percpu_data_lskel(void)
> lskel = test_global_percpu_data_lskel__open_and_load();
> if (!ASSERT_OK_PTR(lskel, "test_global_percpu_data_lskel__open_and_load"))
> goto out;
> + if (!ASSERT_GT(lskel->progs.dump_percpu_data.prog_fd, 0, "dump_percpu_data.prog_fd"))
> + goto out;
When the light skeleton does not skip the non-autoload progs,
lskel->progs.dump_percpu_data.prog_fd is 0, actually.
But, theoretically, this ASSERT_GT() is not good enough to prove the FD
is the correct FD of dump_percpu_data prog.
How about running the iter subtest against the lskel? Factor out
test_global_percpu_data_iter_fd() helper first. Then, add an iter
subtest using lskel.
When factoring out test_global_percpu_data_iter_fd() helper, pls change
'do { } while (0);' to '{ /* no-op */ }' btw.
Thanks,
Leon
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-10 5:49 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-09 4:24 [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 2/3] bpftool: Compute light skeleton ctx.sz without relying on links Thiébaud Weksteen
2026-09-09 4:24 ` [PATCH bpf-next v2 3/3] selftests/bpf: Verify program FD after non-autoload programs in lskel Thiébaud Weksteen
2026-09-10 5:49 ` Leon Hwang
2026-09-09 5:02 ` [PATCH bpf-next v2 1/3] bpftool: Skip non-autoload programs when generating light skeletons 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
all inboxes | Powered by JetHome®