mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
27e24617a8
commit
e86cb73154
|
|
@ -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)
|
+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
|
||||||
endif
|
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
|
# Generated test list headers
|
||||||
|
|
||||||
define gen_tests_hdr
|
define gen_tests_hdr
|
||||||
|
|
@ -457,7 +467,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL) \
|
||||||
$(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
|
$(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
|
||||||
$(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
|
$(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
|
||||||
prog_tests/tests.h map_tests/tests.h \
|
prog_tests/tests.h map_tests/tests.h \
|
||||||
$(RUNNER_OBJS)
|
$(RUNNER_OBJS) $(RUST_CORE)
|
||||||
|
|
||||||
# Runtime fixtures for each test_progs flavor.
|
# Runtime fixtures for each test_progs flavor.
|
||||||
RUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
|
RUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,31 @@ HOST_INCLUDE_DIR := $(INCLUDE_DIR)
|
||||||
endif
|
endif
|
||||||
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
|
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
|
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
|
||||||
ifneq ($(CROSS_COMPILE),)
|
ifneq ($(CROSS_COMPILE),)
|
||||||
CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
|
CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
|
||||||
|
|
|
||||||
|
|
@ -138,4 +138,19 @@ $(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps))
|
||||||
$(BPFTOOL) $(GEN_SKEL) | $(RDIR)
|
$(BPFTOOL) $(GEN_SKEL) | $(RDIR)
|
||||||
$(Q)$(cmd_bpf_link_skel)
|
$(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
|
endif # BPF_CC
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,29 @@ static void test_not_my_arena(bool pin)
|
||||||
data_in_arena__destroy(skel);
|
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)
|
void test_data_in_arena(void)
|
||||||
{
|
{
|
||||||
#if !defined(__x86_64__) && !defined(__aarch64__)
|
#if !defined(__x86_64__) && !defined(__aarch64__)
|
||||||
|
|
@ -188,4 +211,6 @@ void test_data_in_arena(void)
|
||||||
test_not_my_arena(true);
|
test_not_my_arena(true);
|
||||||
if (test__start_subtest("reused_arena"))
|
if (test__start_subtest("reused_arena"))
|
||||||
test_not_my_arena(false);
|
test_not_my_arena(false);
|
||||||
|
if (test__start_subtest("rust"))
|
||||||
|
test_rust();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
Normal file
70
tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user