* [PATCH bpf-next v2 1/8] libbpf: Look up BTF strings without making BTF modifiable
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-23 22:39 ` sashiko-bot
2026-10-23 22:26 ` [PATCH bpf-next v2 2/8] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump Ihor Solodrai
` (7 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 | 36 ++++++++++++++++++++++--------------
1 file changed, 22 insertions(+), 14 deletions(-)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index d2ff6f5d6d5b..d8907281c0ef 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -153,9 +153,9 @@ 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 +1258,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 +2042,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 +2064,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 +2091,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 +2117,6 @@ static int btf_ensure_modifiable(struct btf *btf)
return 0;
err_out:
- strset__free(set);
free(types);
free(layout);
return err;
@@ -2130,8 +2139,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] 20+ messages in thread* Re: [PATCH bpf-next v2 1/8] libbpf: Look up BTF strings without making BTF modifiable
2026-10-23 22:26 ` [PATCH bpf-next v2 1/8] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
@ 2026-10-23 22:39 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:39 UTC (permalink / raw)
To: Ihor Solodrai; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] libbpf: non-standard multi-line comment style in struct btf
--
commit 0c3dbaeec612c1bd0bf2896a8ba4e0a1a8431c92
Author: Ihor Solodrai <ihor.solodrai@linux.dev>
libbpf: Look up BTF strings without making BTF modifiable
This patch updates btf__find_str() to build the string lookup index via a
new btf_ensure_strs_set() helper without making the BTF instance
modifiable, and updates btf__free() to unconditionally free strs_set.
> diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
> index d2ff6f5d6d5b6..d8907281c0efd 100644
> --- a/tools/lib/bpf/btf.c
> +++ b/tools/lib/bpf/btf.c
> @@ -153,9 +153,9 @@ 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().
> */
[Severity: Low]
In struct btf in tools/lib/bpf/btf.c, should the opening /* of this
multi-line comment be placed on its own line to follow the BPF comment
style?
/*
* 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().
*/
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023222652.3927736-1-ihor.solodrai@linux.dev?part=1
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 2/8] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 1/8] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
` (6 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 | 55 ++++++++++++++++---
1 file changed, 48 insertions(+), 7 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..9104c059a315 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -109,17 +109,11 @@ static struct btf *mk_loc_btf(void)
return btf;
}
-static int btf_to_tmpfile(const struct btf *btf, char *path)
+static int raw_to_tmpfile(const void *raw, __u32 sz, char *path)
{
ssize_t written;
- const void *raw;
- __u32 sz;
int fd;
- raw = btf__raw_data(btf, &sz);
- if (!ASSERT_OK_PTR(raw, "raw_data"))
- return -1;
-
snprintf(path, PATH_MAX, "/tmp/bpftool_btf_dump.XXXXXX");
fd = mkstemp(path);
if (!ASSERT_OK_FD(fd, "mkstemp_btf"))
@@ -135,6 +129,18 @@ static int btf_to_tmpfile(const struct btf *btf, char *path)
return 0;
}
+static int btf_to_tmpfile(const struct btf *btf, char *path)
+{
+ const void *raw;
+ __u32 sz;
+
+ raw = btf__raw_data(btf, &sz);
+ if (!ASSERT_OK_PTR(raw, "raw_data"))
+ return -1;
+
+ return raw_to_tmpfile(raw, sz, path);
+}
+
static char *dump_c(const char *btf_path, bool sorted)
{
char args[MAX_BPFTOOL_CMD_LEN];
@@ -262,6 +268,39 @@ static void test_loc_dump(const char *btf_path)
free(dump);
}
+static void test_hdr_extra(const struct btf *btf)
+{
+ const __u32 extra_sz = 4;
+ char path[PATH_MAX];
+ __u32 hdr_len, sz;
+ const void *raw;
+ char *ext;
+ int err;
+
+ raw = btf__raw_data(btf, &sz);
+ if (!ASSERT_OK_PTR(raw, "raw_data"))
+ return;
+
+ hdr_len = ((const struct btf_header *)raw)->hdr_len;
+
+ ext = calloc(1, sz + extra_sz);
+ if (!ASSERT_OK_PTR(ext, "alloc_ext"))
+ return;
+
+ 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;
+
+ err = raw_to_tmpfile(ext, sz + extra_sz, path);
+ free(ext);
+ if (err)
+ return;
+
+ test_dump(path, true);
+ unlink(path);
+}
+
void test_bpftool_btf_dump(void)
{
char path[PATH_MAX];
@@ -278,6 +317,8 @@ void test_bpftool_btf_dump(void)
test_dump(path, true);
if (test__start_subtest("c_unsorted"))
test_dump(path, false);
+ if (test__start_subtest("hdr_extra"))
+ test_hdr_extra(btf);
unlink(path);
btf__free(btf);
--
2.56.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 1/8] libbpf: Look up BTF strings without making BTF modifiable Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 2/8] selftests/bpf: Cover unknown BTF header-extension bytes in btf dump Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-27 18:03 ` Andrii Nakryiko
2026-10-23 22:26 ` [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
` (5 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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
preserve_access_index if compiler supports it.
Clang output is unchanged.
GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
is unaffected 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 | 62 +++++++++++++++----
.../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
.../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
3 files changed, 65 insertions(+), 29 deletions(-)
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index abd661db91e6..5a2c5341b736 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -1092,22 +1092,61 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
return datums;
}
-static int dump_btf_c(const struct btf *btf,
+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;
- d = btf_dump__new(btf, btf_dump_printf, NULL, NULL);
- if (!d)
+ split = btf__new_empty_split(btf);
+ if (!split)
return -errno;
+ /* Retain the native default if the pointer size cannot be inferred. */
+ 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 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");
@@ -1130,10 +1169,10 @@ static int dump_btf_c(const 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;
@@ -1146,20 +1185,17 @@ static int dump_btf_c(const 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..97664633931f 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 preserve_access_index
+#else
+#define __bpf_relocatable
+#endif
#endif
#ifndef __ksym
@@ -30,19 +34,15 @@ struct holey {
long: 32;
long: 32;
int tail;
-};
+} __attribute__((__bpf_relocatable));
struct s {
int f;
-};
+} __attribute__((__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..edc8c5b708d7 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 preserve_access_index
+#else
+#define __bpf_relocatable
+#endif
#endif
#ifndef __ksym
@@ -26,7 +30,7 @@ struct holey {
long: 32;
long: 32;
int tail;
-};
+} __attribute__((__bpf_relocatable));
enum {
E0 = 1,
@@ -34,15 +38,11 @@ enum {
struct s {
int f;
-};
+} __attribute__((__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] 20+ messages in thread* Re: [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-23 22:26 ` [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
@ 2026-10-27 18:03 ` Andrii Nakryiko
2026-10-27 18:15 ` Ihor Solodrai
0 siblings, 1 reply; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 18:03 UTC (permalink / raw)
To: Ihor Solodrai
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>
> 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
> preserve_access_index if compiler supports it.
>
> Clang output is unchanged.
>
> GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
> the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
> is unaffected 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 | 62 +++++++++++++++----
> .../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
> .../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
> 3 files changed, 65 insertions(+), 29 deletions(-)
>
[...]
> 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 preserve_access_index\n");
why not define __bpf_relocatable as
__attribute__((preserve_access_index))? then type definitions will be
a bit cleaner and we can basically drop BPF_NO_PRESERVE_ACCESS_INDEX
eventually, and instead you'll be able to just `#define
__bpf_relocatable` to disable relocations (or if some GCC version
requires some quirk, you'll define it wholesale)
> + 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");
[...]
> -#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 preserve_access_index
> +#else
> +#define __bpf_relocatable
> +#endif
> #endif
>
> #ifndef __ksym
> @@ -30,19 +34,15 @@ struct holey {
> long: 32;
> long: 32;
> int tail;
> -};
> +} __attribute__((__bpf_relocatable));
e.g., I was expecting to see just __bpf_relocatable here, without
__attribute__(()) wrapper
are there any downsides to that? that seems how attributes are
typically hidden in kernel, for instance
pw-bot: cr
[...]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-27 18:03 ` Andrii Nakryiko
@ 2026-10-27 18:15 ` Ihor Solodrai
2026-10-27 18:21 ` Andrii Nakryiko
0 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-27 18:15 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On 10/27/26 11:03 AM, Andrii Nakryiko wrote:
> On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>
>> 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
>> preserve_access_index if compiler supports it.
>>
>> Clang output is unchanged.
>>
>> GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
>> the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
>> is unaffected 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 | 62 +++++++++++++++----
>> .../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
>> .../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
>> 3 files changed, 65 insertions(+), 29 deletions(-)
>>
>
> [...]
>
>> 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 preserve_access_index\n");
>
> why not define __bpf_relocatable as
> __attribute__((preserve_access_index))? then type definitions will be
> a bit cleaner and we can basically drop BPF_NO_PRESERVE_ACCESS_INDEX
> eventually, and instead you'll be able to just `#define
> __bpf_relocatable` to disable relocations (or if some GCC version
> requires some quirk, you'll define it wholesale)
The problem is in how libbpf dumps the attributes.
__attribute__((__vmlinuxh_local_foo)) allows to have a single BTF tag
emission logic in bpftool: always emit the attributes. And they remain
opt-out in the generated vmlinux.h because __attribute__(()) is legal.
In order to enable
#define __bpf_relocatable __attribute__((preserve_access_index))
We need to move the opt-out to bpftool level, that is: optionally not
emit the BTF tags. And that then brings up a question of adding a
runtime flag, passing it through makefiles etc.
This can also potentially break existing progs that depend on
BPF_NO_PRESERVE_ACCESS_INDEX.
So going with __attribute__(()) looked like the best
backward-compatible option to me.
I'm open to other ideas though.
>
>> + 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");
>
> [...]
>
>> -#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 preserve_access_index
>> +#else
>> +#define __bpf_relocatable
>> +#endif
>> #endif
>>
>> #ifndef __ksym
>> @@ -30,19 +34,15 @@ struct holey {
>> long: 32;
>> long: 32;
>> int tail;
>> -};
>> +} __attribute__((__bpf_relocatable));
>
> e.g., I was expecting to see just __bpf_relocatable here, without
> __attribute__(()) wrapper
>
> are there any downsides to that? that seems how attributes are
> typically hidden in kernel, for instance
>
> pw-bot: cr
>
>
> [...]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-27 18:15 ` Ihor Solodrai
@ 2026-10-27 18:21 ` Andrii Nakryiko
2026-10-27 18:26 ` Ihor Solodrai
0 siblings, 1 reply; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 18:21 UTC (permalink / raw)
To: Ihor Solodrai
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On Tue, Oct 27, 2026 at 11:15 AM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>
> On 10/27/26 11:03 AM, Andrii Nakryiko wrote:
> > On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
> >>
> >> 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
> >> preserve_access_index if compiler supports it.
> >>
> >> Clang output is unchanged.
> >>
> >> GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
> >> the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
> >> is unaffected 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 | 62 +++++++++++++++----
> >> .../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
> >> .../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
> >> 3 files changed, 65 insertions(+), 29 deletions(-)
> >>
> >
> > [...]
> >
> >> 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 preserve_access_index\n");
> >
> > why not define __bpf_relocatable as
> > __attribute__((preserve_access_index))? then type definitions will be
> > a bit cleaner and we can basically drop BPF_NO_PRESERVE_ACCESS_INDEX
> > eventually, and instead you'll be able to just `#define
> > __bpf_relocatable` to disable relocations (or if some GCC version
> > requires some quirk, you'll define it wholesale)
>
> The problem is in how libbpf dumps the attributes.
>
> __attribute__((__vmlinuxh_local_foo)) allows to have a single BTF tag
> emission logic in bpftool: always emit the attributes. And they remain
> opt-out in the generated vmlinux.h because __attribute__(()) is legal.
>
> In order to enable
>
> #define __bpf_relocatable __attribute__((preserve_access_index))
>
> We need to move the opt-out to bpftool level, that is: optionally not
> emit the BTF tags. And that then brings up a question of adding a
> runtime flag, passing it through makefiles etc.
>
> This can also potentially break existing progs that depend on
> BPF_NO_PRESERVE_ACCESS_INDEX.
>
> So going with __attribute__(()) looked like the best
> backward-compatible option to me.
>
> I'm open to other ideas though.
So we always emit __bpf_relocatable next to a type, but then at the
top of the header we can define __bpf_relocatable as empty if we want
to disable relocations (either because compiler doesn't support this
or user defined BPF_NO_PRESERVE_ACCESS_INDEX, or user manually defined
__bpf_relocatable).
I must be missing something, why doesn't this work?
>
> >
> >> + 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");
> >
> > [...]
> >
> >> -#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 preserve_access_index
> >> +#else
> >> +#define __bpf_relocatable
> >> +#endif
> >> #endif
> >>
> >> #ifndef __ksym
> >> @@ -30,19 +34,15 @@ struct holey {
> >> long: 32;
> >> long: 32;
> >> int tail;
> >> -};
> >> +} __attribute__((__bpf_relocatable));
> >
> > e.g., I was expecting to see just __bpf_relocatable here, without
> > __attribute__(()) wrapper
> >
> > are there any downsides to that? that seems how attributes are
> > typically hidden in kernel, for instance
> >
> > pw-bot: cr
> >
> >
> > [...]
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-27 18:21 ` Andrii Nakryiko
@ 2026-10-27 18:26 ` Ihor Solodrai
2026-10-27 21:25 ` Ihor Solodrai
0 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-27 18:26 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On 10/27/26 11:21 AM, Andrii Nakryiko wrote:
> On Tue, Oct 27, 2026 at 11:15 AM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>
>> On 10/27/26 11:03 AM, Andrii Nakryiko wrote:
>>> On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>>>
>>>> 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
>>>> preserve_access_index if compiler supports it.
>>>>
>>>> Clang output is unchanged.
>>>>
>>>> GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
>>>> the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
>>>> is unaffected 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 | 62 +++++++++++++++----
>>>> .../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
>>>> .../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
>>>> 3 files changed, 65 insertions(+), 29 deletions(-)
>>>>
>>>
>>> [...]
>>>
>>>> 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 preserve_access_index\n");
>>>
>>> why not define __bpf_relocatable as
>>> __attribute__((preserve_access_index))? then type definitions will be
>>> a bit cleaner and we can basically drop BPF_NO_PRESERVE_ACCESS_INDEX
>>> eventually, and instead you'll be able to just `#define
>>> __bpf_relocatable` to disable relocations (or if some GCC version
>>> requires some quirk, you'll define it wholesale)
>>
>> The problem is in how libbpf dumps the attributes.
>>
>> __attribute__((__vmlinuxh_local_foo)) allows to have a single BTF tag
>> emission logic in bpftool: always emit the attributes. And they remain
>> opt-out in the generated vmlinux.h because __attribute__(()) is legal.
>>
>> In order to enable
>>
>> #define __bpf_relocatable __attribute__((preserve_access_index))
>>
>> We need to move the opt-out to bpftool level, that is: optionally not
>> emit the BTF tags. And that then brings up a question of adding a
>> runtime flag, passing it through makefiles etc.
>>
>> This can also potentially break existing progs that depend on
>> BPF_NO_PRESERVE_ACCESS_INDEX.
>>
>> So going with __attribute__(()) looked like the best
>> backward-compatible option to me.
>>
>> I'm open to other ideas though.
>
> So we always emit __bpf_relocatable next to a type, but then at the
Actually, I just remembered that it is worse than that.
Per-record, libbpf dump can only emit either:
__attribute__((%s))
or, if kflag=0
__attribute__((btf_decl_tag(\"%s\")))
See btf_dump_emit_decl_tag().
There is no way to emit arbitrary __foo identifier.
So the only way to make it work is to employ string replacement hackery.
We can do that, but I don't think it's worth it.
> top of the header we can define __bpf_relocatable as empty if we want
> to disable relocations (either because compiler doesn't support this
> or user defined BPF_NO_PRESERVE_ACCESS_INDEX, or user manually defined
> __bpf_relocatable).
With current implementation defining __bpf_relocatable as empty still
works as opt-out, because __attribute__(()) doesn't affect the
compilation.
>
> I must be missing something, why doesn't this work?
>
>>
>>>
>>>> + 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");
>>>
>>> [...]
>>>
>>>> -#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 preserve_access_index
>>>> +#else
>>>> +#define __bpf_relocatable
>>>> +#endif
>>>> #endif
>>>>
>>>> #ifndef __ksym
>>>> @@ -30,19 +34,15 @@ struct holey {
>>>> long: 32;
>>>> long: 32;
>>>> int tail;
>>>> -};
>>>> +} __attribute__((__bpf_relocatable));
>>>
>>> e.g., I was expecting to see just __bpf_relocatable here, without
>>> __attribute__(()) wrapper
>>>
>>> are there any downsides to that? that seems how attributes are
>>> typically hidden in kernel, for instance
>>>
>>> pw-bot: cr
>>>
>>>
>>> [...]
>>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump
2026-10-27 18:26 ` Ihor Solodrai
@ 2026-10-27 21:25 ` Ihor Solodrai
0 siblings, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-27 21:25 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On 10/27/26 11:26 AM, Ihor Solodrai wrote:
> On 10/27/26 11:21 AM, Andrii Nakryiko wrote:
>> On Tue, Oct 27, 2026 at 11:15 AM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>>
>>> On 10/27/26 11:03 AM, Andrii Nakryiko wrote:
>>>> On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>>>>
>>>>> 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
>>>>> preserve_access_index if compiler supports it.
>>>>>
>>>>> Clang output is unchanged.
>>>>>
>>>>> GCC-BPF programs now can get CO-RE relocations from vmlinux.h, however
>>>>> the selftests GCC-BPF build defines BPF_NO_PRESERVE_ACCESS_INDEX and
>>>>> is unaffected 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 | 62 +++++++++++++++----
>>>>> .../bpf/bpftool_btf_dump_sorted.expected | 16 ++---
>>>>> .../bpf/bpftool_btf_dump_unsorted.expected | 16 ++---
>>>>> 3 files changed, 65 insertions(+), 29 deletions(-)
>>>>>
>>>>
>>>> [...]
>>>>
>>>>> 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 preserve_access_index\n");
>>>>
>>>> why not define __bpf_relocatable as
>>>> __attribute__((preserve_access_index))? then type definitions will be
>>>> a bit cleaner and we can basically drop BPF_NO_PRESERVE_ACCESS_INDEX
>>>> eventually, and instead you'll be able to just `#define
>>>> __bpf_relocatable` to disable relocations (or if some GCC version
>>>> requires some quirk, you'll define it wholesale)
>>>
>>> The problem is in how libbpf dumps the attributes.
>>>
>>> __attribute__((__vmlinuxh_local_foo)) allows to have a single BTF tag
>>> emission logic in bpftool: always emit the attributes. And they remain
>>> opt-out in the generated vmlinux.h because __attribute__(()) is legal.
>>>
>>> In order to enable
>>>
>>> #define __bpf_relocatable __attribute__((preserve_access_index))
>>>
>>> We need to move the opt-out to bpftool level, that is: optionally not
>>> emit the BTF tags. And that then brings up a question of adding a
>>> runtime flag, passing it through makefiles etc.
>>>
>>> This can also potentially break existing progs that depend on
>>> BPF_NO_PRESERVE_ACCESS_INDEX.
>>>
>>> So going with __attribute__(()) looked like the best
>>> backward-compatible option to me.
>>>
>>> I'm open to other ideas though.
>>
>> So we always emit __bpf_relocatable next to a type, but then at the
>
> Actually, I just remembered that it is worse than that.
>
> Per-record, libbpf dump can only emit either:
>
> __attribute__((%s))
>
> or, if kflag=0
>
> __attribute__((btf_decl_tag(\"%s\")))
>
> See btf_dump_emit_decl_tag().
>
> There is no way to emit arbitrary __foo identifier.
Had an off-list discussion with Andrii.
In the next revision I'll make a libbpf change to remove the
limitation discussed in the thread and allow BTF tags to represent
arbitrary identifier, not just attributes.
This will give more freedom to BTF consumers when interpreting the
tags in general, and bpftool with this series will become the first
one using it.
>
> [...]
>>>>
>>>> [...]
>>>
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (2 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 3/8] bpftool: Apply __bpf_relocatable per record on BTF dump Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-27 18:06 ` Andrii Nakryiko
2026-10-23 22:26 ` [PATCH bpf-next v2 5/8] bpftool: Emit __bpf_static_offset " Ihor Solodrai
` (4 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 | 84 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 82 insertions(+), 2 deletions(-)
diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
index 5a2c5341b736..5c88f665e6b9 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -6,6 +6,7 @@
#endif
#include <errno.h>
#include <fcntl.h>
+#include <linux/bitmap.h>
#include <linux/err.h>
#include <stdbool.h>
#include <stdio.h>
@@ -1092,22 +1093,101 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
return datums;
}
+/*
+ * 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_no_pai(const struct btf *btf, __u32 id, unsigned long *no_pai)
+{
+ const struct btf_type *t = btf__type_by_id(btf, id);
+ const struct btf_member *m = btf_members(t);
+ __u32 i;
+
+ __set_bit(id, no_pai);
+ 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 && !test_bit(mid, no_pai))
+ btf_c_mark_no_pai(btf, mid, no_pai);
+ }
+}
+
static int btf_c_annotate_records(struct btf *btf)
{
__u32 i, cnt = btf__type_cnt(btf);
+ unsigned long *no_pai;
int err;
+ no_pai = bitmap_zalloc(cnt);
+ if (!no_pai)
+ return -ENOMEM;
+
+ for (i = 1; i < cnt; i++) {
+ if (btf_c_needs_static_offset(btf, btf__type_by_id(btf, i)))
+ btf_c_mark_no_pai(btf, i, no_pai);
+ }
+
for (i = 1; i < cnt; i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
if (!btf_is_composite(t))
continue;
+ if (test_bit(i, no_pai))
+ continue;
err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
if (err < 0)
- return err;
+ goto out;
}
- return 0;
+ err = 0;
+out:
+ bitmap_free(no_pai);
+ return err;
}
static int dump_btf_c(struct btf *btf,
--
2.56.0
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types
2026-10-23 22:26 ` [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
@ 2026-10-27 18:06 ` Andrii Nakryiko
2026-10-27 18:10 ` Andrii Nakryiko
2026-10-27 18:17 ` Ihor Solodrai
0 siblings, 2 replies; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 18:06 UTC (permalink / raw)
To: Ihor Solodrai
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>
> 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 | 84 ++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 82 insertions(+), 2 deletions(-)
>
> diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
> index 5a2c5341b736..5c88f665e6b9 100644
> --- a/tools/bpf/bpftool/btf.c
> +++ b/tools/bpf/bpftool/btf.c
> @@ -6,6 +6,7 @@
> #endif
> #include <errno.h>
> #include <fcntl.h>
> +#include <linux/bitmap.h>
> #include <linux/err.h>
> #include <stdbool.h>
> #include <stdio.h>
> @@ -1092,22 +1093,101 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
> return datums;
> }
>
> +/*
> + * 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_no_pai(const struct btf *btf, __u32 id, unsigned long *no_pai)
mark_static_offset to keep terminology consistent?
> +{
> + const struct btf_type *t = btf__type_by_id(btf, id);
> + const struct btf_member *m = btf_members(t);
> + __u32 i;
> +
> + __set_bit(id, no_pai);
> + 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 && !test_bit(mid, no_pai))
> + btf_c_mark_no_pai(btf, mid, no_pai);
> + }
> +}
> +
> static int btf_c_annotate_records(struct btf *btf)
> {
> __u32 i, cnt = btf__type_cnt(btf);
> + unsigned long *no_pai;
> int err;
>
> + no_pai = bitmap_zalloc(cnt);
this will cause problems for github mirror that won't have access to
linux-internal include/linux/bitmap.h, I'd just open-code a simple
bitmap or use bool array for simplicity, memory usage increase doesn't
really matter here
> + if (!no_pai)
> + return -ENOMEM;
> +
> + for (i = 1; i < cnt; i++) {
> + if (btf_c_needs_static_offset(btf, btf__type_by_id(btf, i)))
> + btf_c_mark_no_pai(btf, i, no_pai);
> + }
> +
> for (i = 1; i < cnt; i++) {
> const struct btf_type *t = btf__type_by_id(btf, i);
>
> if (!btf_is_composite(t))
> continue;
> + if (test_bit(i, no_pai))
> + continue;
> err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
> if (err < 0)
> - return err;
> + goto out;
> }
>
> - return 0;
> + err = 0;
> +out:
> + bitmap_free(no_pai);
> + return err;
> }
>
> static int dump_btf_c(struct btf *btf,
> --
> 2.56.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types
2026-10-27 18:06 ` Andrii Nakryiko
@ 2026-10-27 18:10 ` Andrii Nakryiko
2026-10-27 18:17 ` Ihor Solodrai
1 sibling, 0 replies; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 18:10 UTC (permalink / raw)
To: Ihor Solodrai
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On Tue, Oct 27, 2026 at 11:06 AM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
> >
> > 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 | 84 ++++++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 82 insertions(+), 2 deletions(-)
> >
> > diff --git a/tools/bpf/bpftool/btf.c b/tools/bpf/bpftool/btf.c
> > index 5a2c5341b736..5c88f665e6b9 100644
> > --- a/tools/bpf/bpftool/btf.c
> > +++ b/tools/bpf/bpftool/btf.c
> > @@ -6,6 +6,7 @@
> > #endif
> > #include <errno.h>
> > #include <fcntl.h>
> > +#include <linux/bitmap.h>
> > #include <linux/err.h>
> > #include <stdbool.h>
> > #include <stdio.h>
> > @@ -1092,22 +1093,101 @@ static struct sort_datum *sort_btf_c(const struct btf *btf)
> > return datums;
> > }
> >
> > +/*
> > + * 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_no_pai(const struct btf *btf, __u32 id, unsigned long *no_pai)
>
> mark_static_offset to keep terminology consistent?
ah, this is a bit different from static offset, it's nested anon types
within static offset... hmm, then maybe "non_relocatable"? "pai" is
quite unconventional abbreviation, I keep reminding myself that it's
preserve access index
>
> > +{
> > + const struct btf_type *t = btf__type_by_id(btf, id);
> > + const struct btf_member *m = btf_members(t);
> > + __u32 i;
> > +
> > + __set_bit(id, no_pai);
> > + 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 && !test_bit(mid, no_pai))
> > + btf_c_mark_no_pai(btf, mid, no_pai);
> > + }
> > +}
> > +
> > static int btf_c_annotate_records(struct btf *btf)
> > {
> > __u32 i, cnt = btf__type_cnt(btf);
> > + unsigned long *no_pai;
> > int err;
> >
> > + no_pai = bitmap_zalloc(cnt);
>
> this will cause problems for github mirror that won't have access to
> linux-internal include/linux/bitmap.h, I'd just open-code a simple
> bitmap or use bool array for simplicity, memory usage increase doesn't
> really matter here
>
> > + if (!no_pai)
> > + return -ENOMEM;
> > +
> > + for (i = 1; i < cnt; i++) {
> > + if (btf_c_needs_static_offset(btf, btf__type_by_id(btf, i)))
> > + btf_c_mark_no_pai(btf, i, no_pai);
> > + }
> > +
> > for (i = 1; i < cnt; i++) {
> > const struct btf_type *t = btf__type_by_id(btf, i);
> >
> > if (!btf_is_composite(t))
> > continue;
> > + if (test_bit(i, no_pai))
> > + continue;
> > err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
> > if (err < 0)
> > - return err;
> > + goto out;
> > }
> >
> > - return 0;
> > + err = 0;
> > +out:
> > + bitmap_free(no_pai);
> > + return err;
> > }
> >
> > static int dump_btf_c(struct btf *btf,
> > --
> > 2.56.0
> >
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types
2026-10-27 18:06 ` Andrii Nakryiko
2026-10-27 18:10 ` Andrii Nakryiko
@ 2026-10-27 18:17 ` Ihor Solodrai
1 sibling, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-27 18:17 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On 10/27/26 11:06 AM, Andrii Nakryiko wrote:
> On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>
>>
>> [...]
>> +
>> +/*
>> + * 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_no_pai(const struct btf *btf, __u32 id, unsigned long *no_pai)
>
> mark_static_offset to keep terminology consistent?
>
>> +{
>> + const struct btf_type *t = btf__type_by_id(btf, id);
>> + const struct btf_member *m = btf_members(t);
>> + __u32 i;
>> +
>> + __set_bit(id, no_pai);
>> + 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 && !test_bit(mid, no_pai))
>> + btf_c_mark_no_pai(btf, mid, no_pai);
>> + }
>> +}
>> +
>> static int btf_c_annotate_records(struct btf *btf)
>> {
>> __u32 i, cnt = btf__type_cnt(btf);
>> + unsigned long *no_pai;
>> int err;
>>
>> + no_pai = bitmap_zalloc(cnt);
>
> this will cause problems for github mirror that won't have access to
> linux-internal include/linux/bitmap.h,
Good point, haven't thought of that.
> ...I'd just open-code a simple
> bitmap or use bool array for simplicity, memory usage increase doesn't
> really matter here
>
>> + if (!no_pai)
>> + return -ENOMEM;
>> +
>> + for (i = 1; i < cnt; i++) {
>> + if (btf_c_needs_static_offset(btf, btf__type_by_id(btf, i)))
>> + btf_c_mark_no_pai(btf, i, no_pai);
>> + }
>> +
>> for (i = 1; i < cnt; i++) {
>> const struct btf_type *t = btf__type_by_id(btf, i);
>>
>> if (!btf_is_composite(t))
>> continue;
>> + if (test_bit(i, no_pai))
>> + continue;
>> err = btf__add_decl_attr(btf, "__bpf_relocatable", i, -1);
>> if (err < 0)
>> - return err;
>> + goto out;
>> }
>>
>> - return 0;
>> + err = 0;
>> +out:
>> + bitmap_free(no_pai);
>> + return err;
>> }
>>
>> static int dump_btf_c(struct btf *btf,
>> --
>> 2.56.0
>>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 5/8] bpftool: Emit __bpf_static_offset on verifier-rewritten context types
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (3 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 4/8] bpftool: Skip __bpf_relocatable on verifier-rewritten context types Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-27 18:10 ` Andrii Nakryiko
2026-10-23 22:26 ` [PATCH bpf-next v2 6/8] selftests/bpf: Cover record attributes in bpftool BTF dumps Ihor Solodrai
` (3 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 5c88f665e6b9..c7f446ab6afe 100644
--- a/tools/bpf/bpftool/btf.c
+++ b/tools/bpf/bpftool/btf.c
@@ -1174,12 +1174,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 (test_bit(i, no_pai))
+ if (!test_bit(i, no_pai))
+ 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;
}
@@ -1228,6 +1233,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 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 97664633931f..9166a99a6077 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 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 edc8c5b708d7..ef01a8a4e6ad 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 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] 20+ messages in thread* Re: [PATCH bpf-next v2 5/8] bpftool: Emit __bpf_static_offset on verifier-rewritten context types
2026-10-23 22:26 ` [PATCH bpf-next v2 5/8] bpftool: Emit __bpf_static_offset " Ihor Solodrai
@ 2026-10-27 18:10 ` Andrii Nakryiko
0 siblings, 0 replies; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 18:10 UTC (permalink / raw)
To: Ihor Solodrai
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Quentin Monnet, bpf,
Alan Maguire, Cupertino Miranda, David Faust, Ian Rogers,
Jose E . Marchesi, Vineet Gupta, Yonghong Song
On Fri, Oct 23, 2026 at 3:27 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>
> 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 5c88f665e6b9..c7f446ab6afe 100644
> --- a/tools/bpf/bpftool/btf.c
> +++ b/tools/bpf/bpftool/btf.c
> @@ -1174,12 +1174,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 (test_bit(i, no_pai))
> + if (!test_bit(i, no_pai))
> + 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;
> }
[...]
> +#ifndef __bpf_static_offset
> +#if __has_attribute(preserve_static_offset)
> +#define __bpf_static_offset 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 edc8c5b708d7..ef01a8a4e6ad 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 preserve_static_offset
same as with __bpf_relocatable, I think this should be full attribute definition
> +#else
> +#define __bpf_static_offset
> +#endif
> +#endif
> +
> #ifndef __ksym
> #define __ksym __attribute__((section(".ksyms")))
> #endif
> --
> 2.56.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 6/8] selftests/bpf: Cover record attributes in bpftool BTF dumps
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (4 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 5/8] bpftool: Emit __bpf_static_offset " Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 7/8] selftests/bpf: Cover context and socket accesses through vmlinux.h Ihor Solodrai
` (2 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 9166a99a6077..38cec98ddedd 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;
+ };
+} __attribute__((__bpf_static_offset));
+
+struct bpf_nf_ctx {
+ int f;
+} __attribute__((__bpf_relocatable));
+
+struct bpf_sk_lookup {
+ union {
+ union {
+ int cookie;
+ };
+ int family;
+ };
+} __attribute__((__bpf_static_offset));
+
+struct bpf_tcp_sock {
+ int snd_cwnd;
+} __attribute__((__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 ef01a8a4e6ad..81f58181da14 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;
} __attribute__((__bpf_relocatable));
+struct __sk_buff {
+ int len;
+ union {
+ int sk;
+ };
+} __attribute__((__bpf_static_offset));
+
+struct bpf_sk_lookup {
+ union {
+ union {
+ int cookie;
+ };
+ int family;
+ };
+} __attribute__((__bpf_static_offset));
+
+struct bpf_tcp_sock {
+ int snd_cwnd;
+} __attribute__((__bpf_static_offset));
+
+struct bpf_nf_ctx {
+ int f;
+} __attribute__((__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 9104c059a315..0277f054410c 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] 20+ messages in thread* [PATCH bpf-next v2 7/8] selftests/bpf: Cover context and socket accesses through vmlinux.h
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (5 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 6/8] selftests/bpf: Cover record attributes in bpftool BTF dumps Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-23 22:26 ` [PATCH bpf-next v2 8/8] bpftool: Document __bpf_relocatable and __bpf_static_offset Ihor Solodrai
2026-10-23 22:28 ` [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Vineet Gupta
8 siblings, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 04740ba37e91..57a8a1abedd8 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -42,6 +42,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"
@@ -234,6 +235,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] 20+ messages in thread* [PATCH bpf-next v2 8/8] bpftool: Document __bpf_relocatable and __bpf_static_offset
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (6 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 7/8] selftests/bpf: Cover context and socket accesses through vmlinux.h Ihor Solodrai
@ 2026-10-23 22:26 ` Ihor Solodrai
2026-10-23 22:28 ` [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Vineet Gupta
8 siblings, 0 replies; 20+ messages in thread
From: Ihor Solodrai @ 2026-10-23 22:26 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 records 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.
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 | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/tools/bpf/bpftool/Documentation/bpftool-btf.rst b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
index c6dc445cd4a1..37fef36a14fb 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-btf.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-btf.rst
@@ -70,6 +70,30 @@ 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. Most
+ records carry ``__attribute__((__bpf_relocatable))``. The generated
+ header defines ``__bpf_relocatable`` as ``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 ``__attribute__((__bpf_static_offset))`` instead, and their nested
+ anonymous records carry no attribute. ``__bpf_static_offset`` expands to
+ ``preserve_static_offset`` where supported (Clang 18 and later), or to
+ 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 bare attribute name, such as
+ ``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] 20+ messages in thread* Re: [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump
2026-10-23 22:26 [PATCH bpf-next v2 0/8] bpftool: Per-record CO-RE attributes in the C dump Ihor Solodrai
` (7 preceding siblings ...)
2026-10-23 22:26 ` [PATCH bpf-next v2 8/8] bpftool: Document __bpf_relocatable and __bpf_static_offset Ihor Solodrai
@ 2026-10-23 22:28 ` Vineet Gupta
8 siblings, 0 replies; 20+ messages in thread
From: Vineet Gupta @ 2026-10-23 22:28 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Quentin Monnet
Cc: bpf, Alan Maguire, Cupertino Miranda, Ian Rogers,
Jose E . Marchesi, Yonghong Song, David Faust
+CC David's new email
On 10/23/26 3:26 PM, Ihor Solodrai wrote:
> 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.
>
> [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/
>
> ---
>
> 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 (8):
> libbpf: Look up BTF strings without making BTF modifiable
> 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
>
> .../bpf/bpftool/Documentation/bpftool-btf.rst | 24 +++
> tools/bpf/bpftool/btf.c | 154 ++++++++++++++++--
> tools/lib/bpf/btf.c | 36 ++--
> .../bpf/bpftool_btf_dump_sorted.expected | 48 +++++-
> .../bpf/bpftool_btf_dump_unsorted.expected | 48 +++++-
> .../bpf/prog_tests/bpftool_btf_dump.c | 102 +++++++++++-
> .../selftests/bpf/prog_tests/verifier.c | 2 +
> .../bpf/progs/verifier_ctx_rewritten.c | 59 +++++++
> 8 files changed, 423 insertions(+), 50 deletions(-)
> create mode 100644 tools/testing/selftests/bpf/progs/verifier_ctx_rewritten.c
>
>
> base-commit: b116ac6087541d3d98031e786adbe9c733cd6df3
^ permalink raw reply [flat|nested] 20+ messages in thread