* [PATCH bpf-next v2 1/5] libbpf: Count entry programs for bpf_gen__init()
2026-11-02 19:41 [PATCH bpf-next v2 0/5] libbpf: Support gen loader attach Mahe Tardy
@ 2026-11-02 19:41 ` Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 2/5] libbpf: Track generated loader link FDs Mahe Tardy
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-11-02 19:41 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 [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 2/5] libbpf: Track generated loader link FDs
2026-11-02 19:41 [PATCH bpf-next v2 0/5] libbpf: Support gen loader attach Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy
@ 2026-11-02 19:41 ` Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-11-02 19:41 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 [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders
2026-11-02 19:41 [PATCH bpf-next v2 0/5] libbpf: Support gen loader attach Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 1/5] libbpf: Count entry programs for bpf_gen__init() Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 2/5] libbpf: Track generated loader link FDs Mahe Tardy
@ 2026-11-02 19:41 ` Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 5/5] selftests/bpf: Test gen loader attach Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-11-02 19:41 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 [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 4/5] bpftool: Add attach option for light skeleton generation
2026-11-02 19:41 [PATCH bpf-next v2 0/5] libbpf: Support gen loader attach Mahe Tardy
` (2 preceding siblings ...)
2026-11-02 19:41 ` [PATCH bpf-next v2 3/5] libbpf: Emit BPF_LINK_CREATE calls in generated loaders Mahe Tardy
@ 2026-11-02 19:41 ` Mahe Tardy
2026-11-02 19:41 ` [PATCH bpf-next v2 5/5] selftests/bpf: Test gen loader attach Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-11-02 19:41 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 74c2c949458a..19670b3454ec 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -567,7 +567,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;
@@ -583,21 +584,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\
@@ -641,6 +651,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("\
@@ -692,7 +704,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 = {};
@@ -703,6 +716,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;
@@ -739,7 +753,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
}
#endif
- codegen_attach_detach(obj, obj_name);
+ codegen_attach_detach(obj, obj_name, gen_attach);
codegen_destroy(obj, obj_name);
@@ -756,6 +770,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;
@@ -1262,6 +1278,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();
@@ -1270,10 +1287,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') {
@@ -1283,6 +1299,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;
@@ -1291,6 +1309,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;
@@ -1454,7 +1477,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;
}
@@ -2018,7 +2041,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 [flat|nested] 6+ messages in thread* [PATCH bpf-next v2 5/5] selftests/bpf: Test gen loader attach
2026-11-02 19:41 [PATCH bpf-next v2 0/5] libbpf: Support gen loader attach Mahe Tardy
` (3 preceding siblings ...)
2026-11-02 19:41 ` [PATCH bpf-next v2 4/5] bpftool: Add attach option for light skeleton generation Mahe Tardy
@ 2026-11-02 19:41 ` Mahe Tardy
4 siblings, 0 replies; 6+ messages in thread
From: Mahe Tardy @ 2026-11-02 19:41 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 [flat|nested] 6+ messages in thread