From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Alan Maguire <almagui@kernel.org>,
John Fastabend <john.fastabend@gmail.com>,
Quentin Monnet <qmo@kernel.org>,
Christian Brauner <brauner@kernel.org>,
KP Singh <kpsingh@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
Sasha Levin <sashal@kernel.org>,
bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Anton Protopopov <a.s.protopopov@gmail.com>
Subject: [PATCH v1 bpf-next 01/18] bpf: Add BTF ID sets for fmod_ret targets
Date: Fri, 6 Nov 2026 21:02:50 +0000 [thread overview]
Message-ID: <20261106210314.1767267-1-a.s.protopopov@gmail.com> (raw)
In-Reply-To: 20261106205523.1765622-1-a.s.protopopov@gmail.com
Add BTF_SET8_FMODRET and the BTF_FMODRET_START() and BTF_FMODRET_END()
macros to declare the functions BPF fmod_ret programs may attach to.
They work like kfunc sets: each entry is a BTF_ID_FLAGS() line and
KF_SLEEPABLE marks targets sleepable programs may attach to.
Teach resolve_btfids to find these sets and tag every listed function in
BTF with a "bpf_fmodret" decl tag, plus "bpf_fmodret_sleepable" when
KF_SLEEPABLE is set. The sleepable tag never comes without "bpf_fmodret"
and readers of the tags may rely on that. Share the set walk with the
kfunc collection. Any other flag is an error. Nothing in the code refers
to the sets, so keep .BTF_ids in the linker script, or --gc-sections may
discard them.
Nothing declares such sets yet and the kernel does not read the tags yet.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
include/asm-generic/vmlinux.lds.h | 2 +-
include/linux/btf_ids.h | 14 ++++
tools/bpf/resolve_btfids/main.c | 116 +++++++++++++++++++++++++-----
tools/include/linux/btf_ids.h | 14 ++++
4 files changed, 127 insertions(+), 19 deletions(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 3eb732942d69..f8254a88e05d 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -714,7 +714,7 @@
BTF_INLINE_LINK \
. = ALIGN(PAGE_SIZE); \
.BTF_ids : AT(ADDR(.BTF_ids) - LOAD_OFFSET) { \
- *(.BTF_ids) \
+ KEEP(*(.BTF_ids)) \
}
#else
#define BTF
diff --git a/include/linux/btf_ids.h b/include/linux/btf_ids.h
index 8b5a9ee92513..f27b7db81100 100644
--- a/include/linux/btf_ids.h
+++ b/include/linux/btf_ids.h
@@ -13,6 +13,12 @@ struct btf_id_set {
/* This flag implies BTF_SET8 holds kfunc(s) */
#define BTF_SET8_KFUNCS (1 << 0)
+/*
+ * This flag implies BTF_SET8 holds functions which BPF fmod_ret, fentry and
+ * fexit programs may attach to. Per-function flags: KF_SLEEPABLE only.
+ */
+#define BTF_SET8_FMODRET (1 << 1)
+
struct btf_id_set8 {
u32 cnt;
u32 flags;
@@ -215,6 +221,12 @@ __BTF_SET8_START(name, local, BTF_SET8_KFUNCS)
#define BTF_KFUNCS_END(name) \
BTF_SET8_END(name)
+#define BTF_FMODRET_START(name) \
+__BTF_SET8_START(name, local, BTF_SET8_FMODRET)
+
+#define BTF_FMODRET_END(name) \
+BTF_SET8_END(name)
+
#else
#define BTF_ID_LIST(name) static u32 __maybe_unused name[128];
@@ -231,6 +243,8 @@ BTF_SET8_END(name)
#define BTF_SET8_END(name)
#define BTF_KFUNCS_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_KFUNCS };
#define BTF_KFUNCS_END(name)
+#define BTF_FMODRET_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_FMODRET };
+#define BTF_FMODRET_END(name)
#endif /* CONFIG_DEBUG_INFO_BTF */
diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
index c16dd93fa6d4..ed1a7fb80ea6 100644
--- a/tools/bpf/resolve_btfids/main.c
+++ b/tools/bpf/resolve_btfids/main.c
@@ -69,6 +69,12 @@
* - rewrites the prototype of KF_IMPLICIT_ARGS kfuncs.
*
* These kfunc annotations were historically produced by pahole.
+ *
+ * Functions found in BTF_SET8_FMODRET sets are the targets of BPF fmod_ret
+ * programs. For each of them resolve_btfids emits a "bpf_fmodret" decl tag,
+ * and also "bpf_fmodret_sleepable" when KF_SLEEPABLE is set, so the sleepable
+ * tag never comes alone. The kernel builds the set of fmod_ret targets from
+ * these tags when it loads BTF.
*/
#define _GNU_SOURCE
@@ -197,7 +203,10 @@ struct btf_links {
#define DECL_TAG_FASTCALL "bpf_fastcall"
#define DECL_TAG_KFUNC "bpf_kfunc"
+#define DECL_TAG_FMODRET "bpf_fmodret"
+#define DECL_TAG_FMODRET_SLEEPABLE "bpf_fmodret_sleepable"
+#define KF_SLEEPABLE (1 << 5)
#define KF_FASTCALL (1 << 12)
#define KF_ARENA_RET (1 << 13)
#define KF_ARENA_ARG1 (1 << 14)
@@ -208,6 +217,7 @@ struct btf_links {
#define PARAM_SUFFIX_ARENA "__arena"
#define PARAM_SUFFIX_ARENA_NULLABLE "__arena__nullable"
+/* A function found in a BTF ID set: a kfunc or an fmod_ret target */
struct kfunc {
struct rb_node rb_node;
const char *name;
@@ -221,6 +231,7 @@ struct btf2btf_context {
u32 nr_decl_tags;
u32 max_decl_tags;
struct rb_root kfuncs;
+ struct rb_root fmodrets;
};
static int verbose;
@@ -1256,16 +1267,16 @@ static int push_decl_tag_id(struct btf2btf_context *ctx, u32 decl_tag_id)
return 0;
}
-static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc)
+static int push_set_func(struct rb_root *root, const char *kind, struct kfunc *kfunc)
{
- struct rb_node **p = &ctx->kfuncs.rb_node;
+ struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
struct kfunc *k;
/*
- * Dedup by BTF ID: collecting the same kfunc twice is a no-op,
- * UNLESS the kfunc flags are inconsistent, in which case we
- * fail hard because it indicates a bug in a kfunc set declaration.
+ * Dedup by BTF ID: collecting the same function twice is a no-op,
+ * UNLESS its flags are inconsistent, in which case we fail hard
+ * because it indicates a bug in a BTF ID set declaration.
*/
while (*p) {
parent = *p;
@@ -1278,8 +1289,8 @@ static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc)
} else if (k->flags == kfunc->flags) {
return 0;
} else {
- pr_err("ERROR: resolve_btfids: kfunc %s has inconsistent flags across BTF ID sets: 0x%x != 0x%x\n",
- kfunc->name, k->flags, kfunc->flags);
+ pr_err("ERROR: resolve_btfids: %s %s has inconsistent flags across BTF ID sets: 0x%x != 0x%x\n",
+ kind, kfunc->name, k->flags, kfunc->flags);
return -EINVAL;
}
}
@@ -1290,7 +1301,7 @@ static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc)
*k = *kfunc;
rb_link_node(&k->rb_node, parent, p);
- rb_insert_color(&k->rb_node, &ctx->kfuncs);
+ rb_insert_color(&k->rb_node, root);
return 0;
}
@@ -1344,7 +1355,16 @@ static bool is_arena_param(const struct btf *btf, const struct btf_param *param)
param_name_has_suffix(name, PARAM_SUFFIX_ARENA_NULLABLE);
}
-static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
+typedef int (*set_func_fn)(struct btf2btf_context *ctx, const char *name,
+ u32 btf_id, u32 flags);
+
+/*
+ * Call FN for each function in the BTF ID sets which have SET_FLAG set. Reject
+ * an entry with flags other than ALLOWED_FLAGS, also if its function is missing.
+ */
+static int collect_set_funcs(struct object *obj, struct btf2btf_context *ctx,
+ u32 set_flag, u32 allowed_flags, const char *kind,
+ set_func_fn fn)
{
Elf_Data *idlist = obj->efile.idlist;
struct btf *btf = ctx->btf;
@@ -1363,17 +1383,31 @@ static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
continue;
set_addr = set_id->addr[0];
+ /* A malformed set must not reach out of the section */
+ if (set_addr < obj->efile.idlist_addr || set_id->cnt < 0 ||
+ set_addr - obj->efile.idlist_addr + sizeof(*set8) +
+ (u64)set_id->cnt * sizeof(set8->pairs[0]) > idlist->d_size) {
+ pr_err("WARN: resolve_btfids: set %s is out of %s\n",
+ set_id->name, BTF_IDS_SECTION);
+ warnings++;
+ continue;
+ }
set8 = idlist->d_buf + (set_addr - obj->efile.idlist_addr);
- if (!(set8->flags & BTF_SET8_KFUNCS))
+ if (!(set8->flags & set_flag))
continue;
for (u32 i = 0; i < set_id->cnt; i++) {
size_t off = (char *)&set8->pairs[i] - (char *)set8;
const char *name = find_name_by_addr(obj, set_addr + off);
- struct kfunc kfunc;
+ u32 flags = set8->pairs[i].flags;
s32 func_id;
int err;
+ if (flags & ~allowed_flags) {
+ pr_err("ERROR: resolve_btfids: %s %s in %s has unsupported flags 0x%x\n",
+ kind, name ?: "?", set_id->name, flags & ~allowed_flags);
+ return -EINVAL;
+ }
if (!name) {
pr_err("WARN: resolve_btfids: no BTF ID symbol for %s entry %u\n",
set_id->name, i);
@@ -1383,18 +1417,15 @@ static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
func_id = btf__find_by_name_kind_own(btf, name, BTF_KIND_FUNC);
if (func_id < 0) {
- pr_err("WARN: resolve_btfids: no BTF func for kfunc %s in %s\n",
- name, set_id->name);
+ pr_err("WARN: resolve_btfids: no BTF func for %s %s in %s\n",
+ kind, name, set_id->name);
warnings++;
continue;
}
- pr_debug("found kfunc %s in %s\n", name, set_id->name);
+ pr_debug("found %s %s in %s\n", kind, name, set_id->name);
- kfunc.name = name;
- kfunc.btf_id = func_id;
- kfunc.flags = set8->pairs[i].flags;
- err = push_kfunc(ctx, &kfunc);
+ err = fn(ctx, name, func_id, flags);
if (err)
return err;
}
@@ -1403,6 +1434,33 @@ static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
return 0;
}
+static int push_kfunc(struct btf2btf_context *ctx, const char *name,
+ u32 btf_id, u32 flags)
+{
+ struct kfunc kfunc = { .name = name, .btf_id = btf_id, .flags = flags };
+
+ return push_set_func(&ctx->kfuncs, "kfunc", &kfunc);
+}
+
+static int push_fmodret(struct btf2btf_context *ctx, const char *name,
+ u32 btf_id, u32 flags)
+{
+ struct kfunc fmodret = { .name = name, .btf_id = btf_id, .flags = flags };
+
+ return push_set_func(&ctx->fmodrets, "fmod_ret target", &fmodret);
+}
+
+static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
+{
+ return collect_set_funcs(obj, ctx, BTF_SET8_KFUNCS, ~0U, "kfunc", push_kfunc);
+}
+
+static int collect_fmodrets(struct object *obj, struct btf2btf_context *ctx)
+{
+ return collect_set_funcs(obj, ctx, BTF_SET8_FMODRET, KF_SLEEPABLE,
+ "fmod_ret target", push_fmodret);
+}
+
static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx)
{
int err;
@@ -1421,6 +1479,12 @@ static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx
return err;
}
+ err = collect_fmodrets(obj, ctx);
+ if (err) {
+ pr_err("ERROR: resolve_btfids: failed to collect fmod_ret targets from BTF\n");
+ return err;
+ }
+
return 0;
}
@@ -1761,10 +1825,26 @@ static int btf2btf(struct object *obj)
goto out;
}
+ for (next = rb_first(&ctx.fmodrets); next; next = rb_next(next)) {
+ struct kfunc *fmodret = rb_entry(next, struct kfunc, rb_node);
+
+ err = add_decl_tag(&ctx, DECL_TAG_FMODRET, fmodret->btf_id, -1);
+ if (err)
+ goto out;
+
+ if (fmodret->flags & KF_SLEEPABLE) {
+ err = add_decl_tag(&ctx, DECL_TAG_FMODRET_SLEEPABLE,
+ fmodret->btf_id, -1);
+ if (err)
+ goto out;
+ }
+ }
+
err = 0;
out:
free(ctx.decl_tags);
free_kfuncs(&ctx.kfuncs);
+ free_kfuncs(&ctx.fmodrets);
return err;
}
diff --git a/tools/include/linux/btf_ids.h b/tools/include/linux/btf_ids.h
index 4fe5c5f1558c..b371ef7241b9 100644
--- a/tools/include/linux/btf_ids.h
+++ b/tools/include/linux/btf_ids.h
@@ -13,6 +13,12 @@ struct btf_id_set {
/* This flag implies BTF_SET8 holds kfunc(s) */
#define BTF_SET8_KFUNCS (1 << 0)
+/*
+ * This flag implies BTF_SET8 holds functions which BPF fmod_ret, fentry and
+ * fexit programs may attach to. Per-function flags: KF_SLEEPABLE only.
+ */
+#define BTF_SET8_FMODRET (1 << 1)
+
struct btf_id_set8 {
u32 cnt;
u32 flags;
@@ -214,6 +220,12 @@ __BTF_SET8_START(name, local, BTF_SET8_KFUNCS)
#define BTF_KFUNCS_END(name) \
BTF_SET8_END(name)
+#define BTF_FMODRET_START(name) \
+__BTF_SET8_START(name, local, BTF_SET8_FMODRET)
+
+#define BTF_FMODRET_END(name) \
+BTF_SET8_END(name)
+
#else
#define BTF_ID_LIST(name) static u32 __maybe_unused name[128];
@@ -230,6 +242,8 @@ BTF_SET8_END(name)
#define BTF_SET8_END(name)
#define BTF_KFUNCS_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_KFUNCS };
#define BTF_KFUNCS_END(name)
+#define BTF_FMODRET_START(name) static struct btf_id_set8 __maybe_unused name = { .flags = BTF_SET8_FMODRET };
+#define BTF_FMODRET_END(name)
#endif /* CONFIG_DEBUG_INFO_BTF */
--
2.43.0
next reply other threads:[~2026-11-06 20:52 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 21:02 Anton Protopopov [this message]
2026-11-06 21:02 ` [PATCH v1 bpf-next 02/18] bpf: Add a btf_id_set8_entry helper Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 03/18] bpf: Build fmod_ret target sets from BTF decl tags Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 04/18] selftests/bpf: Declare bpf_fentry_test1 in tracing tests Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 05/18] bpf: Declare fmod_ret targets with BTF_FMODRET sets Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 06/18] selftests/bpf: Test fmod_ret BTF decl tags and attach permission Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 07/18] scripts: Add LLVM plugin infrastructure Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 08/18] bpf: Add the bpf_scan_ir LLVM plugin Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 09/18] bpf: Find fmod_ret candidates in bpf_scan_ir Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 10/18] bpf: Allow writes to private stack objects " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 11/18] bpf: Follow PHIs " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 12/18] bpf: Follow integer casts " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 13/18] bpf: Check the return values of fmod_ret programs on found targets Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 14/18] bpf: Require the JIT for fmod_ret programs on found 64-bit targets Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 15/18] resolve_btfids: Tag fmod_ret candidates as targets Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 16/18] selftests/bpf: Test fmod_ret candidates found by bpf_scan_ir Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 17/18] selftests/bpf: Enable bpf_scan_ir in the config Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 18/18] bpftool: List and show fmod_ret targets Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106210314.1767267-1-a.s.protopopov@gmail.com \
--to=a.s.protopopov@gmail.com \
--cc=almagui@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=qmo@kernel.org \
--cc=sashal@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®