selftests/bpf: Add test for global data of a program in Rust

data_in_arena_rust.rs shows why libbpf keeps .data, .bss and .rodata of
a program in Rust in arena. A reference in Rust is an address that can
be stored in data. The list in the test has a node in each of the three
sections. IN_BSS.next is stored by the program, IN_DATA.next is
a relocation in .data against .rodata. sum() follows them and reads all
nodes with the same insns:

  5: w0 = *(u32 *)(r1 + 0x8)
  8: r1 = *(u64 *)(r1 + 0x0)

The same program without .arena.data section, when the sections are
array maps:

  libbpf: elf: skipping relo section(9) .rel.data for section(8) .data
  ...
  16: (79) r1 = *(u64 *)(r1 +0)         ; frame1: R1=scalar()
  ...
  13: (61) r0 = *(u32 *)(r1 +8)
  R1 invalid mem access 'scalar'

The program is built by upstream rustc for its bpfel-unknown-none
target. Nothing is done to the output: rustc emits LLVM bitcode and
clang makes the object of it for the cpu version of the test_progs
flavor. rustc has no prebuilt core for the target, so core is built from
the source that comes with rustc, like the kernel does it. That takes
5 seconds.

The subtest is skipped when:
- there is no rustc or it is older than 1.87. core is built as edition
  2024, which it is since then.
- rustc comes without the source of core (rustup component add rust-src).
- clang is older than 23 or older than LLVM of rustc. It can't read
  the bitcode then.

There are no kfuncs in the program. rustc doesn't emit debug info for
extern functions, so there would be no BTF for them.

panic=abort is a stop gap. Nothing in the program panics, so the panic
handler is not loaded. The series from Yonghong are about to add support
for panic=unwind.

Tested with rustc 1.95.0-nightly (LLVM 22.1) and clang 24.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20261002124714.180012-16-alexei.starovoitov@gmail.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Alexei Starovoitov 2026-10-02 12:47:14 +00:00 committed by Kumar Kartikeya Dwivedi
parent 27e24617a8
commit e86cb73154
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
5 changed files with 146 additions and 1 deletions

View File

@ -422,6 +422,16 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
endif
# #![no_std] looks for compiler_builtins too. Nothing of it is used.
ifneq ($(RUST_CORE),)
$(RUST_CORE): $(RUST_CORE_SRC)
$(call msg,RUSTC,,$@)
$(Q)mkdir -p $(@D)
+$(Q)$(RUSTC_BPF) -A warnings --edition 2024 --crate-name core --out-dir $(@D) $<
+$(Q)echo '#![feature(compiler_builtins)] #![compiler_builtins] #![no_std]' | \
$(RUSTC_BPF) -A warnings --crate-name compiler_builtins --out-dir $(@D) -
endif
# Generated test list headers
define gen_tests_hdr
@ -457,7 +467,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL) \
$(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
$(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
prog_tests/tests.h map_tests/tests.h \
$(RUNNER_OBJS)
$(RUNNER_OBJS) $(RUST_CORE)
# Runtime fixtures for each test_progs flavor.
RUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \

View File

@ -109,6 +109,31 @@ HOST_INCLUDE_DIR := $(INCLUDE_DIR)
endif
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
# Programs in Rust are built by upstream rustc. It has no prebuilt core for
# the bpf target, so it has to come with the source of core:
# rustup component add rust-src
# core is built as edition 2024, which it is since rustc 1.87.
# rustc emits LLVM bitcode and clang makes the object of it, so clang has to be
# 23 or newer and not older than LLVM of rustc.
# Otherwise RUST_CORE is empty and the tests are skipped.
RUSTC ?= rustc
RUST_CORE_SRC := $(wildcard $(shell $(RUSTC) --print sysroot 2>/dev/null)$\
/lib/rustlib/src/rust/library/core/src/lib.rs)
ifneq ($(RUST_CORE_SRC),)
ifeq ($(shell { clang=$$(echo __clang_major__ | $(CLANG) -E -P -x c -) && \
llvm=$$($(srctree)/scripts/rustc-llvm-version.sh $(RUSTC)) && \
[ $$($(srctree)/scripts/rustc-version.sh $(RUSTC)) -ge 108700 ] && \
[ $$clang -ge 23 ] && [ $$clang -ge $$((llvm / 10000)) ]; } \
2>/dev/null && echo y),y)
RUST_CORE := $(BUILD_DIR)/rust/libcore.rlib
endif
endif
# RUSTC_BOOTSTRAP=1 is to build core with a stable rustc, like the kernel does.
# panic=abort is a stop gap until panic=unwind is supported.
RUSTC_BPF = RUSTC_BOOTSTRAP=1 $(RUSTC) -O -C panic=abort --crate-type rlib \
--target $(if $(IS_LITTLE_ENDIAN),bpfel,bpfeb)-unknown-none \
-L $(dir $(RUST_CORE))
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
ifneq ($(CROSS_COMPILE),)
CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool

View File

@ -138,4 +138,19 @@ $(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps))
$(BPFTOOL) $(GEN_SKEL) | $(RDIR)
$(Q)$(cmd_bpf_link_skel)
# Programs in Rust, see Makefile.buildvars. No skeletons: the objects may be absent.
ifneq ($(RUST_CORE),)
ifeq ($(BPF_CC),$(CLANG))
RUST_OBJS := $(patsubst progs/%.rs,$(RDIR)/%.bpf.o,$(wildcard progs/*.rs))
BPF_OBJS += $(RUST_OBJS)
$(RUST_OBJS): $(RDIR)/%.bpf.o: progs/%.rs $(RUST_CORE) | $(RDIR)
$(call msg,RUSTC,$(BINARY),$@)
+$(Q)$(RUSTC_BPF) --edition 2021 -C debuginfo=2 --emit=llvm-bc \
$(patsubst -mcpu=%,-C target-cpu=%,$(filter -mcpu=%,$(BPF_CC_FLAGS))) \
-o $(dir $(RUST_CORE))$(BINARY)-$*.bc $< && \
$(BPF_CC) $(BPF_CC_FLAGS) -c $(dir $(RUST_CORE))$(BINARY)-$*.bc -o $@ $(call skip_on_fail,BPF)
endif
endif
endif # BPF_CC

View File

@ -167,6 +167,29 @@ static void test_not_my_arena(bool pin)
data_in_arena__destroy(skel);
}
/* The object is there if rustc and clang can build it, see Makefile.buildvars */
static void test_rust(void)
{
const char *file = "./data_in_arena_rust.bpf.o";
struct bpf_object *obj;
if (access(file, R_OK)) {
test__skip();
return;
}
obj = bpf_object__open_file(file, NULL);
if (!ASSERT_OK_PTR(obj, "open"))
return;
if (!ASSERT_OK(bpf_object__load(obj), "load"))
goto out;
/* libbpf goes on without BTF when the kernel doesn't take it */
ASSERT_GE(bpf_object__btf_fd(obj), 0, "btf_fd");
ASSERT_EQ(run_prog(bpf_object__find_program_by_name(obj, "list_in_data")),
100 + 20 + 3, "retval");
out:
bpf_object__close(obj);
}
void test_data_in_arena(void)
{
#if !defined(__x86_64__) && !defined(__aarch64__)
@ -188,4 +211,6 @@ void test_data_in_arena(void)
test_not_my_arena(true);
if (test__start_subtest("reused_arena"))
test_not_my_arena(false);
if (test__start_subtest("rust"))
test_rust();
}

View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
// Why .data, .bss and .rodata of a program in Rust are in arena.
//
// A reference in Rust is an address. It doesn't say what it points to and it
// can be stored in data. The list below has a node in each of the sections.
// The nodes are linked by references that are in the data:
// IN_BSS.next is stored by the program,
// IN_DATA.next is a relocation in .data against .rodata.
// sum() loads the references back and reads the three nodes with the same insn.
//
// When the sections are array maps libbpf skips the relocation in .data, and
// what sum() loads from a node is a number that can't be dereferenced:
// R1 invalid mem access 'scalar'
// In arena the address of a node is a number to begin with.
#![no_std]
#![no_main]
// Tell libbpf to keep .data, .bss and .rodata in arena.
#[used]
#[link_section = ".arena.data"]
static DATA_IN_ARENA: u8 = 0;
#[used]
#[link_section = "license"]
static LICENSE: [u8; 4] = *b"GPL\0";
// panic=abort is a stop gap until panic=unwind is supported.
// Nothing here panics, so the handler is not a part of the program.
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
pub struct Node {
val: u32,
next: Option<&'static Node>,
}
// no_mangle makes them visible outside, so LLVM can't fold the list into a constant.
#[no_mangle]
static IN_RODATA: Node = Node { val: 3, next: None };
#[no_mangle]
static mut IN_DATA: Node = Node { val: 20, next: Some(&IN_RODATA) };
#[no_mangle]
static mut IN_BSS: Node = Node { val: 0, next: None };
#[inline(never)]
fn sum(mut node: Option<&Node>) -> u32 {
let mut sum = 0;
// The verifier wants a bound.
for _ in 0..8 {
let Some(n) = node else { break };
sum += n.val;
node = n.next;
}
sum
}
#[no_mangle]
#[link_section = "syscall"]
pub extern "C" fn list_in_data(_ctx: *mut u8) -> u32 {
unsafe {
let head = &mut *&raw mut IN_BSS;
head.val = 100;
head.next = Some(&*&raw const IN_DATA);
sum(Some(head))
}
}