All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>
Cc: bpf@vger.kernel.org
Subject: [PATCH bpf-next v1 2/4] libbpf: Render decl_tags in btf_dump
Date: Wed, 16 Sep 2026 18:20:35 -0700	[thread overview]
Message-ID: <20260917012037.1396254-3-ihor.solodrai@linux.dev> (raw)
In-Reply-To: <20260917012037.1396254-1-ihor.solodrai@linux.dev>

BTF_KIND_DECL_TAG is an attribute on a declaration: with kind_flag=0
the tag name is the argument of a btf_decl_tag(), and with kind_flag=1
it encodes a complete __attribute__.

When doing btf_dump, render both forms at every declaration btf_dump
emits - a record, a record member and a typedef:

	struct foo { ... } __attribute__((bar));
	struct foo { int a __attribute__((bar)); };
	typedef struct { ... } __attribute__((bar)) foo_t;

A decl tag is a standalone type pointing at its target.  Build an
index of decl tags in btf_dump_resize().  Keep it as a flat array
sorted by (target ID, tag ID). This allows for a stable emission order
in btf_dump_emit_decl_tags(). Tag IDs are unique, so the comparison is
a total order and qsort not being stable does not matter.

Only composite and typedef targets are indexed. Valid BTF allows for
decl_tags on many types, however btf_dump only supports records,
record members and typedefs and ignores datasec, var and func.

btf_dump_emit_decl_tags() binary searches for where the target's
entries would begin and walks tags while the target matches. A record
shares its target with its members, so the component_idx is matched
there.

A record attribute goes after the closing brace, where
__attribute__((packed)) already goes. A member attribute goes after
the bit-field width: clang rejects one between the declarator and the
':'. A typedef takes it after the declarator, so a typedef of an
anonymous record can carry two groups at once, one binding to the
record and one to the typedef.

Every decl tag in vmlinux BTF is a bpf_kfunc or bpf_fastcall tag on a
FUNC, which has no declaration in the C output, so generated vmlinux.h
does not change.

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

diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index 635fa969e145..1e8236c8b9a5 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -77,6 +77,11 @@ struct btf_dump_data {
 	bool is_array_char;
 };
 
+struct decl_tag_desc {
+	__u32 target_id;
+	__u32 tag_id;
+};
+
 struct btf_dump {
 	const struct btf *btf;
 	btf_dump_printf_fn_t printf_fn;
@@ -93,6 +98,11 @@ struct btf_dump {
 	const char **cached_names;
 	size_t cached_names_cap;
 
+	/* decl tags, sorted by (target ID, tag ID) */
+	struct decl_tag_desc *decl_tags;
+	size_t decl_tags_cnt;
+	size_t decl_tags_cap;
+
 	/* topo-sorted list of dependent type definitions */
 	__u32 *emit_queue;
 	int emit_queue_cap;
@@ -192,9 +202,37 @@ struct btf_dump *btf_dump__new(const struct btf *btf,
 	return libbpf_err_ptr(err);
 }
 
+static int btf_dump_cmp_decl_tags(const void *a, const void *b)
+{
+	const struct decl_tag_desc *x = a, *y = b;
+
+	if (x->target_id != y->target_id)
+		return x->target_id < y->target_id ? -1 : 1;
+	return x->tag_id < y->tag_id ? -1 : 1;
+}
+
+static int btf_dump_push_decl_tag(struct btf_dump *d, __u32 id, const struct btf_type *t)
+{
+	const struct btf_type *target = btf__type_by_id(d->btf, t->type);
+
+	if (!target || (!btf_is_composite(target) && !btf_is_typedef(target)))
+		return 0;
+
+	if (libbpf_ensure_mem((void **)&d->decl_tags, &d->decl_tags_cap,
+			      sizeof(*d->decl_tags), d->decl_tags_cnt + 1))
+		return -ENOMEM;
+
+	d->decl_tags[d->decl_tags_cnt++] = (struct decl_tag_desc) {
+		.target_id = t->type,
+		.tag_id = id,
+	};
+	return 0;
+}
+
 static int btf_dump_resize(struct btf_dump *d)
 {
 	int err, last_id = btf__type_cnt(d->btf) - 1;
+	size_t cnt = d->decl_tags_cnt;
 	const struct btf_type *t;
 	__u32 i;
 
@@ -220,8 +258,18 @@ static int btf_dump_resize(struct btf_dump *d)
 		err = btf_dump_mark_referenced(d, t);
 		if (err)
 			return err;
+
+		if (btf_is_decl_tag(t)) {
+			err = btf_dump_push_decl_tag(d, i, t);
+			if (err)
+				return err;
+		}
 	}
 
+	if (d->decl_tags_cnt != cnt)
+		qsort(d->decl_tags, d->decl_tags_cnt, sizeof(*d->decl_tags),
+		      btf_dump_cmp_decl_tags);
+
 	d->last_id = last_id;
 	return 0;
 }
@@ -256,6 +304,7 @@ void btf_dump__free(struct btf_dump *d)
 		}
 	}
 	free(d->cached_names);
+	free(d->decl_tags);
 	free(d->emit_queue);
 	free(d->decl_stack);
 	btf_dump_free_names(d->type_names);
@@ -962,6 +1011,47 @@ static void btf_dump_emit_struct_fwd(struct btf_dump *d, __u32 id,
 			btf_dump_type_name(d, id));
 }
 
+static void btf_dump_emit_decl_tag(struct btf_dump *d, const struct btf_type *t)
+{
+	const char *name = btf_name_of(d, t->name_off);
+
+	if (btf_kflag(t))
+		btf_dump_printf(d, " __attribute__((%s))", name);
+	else
+		btf_dump_printf(d, " __attribute__((btf_decl_tag(\"%s\")))", name);
+}
+
+/*
+ * btf_dump_resize() keeps d->decl_tags sorted by (target ID, tag ID), so the
+ * tags of one type form a run that binary search finds the start of, in a
+ * fixed order so that the same BTF always renders the same C.
+ *
+ * component_idx is not stored in d->decl_tags: a record and its members share
+ * a target ID, so it is read from each tag.
+ */
+static void btf_dump_emit_decl_tags(struct btf_dump *d, __u32 id, int comp_idx)
+{
+	size_t i, lo = 0, hi = d->decl_tags_cnt, mid;
+	const struct btf_type *t;
+
+	/* lower bound on target_id */
+	while (lo < hi) {
+		mid = lo + (hi - lo) / 2;
+
+		if (d->decl_tags[mid].target_id < id)
+			lo = mid + 1;
+		else
+			hi = mid;
+	}
+
+	for (i = lo; i < d->decl_tags_cnt && d->decl_tags[i].target_id == id; i++) {
+		t = btf__type_by_id(d->btf, d->decl_tags[i].tag_id);
+
+		if (btf_decl_tag(t)->component_idx == comp_idx)
+			btf_dump_emit_decl_tag(d, t);
+	}
+}
+
 static void btf_dump_emit_struct_def(struct btf_dump *d,
 				     __u32 id,
 				     const struct btf_type *t,
@@ -1007,6 +1097,8 @@ static void btf_dump_emit_struct_def(struct btf_dump *d,
 			prev_bitfield = false;
 		}
 
+		/* after the bit-field width; an attribute cannot precede it */
+		btf_dump_emit_decl_tags(d, id, i);
 		btf_dump_printf(d, ";");
 	}
 
@@ -1026,6 +1118,7 @@ static void btf_dump_emit_struct_def(struct btf_dump *d,
 	}
 	if (packed)
 		btf_dump_printf(d, " __attribute__((packed))");
+	btf_dump_emit_decl_tags(d, id, -1);
 }
 
 static const char *missing_base_types[][2] = {
@@ -1203,6 +1296,7 @@ static void btf_dump_emit_typedef_def(struct btf_dump *d, __u32 id,
 
 	btf_dump_printf(d, "typedef ");
 	btf_dump_emit_type_decl(d, t->type, name, lvl);
+	btf_dump_emit_decl_tags(d, id, -1);
 }
 
 static int btf_dump_push_decl_stack_id(struct btf_dump *d, __u32 id)
-- 
2.55.0


  parent reply	other threads:[~2026-09-17  1:21 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-17  1:20 [PATCH bpf-next v1 0/4] libbpf: Render decl_tags in btf_dump Ihor Solodrai
2026-09-17  1:20 ` [PATCH bpf-next v1 1/4] libbpf: Walk types in btf_dump_resize(), not in mark_referenced() Ihor Solodrai
2026-09-17 22:32   ` Eduard Zingerman
2026-09-17  1:20 ` Ihor Solodrai [this message]
2026-09-17  1:30   ` [PATCH bpf-next v1 2/4] libbpf: Render decl_tags in btf_dump sashiko-bot
2026-09-17  2:12   ` Alexei Starovoitov
2026-09-17  3:25     ` Ihor Solodrai
2026-09-17 22:46   ` Eduard Zingerman
2026-09-17 23:59   ` Andrii Nakryiko
2026-09-22 16:59     ` Ihor Solodrai
2026-09-22 19:43       ` Andrii Nakryiko
2026-09-17  1:20 ` [PATCH bpf-next v1 3/4] selftests/bpf: Test btf_dump rendering of decl_tags Ihor Solodrai
2026-09-17 10:30   ` Alan Maguire
2026-09-17 17:38     ` Ihor Solodrai
2026-09-17 23:18   ` Eduard Zingerman
2026-09-17  1:20 ` [PATCH bpf-next v1 4/4] selftests/bpf: Show the tag kflag in the raw BTF dump helper Ihor Solodrai
2026-09-17  1:27   ` sashiko-bot
2026-09-17 23:21   ` Eduard Zingerman

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=20260917012037.1396254-3-ihor.solodrai@linux.dev \
    --to=ihor.solodrai@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=memxor@gmail.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.