BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump
@ 2026-09-29 23:46 Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced() Ihor Solodrai
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

BTF supports arbitrary __attribute__ encoding via decl tags and type
tags [1]. However btf_dump facility only outputs the attributes
encoded in BTF as type tags, which was implemented in commit
2019c58318b8 ("libbpf: Check the kflag of type tags in btf_dump").

BTF_KIND_DECL_TAG has been left out of btf_dump entirely, independent
of the kflag value. An attribute a BTF producer encoded is silently
dropped from the C output.

Implement decl_tag support in btf_dump. The btf_dump limits rendered
BTF types to records, record members and typedefs. Other types,
particularly functions, are deliberately skipped. Therefore decl_tag
rendering works only for the same subset of target types.

To be efficient, decl_tag dump support requires a reverse index from
the target type id to the list of tags pointing at it.

With decl_tag dump support, provided a validly constructed BTF, it is
now possible to obtain C-syntax output like the following example:

	struct foo {
		int a __attribute__((bar));
		int b: 3 __attribute__((bar));
	} __attribute__((bar));
	typedef struct { ... } __attribute__((bar)) foo_t __attribute__((baz));


This closes a generic gap in btf_dump, which silently dropped every
decl tag it was given. The in-tree consumer is:

    bpftool btf dump ... format c

A BPF object now keeps its member tags, such as the "contains:" tags.

[1] https://lore.kernel.org/bpf/20250130201239.1429648-1-ihor.solodrai@linux.dev/

---

v1->v2:
  * index all decl tags in btf_dump (Andrii)
  * roll the decl tag count back on resize errors  (Alexei, sashiko)
  * new patch #3 with the ASSERT_TEXT_EQ() text-comparison helper (Eduard)
  * fold the incremental subtest into test_btf_dump_incremental (Eduard)
  * cut test_btf_attrs down to bare API calls plus VALIDATE_RAW_BTF,
    with no BTF_*_ENC macros (sashiko, Eduard)

v1: https://lore.kernel.org/bpf/20260917012037.1396254-1-ihor.solodrai@linux.dev/

---

Ihor Solodrai (5):
  libbpf: Walk types in btf_dump_resize(), not in mark_referenced()
  libbpf: Render decl_tags in btf_dump
  selftests/bpf: Add ASSERT_TEXT_EQ()
  selftests/bpf: Test btf_dump rendering of decl_tags
  selftests/bpf: Show the tag kflag in the raw BTF dump helper

 tools/lib/bpf/btf_dump.c                      | 214 +++++++++----
 tools/testing/selftests/bpf/btf_helpers.c     |  10 +-
 .../bpf/prog_tests/bpftool_btf_dump.c         |   6 +-
 .../selftests/bpf/prog_tests/btf_dump.c       | 285 +++++++++++++++++-
 .../selftests/bpf/prog_tests/btf_write.c      |  34 +++
 tools/testing/selftests/bpf/test_progs.h      |   3 +
 6 files changed, 475 insertions(+), 77 deletions(-)


base-commit: cc6010e6e5cbe1f743d3011991041f227d976a5e
-- 
2.50.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v2 1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced()
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
@ 2026-09-29 23:46 ` Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 2/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

btf_dump_mark_referenced() both walks every type added since the last
resize and decides what each one references. Move the walk out to
btf_dump_resize() and hand the function one type at a time, so a second
per-type job can share the same pass instead of adding another one.

No functional change.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
 tools/lib/bpf/btf_dump.c | 122 +++++++++++++++++++--------------------
 1 file changed, 60 insertions(+), 62 deletions(-)

diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index fa995c02a170..e0293e6d3492 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -143,7 +143,7 @@ static void btf_dump_printf(const struct btf_dump *d, const char *fmt, ...)
 	va_end(args);
 }
 
-static int btf_dump_mark_referenced(struct btf_dump *d);
+static int btf_dump_mark_referenced(struct btf_dump *d, const struct btf_type *t);
 static int btf_dump_resize(struct btf_dump *d);
 
 struct btf_dump *btf_dump__new(const struct btf *btf,
@@ -195,6 +195,8 @@ struct btf_dump *btf_dump__new(const struct btf *btf,
 static int btf_dump_resize(struct btf_dump *d)
 {
 	int err, last_id = btf__type_cnt(d->btf) - 1;
+	const struct btf_type *t;
+	__u32 i;
 
 	if (last_id <= d->last_id)
 		return 0;
@@ -212,10 +214,13 @@ static int btf_dump_resize(struct btf_dump *d)
 		d->type_states[0].emit_state = EMITTED;
 	}
 
-	/* eagerly determine referenced types for anon enums */
-	err = btf_dump_mark_referenced(d);
-	if (err)
-		return err;
+	for (i = d->last_id + 1; i <= last_id; i++) {
+		t = btf__type_by_id(d->btf, i);
+
+		err = btf_dump_mark_referenced(d, t);
+		if (err)
+			return err;
+	}
 
 	d->last_id = last_id;
 	return 0;
@@ -312,71 +317,64 @@ int btf_dump__dump_type(struct btf_dump *d, __u32 id)
  * top-level anonymous enum won't be referenced by anything, while embedded
  * one will.
  */
-static int btf_dump_mark_referenced(struct btf_dump *d)
+static int btf_dump_mark_referenced(struct btf_dump *d, const struct btf_type *t)
 {
-	int i, j, n = btf__type_cnt(d->btf);
-	const struct btf_type *t;
-	__u32 vlen;
+	__u32 j, vlen = btf_vlen(t);
 
-	for (i = d->last_id + 1; i < n; i++) {
-		t = btf__type_by_id(d->btf, i);
-		vlen = btf_vlen(t);
-
-		switch (btf_kind(t)) {
-		case BTF_KIND_INT:
-		case BTF_KIND_ENUM:
-		case BTF_KIND_ENUM64:
-		case BTF_KIND_FWD:
-		case BTF_KIND_FLOAT:
-		case BTF_KIND_LOC_PARAM:
-		case BTF_KIND_LOC_PROTO:
-		case BTF_KIND_LOCSEC:
-			break;
+	switch (btf_kind(t)) {
+	case BTF_KIND_INT:
+	case BTF_KIND_ENUM:
+	case BTF_KIND_ENUM64:
+	case BTF_KIND_FWD:
+	case BTF_KIND_FLOAT:
+	case BTF_KIND_LOC_PARAM:
+	case BTF_KIND_LOC_PROTO:
+	case BTF_KIND_LOCSEC:
+		break;
 
-		case BTF_KIND_VOLATILE:
-		case BTF_KIND_CONST:
-		case BTF_KIND_RESTRICT:
-		case BTF_KIND_PTR:
-		case BTF_KIND_TYPEDEF:
-		case BTF_KIND_FUNC:
-		case BTF_KIND_VAR:
-		case BTF_KIND_DECL_TAG:
-		case BTF_KIND_TYPE_TAG:
-			d->type_states[t->type].referenced = 1;
-			break;
+	case BTF_KIND_VOLATILE:
+	case BTF_KIND_CONST:
+	case BTF_KIND_RESTRICT:
+	case BTF_KIND_PTR:
+	case BTF_KIND_TYPEDEF:
+	case BTF_KIND_FUNC:
+	case BTF_KIND_VAR:
+	case BTF_KIND_DECL_TAG:
+	case BTF_KIND_TYPE_TAG:
+		d->type_states[t->type].referenced = 1;
+		break;
 
-		case BTF_KIND_ARRAY: {
-			const struct btf_array *a = btf_array(t);
+	case BTF_KIND_ARRAY: {
+		const struct btf_array *a = btf_array(t);
 
-			d->type_states[a->index_type].referenced = 1;
-			d->type_states[a->type].referenced = 1;
-			break;
-		}
-		case BTF_KIND_STRUCT:
-		case BTF_KIND_UNION: {
-			const struct btf_member *m = btf_members(t);
+		d->type_states[a->index_type].referenced = 1;
+		d->type_states[a->type].referenced = 1;
+		break;
+	}
+	case BTF_KIND_STRUCT:
+	case BTF_KIND_UNION: {
+		const struct btf_member *m = btf_members(t);
 
-			for (j = 0; j < vlen; j++, m++)
-				d->type_states[m->type].referenced = 1;
-			break;
-		}
-		case BTF_KIND_FUNC_PROTO: {
-			const struct btf_param *p = btf_params(t);
+		for (j = 0; j < vlen; j++, m++)
+			d->type_states[m->type].referenced = 1;
+		break;
+	}
+	case BTF_KIND_FUNC_PROTO: {
+		const struct btf_param *p = btf_params(t);
 
-			for (j = 0; j < vlen; j++, p++)
-				d->type_states[p->type].referenced = 1;
-			break;
-		}
-		case BTF_KIND_DATASEC: {
-			const struct btf_var_secinfo *v = btf_var_secinfos(t);
+		for (j = 0; j < vlen; j++, p++)
+			d->type_states[p->type].referenced = 1;
+		break;
+	}
+	case BTF_KIND_DATASEC: {
+		const struct btf_var_secinfo *v = btf_var_secinfos(t);
 
-			for (j = 0; j < vlen; j++, v++)
-				d->type_states[v->type].referenced = 1;
-			break;
-		}
-		default:
-			return -EINVAL;
-		}
+		for (j = 0; j < vlen; j++, v++)
+			d->type_states[v->type].referenced = 1;
+		break;
+	}
+	default:
+		return -EINVAL;
 	}
 	return 0;
 }
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v2 2/5] libbpf: Render decl_tags in btf_dump
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced() Ihor Solodrai
@ 2026-09-29 23:46 ` Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 3/5] selftests/bpf: Add ASSERT_TEXT_EQ() Ihor Solodrai
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

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().

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.

This closes a generic gap in btf_dump, which silently dropped every
decl tag it was given.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
 tools/lib/bpf/btf_dump.c | 94 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 93 insertions(+), 1 deletion(-)

diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index e0293e6d3492..996e61b5f11f 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_tag_cnt;
+	size_t decl_tag_cap;
+
 	/* topo-sorted list of dependent type definitions */
 	__u32 *emit_queue;
 	int emit_queue_cap;
@@ -192,9 +202,34 @@ 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;
+	if (x->tag_id != y->tag_id)
+		return x->tag_id < y->tag_id ? -1 : 1;
+	return 0;
+}
+
+static int btf_dump_push_decl_tag(struct btf_dump *d, __u32 id, const struct btf_type *t)
+{
+	if (libbpf_ensure_mem((void **)&d->decl_tags, &d->decl_tag_cap,
+			      sizeof(*d->decl_tags), d->decl_tag_cnt + 1))
+		return -ENOMEM;
+
+	d->decl_tags[d->decl_tag_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_tag_cnt;
 	const struct btf_type *t;
 	__u32 i;
 
@@ -219,11 +254,25 @@ static int btf_dump_resize(struct btf_dump *d)
 
 		err = btf_dump_mark_referenced(d, t);
 		if (err)
-			return err;
+			goto out_err;
+
+		if (btf_is_decl_tag(t)) {
+			err = btf_dump_push_decl_tag(d, i, t);
+			if (err)
+				goto out_err;
+		}
 	}
 
+	if (d->decl_tag_cnt != cnt)
+		qsort(d->decl_tags, d->decl_tag_cnt, sizeof(*d->decl_tags),
+		      btf_dump_cmp_decl_tags);
+
 	d->last_id = last_id;
 	return 0;
+
+out_err:
+	d->decl_tag_cnt = cnt;
+	return err;
 }
 
 static void btf_dump_free_names(struct hashmap *map)
@@ -256,6 +305,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);
@@ -968,6 +1018,44 @@ 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.
+ */
+static void btf_dump_emit_decl_tags(struct btf_dump *d, __u32 id, int comp_idx)
+{
+	size_t i, lo = 0, hi = d->decl_tag_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_tag_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,
@@ -1013,6 +1101,8 @@ static void btf_dump_emit_struct_def(struct btf_dump *d,
 			prev_bitfield = false;
 		}
 
+		/* after the bit-field width */
+		btf_dump_emit_decl_tags(d, id, i);
 		btf_dump_printf(d, ";");
 	}
 
@@ -1032,6 +1122,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] = {
@@ -1209,6 +1300,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.50.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v2 3/5] selftests/bpf: Add ASSERT_TEXT_EQ()
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced() Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 2/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
@ 2026-09-29 23:46 ` Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 4/5] selftests/bpf: Test btf_dump rendering of decl_tags Ihor Solodrai
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

Text comparisons in test_progs repeat the same two statements:
compare_text_to_expected() to print the diff, then an assert to record
the failure. ASSERT_OK() on the raw return also appends a stale errno
to the failure output.

Add ASSERT_TEXT_EQ() after ASSERT_HAS_SUBSTR(). The assert cannot move
into compare_text_to_expected() itself: the helper is linked into
standalone binaries without test__fail(), and the framework unit test
checks the raw mismatch return. Convert the 3 test_progs call sites
plus test_ctx__dump_and_compare(), so multi-line dumps fail with a
diff; prog_tests_framework.c keeps calling the helper directly.

Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../testing/selftests/bpf/prog_tests/bpftool_btf_dump.c  | 6 ++----
 tools/testing/selftests/bpf/prog_tests/btf_dump.c        | 9 ++-------
 tools/testing/selftests/bpf/test_progs.h                 | 3 +++
 3 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
index abc62958b6ed..534aa79a4278 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -207,7 +207,6 @@ static void test_dump(const char *btf_path, bool sorted)
 {
 	const char *exp_path;
 	char *dump, *exp;
-	int err;
 
 	exp_path = sorted ? EXPECTED_SORTED : EXPECTED_UNSORTED;
 
@@ -219,8 +218,7 @@ static void test_dump(const char *btf_path, bool sorted)
 	if (!exp)
 		goto out_dump;
 
-	err = compare_text_to_expected(dump, exp);
-	ASSERT_OK(err, sorted ? "cmp_sorted" : "cmp_unsorted");
+	ASSERT_TEXT_EQ(dump, exp, sorted ? "cmp_sorted" : "cmp_unsorted");
 
 	free(exp);
 out_dump:
@@ -260,7 +258,7 @@ static void test_loc_dump(const char *btf_path)
 	if (!dump)
 		return;
 
-	ASSERT_OK(compare_text_to_expected(dump, expected), "cmp_loc_dump");
+	ASSERT_TEXT_EQ(dump, expected, "cmp_loc_dump");
 	free(dump);
 }
 
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index fe04a955d46c..838f7232f591 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -161,12 +161,7 @@ static int test_btf_dump_case(int n, struct btf_dump_test_case *t)
 		goto done;
 	}
 
-	/*
-	 * The mismatch has already been reported, so this only has to
-	 * register the failure. ASSERT_OK() would append a stale errno to it.
-	 */
-	err = compare_text_to_expected(dump, expected);
-	ASSERT_EQ(err, 0, "compare_text_to_expected");
+	ASSERT_TEXT_EQ(dump, expected, "compare_text_to_expected");
 
 done:
 	free(expected);
@@ -224,7 +219,7 @@ static void test_ctx__dump_and_compare(struct test_ctx *t,
 	fflush(t->dump_buf_file);
 	t->dump_buf[t->dump_buf_sz] = 0; /* some libc implementations don't do this */
 
-	ASSERT_STREQ(t->dump_buf, expected_output, message);
+	ASSERT_TEXT_EQ(t->dump_buf, expected_output, message);
 }
 
 static void test_btf_dump_incremental(void)
diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selftests/bpf/test_progs.h
index ea493c477fbd..adc18f5eceb6 100644
--- a/tools/testing/selftests/bpf/test_progs.h
+++ b/tools/testing/selftests/bpf/test_progs.h
@@ -349,6 +349,9 @@ void hexdump(const char *prefix, const void *buf, size_t len);
 	___ok;								\
 })
 
+#define ASSERT_TEXT_EQ(actual, expected, name) \
+	ASSERT_TRUE(compare_text_to_expected(actual, expected) == 0, name)
+
 #define ASSERT_MEMEQ(actual, expected, len, name) ({			\
 	static int duration = 0;					\
 	const void *__act = actual;					\
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v2 4/5] selftests/bpf: Test btf_dump rendering of decl_tags
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
                   ` (2 preceding siblings ...)
  2026-09-29 23:46 ` [PATCH bpf-next v2 3/5] selftests/bpf: Add ASSERT_TEXT_EQ() Ihor Solodrai
@ 2026-09-29 23:46 ` Ihor Solodrai
  2026-09-29 23:46 ` [PATCH bpf-next v2 5/5] selftests/bpf: Show the tag kflag in the raw BTF dump helper Ihor Solodrai
  2026-09-30  9:10 ` [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump patchwork-bot+netdevbpf
  5 siblings, 0 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

Cover the decl tag rendering the libbpf patch added. One subtest
builds a BTF with every shape that renders, plus two that must not:

 - a named record with both forms of tag
 - a typedef'd anonymous record, whose attribute precedes the declarator
 - a union
 - a record that also carries the derived packed attribute
 - an anonymous record inlined at a member
 - two records whose tags are added in reverse target order, which is
   what pins the sort: without it the records lose their attributes
 - attributes on a var and on a func, which btf_dump never declares

The incremental subtest's second round adds an attribute to the new
struct, pinning the later-walk indexing of appended types.

A separate subtest covers members. A member attribute and a record one
share a key in the index, so it checks they do not leak into each
other's position, over a function pointer, an array and a bit-field -
declarators the attribute has to follow rather than precede.

Another covers typedefs, including a typedef of an anonymous record
carrying three groups at once, binding to the member, the record and
the typedef.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
 .../selftests/bpf/prog_tests/btf_dump.c       | 276 +++++++++++++++++-
 1 file changed, 275 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index 838f7232f591..17fa44cb9c91 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -310,6 +310,8 @@ static void test_btf_dump_incremental(void)
 	ASSERT_OK(err, "field_ok");
 	err = btf__add_field(btf, "s", 6, 64, 0);
 	ASSERT_OK(err, "field_ok");
+	/* attribute on a type added after the first dump: the later walk indexes it */
+	ASSERT_EQ(btf__add_decl_attr(btf, "round2", 7, -1), 8, "round2_attr");
 
 	test_ctx__dump_and_compare(&t,
 "struct s___2 {\n"
@@ -318,7 +320,270 @@ static void test_btf_dump_incremental(void)
 "		Y___2 = 1,\n"
 "	} y;\n"
 "	struct s s;\n"
-"};\n\n" , "c_dump1");
+"} __attribute__((round2));\n\n", "c_dump1");
+
+	test_ctx__free(&t);
+}
+
+static void test_btf_dump_decl_tags(void)
+{
+	struct test_ctx t = {};
+	struct btf *btf;
+	int id;
+
+	if (test_ctx__init(&t))
+		return;
+
+	btf = t.btf;
+
+	/*
+	 * Generate BTF corresponding to the following C code:
+	 *
+	 * struct s1 { int f; } __attribute__((attr1))
+	 *                      __attribute__((btf_decl_tag("plain_tag")));
+	 * typedef struct { int f; } __attribute__((a1)) __attribute__((a2)) t1;
+	 * union u1 { int f; } __attribute__((uattr));
+	 * struct p1 { char c; int i; } __attribute__((packed)) __attribute__((pattr));
+	 * struct outer { struct { int g; } __attribute__((inner)) in; };
+	 * struct s2 { int f; } __attribute__((on_s2));
+	 * struct s3 { int f; } __attribute__((on_s3));
+	 *
+	 * plus attributes on a var and a func, which btf_dump does not declare.
+	 */
+	id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	ASSERT_EQ(id, 1, "int_id");
+	id = btf__add_int(btf, "char", 1, BTF_INT_CHAR);
+	ASSERT_EQ(id, 2, "char_id");
+
+	/* a named record with one attribute of each form */
+	id = btf__add_struct(btf, "s1", 4);
+	ASSERT_EQ(id, 3, "s1_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "s1_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "attr1", 3, -1), 4, "s1_attr");
+	/* no kflag: rendered as a btf_decl_tag(), not as a bare attribute */
+	ASSERT_EQ(btf__add_decl_tag(btf, "plain_tag", 3, -1), 5, "s1_plain_tag");
+
+	/*
+	 * an anonymous record behind a typedef, with two attributes. This is
+	 * the case that cannot be expressed by a caller appending to the dump:
+	 * the attribute has to go before the declarator.
+	 */
+	id = btf__add_struct(btf, NULL, 4);
+	ASSERT_EQ(id, 6, "anon_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "anon_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "a1", 6, -1), 7, "anon_attr1");
+	ASSERT_EQ(btf__add_decl_attr(btf, "a2", 6, -1), 8, "anon_attr2");
+	id = btf__add_typedef(btf, "t1", 6);
+	ASSERT_EQ(id, 9, "typedef_id");
+
+	/* unions are records too */
+	id = btf__add_union(btf, "u1", 4);
+	ASSERT_EQ(id, 10, "u1_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "u1_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "uattr", 10, -1), 11, "u1_attr");
+
+	/* composes with the packed attribute, which is derived from layout */
+	id = btf__add_struct(btf, "p1", 5);
+	ASSERT_EQ(id, 12, "p1_id");
+	ASSERT_OK(btf__add_field(btf, "c", 2, 0, 0), "p1_field_c");
+	ASSERT_OK(btf__add_field(btf, "i", 1, 8, 0), "p1_field_i");
+	ASSERT_EQ(btf__add_decl_attr(btf, "pattr", 12, -1), 13, "p1_attr");
+
+	/* an anonymous record inlined at a member, the third emission position */
+	id = btf__add_struct(btf, NULL, 4);
+	ASSERT_EQ(id, 14, "inner_id");
+	ASSERT_OK(btf__add_field(btf, "g", 1, 0, 0), "inner_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "inner", 14, -1), 15, "inner_attr");
+	id = btf__add_struct(btf, "outer", 4);
+	ASSERT_EQ(id, 16, "outer_id");
+	ASSERT_OK(btf__add_field(btf, "in", 14, 0, 0), "outer_field");
+
+	/* not rendered: a var has no declaration in the C output */
+	id = btf__add_var(btf, "v", BTF_VAR_GLOBAL_ALLOCATED, 3);
+	ASSERT_EQ(id, 17, "var_id");
+	ASSERT_EQ(btf__add_decl_attr(btf, "var_attr", 17, -1), 18, "var_attr");
+	/* not rendered: neither does a func */
+	id = btf__add_func_proto(btf, 1);
+	ASSERT_EQ(id, 19, "proto_id");
+	id = btf__add_func(btf, "fn", BTF_FUNC_GLOBAL, 19);
+	ASSERT_EQ(id, 20, "func_id");
+	ASSERT_EQ(btf__add_decl_attr(btf, "func_attr", 20, -1), 21, "func_attr");
+
+	/*
+	 * Tags added in reverse target order: the tag with the lower type ID
+	 * names the record with the higher one, so the index is in emission
+	 * order only once btf_dump_resize() has sorted it. Without the sort
+	 * the lookups land outside their runs and the records lose their
+	 * attributes.
+	 */
+	id = btf__add_struct(btf, "s2", 4);
+	ASSERT_EQ(id, 22, "s2_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "s2_field");
+	id = btf__add_struct(btf, "s3", 4);
+	ASSERT_EQ(id, 23, "s3_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "s3_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "on_s3", 23, -1), 24, "s3_attr");
+	ASSERT_EQ(btf__add_decl_attr(btf, "on_s2", 22, -1), 25, "s2_attr");
+
+	test_ctx__dump_and_compare(&t,
+"struct s1 {\n"
+"	int f;\n"
+"} __attribute__((attr1)) __attribute__((btf_decl_tag(\"plain_tag\")));\n"
+"\n"
+"typedef struct {\n"
+"	int f;\n"
+"} __attribute__((a1)) __attribute__((a2)) t1;\n"
+"\n"
+"union u1 {\n"
+"	int f;\n"
+"} __attribute__((uattr));\n"
+"\n"
+"struct p1 {\n"
+"	char c;\n"
+"	int i;\n"
+"} __attribute__((packed)) __attribute__((pattr));\n"
+"\n"
+"struct outer {\n"
+"	struct {\n"
+"		int g;\n"
+"	} __attribute__((inner)) in;\n"
+"};\n"
+"\n"
+"struct s2 {\n"
+"	int f;\n"
+"} __attribute__((on_s2));\n"
+"\n"
+"struct s3 {\n"
+"	int f;\n"
+"} __attribute__((on_s3));\n"
+"\n", "dump_and_compare");
+
+	test_ctx__free(&t);
+}
+
+/*
+ * An attribute on a member is emitted at the member declaration, and one on
+ * the record at the closing brace. Both are keyed by the record in the index,
+ * so this also checks that they do not leak into each other's position.
+ */
+static void test_btf_dump_decl_tags_members(void)
+{
+	struct test_ctx t = {};
+	struct btf *btf;
+	int id;
+
+	if (test_ctx__init(&t))
+		return;
+
+	btf = t.btf;
+
+	/*
+	 * Generate BTF corresponding to the following C code:
+	 *
+	 * struct m1 {
+	 *	int (*fp)(void) __attribute__((fp_attr));
+	 *	int a;
+	 *	int b __attribute__((b1)) __attribute__((b2));
+	 *	int arr[4] __attribute__((arr_attr));
+	 * } __attribute__((rec));
+	 * struct bits { int x : 3 __attribute__((x_attr)); int y : 29; };
+	 */
+	id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	ASSERT_EQ(id, 1, "int_id");
+	id = btf__add_array(btf, 1, 1, 4);
+	ASSERT_EQ(id, 2, "array_id");
+	id = btf__add_func_proto(btf, 1);
+	ASSERT_EQ(id, 3, "proto_id");
+	id = btf__add_ptr(btf, 3);
+	ASSERT_EQ(id, 4, "ptr_id");
+
+	id = btf__add_struct(btf, "m1", 32);
+	ASSERT_EQ(id, 5, "m1_id");
+	ASSERT_OK(btf__add_field(btf, "fp", 4, 0, 0), "m1_fp");
+	ASSERT_OK(btf__add_field(btf, "a", 1, 64, 0), "m1_a");
+	ASSERT_OK(btf__add_field(btf, "b", 1, 96, 0), "m1_b");
+	ASSERT_OK(btf__add_field(btf, "arr", 2, 128, 0), "m1_arr");
+	ASSERT_EQ(btf__add_decl_attr(btf, "rec", 5, -1), 6, "m1_rec_attr");
+	/* the attribute follows the whole declarator, pointer and array alike */
+	ASSERT_EQ(btf__add_decl_attr(btf, "fp_attr", 5, 0), 7, "m1_fp_attr");
+	/* two on one member, to pin the ordering */
+	ASSERT_EQ(btf__add_decl_attr(btf, "b1", 5, 2), 8, "m1_b1");
+	ASSERT_EQ(btf__add_decl_attr(btf, "b2", 5, 2), 9, "m1_b2");
+	ASSERT_EQ(btf__add_decl_attr(btf, "arr_attr", 5, 3), 10, "m1_arr_attr");
+
+	/* a bit-field: the attribute has to follow the width */
+	id = btf__add_struct(btf, "bits", 4);
+	ASSERT_EQ(id, 11, "bits_id");
+	ASSERT_OK(btf__add_field(btf, "x", 1, 0, 3), "bits_x");
+	ASSERT_OK(btf__add_field(btf, "y", 1, 3, 29), "bits_y");
+	ASSERT_EQ(btf__add_decl_attr(btf, "x_attr", 11, 0), 12, "bits_x_attr");
+
+	test_ctx__dump_and_compare(&t,
+"struct m1 {\n"
+"	int (*fp)(void) __attribute__((fp_attr));\n"
+"	int a;\n"
+"	int b __attribute__((b1)) __attribute__((b2));\n"
+"	int arr[4] __attribute__((arr_attr));\n"
+"} __attribute__((rec));\n"
+"\n"
+"struct bits {\n"
+"	int x: 3 __attribute__((x_attr));\n"
+"	int y: 29;\n"
+"};\n"
+"\n", "dump_and_compare");
+
+	test_ctx__free(&t);
+}
+
+/*
+ * A typedef is the third and last kind btf_dump declares. Its attributes go
+ * after the declarator, so a typedef of an anonymous record can carry three
+ * groups in one declaration, binding to three different entities.
+ */
+static void test_btf_dump_decl_tags_typedef(void)
+{
+	struct test_ctx t = {};
+	struct btf *btf;
+	int id;
+
+	if (test_ctx__init(&t))
+		return;
+
+	btf = t.btf;
+
+	/*
+	 * Generate BTF corresponding to the following C code:
+	 *
+	 * typedef int td __attribute__((t1))
+	 *                __attribute__((btf_decl_tag("t2")));
+	 * typedef struct {
+	 *	int f __attribute__((memb_attr));
+	 * } __attribute__((rec_attr)) both __attribute__((both_attr));
+	 */
+	id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	ASSERT_EQ(id, 1, "int_id");
+	id = btf__add_typedef(btf, "td", 1);
+	ASSERT_EQ(id, 2, "td_id");
+	ASSERT_EQ(btf__add_decl_attr(btf, "t1", 2, -1), 3, "td_attr1");
+	/* no kflag: rendered as a btf_decl_tag(), not as a bare attribute */
+	ASSERT_EQ(btf__add_decl_tag(btf, "t2", 2, -1), 4, "td_attr2");
+
+	id = btf__add_struct(btf, NULL, 4);
+	ASSERT_EQ(id, 5, "anon_id");
+	ASSERT_OK(btf__add_field(btf, "f", 1, 0, 0), "anon_field");
+	ASSERT_EQ(btf__add_decl_attr(btf, "rec_attr", 5, -1), 6, "anon_rec_attr");
+	ASSERT_EQ(btf__add_decl_attr(btf, "memb_attr", 5, 0), 7, "anon_memb_attr");
+	id = btf__add_typedef(btf, "both", 5);
+	ASSERT_EQ(id, 8, "both_id");
+	ASSERT_EQ(btf__add_decl_attr(btf, "both_attr", 8, -1), 9, "both_attr");
+
+	test_ctx__dump_and_compare(&t,
+"typedef int td __attribute__((t1)) __attribute__((btf_decl_tag(\"t2\")));\n"
+"\n"
+"typedef struct {\n"
+"	int f __attribute__((memb_attr));\n"
+"} __attribute__((rec_attr)) both __attribute__((both_attr));\n"
+"\n", "dump_and_compare");
 
 	test_ctx__free(&t);
 }
@@ -1104,6 +1369,15 @@ void test_btf_dump() {
 	if (test__start_subtest("btf_dump: incremental"))
 		test_btf_dump_incremental();
 
+	if (test__start_subtest("btf_dump: decl_tags"))
+		test_btf_dump_decl_tags();
+
+	if (test__start_subtest("btf_dump: decl_tags_members"))
+		test_btf_dump_decl_tags_members();
+
+	if (test__start_subtest("btf_dump: decl_tags_typedef"))
+		test_btf_dump_decl_tags_typedef();
+
 	if (test__start_subtest("btf_dump: type_tags"))
 		test_btf_dump_type_tags();
 
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v2 5/5] selftests/bpf: Show the tag kflag in the raw BTF dump helper
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
                   ` (3 preceding siblings ...)
  2026-09-29 23:46 ` [PATCH bpf-next v2 4/5] selftests/bpf: Test btf_dump rendering of decl_tags Ihor Solodrai
@ 2026-09-29 23:46 ` Ihor Solodrai
  2026-09-30  9:10 ` [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump patchwork-bot+netdevbpf
  5 siblings, 0 replies; 7+ messages in thread
From: Ihor Solodrai @ 2026-09-29 23:46 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: bpf, Alan Maguire

A kflag on BTF_KIND_DECL_TAG or BTF_KIND_TYPE_TAG means the tag string
is an __attribute__ list rather than a btf_decl_tag()/btf_type_tag()
argument. fprintf_btf_type_raw() did not print it, so a tag added with
btf__add_decl_tag() and an attribute added with btf__add_decl_attr()
dumped identically apart from their type IDs.

Print " kflag=1" for those two kinds, only when the flag is set, so no
existing expected string changes.

Add a btf_write subtest for btf__add_decl_attr() and
btf__add_type_attr().

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
 tools/testing/selftests/bpf/btf_helpers.c     | 10 ++++--
 .../selftests/bpf/prog_tests/btf_write.c      | 34 +++++++++++++++++++
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/bpf/btf_helpers.c b/tools/testing/selftests/bpf/btf_helpers.c
index 85a4d15d1dd1..e04b61c08cd8 100644
--- a/tools/testing/selftests/bpf/btf_helpers.c
+++ b/tools/testing/selftests/bpf/btf_helpers.c
@@ -114,9 +114,12 @@ int fprintf_btf_type_raw(FILE *out, const struct btf *btf, __u32 id)
 	case BTF_KIND_VOLATILE:
 	case BTF_KIND_RESTRICT:
 	case BTF_KIND_TYPEDEF:
-	case BTF_KIND_TYPE_TAG:
 		fprintf(out, " type_id=%u", t->type);
 		break;
+	case BTF_KIND_TYPE_TAG:
+		fprintf(out, " type_id=%u%s", t->type,
+			btf_kflag(t) ? " kflag=1" : "");
+		break;
 	case BTF_KIND_ARRAY: {
 		const struct btf_array *arr = btf_array(t);
 
@@ -203,8 +206,9 @@ int fprintf_btf_type_raw(FILE *out, const struct btf *btf, __u32 id)
 		fprintf(out, " size=%u", t->size);
 		break;
 	case BTF_KIND_DECL_TAG:
-		fprintf(out, " type_id=%u component_idx=%d",
-			t->type, btf_decl_tag(t)->component_idx);
+		fprintf(out, " type_id=%u component_idx=%d%s",
+			t->type, btf_decl_tag(t)->component_idx,
+			btf_kflag(t) ? " kflag=1" : "");
 		break;
 	case BTF_KIND_LOC_PARAM: {
 		struct btf_loc_param *p = btf_loc_param(t);
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_write.c b/tools/testing/selftests/bpf/prog_tests/btf_write.c
index 5c84723cf254..3bed3a28c06a 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_write.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_write.c
@@ -606,6 +606,38 @@ static void test_btf_add_btf_split()
 	btf__free(base);
 }
 
+static void test_btf_attrs(void)
+{
+	struct btf *btf;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "new_empty"))
+		return;
+
+	btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	btf__add_struct(btf, "s", 4);
+	btf__add_field(btf, "f", 1, 0, 0);
+
+	btf__add_decl_tag(btf, "tag", 2, -1);
+	btf__add_decl_attr(btf, "tag", 2, -1);
+	btf__add_decl_attr(btf, "member_attr", 2, 0);
+	btf__add_type_tag(btf, "tag", 1);
+	btf__add_type_attr(btf, "tag", 1);
+
+	VALIDATE_RAW_BTF(
+		btf,
+		"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+		"[2] STRUCT 's' size=4 vlen=1\n"
+		"\t'f' type_id=1 bits_offset=0",
+		"[3] DECL_TAG 'tag' type_id=2 component_idx=-1",
+		"[4] DECL_TAG 'tag' type_id=2 component_idx=-1 kflag=1",
+		"[5] DECL_TAG 'member_attr' type_id=2 component_idx=0 kflag=1",
+		"[6] TYPE_TAG 'tag' type_id=1",
+		"[7] TYPE_TAG 'tag' type_id=1 kflag=1");
+
+	btf__free(btf);
+}
+
 void test_btf_write()
 {
 	if (test__start_subtest("btf_add"))
@@ -614,4 +646,6 @@ void test_btf_write()
 		test_btf_add_btf();
 	if (test__start_subtest("btf_add_btf_split"))
 		test_btf_add_btf_split();
+	if (test__start_subtest("btf_attrs"))
+		test_btf_attrs();
 }
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump
  2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
                   ` (4 preceding siblings ...)
  2026-09-29 23:46 ` [PATCH bpf-next v2 5/5] selftests/bpf: Show the tag kflag in the raw BTF dump helper Ihor Solodrai
@ 2026-09-30  9:10 ` patchwork-bot+netdevbpf
  5 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-30  9:10 UTC (permalink / raw)
  To: Ihor Solodrai; +Cc: ast, andrii, daniel, eddyz87, memxor, bpf, alan.maguire

Hello:

This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Tue, 29 Sep 2026 16:46:03 -0700 you wrote:
> BTF supports arbitrary __attribute__ encoding via decl tags and type
> tags [1]. However btf_dump facility only outputs the attributes
> encoded in BTF as type tags, which was implemented in commit
> 2019c58318b8 ("libbpf: Check the kflag of type tags in btf_dump").
> 
> BTF_KIND_DECL_TAG has been left out of btf_dump entirely, independent
> of the kflag value. An attribute a BTF producer encoded is silently
> dropped from the C output.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v2,1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced()
    https://git.kernel.org/bpf/bpf-next/c/1419795b98cf
  - [bpf-next,v2,2/5] libbpf: Render decl_tags in btf_dump
    https://git.kernel.org/bpf/bpf-next/c/76ea31239021
  - [bpf-next,v2,3/5] selftests/bpf: Add ASSERT_TEXT_EQ()
    https://git.kernel.org/bpf/bpf-next/c/1c36a1a41a47
  - [bpf-next,v2,4/5] selftests/bpf: Test btf_dump rendering of decl_tags
    https://git.kernel.org/bpf/bpf-next/c/096ee6e38267
  - [bpf-next,v2,5/5] selftests/bpf: Show the tag kflag in the raw BTF dump helper
    https://git.kernel.org/bpf/bpf-next/c/2c964f6c5d8e

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-30  9:10 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-29 23:46 [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
2026-09-29 23:46 ` [PATCH bpf-next v2 1/5] libbpf: Walk types in btf_dump_resize(), not in mark_referenced() Ihor Solodrai
2026-09-29 23:46 ` [PATCH bpf-next v2 2/5] libbpf: Render decl_tags in btf_dump Ihor Solodrai
2026-09-29 23:46 ` [PATCH bpf-next v2 3/5] selftests/bpf: Add ASSERT_TEXT_EQ() Ihor Solodrai
2026-09-29 23:46 ` [PATCH bpf-next v2 4/5] selftests/bpf: Test btf_dump rendering of decl_tags Ihor Solodrai
2026-09-29 23:46 ` [PATCH bpf-next v2 5/5] selftests/bpf: Show the tag kflag in the raw BTF dump helper Ihor Solodrai
2026-09-30  9:10 ` [PATCH bpf-next v2 0/5] libbpf: Render decl_tags in btf_dump patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox