selftests/bpf: Add linked_externs test for externs in allocated sections

Add two objects that define an arena global each and declare the
other one's as extern, linked into a single skeleton with the
extern-declaring object first.

The skeleton build relinks the linked object twice more, which fixes
up DATASEC offsets from the ELF symbol table, so the test also drives
the libbpf linker directly: it links linked_arena1.bpf.o on its own,
links the result with linked_arena2.bpf.o, checks that no variable in
the .addr_space.1 DATASEC is left extern, and loads the object. The
same is done with linked_maps1/2.bpf.o, which have an extern map in
.maps.

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20260926000243.2830819-2-andrii@kernel.org
This commit is contained in:
Andrii Nakryiko 2026-09-25 17:02:43 -07:00 committed by Alexei Starovoitov
parent d07d3efd01
commit 34354db1b1
No known key found for this signature in database
4 changed files with 151 additions and 1 deletions

View File

@ -23,7 +23,7 @@ BPF_OBJS := $(patsubst %.c,$(RDIR)/%.bpf.o,$(BPF_SRCS))
SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \
linked_vars.skel.h linked_maps.skel.h \
linked_vars.skel.h linked_maps.skel.h linked_arena.skel.h \
test_subskeleton.skel.h test_subskeleton_lib.skel.h \
test_usdt.skel.h tracing_multi.skel.h \
tracing_multi_module.skel.h \
@ -46,6 +46,7 @@ test_static_linked.skel.h-deps := test_static_linked1.bpf.o test_static_linked2.
linked_funcs.skel.h-deps := linked_funcs1.bpf.o linked_funcs2.bpf.o
linked_vars.skel.h-deps := linked_vars1.bpf.o linked_vars2.bpf.o
linked_maps.skel.h-deps := linked_maps1.bpf.o linked_maps2.bpf.o
linked_arena.skel.h-deps := linked_arena1.bpf.o linked_arena2.bpf.o
# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
# but that's created as a side-effect of the skel.h generation.
test_subskeleton.skel.h-deps := test_subskeleton_lib2.bpf.o test_subskeleton_lib.bpf.o test_subskeleton.bpf.o

View File

@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <bpf/btf.h>
#include "linked_arena.skel.h"
static void test_skel(void)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
struct linked_arena *skel;
int err;
skel = linked_arena__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
return;
ASSERT_EQ(skel->arena->a_val, 1, "a_val_init");
ASSERT_EQ(skel->arena->b_val, 2, "b_val_init");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.sum1), &opts);
ASSERT_OK(err, "sum1_run");
ASSERT_EQ(opts.retval, 1 + 2, "sum1_retval");
err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.bump2), &opts);
ASSERT_OK(err, "bump2_run");
ASSERT_EQ(opts.retval, 11 + 22, "bump2_retval");
ASSERT_EQ(skel->arena->a_val, 11, "a_val");
ASSERT_EQ(skel->arena->b_val, 22, "b_val");
linked_arena__destroy(skel);
}
static int link_objs(const char *out, const char *in1, const char *in2)
{
struct bpf_linker *linker;
int err;
linker = bpf_linker__new(out, NULL);
if (!ASSERT_OK_PTR(linker, "linker_new"))
return -EINVAL;
err = bpf_linker__add_file(linker, in1, NULL);
if (!ASSERT_OK(err, in1))
goto cleanup;
if (in2) {
err = bpf_linker__add_file(linker, in2, NULL);
if (!ASSERT_OK(err, in2))
goto cleanup;
}
err = bpf_linker__finalize(linker);
ASSERT_OK(err, "finalize");
cleanup:
bpf_linker__free(linker);
return err;
}
/* link in1 on its own first, then link the result with in2 */
static void test_relink(const char *in1, const char *in2, const char *sec_name)
{
char out1[] = "/tmp/linked_externs.XXXXXX", out2[] = "/tmp/linked_externs.XXXXXX";
const struct btf_var_secinfo *vi;
struct bpf_object *obj = NULL;
const struct btf_type *t;
struct btf *btf;
int i, id, n;
close(mkstemp(out1));
close(mkstemp(out2));
if (!ASSERT_OK(link_objs(out1, in1, NULL), "link_stage1") ||
!ASSERT_OK(link_objs(out2, out1, in2), "link_stage2"))
goto cleanup;
obj = bpf_object__open_file(out2, NULL);
if (!ASSERT_OK_PTR(obj, "obj_open"))
goto cleanup;
btf = bpf_object__btf(obj);
id = btf__find_by_name_kind(btf, sec_name, BTF_KIND_DATASEC);
if (!ASSERT_GT(id, 0, "find_datasec"))
goto cleanup;
t = btf__type_by_id(btf, id);
vi = btf_var_secinfos(t);
n = btf_vlen(t);
for (i = 0; i < n; i++) {
t = btf__type_by_id(btf, vi[i].type);
ASSERT_NEQ(btf_var(t)->linkage, BTF_VAR_GLOBAL_EXTERN, "var_resolved");
}
ASSERT_OK(bpf_object__load(obj), "obj_load");
cleanup:
bpf_object__close(obj);
unlink(out1);
unlink(out2);
}
void test_linked_externs(void)
{
if (test__start_subtest("skel_arena"))
test_skel();
if (test__start_subtest("relink_arena"))
test_relink("linked_arena1.bpf.o", "linked_arena2.bpf.o", ".addr_space.1");
if (test__start_subtest("relink_maps"))
test_relink("linked_maps1.bpf.o", "linked_maps2.bpf.o", ".maps");
}

View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "bpf_arena_common.h"
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 1); /* number of pages */
} arena SEC(".maps");
long __arena a_val = 1;
extern long __arena b_val; /* defined in linked_arena2.c */
SEC("syscall")
int sum1(void *ctx)
{
return a_val + b_val;
}
char _license[] SEC("license") = "GPL";

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "bpf_arena_common.h"
long __arena b_val = 2;
extern long __arena a_val; /* defined in linked_arena1.c */
SEC("syscall")
int bump2(void *ctx)
{
a_val += 10;
b_val += 20;
return a_val + b_val;
}
char _license[] SEC("license") = "GPL";