* [PATCH bpf-next] selftests/bpf: Fix linked_externs with bpf-gcc
@ 2026-09-28 21:51 Vineet Gupta
2026-09-28 22:42 ` bot+bpf-ci
0 siblings, 1 reply; 3+ messages in thread
From: Vineet Gupta @ 2026-09-28 21:51 UTC (permalink / raw)
To: bpf, andrii, ast, daniel
Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, linux-kernel, Vineet Gupta
This was reported by BPF CI [1]. test_progs-bpf_gcc fails to build:
prog_tests/linked_externs.c:17:23: error: 'struct linked_arena' has no
member named 'arena'
[1] https://github.com/kernel-patches/bpf/actions/runs/36243003457
Two separate problems, both from bpf-gcc not supporting address_space(1).
First, the arena globals are defined with __arena, but that macro is for
pointers -- it marks the pointee address space. The one that carries
placement is __arena_global:
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && !defined(BPF_ARENA_FORCE_ASM)
#define __arena __attribute__((address_space(1))) __attribute__((btf_type_tag("arena")))
#define __arena_global __attribute__((address_space(1)))
#else
#define __arena __attribute__((btf_type_tag("arena")))
#define __arena_global SEC(".addr_space.1")
#endif
Under bpf-gcc __arena is only a BTF type tag, so the variables land in
.data, the arena map gets no initial value, and bpftool does not emit the
typed arena member, hence the build error.
Second, fixing the placement is not enough to run the test. Dereferencing
an arena global needs an addr_space_cast, which only clang emits:
clang: bpf-gcc:
r1 = 0x0 ll r1 = 0x0 ll
r1 = addr_space_cast(r1, 0x0, 0x1) r2 = *(u64 *)(r1 + 0x0)
r1 = *(u64 *)(r1 + 0x0)
so the verifier sees a scalar and rejects the program:
4: (79) r2 = *(u64 *)(r1 +0)
R1 invalid mem access 'scalar'
Guard the program bodies and skip the skeleton subtest, as
arena_atomics.c already does.
The fallback bodies reference the externs without dereferencing them,
otherwise gcc drops the unreferenced extern and relink_arena passes
without having an extern to resolve.
With this, bpf-gcc reports:
#217/1 linked_externs/skel_arena:SKIP
#217/2 linked_externs/relink_arena:OK
#217/3 linked_externs/relink_maps:OK
and clang still passes all three.
Fixes: 34354db1b148 ("selftests/bpf: Add linked_externs test for externs in allocated sections")
Signed-off-by: Vineet Gupta <vineet.gupta@linux.dev>
---
.../selftests/bpf/prog_tests/linked_externs.c | 15 +++++++++++--
.../selftests/bpf/progs/linked_arena1.c | 22 ++++++++++++++++++-
.../selftests/bpf/progs/linked_arena2.c | 6 ++++-
3 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/linked_externs.c b/tools/testing/selftests/bpf/prog_tests/linked_externs.c
index 29358c82dc35..ff568f5cfc42 100644
--- a/tools/testing/selftests/bpf/prog_tests/linked_externs.c
+++ b/tools/testing/selftests/bpf/prog_tests/linked_externs.c
@@ -10,10 +10,20 @@ static void test_skel(void)
struct linked_arena *skel;
int err;
- skel = linked_arena__open_and_load();
- if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ skel = linked_arena__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
+ if (skel->data->skip_tests) {
+ printf("%s:SKIP: no addr_space_cast support in the BPF compiler\n", __func__);
+ test__skip();
+ goto cleanup;
+ }
+
+ err = linked_arena__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
ASSERT_EQ(skel->arena->a_val, 1, "a_val_init");
ASSERT_EQ(skel->arena->b_val, 2, "b_val_init");
@@ -28,6 +38,7 @@ static void test_skel(void)
ASSERT_EQ(skel->arena->a_val, 11, "a_val");
ASSERT_EQ(skel->arena->b_val, 22, "b_val");
+cleanup:
linked_arena__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/linked_arena1.c b/tools/testing/selftests/bpf/progs/linked_arena1.c
index 3e8788dec1d9..dd9c5fa424dd 100644
--- a/tools/testing/selftests/bpf/progs/linked_arena1.c
+++ b/tools/testing/selftests/bpf/progs/linked_arena1.c
@@ -10,13 +10,33 @@ struct {
__uint(max_entries, 1); /* number of pages */
} arena SEC(".maps");
-long __arena a_val = 1;
+/*
+ * Dereferencing an arena global needs the compiler to emit an
+ * addr_space_cast, which only clang does. Keep the variables so the arena
+ * is still populated and the skeleton still has its arena member, but skip
+ * the test elsewhere.
+ */
+#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
+bool skip_tests __attribute((__section__(".data"))) = false;
+#else
+bool skip_tests = true;
+#endif
+
+long __arena_global a_val = 1;
extern long __arena b_val; /* defined in linked_arena2.c */
SEC("syscall")
int sum1(void *ctx)
{
+#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
return a_val + b_val;
+#else
+ /*
+ * Reference the extern without dereferencing it, so that the relink
+ * test still has an extern to resolve.
+ */
+ return (long)&b_val;
+#endif
}
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/linked_arena2.c b/tools/testing/selftests/bpf/progs/linked_arena2.c
index d0bcdf18a4b6..1bc8b97ed8cc 100644
--- a/tools/testing/selftests/bpf/progs/linked_arena2.c
+++ b/tools/testing/selftests/bpf/progs/linked_arena2.c
@@ -4,15 +4,19 @@
#include <bpf/bpf_helpers.h>
#include "bpf_arena_common.h"
-long __arena b_val = 2;
+long __arena_global b_val = 2;
extern long __arena a_val; /* defined in linked_arena1.c */
SEC("syscall")
int bump2(void *ctx)
{
+#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
a_val += 10;
b_val += 20;
return a_val + b_val;
+#else /* see linked_arena1.c */
+ return (long)&a_val;
+#endif
}
char _license[] SEC("license") = "GPL";
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH bpf-next] selftests/bpf: Fix linked_externs with bpf-gcc
2026-09-28 21:51 [PATCH bpf-next] selftests/bpf: Fix linked_externs with bpf-gcc Vineet Gupta
@ 2026-09-28 22:42 ` bot+bpf-ci
2026-09-28 23:11 ` Vineet Gupta
0 siblings, 1 reply; 3+ messages in thread
From: bot+bpf-ci @ 2026-09-28 22:42 UTC (permalink / raw)
To: vineet.gupta, bpf, andrii, ast, daniel
Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, linux-kernel, vineet.gupta, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4314 bytes --]
> diff --git a/tools/testing/selftests/bpf/progs/linked_arena1.c b/tools/testing/selftests/bpf/progs/linked_arena1.c
> index 3e8788dec1d9..dd9c5fa424dd 100644
> --- a/tools/testing/selftests/bpf/progs/linked_arena1.c
> +++ b/tools/testing/selftests/bpf/progs/linked_arena1.c
> @@ -10,13 +10,33 @@ struct {
> __uint(max_entries, 1); /* number of pages */
> } arena SEC(".maps");
>
> -long __arena a_val = 1;
> +/*
> + * Dereferencing an arena global needs the compiler to emit an
> + * addr_space_cast, which only clang does. Keep the variables so the arena
> + * is still populated and the skeleton still has its arena member, but skip
> + * the test elsewhere.
> + */
> +#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
> +bool skip_tests __attribute((__section__(".data"))) = false;
> +#else
> +bool skip_tests = true;
> +#endif
> +
> +long __arena_global a_val = 1;
> extern long __arena b_val; /* defined in linked_arena2.c */
>
> SEC("syscall")
> int sum1(void *ctx)
> {
> +#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
> return a_val + b_val;
> +#else
> + /*
> + * Reference the extern without dereferencing it, so that the relink
> + * test still has an extern to resolve.
> + */
> + return (long)&b_val;
> +#endif
> }
Does the relink_arena test exercise externs in allocated sections under
bpf-gcc as intended?
The commit message explains that __arena is a pointee annotation and
that placement comes from __arena_global. The definition a_val was
switched to __arena_global, but the extern declaration b_val still uses
__arena.
Under gcc, __arena expands to __attribute__((btf_type_tag("arena")))
with no section, so b_val has no explicit section. GCC does not put an
extern that has no explicit section into any BTF DATASEC (GCC
PR112849), which means under bpf-gcc b_val is a free-floating extern
VAR, not an entry of the .addr_space.1 DATASEC.
In tools/lib/bpf/linker.c, find_glob_sym_btf() returns btf_sec_id = 0
for it. linker_append_elf_sym() never takes the branch for externs in a
real allocated section:
if (sym_is_extern && btf_sec_id) {
...
if (strcmp(sec_name, BTF_EXTERN_SEC) != 0) {
src_sec = find_src_sec_by_name(obj, sec_name);
That branch is what d07d3efd0141 ("libbpf: Fix static linking of
externs placed in allocated sections") fixed and what 34354db1b148 is
meant to cover. The check in test_relink() only walks the .addr_space.1
DATASEC:
id = btf__find_by_name_kind(btf, sec_name, BTF_KIND_DATASEC);
...
ASSERT_NEQ(btf_var(t)->linkage, BTF_VAR_GLOBAL_EXTERN, "var_resolved");
Under gcc that DATASEC never holds an extern, so the check cannot fail.
The reported "linked_externs/relink_arena:OK" for bpf-gcc would hold
with or without the linker fix.
By contrast, linked_maps1.c declares its extern with an explicit section
(extern map2_t map2 SEC(".maps");), so relink_maps covers the
allocated-section path under both compilers.
Declaring b_val with __arena_global would do the same here: under gcc
that is SEC(".addr_space.1"), and the bpf_gcc flavor builds with
-Wno-attributes, so the ignored 'used' on an extern is silent; under
clang it is address_space(1), same as today, because clang ignores
btf_type_tag on non-pointer types.
>
> char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/progs/linked_arena2.c b/tools/testing/selftests/bpf/progs/linked_arena2.c
> index d0bcdf18a4b6..1bc8b97ed8cc 100644
> --- a/tools/testing/selftests/bpf/progs/linked_arena2.c
> +++ b/tools/testing/selftests/bpf/progs/linked_arena2.c
> @@ -4,15 +4,19 @@
> #include <bpf/bpf_helpers.h>
> #include "bpf_arena_common.h"
>
> -long __arena b_val = 2;
> +long __arena_global b_val = 2;
> extern long __arena a_val; /* defined in linked_arena1.c */
The same applies to the extern a_val declaration here.
>
> SEC("syscall")
> int bump2(void *ctx)
> {
> +#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
> a_val += 10;
> b_val += 20;
> return a_val + b_val;
> +#else /* see linked_arena1.c */
> + return (long)&a_val;
> +#endif
> }
>
> char _license[] SEC("license") = "GPL";
---
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/36490925455
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH bpf-next] selftests/bpf: Fix linked_externs with bpf-gcc
2026-09-28 22:42 ` bot+bpf-ci
@ 2026-09-28 23:11 ` Vineet Gupta
0 siblings, 0 replies; 3+ messages in thread
From: Vineet Gupta @ 2026-09-28 23:11 UTC (permalink / raw)
To: bot+bpf-ci, bpf, andrii, ast, daniel
Cc: eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, linux-kernel, martin.lau, mason
On 9/28/26 3:42 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/testing/selftests/bpf/progs/linked_arena1.c b/tools/testing/selftests/bpf/progs/linked_arena1.c
>> index 3e8788dec1d9..dd9c5fa424dd 100644
>> --- a/tools/testing/selftests/bpf/progs/linked_arena1.c
>> +++ b/tools/testing/selftests/bpf/progs/linked_arena1.c
>> @@ -10,13 +10,33 @@ struct {
>> __uint(max_entries, 1); /* number of pages */
>> } arena SEC(".maps");
>>
>> -long __arena a_val = 1;
>> +/*
>> + * Dereferencing an arena global needs the compiler to emit an
>> + * addr_space_cast, which only clang does. Keep the variables so the arena
>> + * is still populated and the skeleton still has its arena member, but skip
>> + * the test elsewhere.
>> + */
>> +#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
>> +bool skip_tests __attribute((__section__(".data"))) = false;
>> +#else
>> +bool skip_tests = true;
>> +#endif
>> +
>> +long __arena_global a_val = 1;
>> extern long __arena b_val; /* defined in linked_arena2.c */
>>
>> SEC("syscall")
>> int sum1(void *ctx)
>> {
>> +#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
>> return a_val + b_val;
>> +#else
>> + /*
>> + * Reference the extern without dereferencing it, so that the relink
>> + * test still has an extern to resolve.
>> + */
>> + return (long)&b_val;
>> +#endif
>> }
> Does the relink_arena test exercise externs in allocated sections under
> bpf-gcc as intended?
>
> The commit message explains that __arena is a pointee annotation and
> that placement comes from __arena_global. The definition a_val was
> switched to __arena_global, but the extern declaration b_val still uses
> __arena.
>
> Under gcc, __arena expands to __attribute__((btf_type_tag("arena")))
> with no section, so b_val has no explicit section. GCC does not put an
> extern that has no explicit section into any BTF DATASEC (GCC
> PR112849), which means under bpf-gcc b_val is a free-floating extern
> VAR, not an entry of the .addr_space.1 DATASEC.
>
> In tools/lib/bpf/linker.c, find_glob_sym_btf() returns btf_sec_id = 0
> for it. linker_append_elf_sym() never takes the branch for externs in a
> real allocated section:
>
> if (sym_is_extern && btf_sec_id) {
> ...
> if (strcmp(sec_name, BTF_EXTERN_SEC) != 0) {
> src_sec = find_src_sec_by_name(obj, sec_name);
>
> That branch is what d07d3efd0141 ("libbpf: Fix static linking of
> externs placed in allocated sections") fixed and what 34354db1b148 is
> meant to cover. The check in test_relink() only walks the .addr_space.1
> DATASEC:
>
> id = btf__find_by_name_kind(btf, sec_name, BTF_KIND_DATASEC);
> ...
> ASSERT_NEQ(btf_var(t)->linkage, BTF_VAR_GLOBAL_EXTERN, "var_resolved");
>
> Under gcc that DATASEC never holds an extern, so the check cannot fail.
> The reported "linked_externs/relink_arena:OK" for bpf-gcc would hold
> with or without the linker fix.
>
> By contrast, linked_maps1.c declares its extern with an explicit section
> (extern map2_t map2 SEC(".maps");), so relink_maps covers the
> allocated-section path under both compilers.
>
> Declaring b_val with __arena_global would do the same here: under gcc
> that is SEC(".addr_space.1"), and the bpf_gcc flavor builds with
> -Wno-attributes, so the ignored 'used' on an extern is silent; under
> clang it is address_space(1), same as today, because clang ignores
> btf_type_tag on non-pointer types.
Thanks. Fixed in v2.
I had only checked that b_val survived as an ELF UND symbol and missed
that it was not in the DATASEC test_relink() walks:
[23] DATASEC '.addr_space.1' size=0 vlen=1
type_id=18 offset=0 size=8 (VAR 'a_val')
[18] VAR 'b_val' type_id=11, linkage=extern <- not in the datasec
With __arena_global on the extern declarations it matches clang:
[24] DATASEC '.addr_space.1' size=0 vlen=2
type_id=18 offset=0 size=8 (VAR 'b_val')
type_id=19 offset=0 size=8 (VAR 'a_val')
[18] VAR 'b_val' type_id=13, linkage=extern
One correction: the 'used' that SEC() adds does not keep the extern
alive. With __arena_global on the extern but nothing referencing it,
bpf-gcc still drops it and .addr_space.1 goes back to vlen=1 -- 'used'
is ignored on a declaration, which is what -Wno-attributes is hiding
here. So v2 needs both halves: __arena_global for the placement, and the
return (long)&b_val;
in the !__BPF_FEATURE_ADDR_SPACE_CAST body so the extern is still
referenced. Dropping either one and relink_arena goes back to passing
pointlessly under bpf-gcc.
Thanks,
-Vineet
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-28 23:11 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 21:51 [PATCH bpf-next] selftests/bpf: Fix linked_externs with bpf-gcc Vineet Gupta
2026-09-28 22:42 ` bot+bpf-ci
2026-09-28 23:11 ` Vineet Gupta
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®