[PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS

Ihor Solodrai posted 13 patches 3 weeks, 1 day ago
There is a newer version of this series
[PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Ihor Solodrai 3 weeks, 1 day ago
Implement BTF modifications in resolve_btfids to support BPF kernel
functions with implicit arguments.

For a kfunc marked with KF_IMPLICIT_ARGS flag, a new function
prototype is added to BTF that does not have implicit arguments. The
kfunc's prototype is then updated to a new one in BTF. This prototype
is the intended interface for the BPF programs.

A <func_name>_impl function is added to BTF to make the original kfunc
prototype searchable for the BPF verifier. If a <func_name>_impl
function already exists in BTF, its interpreted as a legacy case, and
this step is skipped.

Whether an argument is implicit is determined by its type:
currently only `struct bpf_prog_aux *` is supported.

As a result, the BTF associated with kfunc is changed from

    __bpf_kfunc bpf_foo(int arg1, struct bpf_prog_aux *aux);

into

    bpf_foo_impl(int arg1, struct bpf_prog_aux *aux);
    __bpf_kfunc bpf_foo(int arg1);

For more context see previous discussions and patches [1][2].

[1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
[2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/resolve_btfids/main.c | 383 ++++++++++++++++++++++++++++++++
 1 file changed, 383 insertions(+)

diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
index 1fcf37af6764..b83316359cfd 100644
--- a/tools/bpf/resolve_btfids/main.c
+++ b/tools/bpf/resolve_btfids/main.c
@@ -152,6 +152,23 @@ struct object {
 	int nr_typedefs;
 };
 
+#define KF_IMPLICIT_ARGS (1 << 16)
+#define KF_IMPL_SUFFIX "_impl"
+
+struct kfunc {
+	const char *name;
+	u32 btf_id;
+	u32 flags;
+};
+
+struct btf2btf_context {
+	struct btf *btf;
+	u32 *decl_tags;
+	u32 nr_decl_tags;
+	struct kfunc *kfuncs;
+	u32 nr_kfuncs;
+};
+
 static int verbose;
 static int warnings;
 
@@ -837,6 +854,369 @@ static int dump_raw_btf(struct btf *btf, const char *out_path)
 	return 0;
 }
 
+static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, s32 type_id)
+{
+	const struct btf_type *t = btf__type_by_id(btf, type_id);
+
+	while (btf_is_mod(t))
+		t = btf__type_by_id(btf, t->type);
+
+	return t;
+}
+
+static const struct btf_decl_tag *btf_type_decl_tag(const struct btf_type *t)
+{
+	return (const struct btf_decl_tag *)(t + 1);
+}
+
+static int collect_decl_tags(struct btf2btf_context *ctx)
+{
+	const u32 type_cnt = btf__type_cnt(ctx->btf);
+	struct btf *btf = ctx->btf;
+	const struct btf_type *t;
+	u32 *tags, *tmp;
+	u32 nr_tags = 0;
+
+	tags = malloc(type_cnt * sizeof(u32));
+	if (!tags)
+		return -ENOMEM;
+
+	for (u32 id = 1; id < type_cnt; id++) {
+		t = btf__type_by_id(btf, id);
+		if (!btf_is_decl_tag(t))
+			continue;
+		tags[nr_tags++] = id;
+	}
+
+	if (nr_tags == 0) {
+		ctx->decl_tags = NULL;
+		free(tags);
+		return 0;
+	}
+
+	tmp = realloc(tags, nr_tags * sizeof(u32));
+	if (!tmp) {
+		free(tags);
+		return -ENOMEM;
+	}
+
+	ctx->decl_tags = tmp;
+	ctx->nr_decl_tags = nr_tags;
+
+	return 0;
+}
+
+/*
+ * To find the kfunc flags having its struct btf_id (with ELF addresses)
+ * we need to find the address that is in range of a set8.
+ * If a set8 is found, then the flags are located at addr + 4 bytes.
+ * Return 0 (no flags!) if not found.
+ */
+static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id)
+{
+	const u32 *elf_data_ptr = obj->efile.idlist->d_buf;
+	u64 set_lower_addr, set_upper_addr, addr;
+	struct btf_id *set_id;
+	struct rb_node *next;
+	u32 flags;
+	u64 idx;
+
+	next = rb_first(&obj->sets);
+	while (next) {
+		set_id = rb_entry(next, struct btf_id, rb_node);
+		if (set_id->kind != BTF_ID_KIND_SET8 || set_id->addr_cnt != 1)
+			goto skip;
+
+		set_lower_addr = set_id->addr[0];
+		set_upper_addr = set_lower_addr + set_id->cnt * sizeof(u64);
+
+		for (u32 i = 0; i < kfunc_id->addr_cnt; i++) {
+			addr = kfunc_id->addr[i];
+			/*
+			 * Lower bound is exclusive to skip the 8-byte header of the set.
+			 * Upper bound is inclusive to capture the last entry at offset 8*cnt.
+			 */
+			if (set_lower_addr < addr && addr <= set_upper_addr) {
+				pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n",
+					 kfunc_id->name, set_id->name);
+				goto found;
+			}
+		}
+skip:
+		next = rb_next(next);
+	}
+
+	return 0;
+
+found:
+	idx = addr - obj->efile.idlist_addr;
+	idx = idx / sizeof(u32) + 1;
+	flags = elf_data_ptr[idx];
+
+	return flags;
+}
+
+static s64 collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
+{
+	struct kfunc *kfunc, *kfuncs, *tmp;
+	const char *tag_name, *func_name;
+	struct btf *btf = ctx->btf;
+	const struct btf_type *t;
+	u32 flags, func_id;
+	struct btf_id *id;
+	s64 nr_kfuncs = 0;
+
+	if (ctx->nr_decl_tags == 0)
+		return 0;
+
+	kfuncs = malloc(ctx->nr_decl_tags * sizeof(*kfuncs));
+	if (!kfuncs)
+		return -ENOMEM;
+
+	for (u32 i = 0; i < ctx->nr_decl_tags; i++) {
+		t = btf__type_by_id(btf, ctx->decl_tags[i]);
+		if (btf_kflag(t) || btf_type_decl_tag(t)->component_idx != -1)
+			continue;
+
+		tag_name = btf__name_by_offset(btf, t->name_off);
+		if (strcmp(tag_name, "bpf_kfunc") != 0)
+			continue;
+
+		func_id = t->type;
+		t = btf__type_by_id(btf, func_id);
+		if (!btf_is_func(t))
+			continue;
+
+		func_name = btf__name_by_offset(btf, t->name_off);
+		if (!func_name)
+			continue;
+
+		id = btf_id__find(&obj->funcs, func_name);
+		if (!id || id->kind != BTF_ID_KIND_SYM)
+			continue;
+
+		flags = find_kfunc_flags(obj, id);
+
+		kfunc = &kfuncs[nr_kfuncs++];
+		kfunc->name = id->name;
+		kfunc->btf_id = func_id;
+		kfunc->flags = flags;
+	}
+
+	if (nr_kfuncs == 0) {
+		ctx->kfuncs = NULL;
+		ctx->nr_kfuncs = 0;
+		free(kfuncs);
+		return 0;
+	}
+
+	tmp = realloc(kfuncs, nr_kfuncs * sizeof(*kfuncs));
+	if (!tmp) {
+		free(kfuncs);
+		return -ENOMEM;
+	}
+
+	ctx->kfuncs = tmp;
+	ctx->nr_kfuncs = nr_kfuncs;
+
+	return 0;
+}
+
+static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx)
+{
+	int err;
+
+	ctx->btf = obj->btf;
+
+	err = collect_decl_tags(ctx);
+	if (err) {
+		pr_err("ERROR: resolve_btfids: failed to collect decl tags from BTF\n");
+		return err;
+	}
+
+	err = collect_kfuncs(obj, ctx);
+	if (err) {
+		pr_err("ERROR: resolve_btfids: failed to collect kfuncs from BTF\n");
+		return err;
+	}
+
+	return 0;
+}
+
+
+/* Implicit BPF kfunc arguments can only be of particular types */
+static bool is_kf_implicit_arg(const struct btf *btf, const struct btf_param *p)
+{
+	static const char *const kf_implicit_arg_types[] = {
+		"bpf_prog_aux",
+	};
+	const struct btf_type *t;
+	const char *name;
+
+	t = btf_type_skip_qualifiers(btf, p->type);
+	if (!btf_is_ptr(t))
+		return false;
+
+	t = btf_type_skip_qualifiers(btf, t->type);
+	if (!btf_is_struct(t))
+		return false;
+
+	name = btf__name_by_offset(btf, t->name_off);
+	if (!name)
+		return false;
+
+	for (int i = 0; i < ARRAY_SIZE(kf_implicit_arg_types); i++)
+		if (strcmp(name, kf_implicit_arg_types[i]) == 0)
+			return true;
+
+	return false;
+}
+
+/*
+ * For a kfunc with KF_IMPLICIT_ARGS we do the following:
+ *   1. Add a new function with _impl suffix in the name, with the prototype
+ *      of the original kfunc.
+ *   2. Add all decl tags except "bpf_kfunc" for the _impl func.
+ *   3. Add a new function prototype with modified list of arguments:
+ *      omitting implicit args.
+ *   4. Change the prototype of the original kfunc to the new one.
+ *
+ * This way we transform the BTF associated with the kfunc from
+ *	__bpf_kfunc bpf_foo(int arg1, void *implicit_arg);
+ * into
+ *	bpf_foo_impl(int arg1, void *implicit_arg);
+ *	__bpf_kfunc bpf_foo(int arg1);
+ *
+ * If a kfunc with KF_IMPLICIT_ARGS already has an _impl counterpart
+ * in BTF, then it's a legacy case: an _impl function is declared in the
+ * source code. In this case, we can skip adding an _impl function, but we
+ * still have to add a func prototype that omits implicit args.
+ */
+static int process_kfunc_with_implicit_args(struct btf2btf_context *ctx, struct kfunc *kfunc)
+{
+	s32 idx, new_proto_id, new_func_id, proto_id;
+	const char *param_name, *tag_name;
+	const struct btf_param *params;
+	enum btf_func_linkage linkage;
+	char tmp_name[KSYM_NAME_LEN];
+	struct btf *btf = ctx->btf;
+	int err, len, nr_params;
+	struct btf_type *t;
+
+	t = (struct btf_type *)btf__type_by_id(btf, kfunc->btf_id);
+	if (!t || !btf_is_func(t)) {
+		pr_err("ERROR: resolve_btfids: btf id %d is not a function\n", kfunc->btf_id);
+		return -EINVAL;
+	}
+
+	linkage = btf_vlen(t);
+
+	proto_id = t->type;
+	t = (struct btf_type *)btf__type_by_id(btf, proto_id);
+	if (!t || !btf_is_func_proto(t)) {
+		pr_err("ERROR: resolve_btfids: btf id %d is not a function prototype\n", proto_id);
+		return -EINVAL;
+	}
+
+	len = snprintf(tmp_name, sizeof(tmp_name), "%s%s", kfunc->name, KF_IMPL_SUFFIX);
+	if (len < 0 || len >= sizeof(tmp_name)) {
+		pr_err("ERROR: function name is too long: %s%s\n", kfunc->name, KF_IMPL_SUFFIX);
+		return -E2BIG;
+	}
+
+	if (btf__find_by_name_kind(btf, tmp_name, BTF_KIND_FUNC) > 0) {
+		pr_debug("resolve_btfids: function %s already exists in BTF\n", tmp_name);
+		goto add_new_proto;
+	}
+
+	/* Add a new function with _impl suffix and original prototype */
+	new_func_id = btf__add_func(btf, tmp_name, linkage, proto_id);
+	if (new_func_id < 0) {
+		pr_err("ERROR: resolve_btfids: failed to add func %s to BTF\n", tmp_name);
+		return new_func_id;
+	}
+
+	/* Copy all decl tags except "bpf_kfunc" from the original kfunc to the new one */
+	for (int i = 0; i < ctx->nr_decl_tags; i++) {
+		t = (struct btf_type *)btf__type_by_id(btf, ctx->decl_tags[i]);
+		if (t->type != kfunc->btf_id)
+			continue;
+
+		tag_name = btf__name_by_offset(btf, t->name_off);
+		if (strcmp(tag_name, "bpf_kfunc") == 0)
+			continue;
+
+		idx = btf_type_decl_tag(t)->component_idx;
+
+		if (btf_kflag(t))
+			err = btf__add_decl_attr(btf, tag_name, new_func_id, idx);
+		else
+			err = btf__add_decl_tag(btf, tag_name, new_func_id, idx);
+
+		if (err < 0) {
+			pr_err("ERROR: resolve_btfids: failed to add decl tag %s for %s\n",
+			       tag_name, tmp_name);
+			return -EINVAL;
+		}
+	}
+
+add_new_proto:
+	t = (struct btf_type *)btf__type_by_id(btf, proto_id);
+	new_proto_id = btf__add_func_proto(btf, t->type);
+	if (new_proto_id < 0) {
+		pr_err("ERROR: resolve_btfids: failed to add func proto for %s\n", kfunc->name);
+		return new_proto_id;
+	}
+
+	/* Add non-implicit args to the new prototype */
+	t = (struct btf_type *)btf__type_by_id(btf, proto_id);
+	nr_params = btf_vlen(t);
+	for (int i = 0; i < nr_params; i++) {
+		params = btf_params(t);
+		if (is_kf_implicit_arg(btf, &params[i]))
+			break;
+		param_name = btf__name_by_offset(btf, params[i].name_off);
+		err = btf__add_func_param(btf, param_name, params[i].type);
+		if (err < 0) {
+			pr_err("ERROR: resolve_btfids: failed to add param %s for %s\n",
+			       param_name, kfunc->name);
+			return err;
+		}
+		t = (struct btf_type *)btf__type_by_id(btf, proto_id);
+	}
+
+	/* Finally change the prototype of the original kfunc to the new one */
+	t = (struct btf_type *)btf__type_by_id(btf, kfunc->btf_id);
+	t->type = new_proto_id;
+
+	pr_debug("resolve_btfids: updated BTF for kfunc with implicit args %s\n", kfunc->name);
+
+	return 0;
+}
+
+static int btf2btf(struct object *obj)
+{
+	struct btf2btf_context ctx = {};
+	int err;
+
+	err = build_btf2btf_context(obj, &ctx);
+	if (err)
+		return err;
+
+	for (u32 i = 0; i < ctx.nr_kfuncs; i++) {
+		struct kfunc *kfunc = &ctx.kfuncs[i];
+
+		if (!(kfunc->flags & KF_IMPLICIT_ARGS))
+			continue;
+
+		err = process_kfunc_with_implicit_args(&ctx, kfunc);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
 /*
  * Sort types by name in ascending order resulting in all
  * anonymous types being placed before named types.
@@ -1126,6 +1506,9 @@ int main(int argc, const char **argv)
 	if (load_btf(&obj))
 		goto out;
 
+	if (btf2btf(&obj))
+		goto out;
+
 	if (finalize_btf(&obj))
 		goto out;
 
-- 
2.52.0
Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Eduard Zingerman 2 weeks, 5 days ago
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Implement BTF modifications in resolve_btfids to support BPF kernel
> functions with implicit arguments.
> 
> For a kfunc marked with KF_IMPLICIT_ARGS flag, a new function
> prototype is added to BTF that does not have implicit arguments. The
> kfunc's prototype is then updated to a new one in BTF. This prototype
> is the intended interface for the BPF programs.
> 
> A <func_name>_impl function is added to BTF to make the original kfunc
> prototype searchable for the BPF verifier. If a <func_name>_impl
> function already exists in BTF, its interpreted as a legacy case, and
> this step is skipped.
> 
> Whether an argument is implicit is determined by its type:
> currently only `struct bpf_prog_aux *` is supported.
> 
> As a result, the BTF associated with kfunc is changed from
> 
>     __bpf_kfunc bpf_foo(int arg1, struct bpf_prog_aux *aux);
> 
> into
> 
>     bpf_foo_impl(int arg1, struct bpf_prog_aux *aux);
>     __bpf_kfunc bpf_foo(int arg1);
> 
> For more context see previous discussions and patches [1][2].
> 
> [1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
> [2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/
> 
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---

Patch logic looks good to me, modulo LLM's memory management concern
and nit from Andrii.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

> @@ -837,6 +854,369 @@ static int dump_raw_btf(struct btf *btf, const char *out_path)
>  	return 0;
>  }
>  
> +static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, s32 type_id)
> +{
> +	const struct btf_type *t = btf__type_by_id(btf, type_id);
> +
> +	while (btf_is_mod(t))
> +		t = btf__type_by_id(btf, t->type);
> +
> +	return t;
> +}
> +
> +static const struct btf_decl_tag *btf_type_decl_tag(const struct btf_type *t)
> +{
> +	return (const struct btf_decl_tag *)(t + 1);
> +}

Nit: there is a utility function btf_decl_tag() in bpf/btf.h
     which does exactly the same.

[...]
Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Andrii Nakryiko 3 weeks, 1 day ago
On Fri, Jan 16, 2026 at 12:17 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>
> Implement BTF modifications in resolve_btfids to support BPF kernel
> functions with implicit arguments.
>
> For a kfunc marked with KF_IMPLICIT_ARGS flag, a new function
> prototype is added to BTF that does not have implicit arguments. The
> kfunc's prototype is then updated to a new one in BTF. This prototype
> is the intended interface for the BPF programs.
>
> A <func_name>_impl function is added to BTF to make the original kfunc
> prototype searchable for the BPF verifier. If a <func_name>_impl
> function already exists in BTF, its interpreted as a legacy case, and
> this step is skipped.
>
> Whether an argument is implicit is determined by its type:
> currently only `struct bpf_prog_aux *` is supported.
>
> As a result, the BTF associated with kfunc is changed from
>
>     __bpf_kfunc bpf_foo(int arg1, struct bpf_prog_aux *aux);
>
> into
>
>     bpf_foo_impl(int arg1, struct bpf_prog_aux *aux);
>     __bpf_kfunc bpf_foo(int arg1);
>
> For more context see previous discussions and patches [1][2].
>
> [1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
> [2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
>  tools/bpf/resolve_btfids/main.c | 383 ++++++++++++++++++++++++++++++++
>  1 file changed, 383 insertions(+)
>

[...]

> +static int collect_decl_tags(struct btf2btf_context *ctx)
> +{
> +       const u32 type_cnt = btf__type_cnt(ctx->btf);
> +       struct btf *btf = ctx->btf;
> +       const struct btf_type *t;
> +       u32 *tags, *tmp;
> +       u32 nr_tags = 0;
> +
> +       tags = malloc(type_cnt * sizeof(u32));

waste of memory, really, see below

> +       if (!tags)
> +               return -ENOMEM;
> +
> +       for (u32 id = 1; id < type_cnt; id++) {
> +               t = btf__type_by_id(btf, id);
> +               if (!btf_is_decl_tag(t))
> +                       continue;
> +               tags[nr_tags++] = id;
> +       }
> +
> +       if (nr_tags == 0) {
> +               ctx->decl_tags = NULL;
> +               free(tags);
> +               return 0;
> +       }
> +
> +       tmp = realloc(tags, nr_tags * sizeof(u32));
> +       if (!tmp) {
> +               free(tags);
> +               return -ENOMEM;
> +       }

This is an interesting realloc() usage pattern, it's quite
unconventional to preallocate too much memory, and then shrink (in C
world)

check libbpf's libbpf_add_mem(), that's a generic "primitive" inside
the libbpf. Do not reuse it as is, but it should give you an idea of a
common pattern: you start with NULL (empty data), when you need to add
a new element, you calculate a new array size which normally would be
some minimal value (to avoid going through 1 -> 2 -> 4 -> 8, many
small and wasteful steps; normally we just jump straight to 16 or so)
or some factor of previous size (doesn't have to be 2x,
libbpf_add_mem() expands by 25%, for instance).

This is a super common approach in C. Please utilize it here as well.

> +
> +       ctx->decl_tags = tmp;
> +       ctx->nr_decl_tags = nr_tags;
> +
> +       return 0;
> +}
> +
> +/*
> + * To find the kfunc flags having its struct btf_id (with ELF addresses)
> + * we need to find the address that is in range of a set8.
> + * If a set8 is found, then the flags are located at addr + 4 bytes.
> + * Return 0 (no flags!) if not found.
> + */
> +static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id)
> +{
> +       const u32 *elf_data_ptr = obj->efile.idlist->d_buf;
> +       u64 set_lower_addr, set_upper_addr, addr;
> +       struct btf_id *set_id;
> +       struct rb_node *next;
> +       u32 flags;
> +       u64 idx;
> +
> +       next = rb_first(&obj->sets);
> +       while (next) {

for(next = rb_first(...); next; next = rb_next(next)) seems like a
good fit here, no?

> +               set_id = rb_entry(next, struct btf_id, rb_node);
> +               if (set_id->kind != BTF_ID_KIND_SET8 || set_id->addr_cnt != 1)
> +                       goto skip;
> +
> +               set_lower_addr = set_id->addr[0];
> +               set_upper_addr = set_lower_addr + set_id->cnt * sizeof(u64);
> +
> +               for (u32 i = 0; i < kfunc_id->addr_cnt; i++) {
> +                       addr = kfunc_id->addr[i];
> +                       /*
> +                        * Lower bound is exclusive to skip the 8-byte header of the set.
> +                        * Upper bound is inclusive to capture the last entry at offset 8*cnt.
> +                        */
> +                       if (set_lower_addr < addr && addr <= set_upper_addr) {
> +                               pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n",
> +                                        kfunc_id->name, set_id->name);
> +                               goto found;

why goto, just do what needs to be done and return?

> +                       }
> +               }
> +skip:
> +               next = rb_next(next);
> +       }
> +
> +       return 0;
> +
> +found:
> +       idx = addr - obj->efile.idlist_addr;
> +       idx = idx / sizeof(u32) + 1;
> +       flags = elf_data_ptr[idx];
> +
> +       return flags;
> +}
> +
> +static s64 collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
> +{
> +       struct kfunc *kfunc, *kfuncs, *tmp;
> +       const char *tag_name, *func_name;
> +       struct btf *btf = ctx->btf;
> +       const struct btf_type *t;
> +       u32 flags, func_id;
> +       struct btf_id *id;
> +       s64 nr_kfuncs = 0;
> +
> +       if (ctx->nr_decl_tags == 0)
> +               return 0;
> +
> +       kfuncs = malloc(ctx->nr_decl_tags * sizeof(*kfuncs));

ditto about realloc() usage pattern

> +       if (!kfuncs)
> +               return -ENOMEM;
> +

[...]

> +/*
> + * For a kfunc with KF_IMPLICIT_ARGS we do the following:
> + *   1. Add a new function with _impl suffix in the name, with the prototype
> + *      of the original kfunc.
> + *   2. Add all decl tags except "bpf_kfunc" for the _impl func.
> + *   3. Add a new function prototype with modified list of arguments:
> + *      omitting implicit args.
> + *   4. Change the prototype of the original kfunc to the new one.
> + *
> + * This way we transform the BTF associated with the kfunc from
> + *     __bpf_kfunc bpf_foo(int arg1, void *implicit_arg);
> + * into
> + *     bpf_foo_impl(int arg1, void *implicit_arg);
> + *     __bpf_kfunc bpf_foo(int arg1);
> + *
> + * If a kfunc with KF_IMPLICIT_ARGS already has an _impl counterpart
> + * in BTF, then it's a legacy case: an _impl function is declared in the
> + * source code. In this case, we can skip adding an _impl function, but we
> + * still have to add a func prototype that omits implicit args.
> + */
> +static int process_kfunc_with_implicit_args(struct btf2btf_context *ctx, struct kfunc *kfunc)
> +{

this logic looks good

> +       s32 idx, new_proto_id, new_func_id, proto_id;
> +       const char *param_name, *tag_name;
> +       const struct btf_param *params;
> +       enum btf_func_linkage linkage;
> +       char tmp_name[KSYM_NAME_LEN];
> +       struct btf *btf = ctx->btf;
> +       int err, len, nr_params;
> +       struct btf_type *t;
> +

[...]
Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Ihor Solodrai 3 weeks, 1 day ago

On 1/16/26 4:06 PM, Andrii Nakryiko wrote:
> On Fri, Jan 16, 2026 at 12:17 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>
>> Implement BTF modifications in resolve_btfids to support BPF kernel
>> functions with implicit arguments.
>>
>> For a kfunc marked with KF_IMPLICIT_ARGS flag, a new function
>> prototype is added to BTF that does not have implicit arguments. The
>> kfunc's prototype is then updated to a new one in BTF. This prototype
>> is the intended interface for the BPF programs.
>>
>> A <func_name>_impl function is added to BTF to make the original kfunc
>> prototype searchable for the BPF verifier. If a <func_name>_impl
>> function already exists in BTF, its interpreted as a legacy case, and
>> this step is skipped.
>>
>> Whether an argument is implicit is determined by its type:
>> currently only `struct bpf_prog_aux *` is supported.
>>
>> As a result, the BTF associated with kfunc is changed from
>>
>>     __bpf_kfunc bpf_foo(int arg1, struct bpf_prog_aux *aux);
>>
>> into
>>
>>     bpf_foo_impl(int arg1, struct bpf_prog_aux *aux);
>>     __bpf_kfunc bpf_foo(int arg1);
>>
>> For more context see previous discussions and patches [1][2].
>>
>> [1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
>> [2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/
>>
>> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
>> ---
>>  tools/bpf/resolve_btfids/main.c | 383 ++++++++++++++++++++++++++++++++
>>  1 file changed, 383 insertions(+)
>>
> 
> [...]
> 
>> +static int collect_decl_tags(struct btf2btf_context *ctx)
>> +{
>> +       const u32 type_cnt = btf__type_cnt(ctx->btf);
>> +       struct btf *btf = ctx->btf;
>> +       const struct btf_type *t;
>> +       u32 *tags, *tmp;
>> +       u32 nr_tags = 0;
>> +
>> +       tags = malloc(type_cnt * sizeof(u32));
> 
> waste of memory, really, see below
> 
>> +       if (!tags)
>> +               return -ENOMEM;
>> +
>> +       for (u32 id = 1; id < type_cnt; id++) {
>> +               t = btf__type_by_id(btf, id);
>> +               if (!btf_is_decl_tag(t))
>> +                       continue;
>> +               tags[nr_tags++] = id;
>> +       }
>> +
>> +       if (nr_tags == 0) {
>> +               ctx->decl_tags = NULL;
>> +               free(tags);
>> +               return 0;
>> +       }
>> +
>> +       tmp = realloc(tags, nr_tags * sizeof(u32));
>> +       if (!tmp) {
>> +               free(tags);
>> +               return -ENOMEM;
>> +       }
> 
> This is an interesting realloc() usage pattern, it's quite
> unconventional to preallocate too much memory, and then shrink (in C
> world)
> 
> check libbpf's libbpf_add_mem(), that's a generic "primitive" inside
> the libbpf. Do not reuse it as is, but it should give you an idea of a
> common pattern: you start with NULL (empty data), when you need to add
> a new element, you calculate a new array size which normally would be
> some minimal value (to avoid going through 1 -> 2 -> 4 -> 8, many
> small and wasteful steps; normally we just jump straight to 16 or so)
> or some factor of previous size (doesn't have to be 2x,
> libbpf_add_mem() expands by 25%, for instance).
> 
> This is a super common approach in C. Please utilize it here as well.

Hi Andrii, thanks for taking a quick look.

I am aware of the typical size doubling (or whatever the multiplier
is) pattern for growing arrays. Amortized cost and all that.

I don't know if this pre-alloc + shrink is common, but I did use it in
pahole before [1], for example.

The chain of thought that makes me like it is:
  * if we knew the array size beforehand, we'd simply pre-allocate it
  * here we don't, but we do know an upper limit (and it's not crazy)
  * if we pre-allocate to upper limit, we can use the array without
    worrying about the bounds checks and growing on every use
  * if we care (we might not), we can shrink to the actual size

The dynamic array approach is certainly more generic, and helpers can
be written to make it easy. But in cases like this - collect something
once and then use - over-pre-allocating makes more sense to me.

Re waste we are talking <1Mb (~100k types * 4), so it's whatever.

In any case it's not super important, so I don't mind changing this if
you insist. Being conventional has it's benefits too.

[1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/tree/btf_encoder.c?h=v1.31#n2182

> 
>> +
>> +       ctx->decl_tags = tmp;
>> +       ctx->nr_decl_tags = nr_tags;
>> +
>> +       return 0;
>> +}
>> +
>> +/*
>> + * To find the kfunc flags having its struct btf_id (with ELF addresses)
>> + * we need to find the address that is in range of a set8.
>> + * If a set8 is found, then the flags are located at addr + 4 bytes.
>> + * Return 0 (no flags!) if not found.
>> + */
>> +static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id)
>> +{
>> +       const u32 *elf_data_ptr = obj->efile.idlist->d_buf;
>> +       u64 set_lower_addr, set_upper_addr, addr;
>> +       struct btf_id *set_id;
>> +       struct rb_node *next;
>> +       u32 flags;
>> +       u64 idx;
>> +
>> +       next = rb_first(&obj->sets);
>> +       while (next) {
> 
> for(next = rb_first(...); next; next = rb_next(next)) seems like a
> good fit here, no?

Looks like it. We could do 'continue' then.

> 
>> +               set_id = rb_entry(next, struct btf_id, rb_node);
>> +               if (set_id->kind != BTF_ID_KIND_SET8 || set_id->addr_cnt != 1)
>> +                       goto skip;
>> +
>> +               set_lower_addr = set_id->addr[0];
>> +               set_upper_addr = set_lower_addr + set_id->cnt * sizeof(u64);
>> +
>> +               for (u32 i = 0; i < kfunc_id->addr_cnt; i++) {
>> +                       addr = kfunc_id->addr[i];
>> +                       /*
>> +                        * Lower bound is exclusive to skip the 8-byte header of the set.
>> +                        * Upper bound is inclusive to capture the last entry at offset 8*cnt.
>> +                        */
>> +                       if (set_lower_addr < addr && addr <= set_upper_addr) {
>> +                               pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n",
>> +                                        kfunc_id->name, set_id->name);
>> +                               goto found;
> 
> why goto, just do what needs to be done and return?

Indeed.

> 
>> +                       }
>> +               }
>> +skip:
>> +               next = rb_next(next);
>> +       }
>> +
>> +       return 0;
>> +
>> +found:
>> +       idx = addr - obj->efile.idlist_addr;
>> +       idx = idx / sizeof(u32) + 1;
>> +       flags = elf_data_ptr[idx];
>> +
>> +       return flags;
>> +}
>> +
>> +static s64 collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
>> +{
>> +       struct kfunc *kfunc, *kfuncs, *tmp;
>> +       const char *tag_name, *func_name;
>> +       struct btf *btf = ctx->btf;
>> +       const struct btf_type *t;
>> +       u32 flags, func_id;
>> +       struct btf_id *id;
>> +       s64 nr_kfuncs = 0;
>> +
>> +       if (ctx->nr_decl_tags == 0)
>> +               return 0;
>> +
>> +       kfuncs = malloc(ctx->nr_decl_tags * sizeof(*kfuncs));
> 
> ditto about realloc() usage pattern
> 
>> +       if (!kfuncs)
>> +               return -ENOMEM;
>> +
> 
> [...]
> 
>> +/*
>> + * For a kfunc with KF_IMPLICIT_ARGS we do the following:
>> + *   1. Add a new function with _impl suffix in the name, with the prototype
>> + *      of the original kfunc.
>> + *   2. Add all decl tags except "bpf_kfunc" for the _impl func.
>> + *   3. Add a new function prototype with modified list of arguments:
>> + *      omitting implicit args.
>> + *   4. Change the prototype of the original kfunc to the new one.
>> + *
>> + * This way we transform the BTF associated with the kfunc from
>> + *     __bpf_kfunc bpf_foo(int arg1, void *implicit_arg);
>> + * into
>> + *     bpf_foo_impl(int arg1, void *implicit_arg);
>> + *     __bpf_kfunc bpf_foo(int arg1);
>> + *
>> + * If a kfunc with KF_IMPLICIT_ARGS already has an _impl counterpart
>> + * in BTF, then it's a legacy case: an _impl function is declared in the
>> + * source code. In this case, we can skip adding an _impl function, but we
>> + * still have to add a func prototype that omits implicit args.
>> + */
>> +static int process_kfunc_with_implicit_args(struct btf2btf_context *ctx, struct kfunc *kfunc)
>> +{
> 
> this logic looks good
> 
>> +       s32 idx, new_proto_id, new_func_id, proto_id;
>> +       const char *param_name, *tag_name;
>> +       const struct btf_param *params;
>> +       enum btf_func_linkage linkage;
>> +       char tmp_name[KSYM_NAME_LEN];
>> +       struct btf *btf = ctx->btf;
>> +       int err, len, nr_params;
>> +       struct btf_type *t;
>> +
> 
> [...]

Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Eduard Zingerman 2 weeks, 5 days ago
On Fri, 2026-01-16 at 22:36 -0800, Ihor Solodrai wrote:

[...]

> > > +static int collect_decl_tags(struct btf2btf_context *ctx)
> > > +{
> > > +       const u32 type_cnt = btf__type_cnt(ctx->btf);
> > > +       struct btf *btf = ctx->btf;
> > > +       const struct btf_type *t;
> > > +       u32 *tags, *tmp;
> > > +       u32 nr_tags = 0;
> > > +
> > > +       tags = malloc(type_cnt * sizeof(u32));
> > 
> > waste of memory, really, see below
> > 
> > > +       if (!tags)
> > > +               return -ENOMEM;
> > > +
> > > +       for (u32 id = 1; id < type_cnt; id++) {
> > > +               t = btf__type_by_id(btf, id);
> > > +               if (!btf_is_decl_tag(t))
> > > +                       continue;
> > > +               tags[nr_tags++] = id;
> > > +       }
> > > +
> > > +       if (nr_tags == 0) {
> > > +               ctx->decl_tags = NULL;
> > > +               free(tags);
> > > +               return 0;
> > > +       }
> > > +
> > > +       tmp = realloc(tags, nr_tags * sizeof(u32));
> > > +       if (!tmp) {
> > > +               free(tags);
> > > +               return -ENOMEM;
> > > +       }
> > 
> > This is an interesting realloc() usage pattern, it's quite
> > unconventional to preallocate too much memory, and then shrink (in C
> > world)
> > 
> > check libbpf's libbpf_add_mem(), that's a generic "primitive" inside
> > the libbpf. Do not reuse it as is, but it should give you an idea of a
> > common pattern: you start with NULL (empty data), when you need to add
> > a new element, you calculate a new array size which normally would be
> > some minimal value (to avoid going through 1 -> 2 -> 4 -> 8, many
> > small and wasteful steps; normally we just jump straight to 16 or so)
> > or some factor of previous size (doesn't have to be 2x,
> > libbpf_add_mem() expands by 25%, for instance).
> > 
> > This is a super common approach in C. Please utilize it here as well.
> 
> Hi Andrii, thanks for taking a quick look.
> 
> I am aware of the typical size doubling (or whatever the multiplier
> is) pattern for growing arrays. Amortized cost and all that.
> 
> I don't know if this pre-alloc + shrink is common, but I did use it in
> pahole before [1], for example.
> 
> The chain of thought that makes me like it is:
>   * if we knew the array size beforehand, we'd simply pre-allocate it
>   * here we don't, but we do know an upper limit (and it's not crazy)
>   * if we pre-allocate to upper limit, we can use the array without
>     worrying about the bounds checks and growing on every use
>   * if we care (we might not), we can shrink to the actual size
> 
> The dynamic array approach is certainly more generic, and helpers can
> be written to make it easy. But in cases like this - collect something
> once and then use - over-pre-allocating makes more sense to me.
> 
> Re waste we are talking <1Mb (~100k types * 4), so it's whatever.
> 
> In any case it's not super important, so I don't mind changing this if
> you insist. Being conventional has it's benefits too.
> 
> [1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/tree/btf_encoder.c?h=v1.31#n2182

In my test kernel there are ~70K types and ~300 decl tags.
Allocating an array of 70K elements to store 300 seem to be quite an overkill.
I'd move to what Andrii suggests just to reduce the surprise factor for the reader.

[...]
Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by bot+bpf-ci@kernel.org 3 weeks, 1 day ago
> diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
> index 1fcf37af6764..b83316359cfd 100644
> --- a/tools/bpf/resolve_btfids/main.c
> +++ b/tools/bpf/resolve_btfids/main.c

[ ... ]

> +struct btf2btf_context {
> +	struct btf *btf;
> +	u32 *decl_tags;
> +	u32 nr_decl_tags;
> +	struct kfunc *kfuncs;
> +	u32 nr_kfuncs;
> +};

[ ... ]

> +static int collect_decl_tags(struct btf2btf_context *ctx)
> +{
> +	const u32 type_cnt = btf__type_cnt(ctx->btf);
> +	struct btf *btf = ctx->btf;
> +	const struct btf_type *t;
> +	u32 *tags, *tmp;
> +	u32 nr_tags = 0;
> +
> +	tags = malloc(type_cnt * sizeof(u32));
> +	if (!tags)
> +		return -ENOMEM;

[ ... ]

> +	tmp = realloc(tags, nr_tags * sizeof(u32));
> +	if (!tmp) {
> +		free(tags);
> +		return -ENOMEM;
> +	}
> +
> +	ctx->decl_tags = tmp;
> +	ctx->nr_decl_tags = nr_tags;
> +
> +	return 0;
> +}

[ ... ]

> +static s64 collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
> +{
> +	struct kfunc *kfunc, *kfuncs, *tmp;

[ ... ]

> +	kfuncs = malloc(ctx->nr_decl_tags * sizeof(*kfuncs));
> +	if (!kfuncs)
> +		return -ENOMEM;

[ ... ]

> +	tmp = realloc(kfuncs, nr_kfuncs * sizeof(*kfuncs));
> +	if (!tmp) {
> +		free(kfuncs);
> +		return -ENOMEM;
> +	}
> +
> +	ctx->kfuncs = tmp;
> +	ctx->nr_kfuncs = nr_kfuncs;
> +
> +	return 0;
> +}
> +
> +static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx)
> +{
> +	int err;
> +
> +	ctx->btf = obj->btf;
> +
> +	err = collect_decl_tags(ctx);
> +	if (err) {
> +		pr_err("ERROR: resolve_btfids: failed to collect decl tags from BTF\n");
> +		return err;
> +	}
> +
> +	err = collect_kfuncs(obj, ctx);
> +	if (err) {
> +		pr_err("ERROR: resolve_btfids: failed to collect kfuncs from BTF\n");
> +		return err;
                  ^^^^

When collect_kfuncs() fails here, ctx->decl_tags has already been allocated
by collect_decl_tags() but is not freed before returning. Should this error
path call free(ctx->decl_tags)?

> +	}
> +
> +	return 0;
> +}

[ ... ]

> +static int btf2btf(struct object *obj)
> +{
> +	struct btf2btf_context ctx = {};
> +	int err;
> +
> +	err = build_btf2btf_context(obj, &ctx);
> +	if (err)
> +		return err;
> +
> +	for (u32 i = 0; i < ctx.nr_kfuncs; i++) {
> +		struct kfunc *kfunc = &ctx.kfuncs[i];
> +
> +		if (!(kfunc->flags & KF_IMPLICIT_ARGS))
> +			continue;
> +
> +		err = process_kfunc_with_implicit_args(&ctx, kfunc);
> +		if (err)
> +			return err;
> +	}
> +
> +	return 0;
> +}
          ^^^^

ctx.decl_tags and ctx.kfuncs are allocated during build_btf2btf_context()
but are never freed on any return path from btf2btf(). While this may be
acceptable for a short-lived build tool where the OS reclaims memory on
exit, should cleanup be added for consistency with the rest of the code
which carefully frees allocations?


---
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/21079944982
Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
Posted by Ihor Solodrai 3 weeks, 1 day ago
On 1/16/26 12:39 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
>> index 1fcf37af6764..b83316359cfd 100644
>> --- a/tools/bpf/resolve_btfids/main.c
>> +++ b/tools/bpf/resolve_btfids/main.c
> 
> [ ... ]
> 
>> +struct btf2btf_context {
>> +	struct btf *btf;
>> +	u32 *decl_tags;
>> +	u32 nr_decl_tags;
>> +	struct kfunc *kfuncs;
>> +	u32 nr_kfuncs;
>> +};
> 
> [ ... ]
> 
>> +static int collect_decl_tags(struct btf2btf_context *ctx)
>> +{
>> +	const u32 type_cnt = btf__type_cnt(ctx->btf);
>> +	struct btf *btf = ctx->btf;
>> +	const struct btf_type *t;
>> +	u32 *tags, *tmp;
>> +	u32 nr_tags = 0;
>> +
>> +	tags = malloc(type_cnt * sizeof(u32));
>> +	if (!tags)
>> +		return -ENOMEM;
> 
> [ ... ]
> 
>> +	tmp = realloc(tags, nr_tags * sizeof(u32));
>> +	if (!tmp) {
>> +		free(tags);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	ctx->decl_tags = tmp;
>> +	ctx->nr_decl_tags = nr_tags;
>> +
>> +	return 0;
>> +}
> 
> [ ... ]
> 
>> +static s64 collect_kfuncs(struct object *obj, struct btf2btf_context *ctx)
>> +{
>> +	struct kfunc *kfunc, *kfuncs, *tmp;
> 
> [ ... ]
> 
>> +	kfuncs = malloc(ctx->nr_decl_tags * sizeof(*kfuncs));
>> +	if (!kfuncs)
>> +		return -ENOMEM;
> 
> [ ... ]
> 
>> +	tmp = realloc(kfuncs, nr_kfuncs * sizeof(*kfuncs));
>> +	if (!tmp) {
>> +		free(kfuncs);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	ctx->kfuncs = tmp;
>> +	ctx->nr_kfuncs = nr_kfuncs;
>> +
>> +	return 0;
>> +}
>> +
>> +static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx)
>> +{
>> +	int err;
>> +
>> +	ctx->btf = obj->btf;
>> +
>> +	err = collect_decl_tags(ctx);
>> +	if (err) {
>> +		pr_err("ERROR: resolve_btfids: failed to collect decl tags from BTF\n");
>> +		return err;
>> +	}
>> +
>> +	err = collect_kfuncs(obj, ctx);
>> +	if (err) {
>> +		pr_err("ERROR: resolve_btfids: failed to collect kfuncs from BTF\n");
>> +		return err;
>                   ^^^^
> 
> When collect_kfuncs() fails here, ctx->decl_tags has already been allocated
> by collect_decl_tags() but is not freed before returning. Should this error
> path call free(ctx->decl_tags)?
> 
>> +	}
>> +
>> +	return 0;
>> +}
> 
> [ ... ]
> 
>> +static int btf2btf(struct object *obj)
>> +{
>> +	struct btf2btf_context ctx = {};
>> +	int err;
>> +
>> +	err = build_btf2btf_context(obj, &ctx);
>> +	if (err)
>> +		return err;
>> +
>> +	for (u32 i = 0; i < ctx.nr_kfuncs; i++) {
>> +		struct kfunc *kfunc = &ctx.kfuncs[i];
>> +
>> +		if (!(kfunc->flags & KF_IMPLICIT_ARGS))
>> +			continue;
>> +
>> +		err = process_kfunc_with_implicit_args(&ctx, kfunc);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>> +	return 0;
>> +}
>           ^^^^
> 
> ctx.decl_tags and ctx.kfuncs are allocated during build_btf2btf_context()
> but are never freed on any return path from btf2btf(). While this may be
> acceptable for a short-lived build tool where the OS reclaims memory on
> exit, should cleanup be added for consistency with the rest of the code
> which carefully frees allocations?

Right. I just noticed this after sending.

As AI mentioned, it's not a sever issue, but we should free everything
properly to be consistent.


> 
> 
> ---
> 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/21079944982