* [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach
@ 2026-11-02 20:13 Mahe Tardy
2026-11-02 20:13 ` [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw)
To: bpf
Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy,
Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel,
linux-kselftest
Hey,
I've been working on this in the context of Shirudo[^1]. The current
limitations we face is that gen_loader cannot attach the program it
loads so we still need a userspace component to be allowed to
BPF_LINK_CREATE and we'd like to gradually remove the allowed operations
done in userspace to solely rely on the signed program.
Note that there's no kernel modification in this series, it adds the
gen_attach options and code generation to libbpf and do the wiring from
bpftool. The kernel side was already allowed thanks to 'b1d18a7574d0
("bpf: Extend sys_bpf commands for bpf_syscall programs.")'.
Also note that I did some analyzis with Sashiko and it's been
complaining because I've followed the approach with the prog fds being
initialized to 0 (and not fixing the two fds to init to -1 sentinel
value), so maybe we should pre-patch this.
Lastly, I don't know much about bpftool so unsure if the way I've added
it is the most appropriate way for the lskel attach option.
v3 updates:
- rebased again because I had a debug commit in between master and my
patches that somehow surprisingly conflicted... sorry for the spam!
v2 updates:
- rebased to includes changes from the 'libbpf: BPF program manual
loading'[^2] patch series that conflicts with that series.
[^1]: https://lpc.events/event/20/contributions/2437/
[^2]: https://lore.kernel.org/bpf/20261029223348.3730738-1-andrey.grodzovsky@crowdstrike.com/
Mahe Tardy (5):
libbpf: Count entry programs for bpf_gen__init()
libbpf: Track generated loader link FDs
libbpf: Emit BPF_LINK_CREATE calls in generated loaders
bpftool: Add attach option for light skeleton generation
selftests/bpf: Test gen loader attach
.../bpf/bpftool/Documentation/bpftool-gen.rst | 11 +-
tools/bpf/bpftool/bash-completion/bpftool | 2 +-
tools/bpf/bpftool/gen.c | 67 +++--
tools/lib/bpf/bpf_gen_internal.h | 7 +
tools/lib/bpf/gen_loader.c | 41 +++-
tools/lib/bpf/libbpf.c | 55 ++++-
tools/lib/bpf/libbpf.h | 3 +-
tools/lib/bpf/skel_internal.h | 4 +
.../bpf/prog_tests/gen_loader_attach.c | 229 ++++++++++++++++++
.../selftests/bpf/progs/gen_loader_attach.c | 48 ++++
10 files changed, 435 insertions(+), 32 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/gen_loader_attach.c
create mode 100644 tools/testing/selftests/bpf/progs/gen_loader_attach.c
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy @ 2026-11-02 20:13 ` Mahe Tardy 2026-11-02 20:45 ` bot+bpf-ci 2026-11-02 20:13 ` [PATCH bpf-next v3 2/5] libbpf: Track generated loader link FDs Mahe Tardy ` (3 subsequent siblings) 4 siblings, 1 reply; 10+ messages in thread From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw) To: bpf Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest Commit '259172bb6514 ("libbpf: Fix gen_loader assumption on number of programs.")' explicitly mention that we may pass a higher number of progs because we include subprogs and autoload=false to bpf_gen__init() generating a bigger cleanup section which is harmless. This is in preparation for the next commits that will introduce cleanup of the link_fds to bpf_gen__init(). We don't want to overallocate but have the correct count of progs in this situation so that the program-slot count matches the programs exposed by the skeleton. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- tools/lib/bpf/libbpf.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 02e75b183ed0..a7cbe7204043 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -9800,7 +9800,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch * permit cross-endian creation of "light skeleton". */ if (obj->gen_loader) { - int nr_func_ptr_maps = 0, nr_progs = 0, i; + int nr_func_ptr_maps = 0, nr_progs = 0, nr_obj_progs = 0, i; bool text_has_callx = false; /* @@ -9815,13 +9815,16 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch if (prog_is_subprog(obj, &obj->programs[i]) && prog_has_callx(&obj->programs[i])) text_has_callx = true; - for (i = 0; i < obj->nr_programs; i++) + for (i = 0; i < obj->nr_programs; i++) { + if (prog_is_subprog(obj, &obj->programs[i])) + continue; + nr_obj_progs++; if (obj->programs[i].load_strategy != BPF_PROG_LOAD_STRATEGY_DISABLED && - !prog_is_subprog(obj, &obj->programs[i]) && (text_has_callx || prog_has_callx(&obj->programs[i]))) nr_progs++; + } bpf_gen__init(obj->gen_loader, obj->log_level | extra_log_level, - obj->nr_programs, obj->nr_maps, nr_func_ptr_maps * nr_progs); + nr_obj_progs, obj->nr_maps, nr_func_ptr_maps * nr_progs); } else if (!is_native_endianness(obj)) { pr_warn("object '%s': loading non-native endianness is unsupported\n", obj->name); return libbpf_err(-LIBBPF_ERRNO__ENDIAN); -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() 2026-11-02 20:13 ` [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy @ 2026-11-02 20:45 ` bot+bpf-ci 0 siblings, 0 replies; 10+ messages in thread From: bot+bpf-ci @ 2026-11-02 20:45 UTC (permalink / raw) To: mahe.tardy, bpf Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, shuah, mahe.tardy, hi, gyutae.bae, sh_def, josef, linux-kernel, linux-kselftest, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2467 bytes --] > libbpf: Count entry programs for bpf_gen__init() TL;DR: Nit only: the comment in bpf_gen__init() in gen_loader.c still says nr_progs includes subprogs, which is now stale after this change. Suggests updating it. > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c > --- a/tools/lib/bpf/libbpf.c > +++ b/tools/lib/bpf/libbpf.c [ ... ] > @@ -9815,13 +9815,16 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch > if (prog_is_subprog(obj, &obj->programs[i]) && > prog_has_callx(&obj->programs[i])) > text_has_callx = true; > - for (i = 0; i < obj->nr_programs; i++) > + for (i = 0; i < obj->nr_programs; i++) { > + if (prog_is_subprog(obj, &obj->programs[i])) > + continue; > + nr_obj_progs++; > if (obj->programs[i].load_strategy != BPF_PROG_LOAD_STRATEGY_DISABLED && > - !prog_is_subprog(obj, &obj->programs[i]) && > (text_has_callx || prog_has_callx(&obj->programs[i]))) > nr_progs++; > + } > bpf_gen__init(obj->gen_loader, obj->log_level | extra_log_level, > - obj->nr_programs, obj->nr_maps, nr_func_ptr_maps * nr_progs); > + nr_obj_progs, obj->nr_maps, nr_func_ptr_maps * nr_progs); This isn't a bug, but the comment in bpf_gen__init() now looks stale: tools/lib/bpf/gen_loader.c:bpf_gen__init() { ... /* nr_progs includes subprogs and progs that are not loaded */ nr_progs = min(nr_progs, MAX_USED_PROGS); ... } With this change, the nr_progs argument no longer includes subprogs. It is the number of entry programs, which is the same count that bpf_object__for_each_program() and the skeleton expose, and it still includes entry programs that are not loaded. The comment came from 259172bb6514 ("libbpf: Fix gen_loader assumption on number of programs."), which the commit message cites. None of the later patches in the series (276212ecac70, 28aa06d2c9ca, c9412c70e104, 5b9c2e5eed2a) update it. 276212ecac70 ("libbpf: Track generated loader link FDs") also sizes link_fds from this same nr_progs value, so the comment now misdescribes a value that later code depends on. Should the comment be changed to something like "nr_progs includes progs that are not loaded, but not subprogs"? --- 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/41519963115 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH bpf-next v3 2/5] libbpf: Track generated loader link FDs 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy @ 2026-11-02 20:13 ` Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy ` (2 subsequent siblings) 4 siblings, 0 replies; 10+ messages in thread From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw) To: bpf Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest Introduce gen_attach field to emit program attachment instructions in generated SYSCALL loaders. Generate failure cleanup that closes any created links before releasing programs and maps reusing emit_sys_close_blob(). Store the link fds in the data buffer similarly to map fds. Append bpf_link_desc outputs after the program descriptors and copy the link FDs to the loader context on success. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- tools/lib/bpf/bpf_gen_internal.h | 2 ++ tools/lib/bpf/gen_loader.c | 17 ++++++++++++++++- tools/lib/bpf/libbpf.h | 3 ++- tools/lib/bpf/skel_internal.h | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h index 206adf28793d..b601269abe45 100644 --- a/tools/lib/bpf/bpf_gen_internal.h +++ b/tools/lib/bpf/bpf_gen_internal.h @@ -39,6 +39,8 @@ struct bpf_gen { ssize_t cleanup_label; __u32 nr_progs; __u32 nr_maps; + int link_fds; + __u32 nr_link_slots; int log_level; int error; struct ksym_relo_desc *relos; diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c index 555d4757a898..7f41b5a517fe 100644 --- a/tools/lib/bpf/gen_loader.c +++ b/tools/lib/bpf/gen_loader.c @@ -122,6 +122,10 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps gen->log_level = log_level; gen->nr_obj_maps = nr_maps; gen->max_func_ptr_maps = max_func_ptr_maps; + if (OPTS_GET(gen->opts, gen_attach, false)) { + gen->link_fds = add_data(gen, NULL, nr_progs * sizeof(int)); + gen->nr_link_slots = nr_progs; + } if (nr_maps + max_func_ptr_maps > MAX_USED_MAPS) { pr_warn("Total maps exceeds %d\n", MAX_USED_MAPS); gen->error = -E2BIG; @@ -150,10 +154,14 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps /* 6 insns for emit_sys_close_blob, * 6 insns for debug_regs in emit_sys_close_blob */ - nr_maps * (6 + (gen->log_level ? 6 : 0)))); + (nr_maps + gen->nr_link_slots) * + (6 + (gen->log_level ? 6 : 0)))); /* remember the label where all error branches will jump to */ gen->cleanup_label = gen->insn_cur - gen->insn_start; + /* Detach any programs activated before a later operation failed. */ + for (i = 0; i < gen->nr_link_slots; i++) + emit_sys_close_blob(gen, gen->link_fds + i * sizeof(int)); /* emit cleanup code: close all temp FDs */ for (i = 0; i < nr_progs_sz; i += 4) { emit(gen, BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_10, -stack_sz + i)); @@ -413,6 +421,13 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps) sizeof(struct bpf_map_desc) * i + offsetof(struct bpf_map_desc, map_fd), 4, blob_fd_array_off(gen, i)); + for (i = 0; i < gen->nr_link_slots; i++) + move_blob2ctx(gen, + sizeof(struct bpf_loader_ctx) + + sizeof(struct bpf_map_desc) * gen->nr_maps + + sizeof(struct bpf_prog_desc) * gen->nr_link_slots + + sizeof(struct bpf_link_desc) * i, 4, + gen->link_fds + i * sizeof(int)); emit(gen, BPF_MOV64_IMM(BPF_REG_0, 0)); emit(gen, BPF_EXIT_INSN()); if (!gen->error) { diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index 22204a544b23..a2ee239c114d 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1990,9 +1990,10 @@ struct gen_loader_opts { __u32 data_sz; __u32 insns_sz; bool gen_hash; + bool gen_attach; }; -#define gen_loader_opts__last_field gen_hash +#define gen_loader_opts__last_field gen_attach LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts); diff --git a/tools/lib/bpf/skel_internal.h b/tools/lib/bpf/skel_internal.h index 9e16b8e8cb7c..cac7b1d2abfb 100644 --- a/tools/lib/bpf/skel_internal.h +++ b/tools/lib/bpf/skel_internal.h @@ -47,6 +47,10 @@ struct bpf_prog_desc { int prog_fd; }; +struct bpf_link_desc { + int link_fd; +}; + enum { BPF_SKEL_KERNEL = (1ULL << 0), }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 2/5] libbpf: Track generated loader link FDs Mahe Tardy @ 2026-11-02 20:13 ` Mahe Tardy 2026-11-03 17:59 ` Alexei Starovoitov 2026-11-02 20:13 ` [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 5/5] selftests/bpf: Test gen loader attach Mahe Tardy 4 siblings, 1 reply; 10+ messages in thread From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw) To: bpf Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest Introduce bpf_gen__link_create() which builds the attachment attributes from the loaded program FD, emits the helper call then save the returned link FD in its data buffer slot for context output or failure cleanup. Generate the attachment instruction after program and map init and before loader finalization. Keep a mapping from dense loaded-program slots to context output slots so disabled programs do not shift the returned program FDs relative to their link descriptors. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- tools/lib/bpf/bpf_gen_internal.h | 5 ++++ tools/lib/bpf/gen_loader.c | 24 +++++++++++++++-- tools/lib/bpf/libbpf.c | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h index b601269abe45..1332094bfd4b 100644 --- a/tools/lib/bpf/bpf_gen_internal.h +++ b/tools/lib/bpf/bpf_gen_internal.h @@ -6,6 +6,8 @@ #include "bpf.h" #include "libbpf_internal.h" +#define MAX_USED_PROGS 32 + struct ksym_relo_desc { const char *name; int kind; @@ -41,6 +43,7 @@ struct bpf_gen { __u32 nr_maps; int link_fds; __u32 nr_link_slots; + int prog_output_idx[MAX_USED_PROGS]; int log_level; int error; struct ksym_relo_desc *relos; @@ -75,6 +78,8 @@ void bpf_gen__prog_load(struct bpf_gen *gen, enum bpf_prog_type prog_type, const char *prog_name, const char *license, struct bpf_insn *insns, size_t insn_cnt, struct bpf_prog_load_opts *load_attr, int prog_idx); +void bpf_gen__link_create(struct bpf_gen *gen, int prog_idx, int link_idx, + enum bpf_attach_type attach_type); void bpf_gen__map_update_elem(struct bpf_gen *gen, int map_idx, void *value, __u32 value_size, __u64 flags); void bpf_gen__map_freeze(struct bpf_gen *gen, int map_idx); diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c index 7f41b5a517fe..06d056a326f0 100644 --- a/tools/lib/bpf/gen_loader.c +++ b/tools/lib/bpf/gen_loader.c @@ -16,7 +16,6 @@ #include "skel_internal.h" #define MAX_USED_MAPS 64 -#define MAX_USED_PROGS 32 #define MAX_KFUNC_DESCS 256 #define MAX_FD_ARRAY_SZ (MAX_USED_MAPS + MAX_KFUNC_DESCS) @@ -412,7 +411,8 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps) move_stack2ctx(gen, sizeof(struct bpf_loader_ctx) + sizeof(struct bpf_map_desc) * gen->nr_maps + - sizeof(struct bpf_prog_desc) * i + + sizeof(struct bpf_prog_desc) * + (gen->nr_link_slots ? gen->prog_output_idx[i] : i) + offsetof(struct bpf_prog_desc, prog_fd), 4, stack_off(prog_fd[i])); for (i = 0; i < gen->nr_maps; i++) @@ -1030,6 +1030,26 @@ static void info_blob_bswap(struct bpf_gen *gen, int func_info, int line_info, bpf_core_relo_bswap(cr++); } +void bpf_gen__link_create(struct bpf_gen *gen, int prog_idx, int link_idx, + enum bpf_attach_type attach_type) +{ + int attr_size = offsetofend(union bpf_attr, link_create.flags); + union bpf_attr attr; + int link_create_attr; + + memset(&attr, 0, attr_size); + attr.link_create.attach_type = tgt_endian(attach_type); + link_create_attr = add_data(gen, &attr, attr_size); + move_stack2blob(gen, attr_field(link_create_attr, link_create.prog_fd), 4, + stack_off(prog_fd[prog_idx])); + emit_sys_bpf(gen, BPF_LINK_CREATE, link_create_attr, attr_size); + debug_ret(gen, "link_create prog_idx %d attach_type %u", prog_idx, attach_type); + emit_check_err(gen); + emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_0, BPF_PSEUDO_MAP_IDX_VALUE, + 0, 0, 0, gen->link_fds + link_idx * sizeof(int))); + emit(gen, BPF_STX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, 0)); +} + void bpf_gen__prog_load(struct bpf_gen *gen, enum bpf_prog_type prog_type, const char *prog_name, const char *license, struct bpf_insn *insns, size_t insn_cnt, diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index a7cbe7204043..32f041e999c5 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -9784,6 +9784,47 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat return 0; } +static bool bpf_program_supports_gen_attach(const struct bpf_program *prog) +{ + switch (prog->type) { + case BPF_PROG_TYPE_LSM: + return prog->expected_attach_type == BPF_LSM_MAC; + case BPF_PROG_TYPE_TRACING: + return prog->expected_attach_type == BPF_TRACE_FENTRY || + prog->expected_attach_type == BPF_TRACE_FEXIT || + prog->expected_attach_type == BPF_MODIFY_RETURN || + prog->expected_attach_type == BPF_TRACE_RAW_TP; + default: + return false; + } +} + +static int bpf_object_gen_links(struct bpf_object *obj) +{ + struct bpf_program *prog; + int prog_idx = -1, link_idx = -1; + + bpf_object__for_each_program(prog, obj) { + link_idx++; + if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) + continue; + prog_idx++; + obj->gen_loader->prog_output_idx[prog_idx] = link_idx; + if (!prog->autoattach) + continue; + if (!bpf_program_supports_gen_attach(prog)) { + pr_warn("prog '%s': loader attachment is not supported for section '%s'\n", + prog->name, prog->sec_name); + return -EOPNOTSUPP; + } + bpf_gen__link_create(obj->gen_loader, prog_idx, link_idx, + prog->expected_attach_type); + if (obj->gen_loader->error) + return obj->gen_loader->error; + } + return 0; +} + static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const char *target_btf_path) { int err; @@ -9843,6 +9884,9 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch /* reset FDs */ if (obj->btf) btf__set_fd(obj->btf, -1); + if (!err && !obj->gen_loader->error && + OPTS_GET(obj->gen_loader->opts, gen_attach, false)) + err = bpf_object_gen_links(obj); if (!err) err = bpf_gen__finish(obj->gen_loader, obj->nr_programs, obj->nr_maps); } -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders 2026-11-02 20:13 ` [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy @ 2026-11-03 17:59 ` Alexei Starovoitov 0 siblings, 0 replies; 10+ messages in thread From: Alexei Starovoitov @ 2026-11-03 17:59 UTC (permalink / raw) To: Mahe Tardy, bpf Cc: Quentin Monnet, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest On Mon, Nov 02, 2026 at 12:13 PM Mahe Tardy <mahe.tardy@gmail.com> wrote: > @@ -412,7 +411,8 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps) > move_stack2ctx(gen, > sizeof(struct bpf_loader_ctx) + > sizeof(struct bpf_map_desc) * gen->nr_maps + > - sizeof(struct bpf_prog_desc) * i + > + sizeof(struct bpf_prog_desc) * > + (gen->nr_link_slots ? gen->prog_output_idx[i] : i) + This is an existing bug. It's not specific to gen_attach. > + if (!bpf_program_supports_gen_attach(prog)) { > + pr_warn("prog '%s': loader attachment is not supported for section '%s'\n", > + prog->name, prog->sec_name); > + return -EOPNOTSUPP; > + } bpf_object__attach_skeleton(), __attach() of lskel and "bpftool prog load ... autoattach" skip the progs they cannot attach. Here one SEC("syscall") or SEC("iter/...") prog fails the generation. bpftool gen skeleton has no way to clear autoattach. Let's skip such progs here too ? Also fsession is not in the list. It goes through bpf_tracing_prog_attach() like fentry. > + bpf_gen__link_create(obj->gen_loader, prog_idx, link_idx, > + prog->expected_attach_type); I think it can be implemented a bit simpler. Keep link_fd next to prog_fd in struct loader_stack. The cleanup loop in bpf_gen__init() closes both. It costs 3 more insns per prog in the cleanup code. Then no need for link_fds, nr_link_slots, link_idx, struct bpf_link_desc and patch 1. In the skeleton __attach() skips the progs that have a link, like bpf_object__attach_skeleton() does. It attaches the rest from user space as before. Then no need for the bool in codegen_attach_detach(). Attach after detach works too. fentry_test.c does that. The diff replaces patches 1-4 and has the fix for the shift. Doc and bash completion are missing. Something like this (not even compile tested): diff --git a/tools/lib/bpf/bpf_gen_internal.h b/tools/lib/bpf/bpf_gen_internal.h --- a/tools/lib/bpf/bpf_gen_internal.h +++ b/tools/lib/bpf/bpf_gen_internal.h @@ -6,6 +6,8 @@ #include "bpf.h" #include "libbpf_internal.h" +#define MAX_USED_PROGS 32 + struct ksym_relo_desc { const char *name; int kind; @@ -39,6 +41,8 @@ struct bpf_gen { ssize_t cleanup_label; __u32 nr_progs; __u32 nr_maps; + /* slot of every loaded prog in progs and links of the loader ctx */ + int prog_ctx_idx[MAX_USED_PROGS]; int log_level; int error; struct ksym_relo_desc *relos; @@ -73,6 +77,7 @@ void bpf_gen__prog_load(struct bpf_gen *gen, enum bpf_prog_type prog_type, const char *prog_name, const char *license, struct bpf_insn *insns, size_t insn_cnt, struct bpf_prog_load_opts *load_attr, int prog_idx); +void bpf_gen__link_create(struct bpf_gen *gen, int loaded_idx, enum bpf_attach_type attach_type); void bpf_gen__map_update_elem(struct bpf_gen *gen, int map_idx, void *value, __u32 value_size, __u64 flags); void bpf_gen__map_freeze(struct bpf_gen *gen, int map_idx); diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c --- a/tools/lib/bpf/gen_loader.c +++ b/tools/lib/bpf/gen_loader.c @@ -16,7 +16,6 @@ #include "skel_internal.h" #define MAX_USED_MAPS 64 -#define MAX_USED_PROGS 32 #define MAX_KFUNC_DESCS 256 #define MAX_FD_ARRAY_SZ (MAX_USED_MAPS + MAX_KFUNC_DESCS) @@ -34,7 +33,10 @@ struct loader_stack { __u32 btf_fd; __u32 inner_map_fd; - __u32 prog_fd[MAX_USED_PROGS]; + struct { + __u32 prog_fd; + __u32 link_fd; + } progs[MAX_USED_PROGS]; }; #define stack_off(field) \ @@ -142,7 +144,7 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps /* nr_progs includes subprogs and progs that are not loaded */ nr_progs = min(nr_progs, MAX_USED_PROGS); /* amount of stack actually used, only used to calculate iterations, not stack offset */ - nr_progs_sz = offsetof(struct loader_stack, prog_fd[nr_progs]); + nr_progs_sz = offsetof(struct loader_stack, progs[nr_progs]); /* jump over cleanup code */ emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, /* size of cleanup code below (including map fd cleanup) */ @@ -400,13 +402,23 @@ int bpf_gen__finish(struct bpf_gen *gen, int nr_progs, int nr_maps) /* programs hold their maps with pointers to functions, nothing else needs them */ for (i = 0; i < gen->nr_func_ptr_maps; i++) emit_sys_close_blob(gen, blob_fd_array_off(gen, gen->nr_obj_maps + i)); - for (i = 0; i < gen->nr_progs; i++) + for (i = 0; i < gen->nr_progs; i++) { move_stack2ctx(gen, sizeof(struct bpf_loader_ctx) + sizeof(struct bpf_map_desc) * gen->nr_maps + - sizeof(struct bpf_prog_desc) * i + + sizeof(struct bpf_prog_desc) * gen->prog_ctx_idx[i] + offsetof(struct bpf_prog_desc, prog_fd), 4, - stack_off(prog_fd[i])); + stack_off(progs[i].prog_fd)); + if (!OPTS_GET(gen->opts, gen_attach, false)) + continue; + /* nr_progs link fds follow nr_progs bpf_prog_desc-s */ + move_stack2ctx(gen, + sizeof(struct bpf_loader_ctx) + + sizeof(struct bpf_map_desc) * gen->nr_maps + + sizeof(struct bpf_prog_desc) * nr_progs + + sizeof(int) * gen->prog_ctx_idx[i], 4, + stack_off(progs[i].link_fd)); + } for (i = 0; i < gen->nr_maps; i++) move_blob2ctx(gen, sizeof(struct bpf_loader_ctx) + @@ -1143,10 +1155,38 @@ void bpf_gen__prog_load(struct bpf_gen *gen, } /* remember prog_fd in the stack, if successful */ emit(gen, BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_7, - stack_off(prog_fd[gen->nr_progs]))); + stack_off(progs[gen->nr_progs].prog_fd))); + gen->prog_ctx_idx[gen->nr_progs] = prog_idx; gen->nr_progs++; } +/* attach the prog that bpf_gen__prog_load() saw as number loaded_idx */ +void bpf_gen__link_create(struct bpf_gen *gen, int loaded_idx, enum bpf_attach_type attach_type) +{ + int attr_size = offsetofend(union bpf_attr, link_create.flags); + int link_create_attr; + union bpf_attr attr; + + if (!OPTS_GET(gen->opts, gen_attach, false)) + return; + + memset(&attr, 0, attr_size); + attr.link_create.attach_type = tgt_endian(attach_type); + link_create_attr = add_data(gen, &attr, attr_size); + pr_debug("gen: link_create: prog %d attach_type %u, attr: off %d size %d\n", + loaded_idx, attach_type, link_create_attr, attr_size); + /* populate union bpf_attr with prog_fd saved in the stack earlier */ + move_stack2blob(gen, attr_field(link_create_attr, link_create.prog_fd), 4, + stack_off(progs[loaded_idx].prog_fd)); + /* emit LINK_CREATE command */ + emit_sys_bpf(gen, BPF_LINK_CREATE, link_create_attr, attr_size); + debug_ret(gen, "link_create prog %d attach_type %d", loaded_idx, attach_type); + emit_check_err(gen); + /* remember link_fd in the stack, if successful */ + emit(gen, BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_7, + stack_off(progs[loaded_idx].link_fd))); +} + /* * if (map_desc[map_idx].initial_value) { * if (ctx->flags & BPF_SKEL_KERNEL) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8593,9 +8593,14 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog, } if (obj->gen_loader) { + int i, prog_idx = 0; + + /* subprogs have no slot in the loader ctx */ + for (i = 0; i < prog - obj->programs; i++) + if (!prog_is_subprog(obj, &obj->programs[i])) + prog_idx++; bpf_gen__prog_load(obj->gen_loader, prog->type, prog->name, - license, insns, insns_cnt, &load_attr, - prog - obj->programs); + license, insns, insns_cnt, &load_attr, prog_idx); *prog_fd = -1; return 0; } @@ -9784,6 +9789,40 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat return 0; } +/* The target is known since the load. BPF_LINK_CREATE needs prog_fd only. */ +static bool gen_loader_can_attach(const struct bpf_program *prog) +{ + switch (prog->type) { + case BPF_PROG_TYPE_LSM: + return prog->expected_attach_type == BPF_LSM_MAC; + case BPF_PROG_TYPE_TRACING: + return prog->expected_attach_type == BPF_TRACE_FENTRY || + prog->expected_attach_type == BPF_TRACE_FEXIT || + prog->expected_attach_type == BPF_TRACE_FSESSION || + prog->expected_attach_type == BPF_MODIFY_RETURN || + prog->expected_attach_type == BPF_TRACE_RAW_TP; + default: + return false; + } +} + +static int bpf_object_gen_finish(struct bpf_object *obj) +{ + int nr_progs = 0, nr_loaded = 0; + struct bpf_program *prog; + + bpf_object__for_each_program(prog, obj) { + nr_progs++; + if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) + continue; + if (prog->autoattach && gen_loader_can_attach(prog)) + bpf_gen__link_create(obj->gen_loader, nr_loaded, + prog->expected_attach_type); + nr_loaded++; + } + return bpf_gen__finish(obj->gen_loader, nr_progs, obj->nr_maps); +} + static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const char *target_btf_path) { int err; @@ -9841,7 +9880,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch if (obj->btf) btf__set_fd(obj->btf, -1); if (!err) - err = bpf_gen__finish(obj->gen_loader, obj->nr_programs, obj->nr_maps); + err = bpf_object_gen_finish(obj); } bpf_object_post_load_cleanup(obj, false); diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -1990,9 +1990,10 @@ struct gen_loader_opts { __u32 data_sz; __u32 insns_sz; bool gen_hash; + bool gen_attach; }; -#define gen_loader_opts__last_field gen_hash +#define gen_loader_opts__last_field gen_attach LIBBPF_API int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts); diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c --- a/tools/bpf/bpftool/gen.c +++ b/tools/bpf/bpftool/gen.c @@ -621,7 +621,8 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name) bpf_object__for_each_program(prog, obj) { codegen("\ \n\ - ret = ret < 0 ? ret : %1$s__%2$s__attach(skel); \n\ + if (ret >= 0 && skel->links.%2$s_fd <= 0) \n\ + ret = %1$s__%2$s__attach(skel); \n\ ", obj_name, bpf_program__name(prog)); } @@ -639,6 +640,7 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name) codegen("\ \n\ skel_closenz(skel->links.%1$s_fd); \n\ + skel->links.%1$s_fd = 0; \n\ ", bpf_program__name(prog)); } @@ -691,7 +693,8 @@ 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, + bool gen_attach) { DECLARE_LIBBPF_OPTS(gen_loader_opts, opts); struct bpf_load_and_run_opts sopts = {}; @@ -702,6 +705,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h char ident[256]; int err = 0; + opts.gen_attach = gen_attach; if (sign_progs) opts.gen_hash = true; @@ -740,7 +744,7 @@ 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\ + skel->ctx.sz = (char *)(&skel->links + 1) - (char *)skel; \n\ ", obj_name, opts.data_sz); bpf_object__for_each_map(map, obj) { @@ -1249,6 +1253,7 @@ static int do_skeleton(int argc, char **argv) struct bpf_map *map; struct btf *btf; struct stat st; + bool gen_attach = false; if (!REQ_ARGS(1)) { usage(); @@ -1257,10 +1262,9 @@ static int do_skeleton(int argc, char **argv) file = GET_ARG(); while (argc) { - if (!REQ_ARGS(2)) - return -1; - if (is_prefix(*argv, "name")) { + if (!REQ_ARGS(2)) + return -1; NEXT_ARG(); if (obj_name[0] != '\0') { @@ -1270,6 +1274,8 @@ static int do_skeleton(int argc, char **argv) strncpy(obj_name, *argv, MAX_OBJ_NAME_LEN - 1); obj_name[MAX_OBJ_NAME_LEN - 1] = '\0'; + } else if (is_prefix(*argv, "autoattach")) { + gen_attach = true; } else { p_err("unknown arg %s", *argv); return -1; @@ -1278,6 +1284,11 @@ static int do_skeleton(int argc, char **argv) NEXT_ARG(); } + if (gen_attach && !use_loader) { + p_err("autoattach requires a light skeleton (-L)"); + return -1; + } + if (argc) { p_err("extra unknown arguments"); return -1; @@ -1441,7 +1452,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_attach); goto out; } @@ -2005,7 +2016,7 @@ static int do_help(int argc, char **argv) fprintf(stderr, "Usage: %1$s %2$s object OUTPUT_FILE INPUT_FILE [INPUT_FILE...]\n" - " %1$s %2$s skeleton FILE [name OBJECT_NAME]\n" + " %1$s %2$s skeleton FILE [name OBJECT_NAME] [autoattach]\n" " %1$s %2$s subskeleton FILE [name OBJECT_NAME]\n" " %1$s %2$s min_core_btf INPUT OUTPUT OBJECT [OBJECT...]\n" " %1$s %2$s help\n" pw-bot: cr ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy ` (2 preceding siblings ...) 2026-11-02 20:13 ` [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy @ 2026-11-02 20:13 ` Mahe Tardy 2026-11-02 20:41 ` sashiko-bot 2026-11-02 20:56 ` bot+bpf-ci 2026-11-02 20:13 ` [PATCH bpf-next v3 5/5] selftests/bpf: Test gen loader attach Mahe Tardy 4 siblings, 2 replies; 10+ messages in thread From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw) To: bpf Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest Add an optional attach keyword to gen skeleton when the loader backend is selected, and pass the request through gen_loader_opts.gen_attach. Extend the loader context to receive link FDs, using zero for empty slots. Generate attach functions that check the returned links, and detach functions that close and reset the FDs. Document the option and add shell completion. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- .../bpf/bpftool/Documentation/bpftool-gen.rst | 11 ++- tools/bpf/bpftool/bash-completion/bpftool | 2 +- tools/bpf/bpftool/gen.c | 67 +++++++++++++------ 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/tools/bpf/bpftool/Documentation/bpftool-gen.rst b/tools/bpf/bpftool/Documentation/bpftool-gen.rst index d0a36f442db7..4bf8b0b99ea2 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-gen.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-gen.rst @@ -24,7 +24,7 @@ GEN COMMANDS ============= | **bpftool** **gen object** *OUTPUT_FILE* *INPUT_FILE* [*INPUT_FILE*...] -| **bpftool** **gen skeleton** *FILE* [**name** *OBJECT_NAME*] +| **bpftool** **gen skeleton** *FILE* [**name** *OBJECT_NAME*] [**attach**] | **bpftool** **gen subskeleton** *FILE* [**name** *OBJECT_NAME*] | **bpftool** **gen min_core_btf** *INPUT* *OUTPUT* *OBJECT* [*OBJECT*...] | **bpftool** **gen help** @@ -51,6 +51,15 @@ bpftool gen object *OUTPUT_FILE* *INPUT_FILE* [*INPUT_FILE*...] bpftool gen skeleton *FILE* Generate BPF skeleton C header file for a given *FILE*. + With **-L**, **attach** requests attachment during execution of the + generated loader. Supported programs are ordinary LSM programs and + BTF-based tracing programs using **fentry**, **fexit**, **fmod_ret**, or + **tp_btf** sections, with kernel targets resolved during loading. + The generated **__load()** function loads and attaches the programs; + **__attach()** checks that the links exist, and **__detach()** closes them. + Reattaching after detachment is not supported in this mode. Without + **attach**, loading and attachment remain separate operations. + BPF skeleton is an alternative interface to existing libbpf APIs for working with BPF objects. Skeleton code is intended to significantly shorten and simplify code to load and work with BPF programs from userspace diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool index a28af31937ad..9247a9fdcaef 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -1021,7 +1021,7 @@ _bpftool() return 0 ;; *) - _bpftool_once_attr 'name' + _bpftool_once_attr 'name attach' return 0 ;; esac diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c index ee680f7c2116..62e21bd773ff 100644 --- a/tools/bpf/bpftool/gen.c +++ b/tools/bpf/bpftool/gen.c @@ -566,7 +566,8 @@ static void codegen_asserts(struct bpf_object *obj, const char *obj_name) "); } -static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name) +static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name, + bool gen_attach) { struct bpf_program *prog; @@ -582,21 +583,30 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name) int prog_fd = skel->progs.%2$s.prog_fd; \n\ ", obj_name, bpf_program__name(prog)); - switch (bpf_program__type(prog)) { - case BPF_PROG_TYPE_RAW_TRACEPOINT: - tp_name = strchr(bpf_program__section_name(prog), '/') + 1; - printf("\tint fd = skel_raw_tracepoint_open(\"%s\", prog_fd);\n", tp_name); - break; - case BPF_PROG_TYPE_TRACING: - case BPF_PROG_TYPE_LSM: - if (bpf_program__expected_attach_type(prog) == BPF_TRACE_ITER) - printf("\tint fd = skel_link_create(prog_fd, 0, BPF_TRACE_ITER);\n"); + if (gen_attach) { + if (bpf_program__autoload(prog) && bpf_program__autoattach(prog)) + printf("\tint fd = ((void)prog_fd, skel->links.%s_fd <= 0 ? -ENOLINK : 0);\n", + bpf_program__name(prog)); else - printf("\tint fd = skel_raw_tracepoint_open(NULL, prog_fd);\n"); - break; - default: - printf("\tint fd = ((void)prog_fd, 0); /* auto-attach not supported */\n"); - break; + printf("\tint fd = ((void)prog_fd, 0);\n"); + } else { + switch (bpf_program__type(prog)) { + case BPF_PROG_TYPE_RAW_TRACEPOINT: + tp_name = strchr(bpf_program__section_name(prog), '/') + 1; + printf("\tint fd = skel_raw_tracepoint_open(\"%s\", prog_fd);\n", + tp_name); + break; + case BPF_PROG_TYPE_TRACING: + case BPF_PROG_TYPE_LSM: + if (bpf_program__expected_attach_type(prog) == BPF_TRACE_ITER) + printf("\tint fd = skel_link_create(prog_fd, 0, BPF_TRACE_ITER);\n"); + else + printf("\tint fd = skel_raw_tracepoint_open(NULL, prog_fd);\n"); + break; + default: + printf("\tint fd = ((void)prog_fd, 0); /* auto-attach not supported */\n"); + break; + } } codegen("\ \n\ @@ -640,6 +650,8 @@ static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name) \n\ skel_closenz(skel->links.%1$s_fd); \n\ ", bpf_program__name(prog)); + if (gen_attach) + printf("\tskel->links.%s_fd = 0;\n", bpf_program__name(prog)); } codegen("\ @@ -691,7 +703,8 @@ 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, + bool gen_attach) { DECLARE_LIBBPF_OPTS(gen_loader_opts, opts); struct bpf_load_and_run_opts sopts = {}; @@ -702,6 +715,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h char ident[256]; int err = 0; + opts.gen_attach = gen_attach; if (sign_progs) opts.gen_hash = true; @@ -726,7 +740,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h ", obj_name); - codegen_attach_detach(obj, obj_name); + codegen_attach_detach(obj, obj_name, gen_attach); codegen_destroy(obj, obj_name); @@ -743,6 +757,8 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h skel->ctx.sz = (char *)&skel->links - (char *)skel; \n\ ", obj_name, opts.data_sz); + if (gen_attach) + printf("\tskel->ctx.sz += sizeof(skel->links);\n"); bpf_object__for_each_map(map, obj) { const void *mmap_data = NULL; size_t mmap_size = 0; @@ -1249,6 +1265,7 @@ static int do_skeleton(int argc, char **argv) struct bpf_map *map; struct btf *btf; struct stat st; + bool gen_attach = false; if (!REQ_ARGS(1)) { usage(); @@ -1257,10 +1274,9 @@ static int do_skeleton(int argc, char **argv) file = GET_ARG(); while (argc) { - if (!REQ_ARGS(2)) - return -1; - if (is_prefix(*argv, "name")) { + if (!REQ_ARGS(2)) + return -1; NEXT_ARG(); if (obj_name[0] != '\0') { @@ -1270,6 +1286,8 @@ static int do_skeleton(int argc, char **argv) strncpy(obj_name, *argv, MAX_OBJ_NAME_LEN - 1); obj_name[MAX_OBJ_NAME_LEN - 1] = '\0'; + } else if (is_prefix(*argv, "attach")) { + gen_attach = true; } else { p_err("unknown arg %s", *argv); return -1; @@ -1278,6 +1296,11 @@ static int do_skeleton(int argc, char **argv) NEXT_ARG(); } + if (gen_attach && !use_loader) { + p_err("attach requires a light skeleton (-L)"); + return -1; + } + if (argc) { p_err("extra unknown arguments"); return -1; @@ -1441,7 +1464,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_attach); goto out; } @@ -2005,7 +2028,7 @@ static int do_help(int argc, char **argv) fprintf(stderr, "Usage: %1$s %2$s object OUTPUT_FILE INPUT_FILE [INPUT_FILE...]\n" - " %1$s %2$s skeleton FILE [name OBJECT_NAME]\n" + " %1$s %2$s skeleton FILE [name OBJECT_NAME] [attach]\n" " %1$s %2$s subskeleton FILE [name OBJECT_NAME]\n" " %1$s %2$s min_core_btf INPUT OUTPUT OBJECT [OBJECT...]\n" " %1$s %2$s help\n" -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation 2026-11-02 20:13 ` [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy @ 2026-11-02 20:41 ` sashiko-bot 2026-11-02 20:56 ` bot+bpf-ci 1 sibling, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-11-02 20:41 UTC (permalink / raw) To: Mahe Tardy, bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] bpftool: bash completion suggests attach after name in _bpftool() -- commit f5b94ca511f36c96410ef0757d692a7a6f4ea1cf Author: Mahe Tardy <mahe.tardy@gmail.com> bpftool: Add attach option for light skeleton generation This commit adds an optional attach keyword to bpftool gen skeleton when generating light skeletons with the loader backend (-L), passing the request through gen_loader_opts.gen_attach and updating the generated attach and detach helpers, documentation, and bash completion. > diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool > index a28af31937ad4..9247a9fdcaef9 100644 > --- a/tools/bpf/bpftool/bash-completion/bpftool > +++ b/tools/bpf/bpftool/bash-completion/bpftool > @@ -1021,7 +1021,7 @@ _bpftool() > return 0 > ;; > *) > - _bpftool_once_attr 'name' > + _bpftool_once_attr 'name attach' [Severity: Low] In _bpftool() under the gen -> skeleton case: skeleton) case $prev in $command) _filedir return 0 ;; *) _bpftool_once_attr 'name attach' return 0 ;; esac When a user types "bpftool gen skeleton <file> name <TAB>", $prev is "name" and falls through to the wildcard branch. Because "name" is already on the command line and "attach" is not, won't _bpftool_once_attr 'name attach' autocomplete "attach" as the object name argument to "name"? Should there be a "name) return 0 ;;" case before the wildcard branch so that completion does not suggest "attach" when expecting the user-supplied object name? -- Sashiko AI review · https://sashiko.dev/#/patchset/20261102201356.5798-1-mahe.tardy@gmail.com?part=4 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation 2026-11-02 20:13 ` [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy 2026-11-02 20:41 ` sashiko-bot @ 2026-11-02 20:56 ` bot+bpf-ci 1 sibling, 0 replies; 10+ messages in thread From: bot+bpf-ci @ 2026-11-02 20:56 UTC (permalink / raw) To: mahe.tardy, bpf Cc: qmo, ast, daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil, ihor.solodrai, shuah, mahe.tardy, hi, gyutae.bae, sh_def, josef, linux-kernel, linux-kselftest, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 1954 bytes --] > bpftool: Add attach option for light skeleton generation TL;DR: bpftool bash completion for "gen skeleton" may offer "attach" as the object name after "name <TAB>", since there is no name case before the catch-all branch. Minor completion bug. > diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool > index a28af31937ad..9247a9fdcaef 100644 > --- a/tools/bpf/bpftool/bash-completion/bpftool > +++ b/tools/bpf/bpftool/bash-completion/bpftool > @@ -1021,7 +1021,7 @@ _bpftool() > return 0 > ;; > *) > - _bpftool_once_attr 'name' > + _bpftool_once_attr 'name attach' > return 0 > ;; > esac Does this completion offer "attach" as the object name? The skeleton branch of _bpftool() has no case for prev == name, so when the cursor is on the OBJECT_NAME value it falls into the catch-all *) branch and runs _bpftool_once_attr 'name attach'. The 'name' keyword is skipped because it is already on the line, but 'attach' is not. So "bpftool gen skeleton prog.o name <TAB>" now offers "attach", and "name a<TAB>" completes to "name attach". That sets the object name to "attach" and does not request loader attachment. Before this change, _bpftool_once_attr 'name' offered nothing in that position. Other value-taking keywords in this file are handled explicitly, for example the 'map create' branch has a name) case that returns 0. Would adding a name) case that does "return 0" ahead of the *) branch for skeleton restore the previous behaviour? --- 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/41519963115 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH bpf-next v3 5/5] selftests/bpf: Test gen loader attach 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy ` (3 preceding siblings ...) 2026-11-02 20:13 ` [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy @ 2026-11-02 20:13 ` Mahe Tardy 4 siblings, 0 replies; 10+ messages in thread From: Mahe Tardy @ 2026-11-02 20:13 UTC (permalink / raw) To: bpf Cc: Quentin Monnet, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Shuah Khan, Mahe Tardy, Tianyi Chen, Gyutae Bae, Hui Su, Josef Bacik, linux-kernel, linux-kselftest Tests that nothing changes when only loading without attach, verifies that unsupported program types return an error, and check that all tracing program types successfully generate loader attach prog. Then actually checks execution of the generated loader attach for two LSM progs, verifying the links are correctly loading the appropriate programs and that autoload=false and autoattach=false disable it. Finally, checks that if one program out of many fails to attach, the cleanup with detach is done correctly. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- .../bpf/prog_tests/gen_loader_attach.c | 229 ++++++++++++++++++ .../selftests/bpf/progs/gen_loader_attach.c | 48 ++++ 2 files changed, 277 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/gen_loader_attach.c create mode 100644 tools/testing/selftests/bpf/progs/gen_loader_attach.c diff --git a/tools/testing/selftests/bpf/prog_tests/gen_loader_attach.c b/tools/testing/selftests/bpf/prog_tests/gen_loader_attach.c new file mode 100644 index 000000000000..1277709d7ef2 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/gen_loader_attach.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ +#include <test_progs.h> +#include <dirent.h> +#include <bpf/skel_internal.h> + +#define NR_PROGS 7 + +enum execution_case { + ATTACH, + NO_AUTOLOAD, + NO_AUTOATTACH, + ROLLBACK, +}; + +static int link_create_attr_offset(const struct gen_loader_opts *opts, int index) +{ + const struct bpf_insn *insns = (const void *)opts->insns; + int i, count = opts->insns_sz / sizeof(*insns); + + for (i = 4; i < count; i++) { + if (insns[i].code != (BPF_JMP | BPF_CALL) || + insns[i].imm != BPF_FUNC_sys_bpf || + insns[i - 4].code != (BPF_ALU64 | BPF_MOV | BPF_K) || + insns[i - 4].dst_reg != BPF_REG_1 || + insns[i - 4].imm != BPF_LINK_CREATE) + continue; + if (!index--) + return insns[i - 2].imm; + } + return -1; +} + +static struct bpf_object *generate(struct gen_loader_opts *opts, enum execution_case test) +{ + struct bpf_program *prog; + struct bpf_object *obj; + + obj = bpf_object__open_file("gen_loader_attach.bpf.o", NULL); + if (!ASSERT_OK_PTR(obj, "open")) + return NULL; + prog = bpf_object__find_program_by_name(obj, "lsm_open"); + bpf_program__set_autoload(prog, test != NO_AUTOLOAD); + bpf_program__set_autoattach(prog, test != NO_AUTOATTACH); + if (!ASSERT_OK(bpf_object__gen_loader(obj, opts), "gen_loader") || + !ASSERT_OK(bpf_object__load(obj), "generate")) + goto err_out; + return obj; + +err_out: + bpf_object__close(obj); + return NULL; +} + +static void check_load_only(void) +{ + LIBBPF_OPTS(gen_loader_opts, opts); + struct bpf_object *obj; + + obj = generate(&opts, ATTACH); + if (!obj) + return; + ASSERT_EQ(link_create_attr_offset(&opts, 0), -1, "no_link_create"); + bpf_object__close(obj); +} + +static void check_unsupported(void) +{ + LIBBPF_OPTS(gen_loader_opts, opts, .gen_attach = true); + struct bpf_program *prog; + struct bpf_object *obj; + + obj = bpf_object__open_file("gen_loader_attach.bpf.o", NULL); + if (!ASSERT_OK_PTR(obj, "open")) + return; + prog = bpf_object__find_program_by_name(obj, "socket"); + bpf_program__set_autoload(prog, true); + if (ASSERT_OK(bpf_object__gen_loader(obj, &opts), "gen_loader")) + ASSERT_EQ(bpf_object__load(obj), -EOPNOTSUPP, "unsupported_attach"); + bpf_object__close(obj); +} + +static void check_tracing_generation(void) +{ + LIBBPF_OPTS(gen_loader_opts, opts, .gen_attach = true); + struct bpf_program *prog; + struct bpf_object *obj; + int index = 0; + + obj = bpf_object__open_file("gen_loader_attach.bpf.o", NULL); + if (!ASSERT_OK_PTR(obj, "open")) + return; + bpf_object__for_each_program(prog, obj) + bpf_program__set_autoload(prog, bpf_program__type(prog) == BPF_PROG_TYPE_TRACING); + if (!ASSERT_OK(bpf_object__gen_loader(obj, &opts), "gen_loader") || + !ASSERT_OK(bpf_object__load(obj), "generate")) + goto out; + bpf_object__for_each_program(prog, obj) { + const union bpf_attr *attr; + int off; + + if (!bpf_program__autoload(prog)) + continue; + off = link_create_attr_offset(&opts, index++); + if (!ASSERT_GE(off, 0, bpf_program__name(prog))) + goto out; + attr = (const void *)(opts.data + off); + ASSERT_EQ(attr->link_create.attach_type, + bpf_program__expected_attach_type(prog), bpf_program__name(prog)); + } + ASSERT_EQ(link_create_attr_offset(&opts, index), -1, "link_count"); +out: + bpf_object__close(obj); +} + +static int count_fds(void) +{ + struct dirent *entry; + int count = 0; + DIR *dir; + + dir = opendir("/proc/self/fd"); + if (!dir) + return -errno; + while ((entry = readdir(dir))) + if (entry->d_name[0] != '.') + count++; + closedir(dir); + return count; +} + +static void check_execution(enum execution_case test) +{ + LIBBPF_OPTS(gen_loader_opts, gen, .gen_attach = true); + struct { + struct bpf_loader_ctx ctx; + struct bpf_prog_desc progs[NR_PROGS]; + struct bpf_link_desc links[NR_PROGS]; + } ctx = { .ctx.sz = sizeof(ctx) }; + struct bpf_load_and_run_opts run = { .ctx = &ctx.ctx }; + struct bpf_object *obj; + int i, off, err, before; + + obj = generate(&gen, test); + if (!obj) + return; + run.insns = gen.insns; + run.insns_sz = gen.insns_sz; + run.data = gen.data; + run.data_sz = gen.data_sz; + + if (test == ROLLBACK) { + off = link_create_attr_offset(&gen, 1); + if (!ASSERT_GE(off, 0, "second_link_attr")) + goto out; + /* Force the second attachment to fail after the first succeeded. */ + ((union bpf_attr *)(gen.data + off))->link_create.attach_type = BPF_TRACE_FENTRY; + + before = count_fds(); + if (!ASSERT_GE(before, 0, "count_fds")) + goto out; + ASSERT_EQ(bpf_load_and_run(&run), -EINVAL, "second_attach_failed"); + ASSERT_EQ(count_fds(), before, "rollback_fds"); + goto out; + } + + err = bpf_load_and_run(&run); + if (!ASSERT_OK(err, "run_loader")) + goto out; + + for (i = 0; i < NR_PROGS; i++) { + struct bpf_link_info link = {}; + struct bpf_prog_info prog = {}; + __u32 size; + + /* + * Only the first two programs load by default; NO_AUTOLOAD also + * skips the first. NO_AUTOATTACH must still return its program FD. + */ + if (i >= 2 || (!i && test == NO_AUTOLOAD)) { + ASSERT_EQ(ctx.progs[i].prog_fd, 0, "skipped_program"); + } else { + size = sizeof(prog); + if (!ASSERT_GT(ctx.progs[i].prog_fd, 0, "program_fd") || + !ASSERT_OK(bpf_prog_get_info_by_fd(ctx.progs[i].prog_fd, &prog, &size), + "prog_info")) + continue; + } + + /* + * Unloaded programs have no link. NO_AUTOATTACH also leaves the + * first program's link slot zero, even though its program is loaded. + */ + if (i >= 2 || (!i && (test == NO_AUTOLOAD || test == NO_AUTOATTACH))) { + ASSERT_EQ(ctx.links[i].link_fd, 0, "skipped_link"); + continue; + } + /* Verify the link references the corresponding loaded program. */ + size = sizeof(link); + if (!ASSERT_OK(bpf_link_get_info_by_fd(ctx.links[i].link_fd, &link, &size), + "link_info")) + continue; + ASSERT_EQ(link.prog_id, prog.id, "attached_program"); + } +out: + for (i = 0; i < NR_PROGS; i++) { + skel_closenz(ctx.links[i].link_fd); + skel_closenz(ctx.progs[i].prog_fd); + } + bpf_object__close(obj); +} + +void test_gen_loader_attach(void) +{ + if (test__start_subtest("load_only")) + check_load_only(); + if (test__start_subtest("unsupported")) + check_unsupported(); + if (test__start_subtest("tracing")) + check_tracing_generation(); + if (test__start_subtest("execute")) + check_execution(ATTACH); + if (test__start_subtest("execute_no_autoload")) + check_execution(NO_AUTOLOAD); + if (test__start_subtest("execute_no_autoattach")) + check_execution(NO_AUTOATTACH); + if (test__start_subtest("rollback")) + check_execution(ROLLBACK); +} diff --git a/tools/testing/selftests/bpf/progs/gen_loader_attach.c b/tools/testing/selftests/bpf/progs/gen_loader_attach.c new file mode 100644 index 000000000000..9a38fe4ac7bb --- /dev/null +++ b/tools/testing/selftests/bpf/progs/gen_loader_attach.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> + +SEC("lsm/file_open") +int lsm_open(void *ctx) +{ + return 0; +} + +SEC("lsm/file_permission") +int lsm_perm(void *ctx) +{ + return 0; +} + +SEC("?socket") +int socket(void *ctx) +{ + return 0; +} + +SEC("?fentry/security_inode_getattr") +int fentry(void *ctx) +{ + return 0; +} + +SEC("?fexit/security_inode_getattr") +int fexit(void *ctx) +{ + return 0; +} + +SEC("?fmod_ret/security_inode_getattr") +int fmod_ret(void *ctx) +{ + return 0; +} + +SEC("?tp_btf/sys_enter") +int tp_btf(void *ctx) +{ + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-03 17:59 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-02 20:13 [PATCH bpf-next v3 0/5] libbpf: Support gen loader attach Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy 2026-11-02 20:45 ` bot+bpf-ci 2026-11-02 20:13 ` [PATCH bpf-next v3 2/5] libbpf: Track generated loader link FDs Mahe Tardy 2026-11-02 20:13 ` [PATCH bpf-next v3 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy 2026-11-03 17:59 ` Alexei Starovoitov 2026-11-02 20:13 ` [PATCH bpf-next v3 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy 2026-11-02 20:41 ` sashiko-bot 2026-11-02 20:56 ` bot+bpf-ci 2026-11-02 20:13 ` [PATCH bpf-next v3 5/5] selftests/bpf: Test gen loader attach Mahe Tardy
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox