From: Zhi Wang <zhiw@nvidia.com>
To: <rust-for-linux@vger.kernel.org>, <linux-kernel@vger.kernel.org>
Cc: <andrewjballance@gmail.com>, <daniel.almeida@collabora.com>,
<dakr@kernel.org>, <aliceryhl@google.com>, <ojeda@kernel.org>,
<boqun@kernel.org>, <gary@garyguo.net>,
<bjorn3_gh@protonmail.com>, <lossin@kernel.org>,
<a.hindborg@kernel.org>, <tmgross@umich.edu>, <cjia@nvidia.com>,
<smitra@nvidia.com>, <ankita@nvidia.com>, <aniketa@nvidia.com>,
<kwankhede@nvidia.com>, <targupta@nvidia.com>, <kjaju@nvidia.com>,
<alkumar@nvidia.com>, <acourbot@nvidia.com>,
<jhubbard@nvidia.com>, <zhiwang@kernel.org>,
Zhi Wang <zhiw@nvidia.com>
Subject: [PATCH v3 4/5] rust: print: add dynamic debug support to pr_debug!
Date: Thu, 1 Oct 2026 17:59:39 +0300 [thread overview]
Message-ID: <20261001145940.1077801-5-zhiw@nvidia.com> (raw)
In-Reply-To: <20261001145940.1077801-1-zhiw@nvidia.com>
From: Andrew Ballance <andrewjballance@gmail.com>
Register Rust callsites in __dyndbg and use the existing C dynamic debug
core for selection, prefixes, stack output and module lifetime handling.
Keep the original Rust format string in the catalogue and format actual
arguments through %pA, preserving the module prefix.
Keep the descriptor in static mut storage and access it through raw
pointers, following the RFC v2 design. Use the nested-field static
branch when jump labels are enabled; otherwise read the print flag via a
C helper. Initialize every callsite as disabled, independently of Rust
debug assertions, and let dynamic debug control its print state. Without
dynamic debug for the crate, keep messages disabled while type-checking
their arguments. Evaluate arguments only when enabled, and keep user
expressions outside the macro's unsafe block. Use match scrutinees to
keep formatting temporaries alive through the synchronous printing call.
Use used(compiler) for the descriptor: the GNU retain flag emitted by
plain used collides with the module loader's SHF_RO_AFTER_INIT flag.
Provide a host-test stub matching the existing printing macros.
Pass Kbuild's normalized module name through RUST_MODNAME for callsite
identity, independently of the existing module! log prefix. Keep metadata
items in a separate scope so implicit argument captures cannot be
shadowed by macro-internal names.
Function names are deliberately recorded as <unknown>; module, file,
line and original-format selection remain available without inferred
function names or compiler-specific name extraction.
Link: https://lore.kernel.org/rust-for-linux/20250620210533.400889-4-andrewjballance@gmail.com/
Signed-off-by: Andrew Ballance <andrewjballance@gmail.com>
[Zhi: Rebase onto driver-core-testing; retain raw-pointer descriptor access;
revise bitfield initialization, configuration gates, Kbuild
module identity, macro hygiene and synchronous printing; retain
<unknown> function metadata and add maintainer routing.]
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
MAINTAINERS | 2 +
rust/Makefile | 3 +
rust/bindings/bindings_helper.h | 1 +
rust/helpers/dynamic_debug.c | 25 +++++
rust/helpers/helpers.c | 1 +
rust/kernel/print.rs | 53 +++++++--
rust/kernel/print/dynamic_debug.rs | 165 +++++++++++++++++++++++++++++
scripts/Makefile.build | 1 +
scripts/generate_rust_analyzer.py | 1 +
9 files changed, 246 insertions(+), 6 deletions(-)
create mode 100644 rust/helpers/dynamic_debug.c
create mode 100644 rust/kernel/print/dynamic_debug.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index aec32deef0a1..ecd1312ab35a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9262,6 +9262,8 @@ S: Maintained
F: include/linux/dynamic_debug.h
F: lib/dynamic_debug.c
F: lib/test_dynamic_debug.c
+F: rust/helpers/dynamic_debug.c
+F: rust/kernel/print/dynamic_debug.rs
DYNAMIC INTERRUPT MODERATION
M: Tal Gilboa <talgi@nvidia.com>
diff --git a/rust/Makefile b/rust/Makefile
index da1a7409d984..b8be882be0e0 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -173,6 +173,7 @@ quiet_cmd_rustdoc = RUSTDOC $(if $(rustdoc_host),H, ) $<
cmd_rustdoc = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
+ RUST_MODNAME=$(call name-fix-token,$(subst rustdoc-,,$@)) \
$(RUSTDOC) $(filter-out $(skip_flags) --remap-path-scope=%,$(if $(rustdoc_host),$(rust_common_flags),$(rust_flags))) \
$(rustc_target_flags) -L$(objtree)/$(obj)$(if $(rustdoc_host),/host) \
-Zunstable-options --generate-link-to-definition \
@@ -388,6 +389,7 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $<
mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
+ RUST_MODNAME=kernel \
$(RUSTDOC) --test $(filter-out --remap-path-scope=%,$(rust_flags)) \
-L$(objtree)/$(obj) --extern ffi --extern pin_init \
--extern kernel --extern build_error --extern macros \
@@ -647,6 +649,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
cmd_rustc_library = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
+ RUST_MODNAME=$(call name-fix-token,$(modname)) \
$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
$(filter-out $(skip_flags),$(rust_flags)) $(rustc_target_flags) \
--emit=dep-info=$(depfile) --emit=$(if $(link_helper),llvm-bc=$(patsubst %.o,%.bc,$@),obj=$@) \
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4b31aa7f432f..385d04090628 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -54,6 +54,7 @@
#include <linux/device/faux.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
+#include <linux/dynamic_debug.h>
#include <linux/dma-resv.h>
#include <linux/errname.h>
#include <linux/ethtool.h>
diff --git a/rust/helpers/dynamic_debug.c b/rust/helpers/dynamic_debug.c
new file mode 100644
index 000000000000..0ede4ee2eb79
--- /dev/null
+++ b/rust/helpers/dynamic_debug.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dynamic_debug.h>
+#include <linux/printk.h>
+
+#ifdef CONFIG_DYNAMIC_DEBUG_CORE
+#ifndef CONFIG_JUMP_LABEL
+__rust_helper bool rust_helper_ddebug_enabled(struct _ddebug *descriptor)
+{
+ /* Match the C dynamic debug branch without creating a Rust reference. */
+ return descriptor->flags & _DPRINTK_FLAGS_PRINT;
+}
+#endif
+
+/* The printing helpers receive Rust fmt::Arguments for the %pA bridge. */
+__rust_helper void rust_helper_ddebug_print(struct _ddebug *descriptor,
+ const char *prefix,
+ const void *args)
+{
+ __dynamic_pr_debug(descriptor, "%s: %pA", prefix, args);
+ if (descriptor->flags & _DPRINTK_FLAGS_INCL_STACK)
+ dump_stack();
+}
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 440fb7638e3c..9730ab7fa143 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -62,6 +62,7 @@
#endif
#include "drm.c"
#include "drm_gpuvm.c"
+#include "dynamic_debug.c"
#include "err.c"
#include "fs.c"
#include "fwctl.c"
diff --git a/rust/kernel/print.rs b/rust/kernel/print.rs
index 0d62beeedca5..60502f2e614e 100644
--- a/rust/kernel/print.rs
+++ b/rust/kernel/print.rs
@@ -18,6 +18,10 @@
},
};
+#[cfg(CONFIG_DYNAMIC_DEBUG_CORE)]
+#[doc(hidden)]
+pub mod dynamic_debug;
+
// Called from `vsprintf` with format specifier `%pA`.
#[expect(clippy::missing_safety_doc)]
#[export]
@@ -376,13 +380,24 @@ macro_rules! pr_info (
///
/// Use this level for debug messages.
///
-/// Equivalent to the kernel's [`pr_debug`] macro, except that it doesn't support dynamic debug
-/// yet.
+/// Equivalent to the kernel's [`pr_debug`] macro.
+///
+/// Supports [dynamic debug] when `CONFIG_DYNAMIC_DEBUG` is enabled, or when
+/// `CONFIG_DYNAMIC_DEBUG_CORE` is enabled and the crate is built with
+/// `--cfg DYNAMIC_DEBUG_MODULE`. Messages can then be enabled and disabled at runtime,
+/// starting disabled. Rust's `debug_assertions` setting does not affect debug logging.
+/// Without dynamic debug for the crate, messages stay disabled. Disabled messages do not
+/// evaluate their formatting arguments, but the arguments are still checked at compile time.
+///
+/// Dynamic debug callsites can be selected by module, file, line, or format. Their function name
+/// is recorded as `<unknown>`, so selecting a Rust function by its name is not supported.
+/// The module selector uses Kbuild's module name, independently of the crate's log prefix.
///
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
/// [`std::format!`] for information about the formatting syntax.
///
/// [`pr_debug`]: https://docs.kernel.org/core-api/printk-basics.html#c.pr_debug
+/// [dynamic debug]: https://docs.kernel.org/admin-guide/dynamic-debug-howto.html
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
/// [`std::format!`]: https://doc.rust-lang.org/std/macro.format.html
///
@@ -393,12 +408,38 @@ macro_rules! pr_info (
/// ```
#[macro_export]
#[doc(alias = "print")]
+#[expect(clippy::crate_in_macro_def)]
macro_rules! pr_debug (
- ($($arg:tt)*) => (
- if cfg!(debug_assertions) {
- $crate::print_macro!($crate::print::format_strings::DEBUG, false, $($arg)*)
+ ($($arg:tt)*) => ({
+ #[cfg(any(CONFIG_DYNAMIC_DEBUG, all(CONFIG_DYNAMIC_DEBUG_CORE, DYNAMIC_DEBUG_MODULE)))]
+ {
+ $crate::dynamic_debug!(__descriptor, ($($arg)*), {
+ match (
+ const {
+ match $crate::str::CStr::from_bytes_with_nul(crate::__LOG_PREFIX) {
+ Ok(prefix) => prefix,
+ Err(_) => panic!("the module log prefix must be a C string"),
+ }
+ },
+ $crate::prelude::fmt!($($arg)*),
+ ) {
+ // SAFETY: `dynamic_debug!` supplies a live, initialized static descriptor.
+ // Its mutable fields are accessed only by the C dynamic debug code.
+ (prefix, args) => unsafe {
+ $crate::print::dynamic_debug::Descriptor::print(__descriptor, prefix, args)
+ },
+ }
+ });
}
- )
+
+ #[cfg(not(any(CONFIG_DYNAMIC_DEBUG, all(CONFIG_DYNAMIC_DEBUG_CORE, DYNAMIC_DEBUG_MODULE))))]
+ {
+ // Keep checking arguments even when dynamic debug is unavailable.
+ if false {
+ $crate::print_macro!($crate::print::format_strings::DEBUG, false, $($arg)*)
+ }
+ }
+ })
);
/// Continues a previous log message in the same line.
diff --git a/rust/kernel/print/dynamic_debug.rs b/rust/kernel/print/dynamic_debug.rs
new file mode 100644
index 000000000000..da4c40ed8fcc
--- /dev/null
+++ b/rust/kernel/print/dynamic_debug.rs
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Callsite metadata and printing for dynamic debug.
+//!
+//! C header: [`include/linux/dynamic_debug.h`](srctree/include/linux/dynamic_debug.h).
+//!
+//! The printing macros place descriptors in `__dyndbg`. The existing C dynamic debug core
+//! discovers them at boot or module load and removes them before the module is freed.
+
+use crate::{
+ fmt,
+ prelude::*,
+ str::as_char_ptr_in_const_context, //
+};
+
+/// A dynamic debug callsite descriptor.
+///
+/// The [`crate::dynamic_debug!`] macro places descriptors at stable addresses in `__dyndbg`.
+#[repr(transparent)]
+pub struct Descriptor {
+ /// The C descriptor, exposed for static-branch field addressing.
+ pub inner: bindings::_ddebug,
+}
+
+impl Descriptor {
+ /// Creates an initially disabled descriptor.
+ pub const fn new(
+ module: &'static CStr,
+ file: &'static CStr,
+ format: &'static CStr,
+ line: u32,
+ ) -> Self {
+ // SAFETY: `_ddebug` contains raw pointers, integers and, with jump labels, a static key.
+ // All accept zero; the pointers and bitfields are filled in before publication.
+ let mut descriptor: bindings::_ddebug = unsafe { core::mem::zeroed() };
+ descriptor.modname = as_char_ptr_in_const_context(module);
+ descriptor.function = as_char_ptr_in_const_context(c"<unknown>");
+ descriptor.filename = as_char_ptr_in_const_context(file);
+ descriptor.format = as_char_ptr_in_const_context(format);
+
+ // C packs `lineno:18`, `class_id:6` and `flags:8` into an unsigned int. Bindgen's setters
+ // are not const, so encode the target's bitfield ordering explicitly. Truncate line numbers
+ // as the C bitfield assignment does.
+ let line = line & ((1 << 18) - 1);
+ let class = bindings::_DPRINTK_CLASS_DFLT;
+ let flags = bindings::_DPRINTK_FLAGS_NONE;
+ #[cfg(target_endian = "little")]
+ let bits = (line | (class << 18) | (flags << 24)).to_le_bytes();
+ #[cfg(target_endian = "big")]
+ let bits = ((line << 14) | (class << 8) | flags).to_be_bytes();
+ descriptor._bitfield_1 = bindings::__BindgenBitfieldUnit::new(bits);
+
+ #[cfg(CONFIG_JUMP_LABEL)]
+ // An all-zero key is `STATIC_KEY_INIT_FALSE`.
+ static_assert!(bindings::JUMP_TYPE_FALSE == 0);
+
+ Self { inner: descriptor }
+ }
+
+ /// Checks the print flag when jump labels are unavailable.
+ ///
+ /// # Safety
+ ///
+ /// `descriptor` must point to an initialized descriptor valid for this call. Concurrent access
+ /// to its fields must follow the C dynamic debug and jump label synchronization rules, without
+ /// conflicting Rust accesses.
+ #[cfg(not(CONFIG_JUMP_LABEL))]
+ #[inline]
+ pub unsafe fn enabled(descriptor: *mut Self) -> bool {
+ // SAFETY: The pointer is valid by the caller's guarantee. The helper uses the same flag
+ // access as the C dynamic debug branch, without forming a Rust reference to it.
+ unsafe { bindings::ddebug_enabled(&raw mut (*descriptor).inner) }
+ }
+
+ /// Prints arguments with the crate's log prefix and the dynamic debug prefixes.
+ ///
+ /// # Safety
+ ///
+ /// `descriptor` must point to an initialized descriptor valid for this call, whose string
+ /// pointers refer to valid NUL-terminated C strings. Concurrent access must follow the C
+ /// dynamic debug and jump label synchronization rules, without conflicting Rust accesses.
+ pub unsafe fn print(descriptor: *mut Self, prefix: &CStr, args: fmt::Arguments<'_>) {
+ // SAFETY: The descriptor is valid by the caller's guarantee. The helper uses the C string
+ // `prefix` and consumes `args` through `%pA` synchronously, so their borrows remain valid.
+ unsafe {
+ bindings::ddebug_print(
+ &raw mut (*descriptor).inner,
+ prefix.as_char_ptr(),
+ core::ptr::from_ref(&args).cast(),
+ )
+ };
+ }
+}
+
+/// Extracts only the format string for the callsite catalogue.
+///
+/// The original tokens are separately passed to `fmt!`, preserving implicit argument capture.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! dynamic_debug_format {
+ ($format:expr $(, $($rest:tt)*)?) => {
+ // The C catalogue ends at the first NUL; Rust formatting still uses the full string.
+ match $crate::str::CStr::from_bytes_until_nul(concat!($format, "\0").as_bytes()) {
+ Ok(format) => format,
+ Err(_) => panic!("the format string must be NUL-terminated"),
+ }
+ };
+}
+
+/// Defines a callsite and evaluates a printing expression only when it is enabled.
+#[doc(hidden)]
+#[macro_export]
+#[cfg(not(testlib))]
+macro_rules! dynamic_debug {
+ ($descriptor:ident, ($($arg:tt)*), $body:block) => {{
+ // Keep item names out of the user's scope, including implicit format captures.
+ let (descriptor, enabled) = {
+ // `used(compiler)` avoids `SHF_GNU_RETAIN`, which the module loader interprets as
+ // `SHF_RO_AFTER_INIT`.
+ #[used(compiler)]
+ #[unsafe(link_section = "__dyndbg")]
+ static mut DESCRIPTOR: $crate::print::dynamic_debug::Descriptor =
+ $crate::print::dynamic_debug::Descriptor::new(
+ $crate::c_str!(env!("RUST_MODNAME")),
+ $crate::c_str!(file!()),
+ $crate::dynamic_debug_format!($($arg)*),
+ line!(),
+ );
+
+ #[cfg(CONFIG_JUMP_LABEL)]
+ // SAFETY: `DESCRIPTOR` contains an initialized false key at the specified field and
+ // remains valid until the C core unregisters the module's jump label entries.
+ let enabled = unsafe {
+ $crate::jump_label::static_branch_unlikely!(
+ DESCRIPTOR,
+ $crate::print::dynamic_debug::Descriptor,
+ inner.key.dd_key_false
+ )
+ };
+
+ #[cfg(not(CONFIG_JUMP_LABEL))]
+ // SAFETY: The static descriptor is initialized and accessed only by the C helpers
+ // and dynamic debug core, which use the C synchronization rules.
+ let enabled = unsafe {
+ $crate::print::dynamic_debug::Descriptor::enabled(&raw mut DESCRIPTOR)
+ };
+
+ (&raw mut DESCRIPTOR, enabled)
+ };
+
+ if enabled {
+ let $descriptor = descriptor;
+ $body
+ }
+ }};
+}
+
+/// Stub for host doctests, matching [`crate::print_macro!`].
+#[cfg(testlib)]
+#[macro_export]
+macro_rules! dynamic_debug {
+ ($descriptor:ident, ($($arg:tt)*), $body:block) => {
+ ()
+ };
+}
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 4349108e75e1..19ed7f2a2d49 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -325,6 +325,7 @@ rust_allowed_features := arbitrary_self_types,asm_goto,generic_arg_infer,used_wi
# modules case.
rust_common_cmd = \
OBJTREE=$(abspath $(objtree)) \
+ RUST_MODNAME=$(call name-fix-token,$(modname)) \
RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \
-Zallow-features=$(rust_allowed_features) \
-Zcrate-attr=no_std \
diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
index dc1219736f77..5c9cb4df6e48 100755
--- a/scripts/generate_rust_analyzer.py
+++ b/scripts/generate_rust_analyzer.py
@@ -104,6 +104,7 @@ def generate_crates(
edition = edition if edition is not None else "2021"
crate_env = {
"RUST_MODFILE": "This is only for rust-analyzer",
+ "RUST_MODNAME": display_name.replace("-", "_"),
**crates_envs.get(display_name, {}),
}
return {
--
2.53.0
next prev parent reply other threads:[~2026-10-01 15:00 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 14:59 [PATCH v3 0/5] Rust dynamic debug support for pr_debug! and dev_dbg! Zhi Wang
2026-10-01 14:59 ` [PATCH v3 1/5] gpu: nova-core: validate firmware versions before debug logging Zhi Wang
2026-10-01 14:59 ` [PATCH v3 2/5] rust: jump label: add support for nested arguments Zhi Wang
2026-10-01 14:59 ` [PATCH v3 3/5] dynamic_debug: expose printing declarations with the core enabled Zhi Wang
2026-10-01 14:59 ` Zhi Wang [this message]
2026-10-01 14:59 ` [PATCH v3 5/5] rust: device: add dynamic debug support to dev_dbg! Zhi Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261001145940.1077801-5-zhiw@nvidia.com \
--to=zhiw@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=alkumar@nvidia.com \
--cc=andrewjballance@gmail.com \
--cc=aniketa@nvidia.com \
--cc=ankita@nvidia.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=cjia@nvidia.com \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=kjaju@nvidia.com \
--cc=kwankhede@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=smitra@nvidia.com \
--cc=targupta@nvidia.com \
--cc=tmgross@umich.edu \
--cc=zhiwang@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®