Commit dff64b07 authored by FUJITA Tomonori's avatar FUJITA Tomonori Committed by Miguel Ojeda
Browse files

rust: Add warn_on macro



Add warn_on macro, uses the BUG/WARN feature (lib/bug.c) via assembly
for x86_64/arm64/riscv.

The current Rust code simply wraps BUG() macro but doesn't provide the
proper debug information. The BUG/WARN feature can only be used from
assembly.

This uses the assembly code exported by the C side via ARCH_WARN_ASM
macro. To avoid duplicating the assembly code, this approach follows
the same strategy as the static branch code: it generates the assembly
code for Rust using the C preprocessor at compile time.

Similarly, ARCH_WARN_REACHABLE is also used at compile time to
generate the assembly code; objtool's reachable annotation code. It's
used for only architectures that use objtool.

For now, Loongarch and arm just use a wrapper for WARN macro.

UML doesn't use the assembly BUG/WARN feature; just wrapping generic
BUG/WARN functions implemented in C works.

Signed-off-by: default avatarFUJITA Tomonori <fujita.tomonori@gmail.com>
Reviewed-by: default avatarAlice Ryhl <aliceryhl@google.com>
Link: https://lore.kernel.org/r/20250502094537.231725-5-fujita.tomonori@gmail.com


[ Avoid evaluating the condition twice (a good idea in general,
  but it also matches the C side). Simplify with `as_char_ptr()`
  to avoid a cast. Cast to `ffi` integer types for
  `warn_slowpath_fmt`. Avoid cast for `null()`. - Miguel ]
Signed-off-by: default avatarMiguel Ojeda <ojeda@kernel.org>
parent 82623097
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -34,6 +34,9 @@ obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.o
obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.o

always-$(subst y,$(CONFIG_RUST),$(CONFIG_JUMP_LABEL)) += kernel/generated_arch_static_branch_asm.rs
ifndef CONFIG_UML
always-$(subst y,$(CONFIG_RUST),$(CONFIG_BUG)) += kernel/generated_arch_warn_asm.rs kernel/generated_arch_reachable_asm.rs
endif

# Avoids running `$(RUSTC)` when it may not be available.
ifdef CONFIG_RUST
@@ -540,5 +543,10 @@ $(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/build_error.o $(obj)/pin_init.o \
ifdef CONFIG_JUMP_LABEL
$(obj)/kernel.o: $(obj)/kernel/generated_arch_static_branch_asm.rs
endif
ifndef CONFIG_UML
ifdef CONFIG_BUG
$(obj)/kernel.o: $(obj)/kernel/generated_arch_warn_asm.rs $(obj)/kernel/generated_arch_reachable_asm.rs
endif
endif

endif # CONFIG_RUST
+5 −0
Original line number Diff line number Diff line
@@ -6,3 +6,8 @@ __noreturn void rust_helper_BUG(void)
{
	BUG();
}

bool rust_helper_WARN_ON(bool cond)
{
	return WARN_ON(cond);
}
+2 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0

/generated_arch_static_branch_asm.rs
/generated_arch_warn_asm.rs
/generated_arch_reachable_asm.rs

rust/kernel/bug.rs

0 → 100644
+126 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024, 2025 FUJITA Tomonori <fujita.tomonori@gmail.com>

//! Support for BUG and WARN functionality.
//!
//! C header: [`include/asm-generic/bug.h`](srctree/include/asm-generic/bug.h)

#[macro_export]
#[doc(hidden)]
#[cfg(all(CONFIG_BUG, not(CONFIG_UML), not(CONFIG_LOONGARCH), not(CONFIG_ARM)))]
#[cfg(CONFIG_DEBUG_BUGVERBOSE)]
macro_rules! warn_flags {
    ($flags:expr) => {
        const FLAGS: u32 = $crate::bindings::BUGFLAG_WARNING | $flags;
        const _FILE: &[u8] = file!().as_bytes();
        // Plus one for null-terminator.
        static FILE: [u8; _FILE.len() + 1] = {
            let mut bytes = [0; _FILE.len() + 1];
            let mut i = 0;
            while i < _FILE.len() {
                bytes[i] = _FILE[i];
                i += 1;
            }
            bytes
        };

        // SAFETY:
        // - `file`, `line`, `flags`, and `size` are all compile-time constants or
        // symbols, preventing any invalid memory access.
        // - The asm block has no side effects and does not modify any registers
        // or memory. It is purely for embedding metadata into the ELF section.
        unsafe {
            $crate::asm!(
                concat!(
                    "/* {size} */",
                    include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm.rs")),
                    include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_reachable_asm.rs")));
                file = sym FILE,
                line = const line!(),
                flags = const FLAGS,
                size = const ::core::mem::size_of::<$crate::bindings::bug_entry>(),
            );
        }
    }
}

#[macro_export]
#[doc(hidden)]
#[cfg(all(CONFIG_BUG, not(CONFIG_UML), not(CONFIG_LOONGARCH), not(CONFIG_ARM)))]
#[cfg(not(CONFIG_DEBUG_BUGVERBOSE))]
macro_rules! warn_flags {
    ($flags:expr) => {
        const FLAGS: u32 = $crate::bindings::BUGFLAG_WARNING | $flags;

        // SAFETY:
        // - `flags` and `size` are all compile-time constants, preventing
        // any invalid memory access.
        // - The asm block has no side effects and does not modify any registers
        // or memory. It is purely for embedding metadata into the ELF section.
        unsafe {
            $crate::asm!(
                concat!(
                    "/* {size} */",
                    include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm.rs")),
                    include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_reachable_asm.rs")));
                flags = const FLAGS,
                size = const ::core::mem::size_of::<$crate::bindings::bug_entry>(),
            );
        }
    }
}

#[macro_export]
#[doc(hidden)]
#[cfg(all(CONFIG_BUG, CONFIG_UML))]
macro_rules! warn_flags {
    ($flags:expr) => {
        // SAFETY: It is always safe to call `warn_slowpath_fmt()`
        // with a valid null-terminated string.
        unsafe {
            $crate::bindings::warn_slowpath_fmt(
                $crate::c_str!(::core::file!()).as_char_ptr(),
                line!() as $crate::ffi::c_int,
                $flags as $crate::ffi::c_uint,
                ::core::ptr::null(),
            );
        }
    };
}

#[macro_export]
#[doc(hidden)]
#[cfg(all(CONFIG_BUG, any(CONFIG_LOONGARCH, CONFIG_ARM)))]
macro_rules! warn_flags {
    ($flags:expr) => {
        // SAFETY: It is always safe to call `WARN_ON()`.
        unsafe { $crate::bindings::WARN_ON(true) }
    };
}

#[macro_export]
#[doc(hidden)]
#[cfg(not(CONFIG_BUG))]
macro_rules! warn_flags {
    ($flags:expr) => {};
}

#[doc(hidden)]
pub const fn bugflag_taint(value: u32) -> u32 {
    value << 8
}

/// Report a warning if `cond` is true and return the condition's evaluation result.
#[macro_export]
macro_rules! warn_on {
    ($cond:expr) => {{
        let cond = $cond;
        if cond {
            const WARN_ON_FLAGS: u32 = $crate::bug::bugflag_taint($crate::bindings::TAINT_WARN);

            $crate::warn_flags!(WARN_ON_FLAGS);
        }
        cond
    }};
}
+7 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */

#include <linux/bug.h>

// Cut here.

::kernel::concat_literals!(ARCH_WARN_REACHABLE)
Loading