BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump
@ 2026-10-30 23:33 Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 01/13] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
                   ` (12 more replies)
  0 siblings, 13 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

bpftool's C dump wraps its output in a clang attribute pragma:

  #pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)

GCC-BPF implements preserve_access_index, but not the pragma. Since
the commit 675b4e24bc50 ("bpf: Temporarily define
BPF_NO_PRESEVE_ACCESS_INDEX for GCC") the BPF selftests build GCC
objects with -DBPF_NO_PRESERVE_ACCESS_INDEX, which drops CO-RE from
vmlinux.h altogether.

This series replaces the pragma with an explicit attribute on each
record definition, and uses the same mechanism to annotate the context
types that the verifier rewrites with preserve_static_offset.

Jose proposed per-record attributes in May 2024 [1]. It stalled on a
requirement from preserve_static_offset (PSO) work [2] to add
attributes on selected types only, which a fixed string cannot
express.

BTF decl tags can carry an arbitrary attribute list since commit
51d1b1d42841 ("libbpf: Introduce kflag for type_tags and decl_tags in
BTF"), and btf_dump renders them on record definitions, since
76ea31239021 ("libbpf: Render decl_tags in btf_dump"). This allows
bpftool to put any attribute on any subset of records, with no new
libbpf API.

Patch "libbpf: Dump kflagged BTF tags as is" makes btf_dump print
kflagged tag strings as is, instead of wrapping them in
__attribute__((...)). Record definitions then carry plain macro names,
which the generated header defines. resolve_btfids, the only in-tree
producer of kflagged tags, now encodes the complete arena specifier
__attribute__((address_space(1))), and bpftool handles the legacy
encoding correctly for backwards compatibility with older kernels.
Old bpftool emits invalid C from the new kernel BTF; this is accepted.

[1] https://lore.kernel.org/bpf/20240503111836.25275-1-jose.marchesi@oracle.com/
[2] https://lore.kernel.org/bpf/20231208000531.19179-1-eddyz87@gmail.com/

---

v2->v3:
  * Print kflagged decl and type tags as is in btf_dump, and encode
    the complete arena specifier in resolve_btfids (Andrii)
  * Define __bpf_relocatable and __bpf_static_offset as complete
    attribute specifiers, so record definitions carry only the macro
    names (Andrii)
  * New patch "bpftool: Normalize legacy arena type tags before C dumps"
    keeps C dumps of older kernel BTF valid
  * Rename the no_pai map and helper to non_relocatable, and use a bool
    array instead of linux/bitmap.h (Andrii)
  * selftest refactoring: build bpftool_btf_dump fixtures in
    btf_to_tmpfile()
  * Commit message and documentation rewording, other nits

v2: https://lore.kernel.org/bpf/20261023222652.3927736-1-ihor.solodrai@linux.dev/

v1->v2:
  * Rename __bpf_core_pai to __bpf_relocatable
  * Rename __bpf_core_pso to __bpf_static_offset
  * Use a split BTF to add decl_tags in bpftoo instead of a detached
    copy (Alexei, Alan)
    * New patch "libbpf: Look up BTF strings without making BTF
      modifiable" lets btf__find_str() build its string index without
      making BTF modifiable
    * Dropped patch "bpftool: Dump C from a detached BTF copy"
  * The Clang crash on __builtin_preserve_field_info over PSO types is
    fixed in LLVM. PSO stays on by default; -D__bpf_static_offset=
    remains the documented workaround for Clang 18 through 23
  * GCC-BPF gets __bpf_relocatable from patch #3, so its narrow loads
    from context types fail verification until patch #4 (bpf-ci bot),
    left as is
  * Commit message and documentation rewording

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

---

Ihor Solodrai (13):
  libbpf: Look up BTF strings without making BTF modifiable
  libbpf: Dump kflagged BTF tags as is
  docs, libbpf: Document complete BTF annotation strings
  bpftool: Normalize legacy arena type tags before C dumps
  selftests/bpf: Build BTF fixtures in btf_to_tmpfile()
  selftests/bpf: Cover legacy arena type tags in bpftool C dumps
  selftests/bpf: Cover unknown BTF header-extension bytes in btf dump
  bpftool: Apply __bpf_relocatable per record on BTF dump
  bpftool: Skip __bpf_relocatable on verifier-rewritten context types
  bpftool: Emit __bpf_static_offset on verifier-rewritten context types
  selftests/bpf: Cover record attributes in bpftool BTF dumps
  selftests/bpf: Cover context and socket accesses through vmlinux.h
  bpftool: Document __bpf_relocatable and __bpf_static_offset

 Documentation/bpf/btf.rst                     |  14 +-
 Documentation/bpf/kfuncs.rst                  |   3 +-
 .../bpf/bpftool/Documentation/bpftool-btf.rst |  26 +++
 tools/bpf/bpftool/btf.c                       | 188 +++++++++++++++--
 tools/bpf/resolve_btfids/main.c               |   2 +-
 tools/lib/bpf/btf.c                           |  43 ++--
 tools/lib/bpf/btf_dump.c                      |   4 +-
 .../bpf/bpftool_btf_dump_sorted.expected      |  48 ++++-
 .../bpf/bpftool_btf_dump_unsorted.expected    |  48 ++++-
 .../bpf/prog_tests/bpftool_btf_dump.c         | 194 +++++++++++++++---
 .../selftests/bpf/prog_tests/btf_dump.c       |  91 ++++----
 .../selftests/bpf/prog_tests/btf_write.c      |   4 +-
 .../selftests/bpf/prog_tests/resolve_btfids.c |   4 +-
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_ctx_rewritten.c        |  59 ++++++
 15 files changed, 601 insertions(+), 129 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c


base-commit: eaae576c34d7e001767b6845a5950b0b8db3e68a
-- 
2.56.0


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

* [PATCH bpf-next v3 01/13] libbpf: Look up BTF strings without making BTF modifiable
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is Ihor Solodrai
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

btf__find_str() needs a hash index of the string section, and gets one
by making BTF modifiable. That copies types and strings out of the raw
data, and fails with -EOPNOTSUPP on BTF with unknown extension bytes.

btf__add_str() and split btf__dedup() look up strings in the base BTF
first. So adding a string to split BTF, or deduping it, makes the base
modifiable as a side effect, and fails if the base comes from a newer
kernel with new header fields.

Build the string index without making BTF modifiable, and reuse it if
BTF becomes modifiable later. The index can now exist on read-only
BTF, so btf__free() frees it unconditionally.

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

diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index d2ff6f5d6d5b..47a04be0a55a 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -153,9 +153,10 @@ struct btf {
 	 */
 	int start_str_off;
 
-	/* only one of strs_data or strs_set can be non-NULL, depending on
-	 * whether BTF is in a modifiable state (strs_set is used) or not
-	 * (strs_data points inside raw_data)
+	/*
+	 * strs_data points inside raw_data until BTF becomes modifiable,
+	 * then strings live in strs_set. Before that, strs_set may exist
+	 * as a lookup index for btf__find_str().
 	 */
 	void *strs_data;
 	/* a set of unique strings */
@@ -1258,9 +1259,9 @@ void btf__free(struct btf *btf)
 		 * which will be unconditionally freed below.
 		 */
 		free(btf->types_data);
-		strset__free(btf->strs_set);
 		free(btf->layout);
 	}
+	strset__free(btf->strs_set);
 	btf_free_raw_data(btf);
 	free(btf->raw_data_swapped);
 	free(btf->type_offs);
@@ -2042,6 +2043,21 @@ static void btf_invalidate_raw_data(struct btf *btf)
 	btf->named_start_id = 0;
 }
 
+static int btf_ensure_strs_set(struct btf *btf)
+{
+	struct strset *set;
+
+	if (btf->strs_set)
+		return 0;
+
+	set = strset__new(BTF_MAX_STR_OFFSET, btf->strs_data, btf->hdr.str_len);
+	if (IS_ERR(set))
+		return PTR_ERR(set);
+
+	btf->strs_set = set;
+	return 0;
+}
+
 /* Ensure BTF is ready to be modified (by splitting into a three memory
  * regions for types, strings and layout. Also invalidate cached
  * raw_data, if any.
@@ -2049,7 +2065,6 @@ static void btf_invalidate_raw_data(struct btf *btf)
 static int btf_ensure_modifiable(struct btf *btf)
 {
 	void *types, *layout = NULL;
-	struct strset *set = NULL;
 	int err = -ENOMEM;
 
 	if (btf_is_modifiable(btf)) {
@@ -2077,18 +2092,14 @@ static int btf_ensure_modifiable(struct btf *btf)
 		       btf->hdr.layout_len);
 	}
 
-	/* build lookup index for all strings */
-	set = strset__new(BTF_MAX_STR_OFFSET, btf->strs_data, btf->hdr.str_len);
-	if (IS_ERR(set)) {
-		err = PTR_ERR(set);
+	err = btf_ensure_strs_set(btf);
+	if (err)
 		goto err_out;
-	}
 
 	/* only when everything was successful, update internal state */
 	btf->types_data = types;
 	btf->types_data_cap = btf->hdr.type_len;
 	btf->strs_data = NULL;
-	btf->strs_set = set;
 	if (layout)
 		btf->layout = layout;
 	/* if BTF was created from scratch, all strings are guaranteed to be
@@ -2107,7 +2118,6 @@ static int btf_ensure_modifiable(struct btf *btf)
 	return 0;
 
 err_out:
-	strset__free(set);
 	free(types);
 	free(layout);
 	return err;
@@ -2130,8 +2140,7 @@ int btf__find_str(struct btf *btf, const char *s)
 			return off;
 	}
 
-	/* BTF needs to be in a modifiable state to build string lookup index */
-	err = btf_ensure_modifiable(btf);
+	err = btf_ensure_strs_set(btf);
 	if (err)
 		return libbpf_err(err);
 
-- 
2.56.0


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

* [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 01/13] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-31  0:16   ` bot+bpf-ci
  2026-10-30 23:33 ` [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings Ihor Solodrai
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

btf_dump wraps kflagged decl and type tag strings in an attribute
specifier. This unnecessarily limits how a BTF tag can be interpreted
by the consumers: only as an __attribute__.

Drop this limitation by printing the BTF tag's (with kflag=1) string
directly. This allows to dump an arbitrary identifier, and the
definition of that identifier can then be controlled by the end
consumer of the dump.

This change is backwards incompatible for the consumers expecting
__attribute__ wrapping to be applied by libbpf on dump. In-tree, only
resolve_btfids applying address_space(1) attribute is affected.

Fix resolve_btfids to encode the complete arena attribute specifier
with the renderer change.

Update the existing attribute tests.

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/resolve_btfids/main.c               |  2 +-
 tools/lib/bpf/btf_dump.c                      |  4 +-
 .../selftests/bpf/prog_tests/btf_dump.c       | 91 +++++++++++--------
 .../selftests/bpf/prog_tests/btf_write.c      |  4 +-
 .../selftests/bpf/prog_tests/resolve_btfids.c |  4 +-
 5 files changed, 58 insertions(+), 47 deletions(-)

diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
index 5ad547356303..c16dd93fa6d4 100644
--- a/tools/bpf/resolve_btfids/main.c
+++ b/tools/bpf/resolve_btfids/main.c
@@ -204,7 +204,7 @@ struct btf_links {
 #define KF_ARENA_ARG2	(1 << 15)
 #define KF_IMPLICIT_ARGS (1 << 16)
 #define KF_IMPL_SUFFIX "_impl"
-#define TYPE_ATTR_ARENA "address_space(1)"
+#define TYPE_ATTR_ARENA "__attribute__((address_space(1)))"
 #define PARAM_SUFFIX_ARENA "__arena"
 #define PARAM_SUFFIX_ARENA_NULLABLE "__arena__nullable"
 
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index ea9a34db4c00..faa9aee52d74 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -1023,7 +1023,7 @@ 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);
+		btf_dump_printf(d, " %s", name);
 	else
 		btf_dump_printf(d, " __attribute__((btf_decl_tag(\"%s\")))", name);
 }
@@ -1594,7 +1594,7 @@ static void btf_dump_emit_type_chain(struct btf_dump *d,
 			btf_dump_emit_mods(d, decls);
 			name = btf_name_of(d, t->name_off);
 			if (btf_kflag(t))
-				btf_dump_printf(d, " __attribute__((%s))", name);
+				btf_dump_printf(d, " %s", name);
 			else if (strcmp(name, "arena") == 0)
 				/* the tag denotes a pointer into the BPF arena address space */
 				btf_dump_printf(d, " __attribute__((address_space(1))) __attribute__((btf_type_tag(\"arena\")))");
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index dbef014f75ac..3b9cf564c35d 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -320,11 +320,12 @@ static void test_btf_dump_incremental(void)
 "		Y___2 = 1,\n"
 "	} y;\n"
 "	struct s s;\n"
-"} __attribute__((round2));\n\n", "c_dump1");
+"} round2;\n\n", "c_dump1");
 
 	test_ctx__free(&t);
 }
 
+/* Bare annotation names in these fixtures stand for user-defined macros. */
 static void test_btf_dump_decl_tags(void)
 {
 	struct test_ctx t = {};
@@ -339,14 +340,14 @@ static void test_btf_dump_decl_tags(void)
 	/*
 	 * Generate BTF corresponding to the following C code:
 	 *
-	 * struct s1 { int f; } __attribute__((attr1))
+	 * struct s1 { int f; } __attribute__((aligned(4), packed))
 	 *                      __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));
+	 * typedef struct { int f; } a1 a2 t1;
+	 * union u1 { int f; } uattr;
+	 * struct p1 { char c; int i; } __attribute__((packed)) pattr;
+	 * struct outer { struct { int g; } inner in; };
+	 * struct s2 { int f; } on_s2;
+	 * struct s3 { int f; } on_s3;
 	 *
 	 * plus attributes on a var and a func, which btf_dump does not declare.
 	 */
@@ -355,12 +356,13 @@ static void test_btf_dump_decl_tags(void)
 	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 */
+	/* a complete attribute specifier next to a normal decl tag */
 	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_attr(btf, "__attribute__((aligned(4), packed))", 3, -1),
+		  4, "s1_attr");
+	/* no kflag: rendered as a btf_decl_tag() */
 	ASSERT_EQ(btf__add_decl_tag(btf, "plain_tag", 3, -1), 5, "s1_plain_tag");
 
 	/*
@@ -428,34 +430,34 @@ static void test_btf_dump_decl_tags(void)
 	test_ctx__dump_and_compare(&t,
 "struct s1 {\n"
 "	int f;\n"
-"} __attribute__((attr1)) __attribute__((btf_decl_tag(\"plain_tag\")));\n"
+"} __attribute__((aligned(4), packed)) __attribute__((btf_decl_tag(\"plain_tag\")));\n"
 "\n"
 "typedef struct {\n"
 "	int f;\n"
-"} __attribute__((a1)) __attribute__((a2)) t1;\n"
+"} a1 a2 t1;\n"
 "\n"
 "union u1 {\n"
 "	int f;\n"
-"} __attribute__((uattr));\n"
+"} uattr;\n"
 "\n"
 "struct p1 {\n"
 "	char c;\n"
 "	int i;\n"
-"} __attribute__((packed)) __attribute__((pattr));\n"
+"} __attribute__((packed)) pattr;\n"
 "\n"
 "struct outer {\n"
 "	struct {\n"
 "		int g;\n"
-"	} __attribute__((inner)) in;\n"
+"	} inner in;\n"
 "};\n"
 "\n"
 "struct s2 {\n"
 "	int f;\n"
-"} __attribute__((on_s2));\n"
+"} on_s2;\n"
 "\n"
 "struct s3 {\n"
 "	int f;\n"
-"} __attribute__((on_s3));\n"
+"} on_s3;\n"
 "\n", "dump_and_compare");
 
 	test_ctx__free(&t);
@@ -481,12 +483,12 @@ static void test_btf_dump_decl_tags_members(void)
 	 * Generate BTF corresponding to the following C code:
 	 *
 	 * struct m1 {
-	 *	int (*fp)(void) __attribute__((fp_attr));
+	 *	int (*fp)(void) 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; };
+	 *	int b b1 b2;
+	 *	int arr[4] arr_attr;
+	 * } rec;
+	 * struct bits { int x : 3 x_attr; int y : 29; };
 	 */
 	id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
 	ASSERT_EQ(id, 1, "int_id");
@@ -520,14 +522,14 @@ static void test_btf_dump_decl_tags_members(void)
 
 	test_ctx__dump_and_compare(&t,
 "struct m1 {\n"
-"	int (*fp)(void) __attribute__((fp_attr));\n"
+"	int (*fp)(void) fp_attr;\n"
 "	int a;\n"
-"	int b __attribute__((b1)) __attribute__((b2));\n"
-"	int arr[4] __attribute__((arr_attr));\n"
-"} __attribute__((rec));\n"
+"	int b b1 b2;\n"
+"	int arr[4] arr_attr;\n"
+"} rec;\n"
 "\n"
 "struct bits {\n"
-"	int x: 3 __attribute__((x_attr));\n"
+"	int x: 3 x_attr;\n"
 "	int y: 29;\n"
 "};\n"
 "\n", "dump_and_compare");
@@ -554,18 +556,18 @@ static void test_btf_dump_decl_tags_typedef(void)
 	/*
 	 * Generate BTF corresponding to the following C code:
 	 *
-	 * typedef int td __attribute__((t1))
+	 * typedef int td t1
 	 *                __attribute__((btf_decl_tag("t2")));
 	 * typedef struct {
-	 *	int f __attribute__((memb_attr));
-	 * } __attribute__((rec_attr)) both __attribute__((both_attr));
+	 *	int f memb_attr;
+	 * } rec_attr both 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 */
+	/* no kflag: rendered as a btf_decl_tag() */
 	ASSERT_EQ(btf__add_decl_tag(btf, "t2", 2, -1), 4, "td_attr2");
 
 	id = btf__add_struct(btf, NULL, 4);
@@ -578,11 +580,11 @@ static void test_btf_dump_decl_tags_typedef(void)
 	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"
+"typedef int td 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"
+"	int f memb_attr;\n"
+"} rec_attr both both_attr;\n"
 "\n", "dump_and_compare");
 
 	test_ctx__free(&t);
@@ -603,8 +605,9 @@ static void test_btf_dump_type_tags(void)
 	 *
 	 * struct s {
 	 *   void __attribute__((btf_type_tag(\"void_tag\"))) *p1;
-	 *   void __attribute__((void_attr)) *p2;
+	 *   void __attribute__((aligned(8))) *p2;
 	 *   void __arena *p3;
+	 *   void __type_marker *p4;
 	 * };
 	 *
 	 * The arena tag is rendered with the address space it denotes.
@@ -615,7 +618,7 @@ static void test_btf_dump_type_tags(void)
 	id = btf__add_ptr(btf, id);
 	ASSERT_EQ(id, 2, "void_ptr_id1");
 
-	id = btf__add_type_attr(btf, "void_attr", 0);
+	id = btf__add_type_attr(btf, "__attribute__((aligned(8)))", 0);
 	ASSERT_EQ(id, 3, "type_attr_id");
 	id = btf__add_ptr(btf, id);
 	ASSERT_EQ(id, 4, "void_ptr_id2");
@@ -625,20 +628,28 @@ static void test_btf_dump_type_tags(void)
 	id = btf__add_ptr(btf, id);
 	ASSERT_EQ(id, 6, "void_ptr_id3");
 
-	id = btf__add_struct(btf, "s", 24);
-	ASSERT_EQ(id, 7, "struct_id");
+	id = btf__add_type_attr(btf, "__type_marker", 0);
+	ASSERT_EQ(id, 7, "type_marker_id");
+	id = btf__add_ptr(btf, id);
+	ASSERT_EQ(id, 8, "void_ptr_id4");
+
+	id = btf__add_struct(btf, "s", 32);
+	ASSERT_EQ(id, 9, "struct_id");
 	err = btf__add_field(btf, "p1", 2, 0, 0);
 	ASSERT_OK(err, "field_ok1");
 	err = btf__add_field(btf, "p2", 4, 64, 0);
 	ASSERT_OK(err, "field_ok2");
 	err = btf__add_field(btf, "p3", 6, 128, 0);
 	ASSERT_OK(err, "field_ok3");
+	err = btf__add_field(btf, "p4", 8, 192, 0);
+	ASSERT_OK(err, "field_ok4");
 
 	test_ctx__dump_and_compare(&t,
 "struct s {\n"
 "	void __attribute__((btf_type_tag(\"void_tag\"))) *p1;\n"
-"	void __attribute__((void_attr)) *p2;\n"
+"	void __attribute__((aligned(8))) *p2;\n"
 "	void __attribute__((address_space(1))) __attribute__((btf_type_tag(\"arena\"))) *p3;\n"
+"	void __type_marker *p4;\n"
 "};\n\n", "dump_and_compare");
 
 	test_ctx__free(&t);
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_write.c b/tools/testing/selftests/bpf/prog_tests/btf_write.c
index 3bed3a28c06a..9d295ea22b33 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_write.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_write.c
@@ -620,7 +620,7 @@ static void test_btf_attrs(void)
 
 	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_decl_attr(btf, "__attribute__((aligned(4)))", 2, 0);
 	btf__add_type_tag(btf, "tag", 1);
 	btf__add_type_attr(btf, "tag", 1);
 
@@ -631,7 +631,7 @@ static void test_btf_attrs(void)
 		"\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",
+		"[5] DECL_TAG '__attribute__((aligned(4)))' type_id=2 component_idx=0 kflag=1",
 		"[6] TYPE_TAG 'tag' type_id=1",
 		"[7] TYPE_TAG 'tag' type_id=1 kflag=1");
 
diff --git a/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c b/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
index 3f9949e8227d..e96b209f3fe0 100644
--- a/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
+++ b/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
@@ -14,7 +14,7 @@
 
 #define DECL_TAG_FASTCALL "bpf_fastcall"
 #define DECL_TAG_KFUNC "bpf_kfunc"
-#define TYPE_ATTR_ARENA "address_space(1)"
+#define TYPE_ATTR_ARENA "__attribute__((address_space(1)))"
 #define ARENA_ARG(n) (1U << (n))
 
 #ifndef KF_FASTCALL
@@ -239,7 +239,7 @@ static void check_kfunc_set(struct btf_id_set8 *set)
 	}
 }
 
-/* True if @id is PTR -> TYPE_TAG(kflag=1, "address_space(1)") -> pointee */
+/* True if @id is PTR -> TYPE_TAG(kflag=1, TYPE_ATTR_ARENA) -> pointee */
 static bool is_arena_tagged_ptr(struct btf *btf, __u32 id)
 {
 	const struct btf_type *ptr, *tag;
-- 
2.56.0


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

* [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 01/13] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-31  0:16   ` bot+bpf-ci
  2026-10-30 23:33 ` [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps Ihor Solodrai
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

btf_dump now prints kflagged tag strings without adding an attribute
wrapper. The BTF documentation still describes attribute-list strings.

Update the BTF reference, kfunc documentation and libbpf API comments
to match this format.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 Documentation/bpf/btf.rst    | 14 ++++++++------
 Documentation/bpf/kfuncs.rst |  3 ++-
 tools/lib/bpf/btf.c          |  6 ++++--
 3 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/Documentation/bpf/btf.rst b/Documentation/bpf/btf.rst
index 29de1222c3e7..98c1f0440f8b 100644
--- a/Documentation/bpf/btf.rst
+++ b/Documentation/bpf/btf.rst
@@ -506,10 +506,11 @@ If ``info.kind_flag`` is 0, then this is a normal decl tag, and the
 ``name_off`` encodes btf_decl_tag attribute string.
 
 If ``info.kind_flag`` is 1, then the decl tag represents an arbitrary
-__attribute__. In this case, ``name_off`` encodes a string
-representing the attribute-list of the attribute specifier. For
-example, for an ``__attribute__((aligned(4)))`` the string's contents
-is ``aligned(4)``.
+annotation. ``name_off`` encodes the complete annotation string, which
+``btf_dump`` prints unchanged at the declaration. For example, an attribute
+specifier is encoded as ``__attribute__((aligned(4)))``, including the
+wrapper. A macro such as ``__bpf_relocatable`` can be encoded as that
+identifier, leaving its definition to the generated header or its user.
 
 2.2.18 BTF_KIND_TYPE_TAG
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -539,8 +540,9 @@ normal type tag, and the ``name_off`` encodes btf_type_tag attribute
 string.
 
 If ``info.kind_flag`` is 1, then the type tag represents an arbitrary
-__attribute__, and the ``name_off`` encodes a string representing the
-attribute-list of the attribute specifier.
+annotation. As with decl tags, ``name_off`` encodes the complete annotation
+string, which ``btf_dump`` prints unchanged. An attribute specifier must
+include its wrapper, for example ``__attribute__((address_space(1)))``.
 
 2.2.19 BTF_KIND_ENUM64
 ~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 73578a4c3b1f..d04da2fe7dad 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -560,7 +560,8 @@ At kernel build time the ``resolve_btfids`` tool finds all kfuncs declared with
 For each kfunc it emits a ``bpf_kfunc`` BTF decl tag, a ``bpf_fastcall`` decl
 tag when the kfunc is flagged ``KF_FASTCALL``, and the ``address_space(1)`` type
 attribute on the return value and/or arguments that use arena pointers (see
-sections 2.3.8 and 2.8).
+sections 2.3.8 and 2.8). The type tag stores the complete
+``__attribute__((address_space(1)))`` specifier with ``kind_flag`` set.
 
 2.7  Specifying no-cast aliases with ___init
 --------------------------------------------
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 47a04be0a55a..fd4f409d7847 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -3063,7 +3063,8 @@ int btf__add_type_tag(struct btf *btf, const char *value, int ref_type_id)
  * Append new BTF_KIND_TYPE_TAG type with:
  *   - *value*, non-empty/non-NULL tag value;
  *   - *ref_type_id* - referenced type ID, it might not exist yet;
- * Set info->kflag to 1, indicating this tag is an __attribute__
+ * The kind flag is set; btf_dump prints *value* unchanged.
+ * Attribute specifiers must include the __attribute__((...)) wrapper.
  * Returns:
  *   - >0, type ID of newly added BTF type;
  *   - <0, on error.
@@ -3398,7 +3399,8 @@ int btf__add_decl_tag(struct btf *btf, const char *value, int ref_type_id,
  *   - *ref_type_id* - referenced type ID, it might not exist yet;
  *   - *component_idx* - -1 for tagging reference type, otherwise struct/union
  *     member or function argument index;
- * Set info->kflag to 1, indicating this tag is an __attribute__
+ * The kind flag is set; btf_dump prints *value* unchanged.
+ * Attribute specifiers must include the __attribute__((...)) wrapper.
  * Returns:
  *   - >0, type ID of newly added BTF type;
  *   - <0, on error.
-- 
2.56.0


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

* [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (2 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-31  0:16   ` bot+bpf-ci
  2026-10-30 23:33 ` [PATCH bpf-next v3 05/13] selftests/bpf: Build BTF fixtures in btf_to_tmpfile() Ihor Solodrai
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

Older kernel BTF encodes kflagged arena type tags as
"address_space(1)". pahole emitted them since commit 21cb33c7e065
("kbuild, bpf: Enable --btf_features=attributes"), and resolve_btfids
since commit 27a78c2e7eee ("resolve_btfids: Process KF_ARENA_* flags
in resolve_btfids").

Now that btf_dump prints kflagged tags as is, C dumps of such BTF
declare arena kfunc pointers as "void address_space(1) *", which does
not compile.

Rewrite these tags to "__attribute__((address_space(1)))" in memory
before dumping C. BTF without "address_space(1)" type tags stays
read-only.

Split BTF is dumped as is.

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/bpftool/btf.c | 37 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 385521e6b028..94237befec67 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -26,6 +26,7 @@
 
 #define KFUNC_DECL_TAG		"bpf_kfunc"
 #define FASTCALL_DECL_TAG	"bpf_fastcall"
+#define LEGACY_ARENA_TAG	"address_space(1)"
 
 #define MAX_ROOT_IDS		16
 #define MAX_BTF_FILES		64
@@ -1092,13 +1093,47 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
 	return datums;
 }
 
-static int dump_btf_c(const struct btf *btf,
+/*
+ * Older kernel BTF encodes kflagged arena type tags as "address_space(1)",
+ * which btf_dump prints as is. Rewrite them to the complete specifier.
+ */
+static int btf_c_fixup_legacy_arena_tags(struct btf *btf)
+{
+	__u32 i, cnt = btf__type_cnt(btf);
+	int off;
+
+	if (btf__base_btf(btf) ||
+	    btf__find_by_name_kind(btf, LEGACY_ARENA_TAG, BTF_KIND_TYPE_TAG) < 0)
+		return 0;
+
+	off = btf__add_str(btf, "__attribute__((address_space(1)))");
+	if (off < 0) {
+		p_err("failed to rewrite legacy arena type tags: %s", strerror(-off));
+		return off;
+	}
+
+	for (i = 1; i < cnt; i++) {
+		struct btf_type *t = (struct btf_type *)btf__type_by_id(btf, i);
+
+		if (btf_is_type_tag(t) && btf_kflag(t) &&
+		    !strcmp(btf__name_by_offset(btf, t->name_off), LEGACY_ARENA_TAG))
+			t->name_off = off;
+	}
+
+	return 0;
+}
+
+static int dump_btf_c(struct btf *btf,
 		      __u32 *root_type_ids, int root_type_cnt, bool sort_dump)
 {
 	struct sort_datum *datums = NULL;
 	struct btf_dump *d;
 	int err = 0, i;
 
+	err = btf_c_fixup_legacy_arena_tags(btf);
+	if (err)
+		return err;
+
 	d = btf_dump__new(btf, btf_dump_printf, NULL, NULL);
 	if (!d)
 		return -errno;
-- 
2.56.0


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

* [PATCH bpf-next v3 05/13] selftests/bpf: Build BTF fixtures in btf_to_tmpfile()
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (3 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 06/13] selftests/bpf: Cover legacy arena type tags in bpftool C dumps Ihor Solodrai
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

test_bpftool_btf_dump() builds each BTF fixture, writes it to a
temporary file, and frees the BTF after the subtests on that file
finish, although they only read the file. Each fixture repeats this
handling, and one that fails to build skips the subtests of all later
fixtures.

Pass the fixture constructor to btf_to_tmpfile(), which frees the BTF
once it is written. A fixture that fails to build now skips only its
own subtests.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../bpf/prog_tests/bpftool_btf_dump.c         | 58 +++++++++----------
 1 file changed, 26 insertions(+), 32 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 534aa79a4278..80e8e435d702 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -109,30 +109,39 @@ static struct btf *mk_loc_btf(void)
 	return btf;
 }
 
-static int btf_to_tmpfile(const struct btf *btf, char *path)
+static int btf_to_tmpfile(struct btf *(*mk)(void), char *path)
 {
 	ssize_t written;
+	struct btf *btf;
 	const void *raw;
+	int err = -1;
 	__u32 sz;
 	int fd;
 
+	btf = mk();
+	if (!btf)
+		return -1;
+
 	raw = btf__raw_data(btf, &sz);
 	if (!ASSERT_OK_PTR(raw, "raw_data"))
-		return -1;
+		goto out;
 
 	snprintf(path, PATH_MAX, "/tmp/bpftool_btf_dump.XXXXXX");
 	fd = mkstemp(path);
 	if (!ASSERT_OK_FD(fd, "mkstemp_btf"))
-		return -1;
+		goto out;
 
 	written = write(fd, raw, sz);
 	close(fd);
 	if (!ASSERT_EQ(written, sz, "write_btf")) {
 		unlink(path);
-		return -1;
+		goto out;
 	}
 
-	return 0;
+	err = 0;
+out:
+	btf__free(btf);
+	return err;
 }
 
 static char *dump_c(const char *btf_path, bool sorted)
@@ -265,33 +274,18 @@ static void test_loc_dump(const char *btf_path)
 void test_bpftool_btf_dump(void)
 {
 	char path[PATH_MAX];
-	struct btf *btf = NULL;
-
-	btf = mk_btf();
-	if (!btf)
-		return;
-
-	if (btf_to_tmpfile(btf, path))
-		goto out_btf;
 
-	if (test__start_subtest("c_sorted"))
-		test_dump(path, true);
-	if (test__start_subtest("c_unsorted"))
-		test_dump(path, false);
-	unlink(path);
-
-	btf__free(btf);
-
-	btf = mk_loc_btf();
-	if (!btf)
-		return;
-
-	if (btf_to_tmpfile(btf, path))
-		goto out_btf;
+	if (!btf_to_tmpfile(mk_btf, path)) {
+		if (test__start_subtest("c_sorted"))
+			test_dump(path, true);
+		if (test__start_subtest("c_unsorted"))
+			test_dump(path, false);
+		unlink(path);
+	}
 
-	if (test__start_subtest("loc_dump"))
-		test_loc_dump(path);
-	unlink(path);
-out_btf:
-	btf__free(btf);
+	if (!btf_to_tmpfile(mk_loc_btf, path)) {
+		if (test__start_subtest("loc_dump"))
+			test_loc_dump(path);
+		unlink(path);
+	}
 }
-- 
2.56.0


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

* [PATCH bpf-next v3 06/13] selftests/bpf: Cover legacy arena type tags in bpftool C dumps
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (4 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 05/13] selftests/bpf: Build BTF fixtures in btf_to_tmpfile() Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 07/13] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump Ihor Solodrai
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

bpftool rewrites kflagged "address_space(1)" type tags from older
kernel BTF to the complete attribute specifier before dumping C.

Add a bpftool_btf_dump subtest that dumps a minimal BTF with an arena
kfunc in the legacy encoding, and checks that its C prototype carries
the complete specifier, fixed up by bpftool.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../bpf/prog_tests/bpftool_btf_dump.c         | 51 +++++++++++++++++++
 1 file changed, 51 insertions(+)

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 80e8e435d702..e66e686e4e12 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -57,6 +57,38 @@ static struct btf *mk_btf(void)
 	return btf;
 }
 
+/*
+ * Older kernel BTF encodes the arena attribute as "address_space(1)":
+ *
+ *	int __attribute__((address_space(1))) *arena(void);
+ */
+static struct btf *mk_legacy_arena_btf(void)
+{
+	struct btf *btf;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "new_empty"))
+		return NULL;
+
+	btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	btf__add_type_attr(btf, "address_space(1)", 1);
+	btf__add_ptr(btf, 2);
+	btf__add_func_proto(btf, 3);
+	btf__add_func(btf, "arena", BTF_FUNC_GLOBAL, 4);
+	btf__add_decl_tag(btf, "bpf_kfunc", 5, -1);
+
+	VALIDATE_RAW_BTF(
+		btf,
+		"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+		"[2] TYPE_TAG 'address_space(1)' type_id=1 kflag=1",
+		"[3] PTR '(anon)' type_id=2",
+		"[4] FUNC_PROTO '(anon)' ret_type_id=3 vlen=0",
+		"[5] FUNC 'arena' type_id=4 linkage=global",
+		"[6] DECL_TAG 'bpf_kfunc' type_id=5 component_idx=-1");
+
+	return btf;
+}
+
 static struct btf *mk_loc_btf(void)
 {
 	struct btf *btf;
@@ -234,6 +266,19 @@ static void test_dump(const char *btf_path, bool sorted)
 	free(dump);
 }
 
+static void test_legacy_arena(const char *btf_path)
+{
+	char *dump;
+
+	dump = dump_c(btf_path, true);
+	if (!dump)
+		return;
+
+	ASSERT_HAS_SUBSTR(dump, "int __attribute__((address_space(1))) *arena(void)",
+			  "arena_kfunc");
+	free(dump);
+}
+
 static void test_loc_dump(const char *btf_path)
 {
 	const char expected[] =
@@ -283,6 +328,12 @@ void test_bpftool_btf_dump(void)
 		unlink(path);
 	}
 
+	if (!btf_to_tmpfile(mk_legacy_arena_btf, path)) {
+		if (test__start_subtest("legacy_arena"))
+			test_legacy_arena(path);
+		unlink(path);
+	}
+
 	if (!btf_to_tmpfile(mk_loc_btf, path)) {
 		if (test__start_subtest("loc_dump"))
 			test_loc_dump(path);
-- 
2.56.0


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

* [PATCH bpf-next v3 07/13] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (5 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 06/13] selftests/bpf: Cover legacy arena type tags in bpftool C dumps Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 08/13] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

libbpf can parse BTF with a header carrying unknown nonzero extension
bytes, such as BTF from a newer kernel, but refuses to modify it.

It is expected for a C dump of a BTF with extension bytes to match the
dump of the same BTF without them, but there is no test for this.

Add a test case to bpftool_btf_dump to cover it.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../bpf/prog_tests/bpftool_btf_dump.c         | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)

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 e66e686e4e12..3bfbd185a74d 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -57,6 +57,42 @@ static struct btf *mk_btf(void)
 	return btf;
 }
 
+/* mk_btf() with a nonzero byte in an unknown header extension */
+static struct btf *mk_hdr_extra_btf(void)
+{
+	struct btf *btf, *ext_btf = NULL;
+	const __u32 extra_sz = 4;
+	__u32 hdr_len, sz;
+	const void *raw;
+	char *ext;
+
+	btf = mk_btf();
+	if (!btf)
+		return NULL;
+
+	raw = btf__raw_data(btf, &sz);
+	if (!ASSERT_OK_PTR(raw, "raw_data"))
+		goto out;
+
+	hdr_len = ((const struct btf_header *)raw)->hdr_len;
+
+	ext = calloc(1, sz + extra_sz);
+	if (!ASSERT_OK_PTR(ext, "alloc_ext"))
+		goto out;
+
+	memcpy(ext, raw, hdr_len);
+	ext[hdr_len] = 0xA5; /* any nonzero value */
+	memcpy(ext + hdr_len + extra_sz, raw + hdr_len, sz - hdr_len);
+	((struct btf_header *)ext)->hdr_len = hdr_len + extra_sz;
+
+	ext_btf = btf__new(ext, sz + extra_sz);
+	ASSERT_OK_PTR(ext_btf, "btf_new_ext");
+	free(ext);
+out:
+	btf__free(btf);
+	return ext_btf;
+}
+
 /*
  * Older kernel BTF encodes the arena attribute as "address_space(1)":
  *
@@ -328,6 +364,12 @@ void test_bpftool_btf_dump(void)
 		unlink(path);
 	}
 
+	if (!btf_to_tmpfile(mk_hdr_extra_btf, path)) {
+		if (test__start_subtest("hdr_extra"))
+			test_dump(path, true);
+		unlink(path);
+	}
+
 	if (!btf_to_tmpfile(mk_legacy_arena_btf, path)) {
 		if (test__start_subtest("legacy_arena"))
 			test_legacy_arena(path);
-- 
2.56.0


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

* [PATCH bpf-next v3 08/13] bpftool: Apply __bpf_relocatable per record on BTF dump
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (6 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 07/13] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 09/13] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

bpftool wraps the C dump in a clang attribute pragma that applies
preserve_access_index to every struct and union, so field accesses
through vmlinux.h types get CO-RE relocations. GCC-BPF supports the
attribute but not the pragma, so it gets no CO-RE from the header.

Jose proposed replacing the Clang pragmas with per-record attributes
in May 2024 [1]. The declaration attribute support in btf_dump [2] now
provides the per-type control discussed in that thread.

Apply the attribute to each struct and union definition instead, as
kflagged decl tags rendered by btf_dump. Add the tags to a split BTF
over the input, so the input stays read-only. The tags use the
__bpf_relocatable macro, which by default expands to
__attribute__((preserve_access_index)) if the compiler supports it.

Programs built with Clang are unchanged.

GCC-BPF programs can now get CO-RE relocations from vmlinux.h. The
selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX, so it is
not affected yet.

[1]: https://lore.kernel.org/bpf/20240503111836.25275-1-jose.marchesi@oracle.com/
[2]: https://lore.kernel.org/bpf/20260929234608.48503-1-ihor.solodrai@linux.dev/

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/bpftool/btf.c                       | 60 +++++++++++++++----
 .../bpf/bpftool_btf_dump_sorted.expected      | 16 ++---
 .../bpf/bpftool_btf_dump_unsorted.expected    | 16 ++---
 3 files changed, 64 insertions(+), 28 deletions(-)

diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 94237befec67..5f5c054d7366 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -1123,26 +1123,65 @@ static int btf_c_fixup_legacy_arena_tags(struct btf *btf)
 	return 0;
 }
 
+static int btf_c_annotate_records(struct btf *btf)
+{
+	__u32 i, cnt = btf__type_cnt(btf);
+	int err;
+
+	for (i = 1; i < cnt; i++) {
+		const struct btf_type *t = btf__type_by_id(btf, i);
+
+		if (!btf_is_composite(t))
+			continue;
+		err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
 static int dump_btf_c(struct btf *btf,
 		      __u32 *root_type_ids, int root_type_cnt, bool sort_dump)
 {
 	struct sort_datum *datums = NULL;
-	struct btf_dump *d;
+	struct btf_dump *d = NULL;
+	struct btf *split;
+	size_t ptr_sz;
 	int err = 0, i;
 
 	err = btf_c_fixup_legacy_arena_tags(btf);
 	if (err)
 		return err;
 
-	d = btf_dump__new(btf, btf_dump_printf, NULL, NULL);
-	if (!d)
+	split = btf__new_empty_split(btf);
+	if (!split)
 		return -errno;
 
+	/* btf__new_empty_split() does not inherit the base pointer size */
+	ptr_sz = btf__pointer_size(btf);
+	if (ptr_sz)
+		btf__set_pointer_size(split, ptr_sz);
+
+	err = btf_c_annotate_records(split);
+	if (err)
+		goto done;
+
+	d = btf_dump__new(split, btf_dump_printf, NULL, NULL);
+	if (!d) {
+		err = -errno;
+		goto done;
+	}
+
 	printf("#ifndef __VMLINUX_H__\n");
 	printf("#define __VMLINUX_H__\n");
 	printf("\n");
-	printf("#ifndef BPF_NO_PRESERVE_ACCESS_INDEX\n");
-	printf("#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)\n");
+	printf("#ifndef __bpf_relocatable\n");
+	printf("#if !defined(BPF_NO_PRESERVE_ACCESS_INDEX) && __has_attribute(preserve_access_index)\n");
+	printf("#define __bpf_relocatable __attribute__((preserve_access_index))\n");
+	printf("#else\n");
+	printf("#define __bpf_relocatable\n");
+	printf("#endif\n");
 	printf("#endif\n\n");
 	printf("#ifndef __ksym\n");
 	printf("#define __ksym __attribute__((section(\".ksyms\")))\n");
@@ -1165,10 +1204,10 @@ static int dump_btf_c(struct btf *btf,
 				goto done;
 		}
 	} else {
-		int cnt = btf__type_cnt(btf);
+		int cnt = btf__type_cnt(split);
 
 		if (sort_dump)
-			datums = sort_btf_c(btf);
+			datums = sort_btf_c(split);
 		for (i = 0; i < cnt; i++) {
 			int idx = datums ? datums[i].index : i;
 
@@ -1181,20 +1220,17 @@ static int dump_btf_c(struct btf *btf,
 				goto done;
 		}
 
-		err = dump_btf_kfuncs(d, btf);
+		err = dump_btf_kfuncs(d, split);
 		if (err)
 			goto done;
 	}
 
-	printf("#ifndef BPF_NO_PRESERVE_ACCESS_INDEX\n");
-	printf("#pragma clang attribute pop\n");
-	printf("#endif\n");
-	printf("\n");
 	printf("#endif /* __VMLINUX_H__ */\n");
 
 done:
 	free(datums);
 	btf_dump__free(d);
+	btf__free(split);
 	return err;
 }
 
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
index 5470b2b43229..f2389e3dceed 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -1,8 +1,12 @@
 #ifndef __VMLINUX_H__
 #define __VMLINUX_H__
 
-#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
-#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
+#ifndef __bpf_relocatable
+#if !defined(BPF_NO_PRESERVE_ACCESS_INDEX) && __has_attribute(preserve_access_index)
+#define __bpf_relocatable __attribute__((preserve_access_index))
+#else
+#define __bpf_relocatable
+#endif
 #endif
 
 #ifndef __ksym
@@ -30,19 +34,15 @@ struct holey {
 	long: 32;
 	long: 32;
 	int tail;
-};
+} __bpf_relocatable;
 
 struct s {
 	int f;
-};
+} __bpf_relocatable;
 
 
 /* BPF kfuncs */
 #ifndef BPF_NO_KFUNC_PROTOTYPES
 #endif
 
-#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
-#pragma clang attribute pop
-#endif
-
 #endif /* __VMLINUX_H__ */
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
index 4310adc2ed51..549045677a45 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
@@ -1,8 +1,12 @@
 #ifndef __VMLINUX_H__
 #define __VMLINUX_H__
 
-#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
-#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
+#ifndef __bpf_relocatable
+#if !defined(BPF_NO_PRESERVE_ACCESS_INDEX) && __has_attribute(preserve_access_index)
+#define __bpf_relocatable __attribute__((preserve_access_index))
+#else
+#define __bpf_relocatable
+#endif
 #endif
 
 #ifndef __ksym
@@ -26,7 +30,7 @@ struct holey {
 	long: 32;
 	long: 32;
 	int tail;
-};
+} __bpf_relocatable;
 
 enum {
 	E0 = 1,
@@ -34,15 +38,11 @@ enum {
 
 struct s {
 	int f;
-};
+} __bpf_relocatable;
 
 
 /* BPF kfuncs */
 #ifndef BPF_NO_KFUNC_PROTOTYPES
 #endif
 
-#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
-#pragma clang attribute pop
-#endif
-
 #endif /* __VMLINUX_H__ */
-- 
2.56.0


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

* [PATCH bpf-next v3 09/13] bpftool: Skip __bpf_relocatable on verifier-rewritten context types
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (7 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 08/13] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 10/13] bpftool: Emit __bpf_static_offset " Ihor Solodrai
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

The verifier translates accesses to UAPI context and socket types,
such as __sk_buff and bpf_sock, into accesses to kernel structures.
It picks the kernel field by the instruction's offset and keeps the
base register. For example, in bpf_convert_ctx_access() [1]:

	switch (si->off) {
	...
	case offsetof(struct __sk_buff, mark):
		...
		*insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
				      bpf_target_off(struct sk_buff, mark,
						     4, target_size));

So these accesses require a load or store with an immediate offset
from an unmodified pointer. Their UAPI field offsets are fixed, so
CO-RE relocation is unnecessary. With preserve_access_index, the
compiler can instead emit pointer arithmetic, for example
for ((u8 *)&skb->mark)[1].  The verifier then rejects an otherwise
valid program.

Omit the attribute from the 13 such types when dumping C. Also omit it
from their nested anonymous records: keeping it there can root
relocations at an unnamed type, which libbpf cannot resolve.

The last addition to this set was bpf_sk_lookup, introduced in 2020 by
commit e9ddbb7707ff ("bpf: Introduce SK_LOOKUP program type with a
dedicated attach point"). Keep the set hardcoded in bpftool so the
exclusion also works with older kernels.

[1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/filter.c?h=v7.2#n9939

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/bpftool/btf.c | 83 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 81 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 5f5c054d7366..86ab844898db 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -1123,22 +1123,101 @@ static int btf_c_fixup_legacy_arena_tags(struct btf *btf)
 	return 0;
 }
 
+/*
+ * The verifier translates field accesses of some types into accesses
+ * to kernel structures, see bpf_convert_ctx_accesses().
+ * The translation keys on the immediate offset of each load or store,
+ * so offsets into these types must stay immediates.
+ */
+static const char * const btf_c_static_offset_types[] = {
+	"__sk_buff",
+	"bpf_perf_event_data",
+	"bpf_sk_lookup",
+	"bpf_sock",
+	"bpf_sock_addr",
+	"bpf_sock_ops",
+	"bpf_sockopt",
+	"bpf_sysctl",
+	"bpf_tcp_sock",
+	"bpf_xdp_sock",
+	"sk_msg_md",
+	"sk_reuseport_md",
+	"xdp_md",
+};
+
+static bool btf_c_needs_static_offset(const struct btf *btf, const struct btf_type *t)
+{
+	const char *name;
+	size_t i;
+
+	if (!btf_is_struct(t))
+		return false;
+
+	name = btf__name_by_offset(btf, t->name_off);
+	for (i = 0; i < ARRAY_SIZE(btf_c_static_offset_types); i++) {
+		if (!strcmp(name, btf_c_static_offset_types[i]))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Anonymous records within types that need static offsets must not start
+ * CO-RE access chains. Named records, typedefs and pointer targets are
+ * separate types.
+ */
+static void btf_c_mark_non_relocatable(const struct btf *btf, __u32 id, bool *non_relocatable)
+{
+	const struct btf_type *t = btf__type_by_id(btf, id);
+	const struct btf_member *m = btf_members(t);
+	__u32 i;
+
+	non_relocatable[id] = true;
+	for (i = 0; i < btf_vlen(t); i++, m++) {
+		const struct btf_type *mt;
+		__u32 mid = m->type;
+
+		mt = btf__type_by_id(btf, mid);
+		while (btf_is_array(mt) || btf_is_mod(mt)) {
+			mid = btf_is_array(mt) ? btf_array(mt)->type : mt->type;
+			mt = btf__type_by_id(btf, mid);
+		}
+		if (btf_is_composite(mt) && !mt->name_off && !non_relocatable[mid])
+			btf_c_mark_non_relocatable(btf, mid, non_relocatable);
+	}
+}
+
 static int btf_c_annotate_records(struct btf *btf)
 {
 	__u32 i, cnt = btf__type_cnt(btf);
+	bool *non_relocatable;
 	int err;
 
+	non_relocatable = calloc(cnt, sizeof(*non_relocatable));
+	if (!non_relocatable)
+		return -ENOMEM;
+
+	for (i = 1; i < cnt; i++) {
+		if (btf_c_needs_static_offset(btf, btf__type_by_id(btf, i)))
+			btf_c_mark_non_relocatable(btf, i, non_relocatable);
+	}
+
 	for (i = 1; i < cnt; i++) {
 		const struct btf_type *t = btf__type_by_id(btf, i);
 
 		if (!btf_is_composite(t))
 			continue;
+		if (non_relocatable[i])
+			continue;
 		err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
 		if (err < 0)
-			return err;
+			goto out;
 	}
 
-	return 0;
+	err = 0;
+out:
+	free(non_relocatable);
+	return err;
 }
 
 static int dump_btf_c(struct btf *btf,
-- 
2.56.0


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

* [PATCH bpf-next v3 10/13] bpftool: Emit __bpf_static_offset on verifier-rewritten context types
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (8 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 09/13] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 11/13] selftests/bpf: Cover record attributes in bpftool BTF dumps Ihor Solodrai
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

The verifier translates accesses to some UAPI context and socket types
into accesses to kernel structures. These accesses require an
unmodified base and an immediate field offset.

Leaving preserve_access_index (PAI) off does not stop the compiler
from merging loads at different field offsets into one load through an
adjusted pointer. The verifier rejects the resulting program.

preserve_static_offset (PSO), available since Clang 18, preserves
static field offsets in load and store instruction immediates. Apply
it to the 13 types that need static offsets in vmlinux.h through
__bpf_static_offset.

Eduard introduced PSO in Clang and proposed annotating BPF context
types in 2023 [1]. His v3 RFC [2] left pt_regs annotations in
architecture headers unresolved. However, since commit
de6c7d99f898 ("bpf: Relax fixed offset check for PTR_TO_CTX"), program
types without convert_ctx_access() accept fixed offsets from modified
context pointers. pt_regs can now keep PAI, while PSO on
bpf_perf_event_data covers its regs member.

PSO creates no CO-RE relocations, so BPF_NO_PRESERVE_ACCESS_INDEX does
not disable it.

Clang 18 through 23 crash on __builtin_preserve_field_info over a PSO
record, including bpf_core_field_*() and the CO-RE bitfield helpers.
-D__bpf_static_offset= can disable PSO for affected programs. The
crash has already been forward fixed in LLVM [3].

[1]: https://lore.kernel.org/bpf/20231208000531.19179-1-eddyz87@gmail.com/
[2]: https://lore.kernel.org/bpf/20231220133411.22978-1-eddyz87@gmail.com/
[3]: https://github.com/llvm/llvm-project/pull/234280

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 tools/bpf/bpftool/btf.c                          | 16 ++++++++++++++--
 .../bpf/bpftool_btf_dump_sorted.expected         |  8 ++++++++
 .../bpf/bpftool_btf_dump_unsorted.expected       |  8 ++++++++
 3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 86ab844898db..03da4f6821f1 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -1204,12 +1204,17 @@ static int btf_c_annotate_records(struct btf *btf)
 
 	for (i = 1; i < cnt; i++) {
 		const struct btf_type *t = btf__type_by_id(btf, i);
+		const char *attr;
 
 		if (!btf_is_composite(t))
 			continue;
-		if (non_relocatable[i])
+		if (!non_relocatable[i])
+			attr = "__bpf_relocatable";
+		else if (btf_c_needs_static_offset(btf, t))
+			attr = "__bpf_static_offset";
+		else
 			continue;
-		err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
+		err = btf__add_decl_attr(btf, attr, i, -1);
 		if (err < 0)
 			goto out;
 	}
@@ -1262,6 +1267,13 @@ static int dump_btf_c(struct btf *btf,
 	printf("#define __bpf_relocatable\n");
 	printf("#endif\n");
 	printf("#endif\n\n");
+	printf("#ifndef __bpf_static_offset\n");
+	printf("#if __has_attribute(preserve_static_offset)\n");
+	printf("#define __bpf_static_offset __attribute__((preserve_static_offset))\n");
+	printf("#else\n");
+	printf("#define __bpf_static_offset\n");
+	printf("#endif\n");
+	printf("#endif\n\n");
 	printf("#ifndef __ksym\n");
 	printf("#define __ksym __attribute__((section(\".ksyms\")))\n");
 	printf("#endif\n\n");
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
index f2389e3dceed..b56b676b96a5 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -9,6 +9,14 @@
 #endif
 #endif
 
+#ifndef __bpf_static_offset
+#if __has_attribute(preserve_static_offset)
+#define __bpf_static_offset __attribute__((preserve_static_offset))
+#else
+#define __bpf_static_offset
+#endif
+#endif
+
 #ifndef __ksym
 #define __ksym __attribute__((section(".ksyms")))
 #endif
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
index 549045677a45..543db89078af 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
@@ -9,6 +9,14 @@
 #endif
 #endif
 
+#ifndef __bpf_static_offset
+#if __has_attribute(preserve_static_offset)
+#define __bpf_static_offset __attribute__((preserve_static_offset))
+#else
+#define __bpf_static_offset
+#endif
+#endif
+
 #ifndef __ksym
 #define __ksym __attribute__((section(".ksyms")))
 #endif
-- 
2.56.0


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

* [PATCH bpf-next v3 11/13] selftests/bpf: Cover record attributes in bpftool BTF dumps
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (9 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 10/13] bpftool: Emit __bpf_static_offset " Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 12/13] selftests/bpf: Cover context and socket accesses through vmlinux.h Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 13/13] bpftool: Document __bpf_relocatable and __bpf_static_offset Ihor Solodrai
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

Types whose field accesses need static offsets carry __bpf_static_offset,
while their nested anonymous records must stay unannotated. Other
records retain __bpf_relocatable, including context types such as
bpf_nf_ctx whose accesses do not need static offsets.

Extend the existing C dump test fixture with selected context and
socket types and recursively nested anonymous unions.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../bpf/bpftool_btf_dump_sorted.expected      | 24 ++++++++++
 .../bpf/bpftool_btf_dump_unsorted.expected    | 24 ++++++++++
 .../bpf/prog_tests/bpftool_btf_dump.c         | 47 +++++++++++++++++++
 3 files changed, 95 insertions(+)

diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
index b56b676b96a5..190cf16e49a2 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -37,6 +37,30 @@ enum {
 	E0 = 1,
 };
 
+struct __sk_buff {
+	int len;
+	union {
+		int sk;
+	};
+} __bpf_static_offset;
+
+struct bpf_nf_ctx {
+	int f;
+} __bpf_relocatable;
+
+struct bpf_sk_lookup {
+	union {
+		union {
+			int cookie;
+		};
+		int family;
+	};
+} __bpf_static_offset;
+
+struct bpf_tcp_sock {
+	int snd_cwnd;
+} __bpf_static_offset;
+
 struct holey {
 	int c;
 	long: 32;
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
index 543db89078af..1a0a7b016c50 100644
--- a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
@@ -48,6 +48,30 @@ struct s {
 	int f;
 } __bpf_relocatable;
 
+struct __sk_buff {
+	int len;
+	union {
+		int sk;
+	};
+} __bpf_static_offset;
+
+struct bpf_sk_lookup {
+	union {
+		union {
+			int cookie;
+		};
+		int family;
+	};
+} __bpf_static_offset;
+
+struct bpf_tcp_sock {
+	int snd_cwnd;
+} __bpf_static_offset;
+
+struct bpf_nf_ctx {
+	int f;
+} __bpf_relocatable;
+
 
 /* BPF kfuncs */
 #ifndef BPF_NO_KFUNC_PROTOTYPES
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 3bfbd185a74d..b9b3d31bc17b 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -20,6 +20,18 @@
  *	};
  *	enum { E0 = 1 };
  *	struct s { int f; };
+ *	struct __sk_buff {
+ *		int len;
+ *		union { int sk; };
+ *	};
+ *	struct bpf_sk_lookup {
+ *		union {
+ *			union { int cookie; };
+ *			int family;
+ *		};
+ *	};
+ *	struct bpf_tcp_sock { int snd_cwnd; };
+ *	struct bpf_nf_ctx { int f; };
  */
 static struct btf *mk_btf(void)
 {
@@ -42,6 +54,25 @@ static struct btf *mk_btf(void)
 	btf__add_struct(btf, "s", 4);
 	btf__add_field(btf, "f", 1, 0, 0);
 
+	btf__add_union(btf, NULL, 4);
+	btf__add_field(btf, "sk", 1, 0, 0);
+	btf__add_struct(btf, "__sk_buff", 8);
+	btf__add_field(btf, "len", 1, 0, 0);
+	btf__add_field(btf, NULL, 6, 32, 0);
+
+	btf__add_union(btf, NULL, 4);
+	btf__add_field(btf, "cookie", 1, 0, 0);
+	btf__add_union(btf, NULL, 4);
+	btf__add_field(btf, NULL, 8, 0, 0);
+	btf__add_field(btf, "family", 1, 0, 0);
+	btf__add_struct(btf, "bpf_sk_lookup", 4);
+	btf__add_field(btf, NULL, 9, 0, 0);
+
+	btf__add_struct(btf, "bpf_tcp_sock", 4);
+	btf__add_field(btf, "snd_cwnd", 1, 0, 0);
+	btf__add_struct(btf, "bpf_nf_ctx", 4);
+	btf__add_field(btf, "f", 1, 0, 0);
+
 	VALIDATE_RAW_BTF(
 		btf,
 		"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
@@ -52,6 +83,22 @@ static struct btf *mk_btf(void)
 		"[4] ENUM '(anon)' encoding=UNSIGNED size=4 vlen=1\n"
 		"\t'E0' val=1",
 		"[5] STRUCT 's' size=4 vlen=1\n"
+		"\t'f' type_id=1 bits_offset=0",
+		"[6] UNION '(anon)' size=4 vlen=1\n"
+		"\t'sk' type_id=1 bits_offset=0",
+		"[7] STRUCT '__sk_buff' size=8 vlen=2\n"
+		"\t'len' type_id=1 bits_offset=0\n"
+		"\t'(anon)' type_id=6 bits_offset=32",
+		"[8] UNION '(anon)' size=4 vlen=1\n"
+		"\t'cookie' type_id=1 bits_offset=0",
+		"[9] UNION '(anon)' size=4 vlen=2\n"
+		"\t'(anon)' type_id=8 bits_offset=0\n"
+		"\t'family' type_id=1 bits_offset=0",
+		"[10] STRUCT 'bpf_sk_lookup' size=4 vlen=1\n"
+		"\t'(anon)' type_id=9 bits_offset=0",
+		"[11] STRUCT 'bpf_tcp_sock' size=4 vlen=1\n"
+		"\t'snd_cwnd' type_id=1 bits_offset=0",
+		"[12] STRUCT 'bpf_nf_ctx' size=4 vlen=1\n"
 		"\t'f' type_id=1 bits_offset=0");
 
 	return btf;
-- 
2.56.0


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

* [PATCH bpf-next v3 12/13] selftests/bpf: Cover context and socket accesses through vmlinux.h
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (10 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 11/13] selftests/bpf: Cover record attributes in bpftool BTF dumps Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  2026-10-30 23:33 ` [PATCH bpf-next v3 13/13] bpftool: Document __bpf_relocatable and __bpf_static_offset Ihor Solodrai
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

Add load tests for context and socket accesses through the generated
vmlinux.h:

- fields in nested anonymous unions of context types, which vmlinux.h
  leaves unannotated, and a bpf_sock field.
- skb_narrow_field: a narrow skb->mark load, which the verifier
  rejects if __sk_buff carries __bpf_relocatable.
- skb_merge_loads: skb field loads merged across branches, which the
  verifier rejects without __bpf_static_offset. Built only if the
  compiler supports preserve_static_offset.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../selftests/bpf/prog_tests/verifier.c       |  2 +
 .../bpf/progs/verifier_ctx_rewritten.c        | 59 +++++++++++++++++++
 2 files changed, 61 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c

diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 386ad81051a9..40dea921f272 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -41,6 +41,7 @@
 #include "verifier_const.skel.h"
 #include "verifier_const_or.skel.h"
 #include "verifier_ctx.skel.h"
+#include "verifier_ctx_rewritten.skel.h"
 #include "verifier_ctx_sk_msg.skel.h"
 #include "verifier_d_path.skel.h"
 #include "verifier_default_trusted_ptr.skel.h"
@@ -233,6 +234,7 @@ void test_verifier_cgroup_storage(void)       { RUN(verifier_cgroup_storage); }
 void test_verifier_const(void)                { RUN(verifier_const); }
 void test_verifier_const_or(void)             { RUN(verifier_const_or); }
 void test_verifier_ctx(void)                  { RUN_TESTS(verifier_ctx); }
+void test_verifier_ctx_rewritten(void)        { RUN_TESTS(verifier_ctx_rewritten); }
 void test_verifier_ctx_sk_msg(void)           { RUN(verifier_ctx_sk_msg); }
 void test_verifier_d_path(void)               { RUN(verifier_d_path); }
 void test_verifier_default_trusted_ptr(void)  { RUN_TESTS(verifier_default_trusted_ptr); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c b/tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c
new file mode 100644
index 000000000000..33eb975a170a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+SEC("tc")
+__success
+int skb_socket(struct __sk_buff *skb)
+{
+	struct bpf_sock *sk = skb->sk;
+
+	return sk ? sk->family : 0;
+}
+
+SEC("sockops")
+__success
+int sockops_args_reply(struct bpf_sock_ops *skops)
+{
+	skops->reply = skops->args[1];
+	return 0;
+}
+
+SEC("sk_msg")
+__success
+int sk_msg_data(struct sk_msg_md *msg)
+{
+	return msg->data < msg->data_end;
+}
+
+SEC("cgroup/getsockopt")
+__success
+int sockopt_optval(struct bpf_sockopt *ctx)
+{
+	return ctx->optval < ctx->optval_end;
+}
+
+SEC("tc")
+__success
+int skb_narrow_field(struct __sk_buff *skb)
+{
+	return ((__u8 *)&skb->mark)[1];
+}
+
+#if __has_attribute(preserve_static_offset)
+SEC("tc")
+__success
+int skb_merge_loads(struct __sk_buff *skb)
+{
+	__u32 value;
+
+	if (skb->len)
+		value = skb->pkt_type;
+	else
+		value = skb->mark;
+	return value;
+}
+#endif
+
+char _license[] SEC("license") = "GPL";
-- 
2.56.0


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

* [PATCH bpf-next v3 13/13] bpftool: Document __bpf_relocatable and __bpf_static_offset
  2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
                   ` (11 preceding siblings ...)
  2026-10-30 23:33 ` [PATCH bpf-next v3 12/13] selftests/bpf: Cover context and socket accesses through vmlinux.h Ihor Solodrai
@ 2026-10-30 23:33 ` Ihor Solodrai
  12 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-30 23:33 UTC (permalink / raw)
  To: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet
  Cc: bpf, Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
	Jose E . Marchesi, Vineet Gupta, Yonghong Song

The C dump annotates record definitions with the __bpf_relocatable and
__bpf_static_offset macros. Programs control the attributes through
these macros and BPF_NO_PRESERVE_ACCESS_INDEX, which disables only
__bpf_relocatable.

Both macros expand to complete attribute specifiers and can be
pre-defined by the user.

Update bpftool documentation to explain both macros, and how to avoid
the Clang crash on CO-RE field-info builtins over __bpf_static_offset
types.

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
---
 .../bpf/bpftool/Documentation/bpftool-btf.rst | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/tools/bpf/bpftool/Documentation/bpftool-btf.rst b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
index c6dc445cd4a1..6840d5d3c39d 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-btf.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
@@ -70,6 +70,32 @@ bpftool btf dump *BTF_SRC* [format *FORMAT*] [root_id *ROOT_ID*]
     formatting, the output is sorted by default. Use the **unsorted** option
     to avoid sorting the output.
 
+    C output annotates struct and union definitions individually, with or
+    without **unsorted**. Most records carry ``__bpf_relocatable``. The
+    generated header defines it as
+    ``__attribute__((preserve_access_index))`` where supported, or as empty
+    when **BPF_NO_PRESERVE_ACCESS_INDEX** is defined or the compiler lacks
+    the attribute. GCC-BPF can apply CO-RE through these attributes.
+
+    Context and socket types whose field accesses the verifier rewrites
+    carry ``__bpf_static_offset`` instead, and their nested anonymous
+    records carry no attribute. Where supported (Clang 18 and later),
+    ``__bpf_static_offset`` expands to
+    ``__attribute__((preserve_static_offset))``. It is empty otherwise.
+    The attribute keeps static field offsets in load and store instruction
+    immediates. **BPF_NO_PRESERVE_ACCESS_INDEX** does not affect
+    ``__bpf_static_offset``.
+
+    Either macro may be pre-defined as a complete annotation, such as
+    ``__attribute__((preserve_access_index))``, or as empty to override
+    the policy.
+
+    Clang 18 through 23 crash when ``__builtin_preserve_field_info`` is used
+    on a type marked ``__bpf_static_offset``, including through
+    ``bpf_core_field_*`` and the CO-RE bitfield helpers. Compile with
+    ``-D__bpf_static_offset=`` to use these helpers with an affected
+    compiler.
+
     **root_id** option can be used to filter a dump to a single type and all
     its dependent types. It cannot be used with any other types of filtering
     (such as the "key", "value", or "kv" arguments when dumping BTF for a map).
-- 
2.56.0


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

* Re: [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings
  2026-10-30 23:33 ` [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings Ihor Solodrai
@ 2026-10-31  0:16   ` bot+bpf-ci
  2026-10-31  0:27     ` Ihor Solodrai
  0 siblings, 1 reply; 21+ messages in thread
From: bot+bpf-ci @ 2026-10-31  0:16 UTC (permalink / raw)
  To: ihor.solodrai, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2941 bytes --]

> docs, libbpf: Document complete BTF annotation strings

TL;DR: The BTF docs drop the old bare attribute-list form for kflagged tags
without noting it. Older kernel BTF uses it, so consumers following the new
text may print "address_space(1)" uncompilably. Should btf.rst mention it?

> diff --git a/Documentation/bpf/btf.rst b/Documentation/bpf/btf.rst
> index 29de1222c3e7..98c1f0440f8b 100644
> --- a/Documentation/bpf/btf.rst
> +++ b/Documentation/bpf/btf.rst

[ ... ]

> @@ -539,8 +540,9 @@ normal type tag, and the ``name_off`` encodes btf_type_tag attribute
>  string.
>
>  If ``info.kind_flag`` is 1, then the type tag represents an arbitrary
> -__attribute__, and the ``name_off`` encodes a string representing the
> -attribute-list of the attribute specifier.
> +annotation. As with decl tags, ``name_off`` encodes the complete annotation
> +string, which ``btf_dump`` prints unchanged. An attribute specifier must
> +include its wrapper, for example ``__attribute__((address_space(1)))``.
>
>  2.2.19 BTF_KIND_ENUM64
>  ~~~~~~~~~~~~~~~~~~~~~~

The old text, which described the string as the attribute-list (for example
aligned(4)), is removed without any note that BTF in that older form exists.
Should the format reference say something about it?

Kernel BTF already in use is in the older form.  pahole emitted kflagged
arena type tags as "address_space(1)" via --btf_features=attributes (commit
21cb33c7e065, "kbuild, bpf: Enable --btf_features=attributes") until
27a78c2e7eee dropped that pahole feature.  resolve_btfids then emitted the
same string:

    #define TYPE_ATTR_ARENA "address_space(1)"

until 0c5eab4faab1 changed it to "__attribute__((address_space(1)))".

The kernel accepts both forms.  btf_ref_type_check_meta() only lets kflag
through for type tags, and btf_type_tag_walk() / btf_ptr_has_type_tag() skip
kflagged tags without looking at the string.

A consumer written from the updated text prints such a tag unchanged and
gets "void address_space(1) *", which does not compile.  libbpf's own
btf_dump_emit_type_chain() now does exactly that:

    if (btf_kflag(t))
            btf_dump_printf(d, " %s", name);

Later in this series, fd1278be39e3 ("bpftool: Normalize legacy arena type
tags before C dumps") adds a bpftool special case for this string, and its
message says "Older kernel BTF encodes kflagged arena type tags as
"address_space(1)"".  No later commit in the series touches btf.rst, so the
format reference never mentions the legacy encoding.

Could sections 2.2.17 and 2.2.18 add a note that BTF produced before this
change (pahole --btf_features=attributes, older resolve_btfids) stores the
bare attribute-list, so that consumers know to expect it?


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

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

* Re: [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps
  2026-10-30 23:33 ` [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps Ihor Solodrai
@ 2026-10-31  0:16   ` bot+bpf-ci
  2026-10-31  0:31     ` Ihor Solodrai
  0 siblings, 1 reply; 21+ messages in thread
From: bot+bpf-ci @ 2026-10-31  0:16 UTC (permalink / raw)
  To: ihor.solodrai, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3666 bytes --]

> bpftool: Normalize legacy arena type tags before C dumps

TL;DR: The legacy arena tag fixup skips split BTF, so module C dumps on older
kernels may still print "address_space(1)" for vmlinux arena kfuncs and fail
to compile; commit message doesn't say so.

> diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
> --- a/tools/bpf/bpftool/btf.c
> +++ b/tools/bpf/bpftool/btf.c

[ ... ]

> @@ -1092,13 +1093,47 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
>  	return datums;
>  }
>  
> -static int dump_btf_c(const struct btf *btf,
> +/*
> + * Older kernel BTF encodes kflagged arena type tags as "address_space(1)",
> + * which btf_dump prints as is. Rewrite them to the complete specifier.
> + */
> +static int btf_c_fixup_legacy_arena_tags(struct btf *btf)
> +{
> +	__u32 i, cnt = btf__type_cnt(btf);
> +	int off;
> +
> +	if (btf__base_btf(btf) ||
> +	    btf__find_by_name_kind(btf, LEGACY_ARENA_TAG, BTF_KIND_TYPE_TAG) < 0)
> +		return 0;

Can this early return for split BTF leave module C dumps broken on older
kernels?

The commit message says only "Split BTF is dumped as is."  But dump_btf_c()
walks every type ID up to btf__type_cnt(btf), and for split BTF that count
is start_id + nr_types, so it covers the base (vmlinux) types as well:

    int cnt = btf__type_cnt(btf);
    ...
    for (i = 0; i < cnt; i++) {

dump_btf_kfuncs() scans the same range with "for (i = 1; i < cnt; i++)".
This differs from dump_btf_raw(), which starts at btf__type_cnt(base).

On an older kernel whose vmlinux BTF has kflagged "address_space(1)" tags,
these commands take the split path:

    bpftool btf dump file /sys/kernel/btf/<module> format c
    bpftool btf dump id <module btf id> format c

do_dump() auto-detects the sysfs prefix, loads base through
get_vmlinux_btf_from_sysfs() and calls btf__parse_split(files[0], base).
Multi-file merges through merge_btf_files() end up in the same place.

The fixup then returns 0, and the dump still prints the vmlinux arena kfuncs
(for example bpf_arena_alloc_pages) as "void address_space(1) *...", which
does not compile unless BPF_NO_KFUNC_PROTOTYPES is defined.  Before commit
0c5eab4faab1 ("libbpf: Dump kflagged BTF tags as is"), btf_dump wrapped
kflagged tags in __attribute__((...)), so the same module dump compiled.

No later commit up to 6ca27da0fce8 changes this:
btf_c_fixup_legacy_arena_tags() is the same there, and the selftests added in
15fdeb448400 ("selftests/bpf: Cover legacy arena type tags in bpftool C
dumps") only cover non-split BTF.

Should the commit message state that module C dumps on older kernels still
fail to compile, and explain why that case can't be handled?

Alternatively, could split dumps be handled too, for example by adding the
string to the split BTF and pointing the base tags' name_off at it
(btf_dump resolves names through d->btf, which is the split BTF), or by
emitting a compatibility definition in the dump preamble?

> +
> +	off = btf__add_str(btf, "__attribute__((address_space(1)))");
> +	if (off < 0) {
> +		p_err("failed to rewrite legacy arena type tags: %s", strerror(-off));
> +		return off;
> +	}
> +
> +	for (i = 1; i < cnt; i++) {
> +		struct btf_type *t = (struct btf_type *)btf__type_by_id(btf, i);
> +
> +		if (btf_is_type_tag(t) && btf_kflag(t) &&
> +		    !strcmp(btf__name_by_offset(btf, t->name_off), LEGACY_ARENA_TAG))
> +			t->name_off = off;
> +	}
> +
> +	return 0;
> +}

[ ... ]


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

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

* Re: [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is
  2026-10-30 23:33 ` [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is Ihor Solodrai
@ 2026-10-31  0:16   ` bot+bpf-ci
  2026-10-31  0:36     ` Ihor Solodrai
  0 siblings, 1 reply; 21+ messages in thread
From: bot+bpf-ci @ 2026-10-31  0:16 UTC (permalink / raw)
  To: ihor.solodrai, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 5118 bytes --]

> libbpf: Dump kflagged BTF tags as is

TL;DR: Printing kflag type tags as is may make C dumps of existing kernel BTF
emit invalid "address_space(1)" for arena kfuncs, possibly breaking vmlinux.h
and bpftool builds until a later patch in the series.

> diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
> index 5ad547356303..c16dd93fa6d4 100644
> --- a/tools/bpf/resolve_btfids/main.c
> +++ b/tools/bpf/resolve_btfids/main.c
> @@ -204,7 +204,7 @@ struct btf_links {
>  #define KF_ARENA_ARG2	(1 << 15)
>  #define KF_IMPLICIT_ARGS (1 << 16)
>  #define KF_IMPL_SUFFIX "_impl"
> -#define TYPE_ATTR_ARENA "address_space(1)"
> +#define TYPE_ATTR_ARENA "__attribute__((address_space(1)))"
>  #define PARAM_SUFFIX_ARENA "__arena"
>  #define PARAM_SUFFIX_ARENA_NULLABLE "__arena__nullable"
>  

[ ... ]

> diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
> index ea9a34db4c00..faa9aee52d74 100644
> --- a/tools/lib/bpf/btf_dump.c
> +++ b/tools/lib/bpf/btf_dump.c

[ ... ]

> @@ -1594,7 +1594,7 @@ static void btf_dump_emit_type_chain(struct btf_dump *d,
>  			btf_dump_emit_mods(d, decls);
>  			name = btf_name_of(d, t->name_off);
>  			if (btf_kflag(t))
> -				btf_dump_printf(d, " __attribute__((%s))", name);
> +				btf_dump_printf(d, " %s", name);
>  			else if (strcmp(name, "arena") == 0)
>  				/* the tag denotes a pointer into the BPF arena address space */
>  				btf_dump_printf(d, " __attribute__((address_space(1))) __attribute__((btf_type_tag(\"arena\")))");

Can this break C dumps of BTF that is already deployed?

Kernels built with pahole >= 1.30 have the arena kfuncs
(bpf_arena_alloc_pages, bpf_arena_free_pages, bpf_arena_reserve_pages) in
vmlinux BTF with a TYPE_TAG with kflag=1 and the string "address_space(1)",
since commit 21cb33c7e065 ("kbuild, bpf: Enable --btf_features=attributes").
resolve_btfids has emitted the same string since 27a78c2e7eee, and still does
up to the parent of this commit.

The old code depended on btf_dump adding the wrapper:

    if (btf_kflag(t))
        btf_dump_printf(d, " __attribute__((%s))", name);

With this change the name is printed as is.  So running:

    bpftool btf dump file /sys/kernel/btf/vmlinux format c

against any of those kernels goes through:

    dump_btf_c() -> dump_btf_kfuncs() -> btf_dump__emit_type_decl() ->
    btf_dump_emit_type_chain()

and emits:

    extern void address_space(1) *bpf_arena_alloc_pages(void *p__map,
        void address_space(1) *addr__ign, ...) __weak __ksym;

That is not valid C.  Won't any BPF program that includes the generated
vmlinux.h without BPF_NO_KFUNC_PROTOTYPES fail to build?

That includes bpftool's own skeletons.  tools/bpf/bpftool/Makefile takes
VMLINUX_BTF from /sys/kernel/btf/vmlinux when no built vmlinux is present and
runs:

    $(BPFTOOL_BOOTSTRAP) btf dump file $< format c > vmlinux.h

skeleton/pid_iter.bpf.c and skeleton/profiler.bpf.c then #include <vmlinux.h>,
so would a standalone bpftool build from this commit, on a host running such
a kernel, fail?  BPF selftests and other tools/ users that fall back to the
running kernel's BTF look to be hit the same way.

The commit message says:

> In-tree, only resolve_btfids applying address_space(1) attribute is
> affected.

Does that account for BTF that resolve_btfids and pahole already produced,
which is now on running systems and which bpftool, also in-tree, consumes?

At this commit, Documentation/bpf/btf.rst still says that for kind_flag=1 the
string is the attribute-list ("for an __attribute__((aligned(4))) the
string's contents is aligned(4)"), and the btf__add_type_attr() and
btf__add_decl_attr() comments in tools/lib/bpf/btf.c still describe the tag
as an __attribute__.  Doesn't a producer that follows the documentation now
get output that does not compile?

A later commit in the series, fd1278be39e3 ("bpftool: Normalize legacy arena
type tags before C dumps"), rewrites "address_space(1)" type tags to
"__attribute__((address_space(1)))" before bpftool dumps C.  Its commit
message describes this same breakage: "C dumps of such BTF declare arena
kfunc pointers as "void address_space(1) *", which does not compile".

That fix only covers bpftool, and only non-split BTF ("Split BTF is dumped as
is").  Other users of libbpf's public btf_dump__dump_type() and
btf_dump__emit_type_decl() API that dump existing kernel BTF still get the
uncompilable output.

5890b58f3c99 ("docs, libbpf: Document complete BTF annotation strings") later
updates the documentation and API comments.  On its own, this commit still
breaks bisection for anyone building bpftool or vmlinux.h against a v6.16+
kernel.

Should the bpftool normalization land before, or together with, the
btf_dump change?  And should libbpf keep handling the legacy
"address_space(1)" string itself, since libbpf ships separately and is used
with arbitrary kernel BTF?


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

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

* Re: [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings
  2026-10-31  0:16   ` bot+bpf-ci
@ 2026-10-31  0:27     ` Ihor Solodrai
  0 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-31  0:27 UTC (permalink / raw)
  To: bot+bpf-ci, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, martin.lau, mason

On 10/30/26 5:16 PM, bot+bpf-ci@kernel.org wrote:
>> docs, libbpf: Document complete BTF annotation strings
> 
> TL;DR: The BTF docs drop the old bare attribute-list form for kflagged tags
> without noting it. Older kernel BTF uses it, so consumers following the new
> text may print "address_space(1)" uncompilably. Should btf.rst mention it?
> 
>> diff --git a/Documentation/bpf/btf.rst b/Documentation/bpf/btf.rst
>> index 29de1222c3e7..98c1f0440f8b 100644
>> --- a/Documentation/bpf/btf.rst
>> +++ b/Documentation/bpf/btf.rst
> 
> [ ... ]
> 
>> @@ -539,8 +540,9 @@ normal type tag, and the ``name_off`` encodes btf_type_tag attribute
>>  string.
>>
>>  If ``info.kind_flag`` is 1, then the type tag represents an arbitrary
>> -__attribute__, and the ``name_off`` encodes a string representing the
>> -attribute-list of the attribute specifier.
>> +annotation. As with decl tags, ``name_off`` encodes the complete annotation
>> +string, which ``btf_dump`` prints unchanged. An attribute specifier must
>> +include its wrapper, for example ``__attribute__((address_space(1)))``.
>>
>>  2.2.19 BTF_KIND_ENUM64
>>  ~~~~~~~~~~~~~~~~~~~~~~
> 
> The old text, which described the string as the attribute-list (for example
> aligned(4)), is removed without any note that BTF in that older form exists.
> Should the format reference say something about it?

I don't think so, because the only significant consumer of the old
format is bpftool, and it handles it correctly.

> 
> [...]

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

* Re: [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps
  2026-10-31  0:16   ` bot+bpf-ci
@ 2026-10-31  0:31     ` Ihor Solodrai
  0 siblings, 0 replies; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-31  0:31 UTC (permalink / raw)
  To: bot+bpf-ci, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, martin.lau, mason

On 10/30/26 5:16 PM, bot+bpf-ci@kernel.org wrote:
>> bpftool: Normalize legacy arena type tags before C dumps
> 
> TL;DR: The legacy arena tag fixup skips split BTF, so module C dumps on older
> kernels may still print "address_space(1)" for vmlinux arena kfuncs and fail
> to compile; commit message doesn't say so.
> 
>> diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
>> --- a/tools/bpf/bpftool/btf.c
>> +++ b/tools/bpf/bpftool/btf.c
> 
> [ ... ]
> 
>> @@ -1092,13 +1093,47 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
>>  	return datums;
>>  }
>>  
>> -static int dump_btf_c(const struct btf *btf,
>> +/*
>> + * Older kernel BTF encodes kflagged arena type tags as "address_space(1)",
>> + * which btf_dump prints as is. Rewrite them to the complete specifier.
>> + */
>> +static int btf_c_fixup_legacy_arena_tags(struct btf *btf)
>> +{
>> +	__u32 i, cnt = btf__type_cnt(btf);
>> +	int off;
>> +
>> +	if (btf__base_btf(btf) ||
>> +	    btf__find_by_name_kind(btf, LEGACY_ARENA_TAG, BTF_KIND_TYPE_TAG) < 0)
>> +		return 0;
> 
> Can this early return for split BTF leave module C dumps broken on older
> kernels?

Yes, it will. That's intentional: the fixup targets only vmlinux BTF, which is
always the base.

I don't know of any users relying on btf C dump for modules, but if
there are any they are advised to re-compile their modules with
updated resolve_btfids to use a new format.

> 
> [...]

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

* Re: [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is
  2026-10-31  0:16   ` bot+bpf-ci
@ 2026-10-31  0:36     ` Ihor Solodrai
  2026-10-31 17:19       ` Alexei Starovoitov
  0 siblings, 1 reply; 21+ messages in thread
From: Ihor Solodrai @ 2026-10-31  0:36 UTC (permalink / raw)
  To: bot+bpf-ci, ast, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, martin.lau, mason

On 10/30/26 5:16 PM, bot+bpf-ci@kernel.org wrote:
>> libbpf: Dump kflagged BTF tags as is
> 
> TL;DR: Printing kflag type tags as is may make C dumps of existing kernel BTF
> emit invalid "address_space(1)" for arena kfuncs, possibly breaking vmlinux.h
> and bpftool builds until a later patch in the series.

This is the reason for resolve_btfids change and bpftool patch that
handles "address_space(1)".

The in-tree bpftool and selftests compile and run on every commit
of the series.

The incompatibility affects:
  - old kernel BTF + bpftool before patch "bpftool: Normalize legacy 
    arena type tags before C dumps"
  - new kernel + old bpftool, in which case the users will have to
    update their bpftool


> 
>> [...]

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

* Re: [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is
  2026-10-31  0:36     ` Ihor Solodrai
@ 2026-10-31 17:19       ` Alexei Starovoitov
  0 siblings, 0 replies; 21+ messages in thread
From: Alexei Starovoitov @ 2026-10-31 17:19 UTC (permalink / raw)
  To: Ihor Solodrai, bot+bpf-ci, andrii, daniel, eddyz87, memxor, qmo
  Cc: bpf, alan.maguire, cupertino.miranda, david.faust, irogers,
	jose.marchesi, vineet.gupta, yonghong.song, martin.lau, mason

On Fri, Oct 30, 2026 at 05:36 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
> The incompatibility affects:
>   - old kernel BTF + bpftool before patch "bpftool: Normalize legacy
>     arena type tags before C dumps"
>   - new kernel + old bpftool, in which case the users will have to
>     update their bpftool

I don't think we can accept the second one.
Every bpftool since v6.15 wraps the new string and prints

  void __attribute__((__attribute__((address_space(1))))) *

for the arena kfuncs.
After a kernel upgrade no prog that includes it compiles, unless it
defines BPF_NO_KFUNC_PROTOTYPES. It's not only arena progs.

The list misses module BTF. Patch 4 skips split BTF, so on old kernels
the new bpftool prints "void address_space(1) *" there.
People do such dumps. See commit d8d5c0151148
("bpftool: Support merging multiple module BTFs in btf dump").

can we leave type tags alone and print only decl tags as is ?
bpftool adds decl tags only. It adds them to a split BTF in memory and
dumps them with its own libbpf. Nobody else sees them.
Kernel BTF has no kflagged decl tags on records, I think.
Then no need for the resolve_btfids change, patches 4 and 6, etc.

pw-bot: cr

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

end of thread, other threads:[~2026-10-31 17:19 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 23:33 [PATCH bpf-next v3 00/13] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 01/13] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 02/13] libbpf: Dump kflagged BTF tags as is Ihor Solodrai
2026-10-31  0:16   ` bot+bpf-ci
2026-10-31  0:36     ` Ihor Solodrai
2026-10-31 17:19       ` Alexei Starovoitov
2026-10-30 23:33 ` [PATCH bpf-next v3 03/13] docs, libbpf: Document complete BTF annotation strings Ihor Solodrai
2026-10-31  0:16   ` bot+bpf-ci
2026-10-31  0:27     ` Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 04/13] bpftool: Normalize legacy arena type tags before C dumps Ihor Solodrai
2026-10-31  0:16   ` bot+bpf-ci
2026-10-31  0:31     ` Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 05/13] selftests/bpf: Build BTF fixtures in btf_to_tmpfile() Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 06/13] selftests/bpf: Cover legacy arena type tags in bpftool C dumps Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 07/13] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 08/13] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 09/13] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 10/13] bpftool: Emit __bpf_static_offset " Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 11/13] selftests/bpf: Cover record attributes in bpftool BTF dumps Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 12/13] selftests/bpf: Cover context and socket accesses through vmlinux.h Ihor Solodrai
2026-10-30 23:33 ` [PATCH bpf-next v3 13/13] bpftool: Document __bpf_relocatable and __bpf_static_offset Ihor Solodrai

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