[RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

Subsystems: arm64 port (aarch64 architecture), linux for powerpc (32-bit and 64-bit), risc-v architecture, rust, s390 architecture, the rest, x86 architecture (32-bit and 64-bit)

17 messages, 5 authors, 21d ago · open the first message on its own page

[RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya (IBM) <hidden>
Date: 2026-09-22 05:51:24

Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, and in
the new generated_arch_warn_asm_trap_label.rs template, which the
C preprocessor expands at build time into a .rs file included by the
test's global_asm! block.

global_asm! is used instead of asm! because LLVM eliminates asm! blocks
in dead branches; global_asm! is file-scope and always emitted.
BUG_KUNIT_TRAP_ADDR is defined as a .global symbol in .data on top of a
.dc.a {trap_label}b relocation so the linker resolves the trap address
into it at link time. A Rust static initialized to zero would land in
BSS where relocations are not applied.

The global_asm! string mirrors warn_flags!() exactly — same "/* {size} */"
prefix, same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs
includes, same operands — plus the .pushsection/.dc.a/.popsection tail.
This keeps the test structurally in sync with the macro it exercises.

The CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST option depends on GENERIC_BUG
and excludes ARM, LOONGARCH, and UML (which use a different warn_flags!
path without ARCH_WARN_ASM).

Five tests are included in the rust_kernel_bug_warn_asm suite:

  bug_entry_found           - find_bug() returns non-NULL for the trap
                              address, proving bug_addr is correct
  bug_entry_is_warning      - the emitted entry has BUGFLAG_WARNING set
  bug_entry_file            - bug_get_file_line() returns the correct
                              source file (CONFIG_DEBUG_BUGVERBOSE only)
  bug_entry_line            - recorded line number is non-zero, confirming
                              the {line} operand was substituted correctly
                              (CONFIG_DEBUG_BUGVERBOSE only)
  bug_entry_addr_is_in_text - kernel_text_address() confirms the trap
                              address lies in kernel text

Tested (pass:5 fail:0 skip:0) on:
  x86_64  - QEMU x86_64
  powerpc - QEMU pseries ppc64le (pseries_le_defconfig)
  riscv   - QEMU riscv64
  arm64   - QEMU aarch64
  s390    - QEMU s390x

Signed-off-by: Mukesh Kumar Chaurasiya (IBM) <redacted>
---
Note: I sent this out as a powerpc specific tests earlier. During review
process some people wanted these to be arch independent. Hence sending
this out as an RFC.

Earlier version: https://lore.kernel.org/all/20260915090453.1227034-3-mkchauras@gmail.com/

 arch/arm64/include/asm/asm-bug.h              |   3 +
 arch/powerpc/include/asm/bug.h                |  22 +-
 arch/riscv/include/asm/bug.h                  |   5 +-
 arch/s390/include/asm/bug.h                   |   4 +-
 arch/x86/include/asm/bug.h                    |   5 +-
 rust/Makefile                                 |   2 +-
 rust/kernel/.gitignore                        |   1 +
 rust/kernel/Kconfig.test                      |  15 ++
 rust/kernel/bug.rs                            | 191 ++++++++++++++++++
 .../generated_arch_warn_asm_trap_label.rs.S   |   7 +
 10 files changed, 239 insertions(+), 16 deletions(-)
 create mode 100644 rust/kernel/generated_arch_warn_asm_trap_label.rs.S
diff --git a/arch/arm64/include/asm/asm-bug.h b/arch/arm64/include/asm/asm-bug.h
index a5f13801b784..e0694543115e 100644
--- a/arch/arm64/include/asm/asm-bug.h
+++ b/arch/arm64/include/asm/asm-bug.h
@@ -60,6 +60,9 @@ _BUGVERBOSE_LOCATION(__FILE__, __LINE__)		\
 		".short " flags ";"			\
 		__stringify(__BUG_ENTRY_END)
 
+/* Trap instruction is placed at label 14471: by __BUG_ENTRY_END */
+#define ARCH_WARN_ASM_TRAP_LABEL "14471"
+
 #define ARCH_WARN_ASM(file, line, flags, size)		\
 	__BUG_ENTRY_STRING(file, line, flags)		\
 	__stringify(brk BUG_BRK_IMM)
diff --git a/arch/powerpc/include/asm/bug.h b/arch/powerpc/include/asm/bug.h
index bf31ee1e902a..f0333c564d4f 100644
--- a/arch/powerpc/include/asm/bug.h
+++ b/arch/powerpc/include/asm/bug.h
@@ -32,6 +32,8 @@
 #endif /* verbose */
 
 #else /* !__ASSEMBLER__ */
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
+
 #ifdef CONFIG_DEBUG_BUGVERBOSE
 #define _EMIT_BUG_ENTRY(bug_entry, trap, file, line, flags)	\
 	".section __bug_table,\"aw\"\n"				\
@@ -45,21 +47,21 @@
 	"		.short " flags "\n"
 #endif
 
-#define BUG_ENTRY(cond_str, insn, flags, ...)			\
-	__asm__ __volatile__(					\
-		"1:	" insn "\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")	\
-		".org 2b+%3\n"					\
-		".previous\n"					\
+#define BUG_ENTRY(cond_str, insn, flags, ...)				\
+	__asm__ __volatile__(						\
+		ARCH_WARN_ASM_TRAP_LABEL ":	" insn "\n"		\
+		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")		\
+		".org 2b+%3\n"						\
+		".previous\n"						\
 		: : "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__),	\
 		  "i" (flags),					\
 		  "i" (sizeof(struct bug_entry)),		\
 		  ##__VA_ARGS__)
 
-#define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:	twi 31, 0, 0\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)	\
-		".org 2b+" size "\n"				\
+#define ARCH_WARN_ASM(file, line, flags, size)					\
+		ARCH_WARN_ASM_TRAP_LABEL ":	twi 31, 0, 0\n"			\
+		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)			\
+		".org 2b+" size "\n"						\
 		".previous\n"
 
 #define ARCH_WARN_REACHABLE
diff --git a/arch/riscv/include/asm/bug.h b/arch/riscv/include/asm/bug.h
index 699c0cf3e4ef..def6b0d153f2 100644
--- a/arch/riscv/include/asm/bug.h
+++ b/arch/riscv/include/asm/bug.h
@@ -29,7 +29,8 @@
 
 typedef u32 bug_insn_t;
 
-#define __BUG_ENTRY_ADDR	RISCV_INT " 1b - ."
+#define ARCH_WARN_ASM_TRAP_LABEL	"1"
+#define __BUG_ENTRY_ADDR	RISCV_INT " " ARCH_WARN_ASM_TRAP_LABEL "b - ."
 #define __BUG_ENTRY_FILE(file)	RISCV_INT " " file " - ."
 
 #ifdef CONFIG_DEBUG_BUGVERBOSE
@@ -47,7 +48,7 @@ typedef u32 bug_insn_t;
 #ifdef CONFIG_GENERIC_BUG
 
 #define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:\n\t"					\
+		ARCH_WARN_ASM_TRAP_LABEL ":\n\t"		\
 			"ebreak\n"				\
 			".pushsection __bug_table,\"aw\"\n\t"	\
 		"2:\n\t"					\
diff --git a/arch/s390/include/asm/bug.h b/arch/s390/include/asm/bug.h
index 2010342c97b1..3c0fa1acc92a 100644
--- a/arch/s390/include/asm/bug.h
+++ b/arch/s390/include/asm/bug.h
@@ -26,9 +26,11 @@
 #define WARN_CONDITION_STR(cond_str) ""
 #endif
 
+#define ARCH_WARN_ASM_TRAP_LABEL "0"
+
 #define __BUG_ENTRY(format, file, line, flags, size)			\
 		"	.section __bug_table,\"aw\"\n"			\
-		"1:	.long	0b - .		# bug_entry::bug_addr\n"\
+		"1:	.long	" ARCH_WARN_ASM_TRAP_LABEL "b - .	# bug_entry::bug_addr\n" \
 		"	.long	" format " - .	# bug_entry::format\n"	\
 		__BUG_ENTRY_VERBOSE(file, line)				\
 		"	.short	"flags"		# bug_entry::flags\n"	\
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
 #define HAVE_ARCH_BUG_FORMAT_ARGS
 #endif
 
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
 #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
 	__BUG_ENTRY_FORMAT(format)					\
 	__BUG_ENTRY_VERBOSE(file, line)					\
 	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
 	"99:\n"								\
 	"\t.string \"\"\n"						\
 	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
 	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
 
 #else
diff --git a/rust/Makefile b/rust/Makefile
index da1a7409d984..8f253d516495 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -46,7 +46,7 @@ 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
+always-$(subst y,$(CONFIG_RUST),$(CONFIG_BUG)) += kernel/generated_arch_warn_asm.rs kernel/generated_arch_reachable_asm.rs kernel/generated_arch_warn_asm_trap_label.rs
 endif
 
 # Avoids running `$(RUSTC)` when it may not be available.
diff --git a/rust/kernel/.gitignore b/rust/kernel/.gitignore
index f636ad95aaf3..05bb00bbb83c 100644
--- a/rust/kernel/.gitignore
+++ b/rust/kernel/.gitignore
@@ -3,3 +3,4 @@
 /generated_arch_static_branch_asm.rs
 /generated_arch_warn_asm.rs
 /generated_arch_reachable_asm.rs
+/generated_arch_warn_asm_trap_label.rs
diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
index e6a5c7a795f0..8cd7c8f91b07 100644
--- a/rust/kernel/Kconfig.test
+++ b/rust/kernel/Kconfig.test
@@ -83,4 +83,19 @@ config RUST_BITFIELD_KUNIT_TEST
 
 	  If unsure, say N.
 
+config RUST_BUG_WARN_ASM_KUNIT_TEST
+	bool "KUnit tests for ARCH_WARN_ASM bug table emission" if !KUNIT_ALL_TESTS
+	depends on GENERIC_BUG && !ARM && !LOONGARCH && !UML
+	default KUNIT_ALL_TESTS
+	help
+	  This option enables KUnit tests that verify ARCH_WARN_ASM emits a
+	  correct __bug_table entry: the bug_addr displacement must resolve
+	  back to the trap instruction so that find_bug() can locate the
+	  entry — exactly as the real trap handler does.
+
+	  Supported on all architectures that implement ARCH_WARN_ASM
+	  (x86, powerpc, riscv, arm64, s390).
+
+	  If unsure, say N.
+
 endif
diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs
index 3566f0234ca4..9e9b6d3a68b7 100644
--- a/rust/kernel/bug.rs
+++ b/rust/kernel/bug.rs
@@ -152,3 +152,194 @@ macro_rules! warn_on {
         cond
     }};
 }
+
+// Test-only constants and file static referenced by the global_asm block below.
+//
+// global_asm! is file-scope and always emitted — LLVM cannot eliminate it,
+// unlike asm! inside a function which is subject to dead-code removal.
+//
+// BUG_KUNIT_TRAP_ADDR is declared as a .global symbol entirely inside the
+// global_asm! block so the .dc.a {trap_label}b relocation lands directly on it.
+// A Rust static initialized to zero would end up in BSS; the linker does
+// not apply relocations to BSS, so the address would stay zero at runtime.
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+mod test_statics {
+    use crate::bindings::{bug_entry, BUGFLAG_WARNING, TAINT_WARN};
+
+    pub(super) const FLAGS: u32 = BUGFLAG_WARNING | (TAINT_WARN << 8);
+    pub(super) const SIZE: usize = core::mem::size_of::<bug_entry>();
+
+    // LINE and BUG_KUNIT_FILE are only referenced by the {file}/{line}
+    // operands in global_asm!, which are only present when
+    // CONFIG_DEBUG_BUGVERBOSE is set (the non-verbose _EMIT_BUG_ENTRY
+    // drops the file/line fields from the bug table entry entirely).
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    pub(super) const LINE: u32 = line!();
+
+    // Null-terminated source file name — the assembler references this symbol
+    // for the verbose file pointer in __bug_table, same as warn_flags!.
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    const _FILE: &[u8] = file!().as_bytes();
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    #[no_mangle]
+    pub(super) static BUG_KUNIT_FILE: [u8; _FILE.len() + 1] = {
+        let mut bytes = [0u8; _FILE.len() + 1];
+        let mut i = 0;
+        while i < _FILE.len() {
+            bytes[i] = _FILE[i];
+            i += 1;
+        }
+        bytes
+    };
+}
+
+// Emit ARCH_WARN_ASM at file scope and capture the trap address.
+//
+// The asm string mirrors warn_flags!() exactly — same "/* {size} */" prefix,
+// same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs includes,
+// same operands — so the two stay in sync as the macro evolves.  The only
+// addition is the .pushsection/.dc.a/.popsection tail that records the trap
+// address into BUG_KUNIT_TRAP_ADDR at link time.
+//
+// BUG_KUNIT_TRAP_ADDR is a .global symbol in .data whose value is set by a
+// .dc.a relocation to the arch's trap label (from
+// generated_arch_warn_asm_trap_label.rs); .dc.a self-aligns to pointer width
+// so no .balign is needed.
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, CONFIG_DEBUG_BUGVERBOSE))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    file  = sym test_statics::BUG_KUNIT_FILE,
+    line  = const test_statics::LINE,
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, not(CONFIG_DEBUG_BUGVERBOSE)))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+#[::kernel::macros::kunit_tests(rust_kernel_bug_warn_asm)]
+mod tests {
+    use crate::bindings;
+
+    fn trap_addr() -> usize {
+        // BUG_KUNIT_TRAP_ADDR is a .global symbol defined in the global_asm!
+        // block above, placed in .data at the exact .dc.a 1b relocation word.
+        // The linker resolves it to the virtual address of the twi instruction
+        // before any Rust code runs, so reading it here is always safe.
+        extern "C" {
+            // .dc.a emits a pointer-width word: 4 bytes on ppc32, 8 on ppc64.
+            // usize matches the native pointer width on both.
+            static BUG_KUNIT_TRAP_ADDR: usize;
+        }
+        // SAFETY: read-only after link time, no concurrent mutation possible.
+        unsafe { BUG_KUNIT_TRAP_ADDR }
+    }
+
+    /// The `__bug_table` entry emitted by `ARCH_WARN_ASM` must be locatable
+    /// via `find_bug()` using the trap instruction's address.  A NULL result
+    /// means the `1b` label reference in `_EMIT_BUG_ENTRY` resolved to the
+    /// wrong address and the real trap handler would not recognise the site.
+    #[test]
+    fn bug_entry_found() {
+        // Non-zero proves the .dc.a relocation was resolved by the linker.
+        assert!(trap_addr() != 0);
+
+        // SAFETY: find_bug() is always safe to call with any address; it
+        // simply walks __bug_table and returns NULL if nothing matches.
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        // Non-NULL proves the bug_addr displacement in _EMIT_BUG_ENTRY is correct.
+        assert!(!entry.is_null());
+    }
+
+    /// The emitted entry must be flagged as a warning (not a hard BUG).
+    #[test]
+    fn bug_entry_is_warning() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+        // SAFETY: entry is non-null and points to a valid bug_entry.
+        let flags = unsafe { (*entry).flags } as u32;
+        assert!(flags & bindings::BUGFLAG_WARNING != 0);
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the entry must record a non-null file
+    /// pointer pointing back into this source file.
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_file() {
+        use kernel::str::CStrExt;
+
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid; file_ptr and line are local
+        // variables passed as out-parameters.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(!file_ptr.is_null());
+        // SAFETY: file_ptr is a non-null, null-terminated C string from BUG_KUNIT_FILE,
+        // valid for the lifetime of this function.
+        let file_str = unsafe { kernel::prelude::CStr::from_char_ptr(file_ptr) }
+            .to_str()
+            .unwrap_or("");
+        assert!(file_str.contains("bug"));
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the recorded line number must be
+    /// non-zero (a zero line would mean the asm operand was not substituted).
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_line() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(line != 0);
+    }
+
+    /// The trap address stored in `__bug_table` must lie within the kernel
+    /// text segment.  If the label reference in `_EMIT_BUG_ENTRY` resolved
+    /// to data or zero, `kernel_text_address()` would return false.
+    #[test]
+    fn bug_entry_addr_is_in_text() {
+        assert!(trap_addr() != 0);
+        // SAFETY: kernel_text_address() is always safe to call with any addr.
+        let in_text = unsafe { bindings::kernel_text_address(trap_addr()) };
+        assert!(in_text != 0);
+    }
+}
diff --git a/rust/kernel/generated_arch_warn_asm_trap_label.rs.S b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
new file mode 100644
index 000000000000..d7bed8751f10
--- /dev/null
+++ b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/bug.h>
+
+// Cut here.
+
+::kernel::concat_literals!(ARCH_WARN_ASM_TRAP_LABEL)
-- 
2.55.0

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Peter Zijlstra <peterz@infradead.org>
Date: 2026-09-22 06:48:04

On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, 
quoted hunk
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
 #define HAVE_ARCH_BUG_FORMAT_ARGS
 #endif
 
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
 #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
 	__BUG_ENTRY_FORMAT(format)					\
 	__BUG_ENTRY_VERBOSE(file, line)					\
 	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
 	"99:\n"								\
 	"\t.string \"\"\n"						\
 	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
 	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
 
 #else
Not really a fan of that. And I can't really tell what you're doing with
it either. The kunit is in Rust and thus unreadable :-(

I would rather you fix up is_valid_bugaddr(), some architectures seem to
have an always true stub because of the callchains always being from the
break instruction.

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-23 06:28:45

On Tue, Sep 22, 2026 at 08:47:35AM +0200, Peter Zijlstra wrote:
On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, 
quoted
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
 #define HAVE_ARCH_BUG_FORMAT_ARGS
 #endif
 
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
 #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
 	__BUG_ENTRY_FORMAT(format)					\
 	__BUG_ENTRY_VERBOSE(file, line)					\
 	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
 	"99:\n"								\
 	"\t.string \"\"\n"						\
 	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
 	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
 
 #else
Not really a fan of that. And I can't really tell what you're doing with
it either. The kunit is in Rust and thus unreadable :-(

I would rather you fix up is_valid_bugaddr(), some architectures seem to
have an always true stub because of the callchains always being from the
break instruction.
Hey Peter,

thanks for the suggestion. I will fix up the is_valid_bugaddr for
powerpc. I am not very good with other archs asm but i can do it for
ppc32 and ppc64.

Regards,
Mukesh

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Peter Zijlstra <peterz@infradead.org>
Date: 2026-09-24 11:16:30

On Wed, Sep 23, 2026 at 11:58:13AM +0530, Mukesh Kumar Chaurasiya wrote:
On Tue, Sep 22, 2026 at 08:47:35AM +0200, Peter Zijlstra wrote:
quoted
On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, 
quoted
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
 #define HAVE_ARCH_BUG_FORMAT_ARGS
 #endif
 
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
 #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
 	__BUG_ENTRY_FORMAT(format)					\
 	__BUG_ENTRY_VERBOSE(file, line)					\
 	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
 	"99:\n"								\
 	"\t.string \"\"\n"						\
 	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
 	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
 
 #else
Not really a fan of that. And I can't really tell what you're doing with
it either. The kunit is in Rust and thus unreadable :-(

I would rather you fix up is_valid_bugaddr(), some architectures seem to
have an always true stub because of the callchains always being from the
break instruction.
Hey Peter,

thanks for the suggestion. I will fix up the is_valid_bugaddr for
powerpc. I am not very good with other archs asm but i can do it for
ppc32 and ppc64.
Untested thingies for arm64 and s390x.
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 914282016069..3d8a969eab5f 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -987,14 +987,13 @@ void do_serror(struct pt_regs *regs, unsigned long esr)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	/*
-	 * bug_brk_handler() only called for BRK #BUG_BRK_IMM.
-	 * So the answer is trivial -- any spurious instances with no
-	 * bug table entry will be rejected by report_bug() and passed
-	 * back to the debug-monitors code and handled as a fatal
-	 * unexpected debug exception.
-	 */
-	return 1;
+	u32 insn;
+
+	if (aarch64_insn_read((u32 *)addr, &insn))
+		return 0;
+
+	/* Match ASM_BUG_FLAGS() / BUG() instructions. */
+	return insn == (0xd4200000 | (BUG_BRK_IMM << 5));
 }
 #endif
 
diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c
index b6ba4465f59d..8bbb082fd5e3 100644
--- a/arch/s390/kernel/traps.c
+++ b/arch/s390/kernel/traps.c
@@ -26,6 +26,7 @@
 #include <linux/entry-common.h>
 #include <linux/kmsan.h>
 #include <linux/bug.h>
+#include <linux/uaccess.h>
 #include <asm/entry-percpu.h>
 #include <asm/asm-extable.h>
 #include <asm/irqflags.h>
@@ -55,7 +56,12 @@ static inline void __user *get_trap_ip(struct pt_regs *regs)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	return 1;
+	u16 insn;
+
+	if (get_kernel_nofault(insn, (u16 *)addr))
+		return 0;
+
+	return insn == 0x0001 || insn == 0x000e;
 }
 #endif
 

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mark Rutland <mark.rutland@arm.com>
Date: 2026-09-24 12:32:20

On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)
I'm not a fan of exposing such a low-level implementation detail out of
the architecture code, and I don't think we should expose this.

What exactly are you trying to verify here, and why? Has there been
some _specific_ issue on powerpc that caused you to look at this?

Mark.

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mark Rutland <mark.rutland@arm.com>
Date: 2026-09-24 12:47:27

On Thu, Sep 24, 2026 at 01:15:57PM +0200, Peter Zijlstra wrote:
On Wed, Sep 23, 2026 at 11:58:13AM +0530, Mukesh Kumar Chaurasiya wrote:
quoted
On Tue, Sep 22, 2026 at 08:47:35AM +0200, Peter Zijlstra wrote:
quoted
On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, 
quoted hunk
quoted
quoted
Not really a fan of that. And I can't really tell what you're doing with
it either. The kunit is in Rust and thus unreadable :-(

I would rather you fix up is_valid_bugaddr(), some architectures seem to
have an always true stub because of the callchains always being from the
break instruction.
Hey Peter,

thanks for the suggestion. I will fix up the is_valid_bugaddr for
powerpc. I am not very good with other archs asm but i can do it for
ppc32 and ppc64.
Untested thingies for arm64 and s390x.
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 914282016069..3d8a969eab5f 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -987,14 +987,13 @@ void do_serror(struct pt_regs *regs, unsigned long esr)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	/*
-	 * bug_brk_handler() only called for BRK #BUG_BRK_IMM.
-	 * So the answer is trivial -- any spurious instances with no
-	 * bug table entry will be rejected by report_bug() and passed
-	 * back to the debug-monitors code and handled as a fatal
-	 * unexpected debug exception.
-	 */
-	return 1;
+	u32 insn;
+
+	if (aarch64_insn_read((u32 *)addr, &insn))
+		return 0;
+
+	/* Match ASM_BUG_FLAGS() / BUG() instructions. */
+	return insn == (0xd4200000 | (BUG_BRK_IMM << 5));
 }
 #endif
What problem is this trying to solve?

Mukesh's original patch seems to be trying to check whether we compiled
things correctly (such that the bug_table entry points at the expected
insturction), and I don't think that warrants a runtime check, but (as
per my reply to him) I don't understand why that's a thing to check in
the first place.

On arm64 we only call is_valid_bugaddr() under do_el1_brk64() ->
call_el1_break_hook() -> bug_brk_handler(). We only call
bug_brk_handler() when HW has taken a BRK exception with the BUG_BRK_IMM
immediate, and that can only be triggered by executing the right
instruction (BRK #BUG_BRK_IMM), which we ONLY use for BUG(). and WARN().

So I don't see why any change is necessary here.

If the HW has somehow taken a BRK exception from any other instruction
(which would presumably be a HW bug), there won't be a bug_table entry,
and so __report_bug() will fail to find an entry, and will return
BUG_TRAP_TYPE_NONE, same as for the !is_valid_bugaddr() case.

Mark.  

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Peter Zijlstra <peterz@infradead.org>
Date: 2026-09-24 12:53:18

On Thu, Sep 24, 2026 at 01:46:59PM +0100, Mark Rutland wrote:
quoted
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 914282016069..3d8a969eab5f 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -987,14 +987,13 @@ void do_serror(struct pt_regs *regs, unsigned long esr)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	/*
-	 * bug_brk_handler() only called for BRK #BUG_BRK_IMM.
-	 * So the answer is trivial -- any spurious instances with no
-	 * bug table entry will be rejected by report_bug() and passed
-	 * back to the debug-monitors code and handled as a fatal
-	 * unexpected debug exception.
-	 */
-	return 1;
+	u32 insn;
+
+	if (aarch64_insn_read((u32 *)addr, &insn))
+		return 0;
+
+	/* Match ASM_BUG_FLAGS() / BUG() instructions. */
+	return insn == (0xd4200000 | (BUG_BRK_IMM << 5));
 }
 #endif
What problem is this trying to solve?

Mukesh's original patch seems to be trying to check whether we compiled
things correctly (such that the bug_table entry points at the expected
insturction), and I don't think that warrants a runtime check, but (as
per my reply to him) I don't understand why that's a thing to check in
the first place.

On arm64 we only call is_valid_bugaddr() under do_el1_brk64() ->
call_el1_break_hook() -> bug_brk_handler(). We only call
bug_brk_handler() when HW has taken a BRK exception with the BUG_BRK_IMM
immediate, and that can only be triggered by executing the right
instruction (BRK #BUG_BRK_IMM), which we ONLY use for BUG(). and WARN().

So I don't see why any change is necessary here.

If the HW has somehow taken a BRK exception from any other instruction
(which would presumably be a HW bug), there won't be a bug_table entry,
and so __report_bug() will fail to find an entry, and will return
BUG_TRAP_TYPE_NONE, same as for the !is_valid_bugaddr() case.
Right, so I wasn't at all sure what he was doing, Rust is still mostly
unreadable garbage to me.

But I figured I'd much rather see him use is_valid_bugaddr() to do
runtime checks than whatever dodgy games he was playing with that label.

So *if* doing runtime checks is what is wanted, then is_valid_bugaddr()
needs to actually work, because then it grows a user outside of the BRK
handler.

Lots of IFs etc, but since I am having a head-ache and am mostly
useless, I figured I'd distract myself doing whatever :-)

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: "Christophe Leroy (CS GROUP)" <chleroy@kernel.org>
Date: 2026-09-24 13:01:22


Le 22/09/2026 à 07:50, Mukesh Kumar Chaurasiya (IBM) a écrit :
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

   x86       "1"     (ud2 at label 1:)
   powerpc   "1"     (twi at label 1:)
Shouldn't it also handle twnei and tdnei used for WARN_ON() and BUG_ON() ?

Christophe

quoted hunk
   riscv     "1"     (ebreak at label 1:)
   arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
   s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, and in
the new generated_arch_warn_asm_trap_label.rs template, which the
C preprocessor expands at build time into a .rs file included by the
test's global_asm! block.

global_asm! is used instead of asm! because LLVM eliminates asm! blocks
in dead branches; global_asm! is file-scope and always emitted.
BUG_KUNIT_TRAP_ADDR is defined as a .global symbol in .data on top of a
.dc.a {trap_label}b relocation so the linker resolves the trap address
into it at link time. A Rust static initialized to zero would land in
BSS where relocations are not applied.

The global_asm! string mirrors warn_flags!() exactly — same "/* {size} */"
prefix, same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs
includes, same operands — plus the .pushsection/.dc.a/.popsection tail.
This keeps the test structurally in sync with the macro it exercises.

The CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST option depends on GENERIC_BUG
and excludes ARM, LOONGARCH, and UML (which use a different warn_flags!
path without ARCH_WARN_ASM).

Five tests are included in the rust_kernel_bug_warn_asm suite:

   bug_entry_found           - find_bug() returns non-NULL for the trap
                               address, proving bug_addr is correct
   bug_entry_is_warning      - the emitted entry has BUGFLAG_WARNING set
   bug_entry_file            - bug_get_file_line() returns the correct
                               source file (CONFIG_DEBUG_BUGVERBOSE only)
   bug_entry_line            - recorded line number is non-zero, confirming
                               the {line} operand was substituted correctly
                               (CONFIG_DEBUG_BUGVERBOSE only)
   bug_entry_addr_is_in_text - kernel_text_address() confirms the trap
                               address lies in kernel text

Tested (pass:5 fail:0 skip:0) on:
   x86_64  - QEMU x86_64
   powerpc - QEMU pseries ppc64le (pseries_le_defconfig)
   riscv   - QEMU riscv64
   arm64   - QEMU aarch64
   s390    - QEMU s390x

Signed-off-by: Mukesh Kumar Chaurasiya (IBM) <redacted>
---
Note: I sent this out as a powerpc specific tests earlier. During review
process some people wanted these to be arch independent. Hence sending
this out as an RFC.

Earlier version: https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flore.kernel.org%2Fall%2F20260915090453.1227034-3-mkchauras%40gmail.com%2F&data=05%7C02%7Cchristophe.leroy%40csgroup.eu%7C2fb25d39088f4c4e7ba308df186d76f5%7C8b87af7d86474dc78df45f69a2011bb5%7C0%7C0%7C639256530592353420%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=lzso63z76C74rUe7h%2FG%2FEkDi1yXK0oPXBVrlG427Z%2BM%3D&reserved=0

  arch/arm64/include/asm/asm-bug.h              |   3 +
  arch/powerpc/include/asm/bug.h                |  22 +-
  arch/riscv/include/asm/bug.h                  |   5 +-
  arch/s390/include/asm/bug.h                   |   4 +-
  arch/x86/include/asm/bug.h                    |   5 +-
  rust/Makefile                                 |   2 +-
  rust/kernel/.gitignore                        |   1 +
  rust/kernel/Kconfig.test                      |  15 ++
  rust/kernel/bug.rs                            | 191 ++++++++++++++++++
  .../generated_arch_warn_asm_trap_label.rs.S   |   7 +
  10 files changed, 239 insertions(+), 16 deletions(-)
  create mode 100644 rust/kernel/generated_arch_warn_asm_trap_label.rs.S
diff --git a/arch/arm64/include/asm/asm-bug.h b/arch/arm64/include/asm/asm-bug.h
index a5f13801b784..e0694543115e 100644
--- a/arch/arm64/include/asm/asm-bug.h
+++ b/arch/arm64/include/asm/asm-bug.h
@@ -60,6 +60,9 @@ _BUGVERBOSE_LOCATION(__FILE__, __LINE__)		\
  		".short " flags ";"			\
  		__stringify(__BUG_ENTRY_END)
  
+/* Trap instruction is placed at label 14471: by __BUG_ENTRY_END */
+#define ARCH_WARN_ASM_TRAP_LABEL "14471"
+
  #define ARCH_WARN_ASM(file, line, flags, size)		\
  	__BUG_ENTRY_STRING(file, line, flags)		\
  	__stringify(brk BUG_BRK_IMM)
diff --git a/arch/powerpc/include/asm/bug.h b/arch/powerpc/include/asm/bug.h
index bf31ee1e902a..f0333c564d4f 100644
--- a/arch/powerpc/include/asm/bug.h
+++ b/arch/powerpc/include/asm/bug.h
@@ -32,6 +32,8 @@
  #endif /* verbose */
  
  #else /* !__ASSEMBLER__ */
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
+
  #ifdef CONFIG_DEBUG_BUGVERBOSE
  #define _EMIT_BUG_ENTRY(bug_entry, trap, file, line, flags)	\
  	".section __bug_table,\"aw\"\n"				\
@@ -45,21 +47,21 @@
  	"		.short " flags "\n"
  #endif
  
-#define BUG_ENTRY(cond_str, insn, flags, ...)			\
-	__asm__ __volatile__(					\
-		"1:	" insn "\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")	\
-		".org 2b+%3\n"					\
-		".previous\n"					\
+#define BUG_ENTRY(cond_str, insn, flags, ...)				\
+	__asm__ __volatile__(						\
+		ARCH_WARN_ASM_TRAP_LABEL ":	" insn "\n"		\
+		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")		\
+		".org 2b+%3\n"						\
+		".previous\n"						\
  		: : "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__),	\
  		  "i" (flags),					\
  		  "i" (sizeof(struct bug_entry)),		\
  		  ##__VA_ARGS__)
  
-#define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:	twi 31, 0, 0\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)	\
-		".org 2b+" size "\n"				\
+#define ARCH_WARN_ASM(file, line, flags, size)					\
+		ARCH_WARN_ASM_TRAP_LABEL ":	twi 31, 0, 0\n"			\
+		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)			\
+		".org 2b+" size "\n"						\
  		".previous\n"
  
  #define ARCH_WARN_REACHABLE
diff --git a/arch/riscv/include/asm/bug.h b/arch/riscv/include/asm/bug.h
index 699c0cf3e4ef..def6b0d153f2 100644
--- a/arch/riscv/include/asm/bug.h
+++ b/arch/riscv/include/asm/bug.h
@@ -29,7 +29,8 @@
  
  typedef u32 bug_insn_t;
  
-#define __BUG_ENTRY_ADDR	RISCV_INT " 1b - ."
+#define ARCH_WARN_ASM_TRAP_LABEL	"1"
+#define __BUG_ENTRY_ADDR	RISCV_INT " " ARCH_WARN_ASM_TRAP_LABEL "b - ."
  #define __BUG_ENTRY_FILE(file)	RISCV_INT " " file " - ."
  
  #ifdef CONFIG_DEBUG_BUGVERBOSE
@@ -47,7 +48,7 @@ typedef u32 bug_insn_t;
  #ifdef CONFIG_GENERIC_BUG
  
  #define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:\n\t"					\
+		ARCH_WARN_ASM_TRAP_LABEL ":\n\t"		\
  			"ebreak\n"				\
  			".pushsection __bug_table,\"aw\"\n\t"	\
  		"2:\n\t"					\
diff --git a/arch/s390/include/asm/bug.h b/arch/s390/include/asm/bug.h
index 2010342c97b1..3c0fa1acc92a 100644
--- a/arch/s390/include/asm/bug.h
+++ b/arch/s390/include/asm/bug.h
@@ -26,9 +26,11 @@
  #define WARN_CONDITION_STR(cond_str) ""
  #endif
  
+#define ARCH_WARN_ASM_TRAP_LABEL "0"
+
  #define __BUG_ENTRY(format, file, line, flags, size)			\
  		"	.section __bug_table,\"aw\"\n"			\
-		"1:	.long	0b - .		# bug_entry::bug_addr\n"\
+		"1:	.long	" ARCH_WARN_ASM_TRAP_LABEL "b - .	# bug_entry::bug_addr\n" \
  		"	.long	" format " - .	# bug_entry::format\n"	\
  		__BUG_ENTRY_VERBOSE(file, line)				\
  		"	.short	"flags"		# bug_entry::flags\n"	\
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
  #define HAVE_ARCH_BUG_FORMAT_ARGS
  #endif
  
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
  #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
  	__BUG_ENTRY_FORMAT(format)					\
  	__BUG_ENTRY_VERBOSE(file, line)					\
  	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
  	"99:\n"								\
  	"\t.string \"\"\n"						\
  	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
  	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
  
  #else
diff --git a/rust/Makefile b/rust/Makefile
index da1a7409d984..8f253d516495 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -46,7 +46,7 @@ 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
+always-$(subst y,$(CONFIG_RUST),$(CONFIG_BUG)) += kernel/generated_arch_warn_asm.rs kernel/generated_arch_reachable_asm.rs kernel/generated_arch_warn_asm_trap_label.rs
  endif
  
  # Avoids running `$(RUSTC)` when it may not be available.
diff --git a/rust/kernel/.gitignore b/rust/kernel/.gitignore
index f636ad95aaf3..05bb00bbb83c 100644
--- a/rust/kernel/.gitignore
+++ b/rust/kernel/.gitignore
@@ -3,3 +3,4 @@
  /generated_arch_static_branch_asm.rs
  /generated_arch_warn_asm.rs
  /generated_arch_reachable_asm.rs
+/generated_arch_warn_asm_trap_label.rs
diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
index e6a5c7a795f0..8cd7c8f91b07 100644
--- a/rust/kernel/Kconfig.test
+++ b/rust/kernel/Kconfig.test
@@ -83,4 +83,19 @@ config RUST_BITFIELD_KUNIT_TEST
  
  	  If unsure, say N.
  
+config RUST_BUG_WARN_ASM_KUNIT_TEST
+	bool "KUnit tests for ARCH_WARN_ASM bug table emission" if !KUNIT_ALL_TESTS
+	depends on GENERIC_BUG && !ARM && !LOONGARCH && !UML
+	default KUNIT_ALL_TESTS
+	help
+	  This option enables KUnit tests that verify ARCH_WARN_ASM emits a
+	  correct __bug_table entry: the bug_addr displacement must resolve
+	  back to the trap instruction so that find_bug() can locate the
+	  entry — exactly as the real trap handler does.
+
+	  Supported on all architectures that implement ARCH_WARN_ASM
+	  (x86, powerpc, riscv, arm64, s390).
+
+	  If unsure, say N.
+
  endif
diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs
index 3566f0234ca4..9e9b6d3a68b7 100644
--- a/rust/kernel/bug.rs
+++ b/rust/kernel/bug.rs
@@ -152,3 +152,194 @@ macro_rules! warn_on {
          cond
      }};
  }
+
+// Test-only constants and file static referenced by the global_asm block below.
+//
+// global_asm! is file-scope and always emitted — LLVM cannot eliminate it,
+// unlike asm! inside a function which is subject to dead-code removal.
+//
+// BUG_KUNIT_TRAP_ADDR is declared as a .global symbol entirely inside the
+// global_asm! block so the .dc.a {trap_label}b relocation lands directly on it.
+// A Rust static initialized to zero would end up in BSS; the linker does
+// not apply relocations to BSS, so the address would stay zero at runtime.
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+mod test_statics {
+    use crate::bindings::{bug_entry, BUGFLAG_WARNING, TAINT_WARN};
+
+    pub(super) const FLAGS: u32 = BUGFLAG_WARNING | (TAINT_WARN << 8);
+    pub(super) const SIZE: usize = core::mem::size_of::<bug_entry>();
+
+    // LINE and BUG_KUNIT_FILE are only referenced by the {file}/{line}
+    // operands in global_asm!, which are only present when
+    // CONFIG_DEBUG_BUGVERBOSE is set (the non-verbose _EMIT_BUG_ENTRY
+    // drops the file/line fields from the bug table entry entirely).
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    pub(super) const LINE: u32 = line!();
+
+    // Null-terminated source file name — the assembler references this symbol
+    // for the verbose file pointer in __bug_table, same as warn_flags!.
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    const _FILE: &[u8] = file!().as_bytes();
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    #[no_mangle]
+    pub(super) static BUG_KUNIT_FILE: [u8; _FILE.len() + 1] = {
+        let mut bytes = [0u8; _FILE.len() + 1];
+        let mut i = 0;
+        while i < _FILE.len() {
+            bytes[i] = _FILE[i];
+            i += 1;
+        }
+        bytes
+    };
+}
+
+// Emit ARCH_WARN_ASM at file scope and capture the trap address.
+//
+// The asm string mirrors warn_flags!() exactly — same "/* {size} */" prefix,
+// same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs includes,
+// same operands — so the two stay in sync as the macro evolves.  The only
+// addition is the .pushsection/.dc.a/.popsection tail that records the trap
+// address into BUG_KUNIT_TRAP_ADDR at link time.
+//
+// BUG_KUNIT_TRAP_ADDR is a .global symbol in .data whose value is set by a
+// .dc.a relocation to the arch's trap label (from
+// generated_arch_warn_asm_trap_label.rs); .dc.a self-aligns to pointer width
+// so no .balign is needed.
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, CONFIG_DEBUG_BUGVERBOSE))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    file  = sym test_statics::BUG_KUNIT_FILE,
+    line  = const test_statics::LINE,
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, not(CONFIG_DEBUG_BUGVERBOSE)))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+#[::kernel::macros::kunit_tests(rust_kernel_bug_warn_asm)]
+mod tests {
+    use crate::bindings;
+
+    fn trap_addr() -> usize {
+        // BUG_KUNIT_TRAP_ADDR is a .global symbol defined in the global_asm!
+        // block above, placed in .data at the exact .dc.a 1b relocation word.
+        // The linker resolves it to the virtual address of the twi instruction
+        // before any Rust code runs, so reading it here is always safe.
+        extern "C" {
+            // .dc.a emits a pointer-width word: 4 bytes on ppc32, 8 on ppc64.
+            // usize matches the native pointer width on both.
+            static BUG_KUNIT_TRAP_ADDR: usize;
+        }
+        // SAFETY: read-only after link time, no concurrent mutation possible.
+        unsafe { BUG_KUNIT_TRAP_ADDR }
+    }
+
+    /// The `__bug_table` entry emitted by `ARCH_WARN_ASM` must be locatable
+    /// via `find_bug()` using the trap instruction's address.  A NULL result
+    /// means the `1b` label reference in `_EMIT_BUG_ENTRY` resolved to the
+    /// wrong address and the real trap handler would not recognise the site.
+    #[test]
+    fn bug_entry_found() {
+        // Non-zero proves the .dc.a relocation was resolved by the linker.
+        assert!(trap_addr() != 0);
+
+        // SAFETY: find_bug() is always safe to call with any address; it
+        // simply walks __bug_table and returns NULL if nothing matches.
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        // Non-NULL proves the bug_addr displacement in _EMIT_BUG_ENTRY is correct.
+        assert!(!entry.is_null());
+    }
+
+    /// The emitted entry must be flagged as a warning (not a hard BUG).
+    #[test]
+    fn bug_entry_is_warning() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+        // SAFETY: entry is non-null and points to a valid bug_entry.
+        let flags = unsafe { (*entry).flags } as u32;
+        assert!(flags & bindings::BUGFLAG_WARNING != 0);
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the entry must record a non-null file
+    /// pointer pointing back into this source file.
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_file() {
+        use kernel::str::CStrExt;
+
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid; file_ptr and line are local
+        // variables passed as out-parameters.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(!file_ptr.is_null());
+        // SAFETY: file_ptr is a non-null, null-terminated C string from BUG_KUNIT_FILE,
+        // valid for the lifetime of this function.
+        let file_str = unsafe { kernel::prelude::CStr::from_char_ptr(file_ptr) }
+            .to_str()
+            .unwrap_or("");
+        assert!(file_str.contains("bug"));
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the recorded line number must be
+    /// non-zero (a zero line would mean the asm operand was not substituted).
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_line() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(line != 0);
+    }
+
+    /// The trap address stored in `__bug_table` must lie within the kernel
+    /// text segment.  If the label reference in `_EMIT_BUG_ENTRY` resolved
+    /// to data or zero, `kernel_text_address()` would return false.
+    #[test]
+    fn bug_entry_addr_is_in_text() {
+        assert!(trap_addr() != 0);
+        // SAFETY: kernel_text_address() is always safe to call with any addr.
+        let in_text = unsafe { bindings::kernel_text_address(trap_addr()) };
+        assert!(in_text != 0);
+    }
+}
diff --git a/rust/kernel/generated_arch_warn_asm_trap_label.rs.S b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
new file mode 100644
index 000000000000..d7bed8751f10
--- /dev/null
+++ b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/bug.h>
+
+// Cut here.
+
+::kernel::concat_literals!(ARCH_WARN_ASM_TRAP_LABEL)

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mark Rutland <mark.rutland@arm.com>
Date: 2026-09-24 13:06:28

On Thu, Sep 24, 2026 at 02:52:53PM +0200, Peter Zijlstra wrote:
On Thu, Sep 24, 2026 at 01:46:59PM +0100, Mark Rutland wrote:
quoted
What problem is this trying to solve?

Mukesh's original patch seems to be trying to check whether we compiled
things correctly (such that the bug_table entry points at the expected
insturction), and I don't think that warrants a runtime check, but (as
per my reply to him) I don't understand why that's a thing to check in
the first place.

On arm64 we only call is_valid_bugaddr() under do_el1_brk64() ->
call_el1_break_hook() -> bug_brk_handler(). We only call
bug_brk_handler() when HW has taken a BRK exception with the BUG_BRK_IMM
immediate, and that can only be triggered by executing the right
instruction (BRK #BUG_BRK_IMM), which we ONLY use for BUG(). and WARN().

So I don't see why any change is necessary here.

If the HW has somehow taken a BRK exception from any other instruction
(which would presumably be a HW bug), there won't be a bug_table entry,
and so __report_bug() will fail to find an entry, and will return
BUG_TRAP_TYPE_NONE, same as for the !is_valid_bugaddr() case.
Right, so I wasn't at all sure what he was doing, Rust is still mostly
unreadable garbage to me.

But I figured I'd much rather see him use is_valid_bugaddr() to do
runtime checks than whatever dodgy games he was playing with that label.

So *if* doing runtime checks is what is wanted, then is_valid_bugaddr()
needs to actually work, because then it grows a user outside of the BRK
handler.
Thanks; I think we're aligned. :)

Before we delve into implementation details, I'd like to understand the
problem Mukesh is trying to solve.

Mark.

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Heiko Carstens <hca@linux.ibm.com>
Date: 2026-09-24 15:14:34

On Thu, Sep 24, 2026 at 01:15:57PM +0200, Peter Zijlstra wrote:
Untested thingies for arm64 and s390x.
...
quoted hunk
diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c
index b6ba4465f59d..8bbb082fd5e3 100644
--- a/arch/s390/kernel/traps.c
+++ b/arch/s390/kernel/traps.c
@@ -26,6 +26,7 @@
 #include <linux/entry-common.h>
 #include <linux/kmsan.h>
 #include <linux/bug.h>
+#include <linux/uaccess.h>
 #include <asm/entry-percpu.h>
 #include <asm/asm-extable.h>
 #include <asm/irqflags.h>
@@ -55,7 +56,12 @@ static inline void __user *get_trap_ip(struct pt_regs *regs)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	return 1;
+	u16 insn;
+
+	if (get_kernel_nofault(insn, (u16 *)addr))
+		return 0;
+
+	return insn == 0x0001 || insn == 0x000e;
I have no idea why anybody wants to do this, but it looks like I'm not
alone :) In any case: the check is wrong.
s390 uses only the monitor call instruction and this should be

	return insn == 0xaf00;

At least it is not possible to set a kprobe on bug instructions, which
would make this "interesting".

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-25 06:22:13

On Thu, Sep 24, 2026 at 01:31:42PM +0100, Mark Rutland wrote:
On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)
I'm not a fan of exposing such a low-level implementation detail out of
the architecture code, and I don't think we should expose this.

What exactly are you trying to verify here, and why? Has there been
some _specific_ issue on powerpc that caused you to look at this?

Mark.
Hey Mark,

During implementing ARCH_WARN_ASM for rust I broke the bug entry for
powerpc so I wrote this to verify. I sent it out mainly for ppc but
few people wanted that if it needed it should be done for all archs.
So separated this thing and sent it out as an RFC to check whether
other archs want this or not.

Regards,
Mukesh

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-25 06:33:05

On Thu, Sep 24, 2026 at 02:05:57PM +0100, Mark Rutland wrote:
On Thu, Sep 24, 2026 at 02:52:53PM +0200, Peter Zijlstra wrote:
quoted
On Thu, Sep 24, 2026 at 01:46:59PM +0100, Mark Rutland wrote:
quoted
What problem is this trying to solve?

Mukesh's original patch seems to be trying to check whether we compiled
things correctly (such that the bug_table entry points at the expected
insturction), and I don't think that warrants a runtime check, but (as
per my reply to him) I don't understand why that's a thing to check in
the first place.

On arm64 we only call is_valid_bugaddr() under do_el1_brk64() ->
call_el1_break_hook() -> bug_brk_handler(). We only call
bug_brk_handler() when HW has taken a BRK exception with the BUG_BRK_IMM
immediate, and that can only be triggered by executing the right
instruction (BRK #BUG_BRK_IMM), which we ONLY use for BUG(). and WARN().

So I don't see why any change is necessary here.

If the HW has somehow taken a BRK exception from any other instruction
(which would presumably be a HW bug), there won't be a bug_table entry,
and so __report_bug() will fail to find an entry, and will return
BUG_TRAP_TYPE_NONE, same as for the !is_valid_bugaddr() case.
Right, so I wasn't at all sure what he was doing, Rust is still mostly
unreadable garbage to me.

But I figured I'd much rather see him use is_valid_bugaddr() to do
runtime checks than whatever dodgy games he was playing with that label.

So *if* doing runtime checks is what is wanted, then is_valid_bugaddr()
needs to actually work, because then it grows a user outside of the BRK
handler.
Thanks; I think we're aligned. :)

Before we delve into implementation details, I'd like to understand the
problem Mukesh is trying to solve.

Mark.
Hey Peter and Mark,

It's not as a problem, it's something i faced while implementing the
ARCH_WARN_ASM, the generated assembly was not correct so i wrote a test
for it, I sent it out along with the same patch[1] but then the
discussion led to it being for all archs so I sent it out as an RFC to
know others opinion.

After Peter's last comment i found is_valid_bugaddr can use a bit more
work(atleast on ppc) so i think i am going to drop this and go ahead
with improving is_valid_bugaddr.

Regards,
Mukesh

[1]https://lore.kernel.org/all/20260915090453.1227034-1-mkchauras@gmail.com/

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-25 06:34:51

On Thu, Sep 24, 2026 at 03:00:50PM +0200, Christophe Leroy (CS GROUP) wrote:

Le 22/09/2026 à 07:50, Mukesh Kumar Chaurasiya (IBM) a écrit :
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

   x86       "1"     (ud2 at label 1:)
   powerpc   "1"     (twi at label 1:)
Shouldn't it also handle twnei and tdnei used for WARN_ON() and BUG_ON() ?

Christophe
Yes it should. But I have decided to drop this and focus on
is_valid_bugaddr() improvement suggested by Peter.

Regards,
Mukesh
quoted
   riscv     "1"     (ebreak at label 1:)
   arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
   s390      "0"     (mc at label 0:)

The label is used consistently: in ARCH_WARN_ASM itself, in the
bug_addr back-reference inside __BUG_ENTRY / _EMIT_BUG_ENTRY, and in
the new generated_arch_warn_asm_trap_label.rs template, which the
C preprocessor expands at build time into a .rs file included by the
test's global_asm! block.

global_asm! is used instead of asm! because LLVM eliminates asm! blocks
in dead branches; global_asm! is file-scope and always emitted.
BUG_KUNIT_TRAP_ADDR is defined as a .global symbol in .data on top of a
.dc.a {trap_label}b relocation so the linker resolves the trap address
into it at link time. A Rust static initialized to zero would land in
BSS where relocations are not applied.

The global_asm! string mirrors warn_flags!() exactly — same "/* {size} */"
prefix, same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs
includes, same operands — plus the .pushsection/.dc.a/.popsection tail.
This keeps the test structurally in sync with the macro it exercises.

The CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST option depends on GENERIC_BUG
and excludes ARM, LOONGARCH, and UML (which use a different warn_flags!
path without ARCH_WARN_ASM).

Five tests are included in the rust_kernel_bug_warn_asm suite:

   bug_entry_found           - find_bug() returns non-NULL for the trap
                               address, proving bug_addr is correct
   bug_entry_is_warning      - the emitted entry has BUGFLAG_WARNING set
   bug_entry_file            - bug_get_file_line() returns the correct
                               source file (CONFIG_DEBUG_BUGVERBOSE only)
   bug_entry_line            - recorded line number is non-zero, confirming
                               the {line} operand was substituted correctly
                               (CONFIG_DEBUG_BUGVERBOSE only)
   bug_entry_addr_is_in_text - kernel_text_address() confirms the trap
                               address lies in kernel text

Tested (pass:5 fail:0 skip:0) on:
   x86_64  - QEMU x86_64
   powerpc - QEMU pseries ppc64le (pseries_le_defconfig)
   riscv   - QEMU riscv64
   arm64   - QEMU aarch64
   s390    - QEMU s390x

Signed-off-by: Mukesh Kumar Chaurasiya (IBM) <redacted>
---
Note: I sent this out as a powerpc specific tests earlier. During review
process some people wanted these to be arch independent. Hence sending
this out as an RFC.

Earlier version: https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Flore.kernel.org%2Fall%2F20260915090453.1227034-3-mkchauras%40gmail.com%2F&data=05%7C02%7Cchristophe.leroy%40csgroup.eu%7C2fb25d39088f4c4e7ba308df186d76f5%7C8b87af7d86474dc78df45f69a2011bb5%7C0%7C0%7C639256530592353420%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=lzso63z76C74rUe7h%2FG%2FEkDi1yXK0oPXBVrlG427Z%2BM%3D&reserved=0

  arch/arm64/include/asm/asm-bug.h              |   3 +
  arch/powerpc/include/asm/bug.h                |  22 +-
  arch/riscv/include/asm/bug.h                  |   5 +-
  arch/s390/include/asm/bug.h                   |   4 +-
  arch/x86/include/asm/bug.h                    |   5 +-
  rust/Makefile                                 |   2 +-
  rust/kernel/.gitignore                        |   1 +
  rust/kernel/Kconfig.test                      |  15 ++
  rust/kernel/bug.rs                            | 191 ++++++++++++++++++
  .../generated_arch_warn_asm_trap_label.rs.S   |   7 +
  10 files changed, 239 insertions(+), 16 deletions(-)
  create mode 100644 rust/kernel/generated_arch_warn_asm_trap_label.rs.S
diff --git a/arch/arm64/include/asm/asm-bug.h b/arch/arm64/include/asm/asm-bug.h
index a5f13801b784..e0694543115e 100644
--- a/arch/arm64/include/asm/asm-bug.h
+++ b/arch/arm64/include/asm/asm-bug.h
@@ -60,6 +60,9 @@ _BUGVERBOSE_LOCATION(__FILE__, __LINE__)		\
  		".short " flags ";"			\
  		__stringify(__BUG_ENTRY_END)
+/* Trap instruction is placed at label 14471: by __BUG_ENTRY_END */
+#define ARCH_WARN_ASM_TRAP_LABEL "14471"
+
  #define ARCH_WARN_ASM(file, line, flags, size)		\
  	__BUG_ENTRY_STRING(file, line, flags)		\
  	__stringify(brk BUG_BRK_IMM)
diff --git a/arch/powerpc/include/asm/bug.h b/arch/powerpc/include/asm/bug.h
index bf31ee1e902a..f0333c564d4f 100644
--- a/arch/powerpc/include/asm/bug.h
+++ b/arch/powerpc/include/asm/bug.h
@@ -32,6 +32,8 @@
  #endif /* verbose */
  #else /* !__ASSEMBLER__ */
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
+
  #ifdef CONFIG_DEBUG_BUGVERBOSE
  #define _EMIT_BUG_ENTRY(bug_entry, trap, file, line, flags)	\
  	".section __bug_table,\"aw\"\n"				\
@@ -45,21 +47,21 @@
  	"		.short " flags "\n"
  #endif
-#define BUG_ENTRY(cond_str, insn, flags, ...)			\
-	__asm__ __volatile__(					\
-		"1:	" insn "\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")	\
-		".org 2b+%3\n"					\
-		".previous\n"					\
+#define BUG_ENTRY(cond_str, insn, flags, ...)				\
+	__asm__ __volatile__(						\
+		ARCH_WARN_ASM_TRAP_LABEL ":	" insn "\n"		\
+		_EMIT_BUG_ENTRY(2, 1b, "%0", "%1", "%2")		\
+		".org 2b+%3\n"						\
+		".previous\n"						\
  		: : "i" (WARN_CONDITION_STR(cond_str) __FILE__), "i" (__LINE__),	\
  		  "i" (flags),					\
  		  "i" (sizeof(struct bug_entry)),		\
  		  ##__VA_ARGS__)
-#define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:	twi 31, 0, 0\n"				\
-		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)	\
-		".org 2b+" size "\n"				\
+#define ARCH_WARN_ASM(file, line, flags, size)					\
+		ARCH_WARN_ASM_TRAP_LABEL ":	twi 31, 0, 0\n"			\
+		_EMIT_BUG_ENTRY(2, 1b, file, line, flags)			\
+		".org 2b+" size "\n"						\
  		".previous\n"
  #define ARCH_WARN_REACHABLE
diff --git a/arch/riscv/include/asm/bug.h b/arch/riscv/include/asm/bug.h
index 699c0cf3e4ef..def6b0d153f2 100644
--- a/arch/riscv/include/asm/bug.h
+++ b/arch/riscv/include/asm/bug.h
@@ -29,7 +29,8 @@
  typedef u32 bug_insn_t;
-#define __BUG_ENTRY_ADDR	RISCV_INT " 1b - ."
+#define ARCH_WARN_ASM_TRAP_LABEL	"1"
+#define __BUG_ENTRY_ADDR	RISCV_INT " " ARCH_WARN_ASM_TRAP_LABEL "b - ."
  #define __BUG_ENTRY_FILE(file)	RISCV_INT " " file " - ."
  #ifdef CONFIG_DEBUG_BUGVERBOSE
@@ -47,7 +48,7 @@ typedef u32 bug_insn_t;
  #ifdef CONFIG_GENERIC_BUG
  #define ARCH_WARN_ASM(file, line, flags, size)			\
-		"1:\n\t"					\
+		ARCH_WARN_ASM_TRAP_LABEL ":\n\t"		\
  			"ebreak\n"				\
  			".pushsection __bug_table,\"aw\"\n\t"	\
  		"2:\n\t"					\
diff --git a/arch/s390/include/asm/bug.h b/arch/s390/include/asm/bug.h
index 2010342c97b1..3c0fa1acc92a 100644
--- a/arch/s390/include/asm/bug.h
+++ b/arch/s390/include/asm/bug.h
@@ -26,9 +26,11 @@
  #define WARN_CONDITION_STR(cond_str) ""
  #endif
+#define ARCH_WARN_ASM_TRAP_LABEL "0"
+
  #define __BUG_ENTRY(format, file, line, flags, size)			\
  		"	.section __bug_table,\"aw\"\n"			\
-		"1:	.long	0b - .		# bug_entry::bug_addr\n"\
+		"1:	.long	" ARCH_WARN_ASM_TRAP_LABEL "b - .	# bug_entry::bug_addr\n" \
  		"	.long	" format " - .	# bug_entry::format\n"	\
  		__BUG_ENTRY_VERBOSE(file, line)				\
  		"	.short	"flags"		# bug_entry::flags\n"	\
diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h
index 23ab05438269..6cca8880660e 100644
--- a/arch/x86/include/asm/bug.h
+++ b/arch/x86/include/asm/bug.h
@@ -62,8 +62,9 @@ extern void __WARN_trap(struct bug_entry *bug, ...);
  #define HAVE_ARCH_BUG_FORMAT_ARGS
  #endif
+#define ARCH_WARN_ASM_TRAP_LABEL "1"
  #define __BUG_ENTRY(format, file, line, flags)				\
-	"\t.long 1b - ."	"\t# bug_entry::bug_addr\n"		\
+	"\t.long " ARCH_WARN_ASM_TRAP_LABEL "b - ." "\t# bug_entry::bug_addr\n" \
  	__BUG_ENTRY_FORMAT(format)					\
  	__BUG_ENTRY_VERBOSE(file, line)					\
  	"\t.word " flags	"\t# bug_entry::flags\n"
@@ -101,7 +102,7 @@ do {									\
  	"99:\n"								\
  	"\t.string \"\"\n"						\
  	".popsection\n"							\
-	"1:\t " ASM_UD2 "\n"						\
+	ARCH_WARN_ASM_TRAP_LABEL ":\t " ASM_UD2 "\n"			\
  	_BUG_FLAGS_ASM("99b", file, line, flags, size, "")
  #else
diff --git a/rust/Makefile b/rust/Makefile
index da1a7409d984..8f253d516495 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -46,7 +46,7 @@ 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
+always-$(subst y,$(CONFIG_RUST),$(CONFIG_BUG)) += kernel/generated_arch_warn_asm.rs kernel/generated_arch_reachable_asm.rs kernel/generated_arch_warn_asm_trap_label.rs
  endif
  # Avoids running `$(RUSTC)` when it may not be available.
diff --git a/rust/kernel/.gitignore b/rust/kernel/.gitignore
index f636ad95aaf3..05bb00bbb83c 100644
--- a/rust/kernel/.gitignore
+++ b/rust/kernel/.gitignore
@@ -3,3 +3,4 @@
  /generated_arch_static_branch_asm.rs
  /generated_arch_warn_asm.rs
  /generated_arch_reachable_asm.rs
+/generated_arch_warn_asm_trap_label.rs
diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
index e6a5c7a795f0..8cd7c8f91b07 100644
--- a/rust/kernel/Kconfig.test
+++ b/rust/kernel/Kconfig.test
@@ -83,4 +83,19 @@ config RUST_BITFIELD_KUNIT_TEST
  	  If unsure, say N.
+config RUST_BUG_WARN_ASM_KUNIT_TEST
+	bool "KUnit tests for ARCH_WARN_ASM bug table emission" if !KUNIT_ALL_TESTS
+	depends on GENERIC_BUG && !ARM && !LOONGARCH && !UML
+	default KUNIT_ALL_TESTS
+	help
+	  This option enables KUnit tests that verify ARCH_WARN_ASM emits a
+	  correct __bug_table entry: the bug_addr displacement must resolve
+	  back to the trap instruction so that find_bug() can locate the
+	  entry — exactly as the real trap handler does.
+
+	  Supported on all architectures that implement ARCH_WARN_ASM
+	  (x86, powerpc, riscv, arm64, s390).
+
+	  If unsure, say N.
+
  endif
diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs
index 3566f0234ca4..9e9b6d3a68b7 100644
--- a/rust/kernel/bug.rs
+++ b/rust/kernel/bug.rs
@@ -152,3 +152,194 @@ macro_rules! warn_on {
          cond
      }};
  }
+
+// Test-only constants and file static referenced by the global_asm block below.
+//
+// global_asm! is file-scope and always emitted — LLVM cannot eliminate it,
+// unlike asm! inside a function which is subject to dead-code removal.
+//
+// BUG_KUNIT_TRAP_ADDR is declared as a .global symbol entirely inside the
+// global_asm! block so the .dc.a {trap_label}b relocation lands directly on it.
+// A Rust static initialized to zero would end up in BSS; the linker does
+// not apply relocations to BSS, so the address would stay zero at runtime.
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+mod test_statics {
+    use crate::bindings::{bug_entry, BUGFLAG_WARNING, TAINT_WARN};
+
+    pub(super) const FLAGS: u32 = BUGFLAG_WARNING | (TAINT_WARN << 8);
+    pub(super) const SIZE: usize = core::mem::size_of::<bug_entry>();
+
+    // LINE and BUG_KUNIT_FILE are only referenced by the {file}/{line}
+    // operands in global_asm!, which are only present when
+    // CONFIG_DEBUG_BUGVERBOSE is set (the non-verbose _EMIT_BUG_ENTRY
+    // drops the file/line fields from the bug table entry entirely).
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    pub(super) const LINE: u32 = line!();
+
+    // Null-terminated source file name — the assembler references this symbol
+    // for the verbose file pointer in __bug_table, same as warn_flags!.
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    const _FILE: &[u8] = file!().as_bytes();
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    #[no_mangle]
+    pub(super) static BUG_KUNIT_FILE: [u8; _FILE.len() + 1] = {
+        let mut bytes = [0u8; _FILE.len() + 1];
+        let mut i = 0;
+        while i < _FILE.len() {
+            bytes[i] = _FILE[i];
+            i += 1;
+        }
+        bytes
+    };
+}
+
+// Emit ARCH_WARN_ASM at file scope and capture the trap address.
+//
+// The asm string mirrors warn_flags!() exactly — same "/* {size} */" prefix,
+// same generated_arch_warn_asm.rs and generated_arch_reachable_asm.rs includes,
+// same operands — so the two stay in sync as the macro evolves.  The only
+// addition is the .pushsection/.dc.a/.popsection tail that records the trap
+// address into BUG_KUNIT_TRAP_ADDR at link time.
+//
+// BUG_KUNIT_TRAP_ADDR is a .global symbol in .data whose value is set by a
+// .dc.a relocation to the arch's trap label (from
+// generated_arch_warn_asm_trap_label.rs); .dc.a self-aligns to pointer width
+// so no .balign is needed.
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, CONFIG_DEBUG_BUGVERBOSE))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    file  = sym test_statics::BUG_KUNIT_FILE,
+    line  = const test_statics::LINE,
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(all(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST, not(CONFIG_DEBUG_BUGVERBOSE)))]
+::core::arch::global_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")),
+        "\n\t.pushsection .data\n\t",
+        ".global BUG_KUNIT_TRAP_ADDR\n\t",
+        "BUG_KUNIT_TRAP_ADDR:\n\t",
+        ".dc.a ",
+        include!(concat!(env!("OBJTREE"), "/rust/kernel/generated_arch_warn_asm_trap_label.rs")),
+        "b\n\t",
+        ".popsection\n",
+    ),
+    flags = const test_statics::FLAGS,
+    size  = const test_statics::SIZE,
+);
+
+#[cfg(CONFIG_RUST_BUG_WARN_ASM_KUNIT_TEST)]
+#[::kernel::macros::kunit_tests(rust_kernel_bug_warn_asm)]
+mod tests {
+    use crate::bindings;
+
+    fn trap_addr() -> usize {
+        // BUG_KUNIT_TRAP_ADDR is a .global symbol defined in the global_asm!
+        // block above, placed in .data at the exact .dc.a 1b relocation word.
+        // The linker resolves it to the virtual address of the twi instruction
+        // before any Rust code runs, so reading it here is always safe.
+        extern "C" {
+            // .dc.a emits a pointer-width word: 4 bytes on ppc32, 8 on ppc64.
+            // usize matches the native pointer width on both.
+            static BUG_KUNIT_TRAP_ADDR: usize;
+        }
+        // SAFETY: read-only after link time, no concurrent mutation possible.
+        unsafe { BUG_KUNIT_TRAP_ADDR }
+    }
+
+    /// The `__bug_table` entry emitted by `ARCH_WARN_ASM` must be locatable
+    /// via `find_bug()` using the trap instruction's address.  A NULL result
+    /// means the `1b` label reference in `_EMIT_BUG_ENTRY` resolved to the
+    /// wrong address and the real trap handler would not recognise the site.
+    #[test]
+    fn bug_entry_found() {
+        // Non-zero proves the .dc.a relocation was resolved by the linker.
+        assert!(trap_addr() != 0);
+
+        // SAFETY: find_bug() is always safe to call with any address; it
+        // simply walks __bug_table and returns NULL if nothing matches.
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        // Non-NULL proves the bug_addr displacement in _EMIT_BUG_ENTRY is correct.
+        assert!(!entry.is_null());
+    }
+
+    /// The emitted entry must be flagged as a warning (not a hard BUG).
+    #[test]
+    fn bug_entry_is_warning() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+        // SAFETY: entry is non-null and points to a valid bug_entry.
+        let flags = unsafe { (*entry).flags } as u32;
+        assert!(flags & bindings::BUGFLAG_WARNING != 0);
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the entry must record a non-null file
+    /// pointer pointing back into this source file.
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_file() {
+        use kernel::str::CStrExt;
+
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid; file_ptr and line are local
+        // variables passed as out-parameters.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(!file_ptr.is_null());
+        // SAFETY: file_ptr is a non-null, null-terminated C string from BUG_KUNIT_FILE,
+        // valid for the lifetime of this function.
+        let file_str = unsafe { kernel::prelude::CStr::from_char_ptr(file_ptr) }
+            .to_str()
+            .unwrap_or("");
+        assert!(file_str.contains("bug"));
+    }
+
+    /// With `CONFIG_DEBUG_BUGVERBOSE` the recorded line number must be
+    /// non-zero (a zero line would mean the asm operand was not substituted).
+    #[test]
+    #[cfg(CONFIG_DEBUG_BUGVERBOSE)]
+    fn bug_entry_line() {
+        assert!(trap_addr() != 0);
+        let entry = unsafe { bindings::find_bug(trap_addr()) };
+        assert!(!entry.is_null());
+
+        let mut file_ptr: *const kernel::ffi::c_char = core::ptr::null();
+        let mut line: u32 = 0;
+        // SAFETY: entry is non-null and valid.
+        unsafe { bindings::bug_get_file_line(entry, &mut file_ptr, &mut line) };
+
+        assert!(line != 0);
+    }
+
+    /// The trap address stored in `__bug_table` must lie within the kernel
+    /// text segment.  If the label reference in `_EMIT_BUG_ENTRY` resolved
+    /// to data or zero, `kernel_text_address()` would return false.
+    #[test]
+    fn bug_entry_addr_is_in_text() {
+        assert!(trap_addr() != 0);
+        // SAFETY: kernel_text_address() is always safe to call with any addr.
+        let in_text = unsafe { bindings::kernel_text_address(trap_addr()) };
+        assert!(in_text != 0);
+    }
+}
diff --git a/rust/kernel/generated_arch_warn_asm_trap_label.rs.S b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
new file mode 100644
index 000000000000..d7bed8751f10
--- /dev/null
+++ b/rust/kernel/generated_arch_warn_asm_trap_label.rs.S
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/bug.h>
+
+// Cut here.
+
+::kernel::concat_literals!(ARCH_WARN_ASM_TRAP_LABEL)

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-25 06:36:51

On Thu, Sep 24, 2026 at 05:13:35PM +0200, Heiko Carstens wrote:
On Thu, Sep 24, 2026 at 01:15:57PM +0200, Peter Zijlstra wrote:
quoted
Untested thingies for arm64 and s390x.
...
quoted
diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c
index b6ba4465f59d..8bbb082fd5e3 100644
--- a/arch/s390/kernel/traps.c
+++ b/arch/s390/kernel/traps.c
@@ -26,6 +26,7 @@
 #include <linux/entry-common.h>
 #include <linux/kmsan.h>
 #include <linux/bug.h>
+#include <linux/uaccess.h>
 #include <asm/entry-percpu.h>
 #include <asm/asm-extable.h>
 #include <asm/irqflags.h>
@@ -55,7 +56,12 @@ static inline void __user *get_trap_ip(struct pt_regs *regs)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	return 1;
+	u16 insn;
+
+	if (get_kernel_nofault(insn, (u16 *)addr))
+		return 0;
+
+	return insn == 0x0001 || insn == 0x000e;
I have no idea why anybody wants to do this, but it looks like I'm not
alone :) In any case: the check is wrong.
s390 uses only the monitor call instruction and this should be

	return insn == 0xaf00;

At least it is not possible to set a kprobe on bug instructions, which
would make this "interesting".
Hey Heiko,

should i send this out along with ppc. I am not paricularly very good
with s390's assembly, but if this looks correct to you, i can send it
out.

Regards,
Mukesh

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-09-25 06:39:17

quoted hunk
Untested thingies for arm64 and s390x.
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index 914282016069..3d8a969eab5f 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -987,14 +987,13 @@ void do_serror(struct pt_regs *regs, unsigned long esr)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	/*
-	 * bug_brk_handler() only called for BRK #BUG_BRK_IMM.
-	 * So the answer is trivial -- any spurious instances with no
-	 * bug table entry will be rejected by report_bug() and passed
-	 * back to the debug-monitors code and handled as a fatal
-	 * unexpected debug exception.
-	 */
-	return 1;
+	u32 insn;
+
+	if (aarch64_insn_read((u32 *)addr, &insn))
+		return 0;
+
+	/* Match ASM_BUG_FLAGS() / BUG() instructions. */
+	return insn == (0xd4200000 | (BUG_BRK_IMM << 5));
 }
 #endif
 
diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c
index b6ba4465f59d..8bbb082fd5e3 100644
--- a/arch/s390/kernel/traps.c
+++ b/arch/s390/kernel/traps.c
@@ -26,6 +26,7 @@
 #include <linux/entry-common.h>
 #include <linux/kmsan.h>
 #include <linux/bug.h>
+#include <linux/uaccess.h>
 #include <asm/entry-percpu.h>
 #include <asm/asm-extable.h>
 #include <asm/irqflags.h>
@@ -55,7 +56,12 @@ static inline void __user *get_trap_ip(struct pt_regs *regs)
 #ifdef CONFIG_GENERIC_BUG
 int is_valid_bugaddr(unsigned long addr)
 {
-	return 1;
+	u16 insn;
+
+	if (get_kernel_nofault(insn, (u16 *)addr))
+		return 0;
+
+	return insn == 0x0001 || insn == 0x000e;
 }
 #endif
 
Thanks for this.
lemme test these and i'll send it out after verifying.

Regards,
Mukesh

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Mark Rutland <mark.rutland@arm.com>
Date: 2026-09-25 08:07:18

On Fri, Sep 25, 2026 at 11:51:33AM +0530, Mukesh Kumar Chaurasiya wrote:
On Thu, Sep 24, 2026 at 01:31:42PM +0100, Mark Rutland wrote:
quoted
On Tue, Sep 22, 2026 at 11:20:00AM +0530, Mukesh Kumar Chaurasiya (IBM) wrote:
quoted
Verify that the __bug_table entry emitted by ARCH_WARN_ASM has a correct
bug_addr displacement — i.e. the arch's trap label reference resolves to
the trap instruction — by calling find_bug() with the exact virtual address
of the trap, mirroring what the real trap handler does.

To support all architectures, each arch that implements ARCH_WARN_ASM now
defines ARCH_WARN_ASM_TRAP_LABEL, a string constant naming the local label
at which the trap instruction is placed:

  x86       "1"     (ud2 at label 1:)
  powerpc   "1"     (twi at label 1:)
  riscv     "1"     (ebreak at label 1:)
  arm64     "14471" (brk placed at 14471: by __BUG_ENTRY_END)
  s390      "0"     (mc at label 0:)
I'm not a fan of exposing such a low-level implementation detail out of
the architecture code, and I don't think we should expose this.

What exactly are you trying to verify here, and why? Has there been
some _specific_ issue on powerpc that caused you to look at this?

Mark.
Hey Mark,

During implementing ARCH_WARN_ASM for rust I broke the bug entry for
powerpc so I wrote this to verify. 
IIUC you mean you encoded the bug_entry::bug_addr field incorrectly?

Did that result in the entry *always* being wrong, or *sometimes* being
wrong?

AFAICT you should be able to use existing the LKDTM 'BUG' and 'WARNING'
tests for that sort of problem, no new code needed. They don't fit into
KUnit, but you should only need to run them when changing the way you
construct a bug_entry, so that doesn't seem like the end of the world.
I sent it out mainly for ppc but few people wanted that if it needed
it should be done for all archs.  So separated this thing and sent it
out as an RFC to check whether other archs want this or not.
Given the information provided so far, I don't think this is necessary.
I do not want this for arm64.

Mark.

Re: [RFC] rust: kernel: Add KUnit tests for ARCH_WARN_ASM bug table emission

From: Heiko Carstens <hca@linux.ibm.com>
Date: 2026-09-25 08:45:25

On Fri, Sep 25, 2026 at 12:06:28PM +0530, Mukesh Kumar Chaurasiya wrote:
On Thu, Sep 24, 2026 at 05:13:35PM +0200, Heiko Carstens wrote:
quoted
On Thu, Sep 24, 2026 at 01:15:57PM +0200, Peter Zijlstra wrote:
quoted
Untested thingies for arm64 and s390x.
 int is_valid_bugaddr(unsigned long addr)
 {
-	return 1;
+	u16 insn;
+
+	if (get_kernel_nofault(insn, (u16 *)addr))
+		return 0;
+
+	return insn == 0x0001 || insn == 0x000e;
I have no idea why anybody wants to do this, but it looks like I'm not
alone :) In any case: the check is wrong.
s390 uses only the monitor call instruction and this should be

	return insn == 0xaf00;
should i send this out along with ppc. I am not paricularly very good
with s390's assembly, but if this looks correct to you, i can send it
out.
No, I still don't get what this fixes, or what the improvement would be
(also not after reading you reply to Mark). The current code is fine for
s390. Unless there is a pressing need I'd like to keep it as it is.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help