[PATCH bpf-next 2/5] libbpf: Track generated loader link FDs

From: Mahe Tardy

Date: Mon Nov 02 2026 - 13:08:58 EST


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@xxxxxxxxx>
---
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 80a6b5306183..17690cbd299b 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -127,6 +127,10 @@ void bpf_gen__init(struct bpf_gen *gen, int log_level, int nr_progs, int nr_maps
gen->error = -E2BIG;
return;
}
+ 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;
@@ -153,10 +157,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));
@@ -416,6 +424,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 03f794f4adb0..f7759cc15a54 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -1953,9 +1953,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