Skip to main content

debug/
ltrace.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7//! Local trace (`LOCAL_TRACE`) logging mechanism for the Zircon kernel.
8//!
9//! # Overview
10//!
11//! This crate provides a compile-time-guarded debug tracing and logging
12//! mechanism (`LOCAL_TRACE`, `ltrace!`, `ltracef!`, etc.) for Zircon kernel
13//! development. It allows developers to maintain detailed, file-scoped debug
14//! logging inside source files with zero overhead in production builds, while
15//! enabling targeted high-verbosity logs when developing or debugging.
16//!
17//! # Defining Local Trace Verbosity
18//!
19//! Unlike unconditional trace macros (`trace!`, `tracef!`, etc.), the `ltrace*`
20//! family of macros requires a `u32` constant named `LOCAL_TRACE` to be defined
21//! in the caller's scope.
22//!
23//! To use local tracing in a Rust file or module (`foo.rs`), define a
24//! file-scoped or module-scoped `u32` constant at the top of the file:
25//!
26//! ```rust
27//! // Disable local tracing by default for this file/module:
28//! const LOCAL_TRACE: u32 = 0;
29//! ```
30//!
31//! # Enabling Trace Output Locally During Debugging
32//!
33//! To locally enable verbose trace logs in a specific file while developing
34//! or debugging, edit the local `const` definition:
35//!
36//! ```rust
37//! const LOCAL_TRACE: u32 = 1; // Or a higher verbosity level like 2
38//! ```
39//!
40//! Because Rust lexical scoping prefers constants defined in the current module
41//! over those in outer modules, crates or parent modules can define a default
42//! fallback `const LOCAL_TRACE: u32 = 0;` at their root, and any specific submodule
43//! or file can override that fallback locally by defining its own `LOCAL_TRACE`.
44//!
45//! # Zero Runtime Overhead When Disabled
46//!
47//! When `LOCAL_TRACE` is `0` at compile-time, trace macros expand to
48//! `if false { ... }`. The Rust compiler (`rustc`/LLVM) type-checks and
49//! syntax-checks the arguments inside the macro block, but eliminates the dead
50//! branch during compilation. Therefore, disabled trace statements incur zero
51//! runtime CPU cost and generate zero string data in `.rodata`.
52//!
53//! # Porting from C++ (`trace.h`)
54//!
55//! When porting C++ kernel code that uses `zircon/kernel/include/trace.h`
56//! (`#define LOCAL_TRACE 0`, `LTRACE_ENTRY`, `LTRACEF`, etc.), trace logging
57//! statements should be preserved using this crate.
58//!
59//! The table below maps C++ `trace.h` macros to their Rust `ltrace` equivalents:
60//!
61//! * `TRACE_ENTRY` -> [`trace_entry!`]
62//! * `TRACE_EXIT` -> [`trace_exit!`]
63//! * `TRACE_ENTRY_OBJ` -> [`trace_entry_obj!`]
64//! * `TRACE_EXIT_OBJ` -> [`trace_exit_obj!`]
65//! * `TRACE` -> [`trace!`]
66//! * `TRACEF(str, x...)` -> [`tracef!`]
67//! * `LTRACE_ENTRY` -> [`ltrace_entry!`]
68//! * `LTRACE_EXIT` -> [`ltrace_exit!`]
69//! * `LTRACE_ENTRY_OBJ` -> [`ltrace_entry_obj!`]
70//! * `LTRACE_EXIT_OBJ` -> [`ltrace_exit_obj!`]
71//! * `LTRACE` -> [`ltrace!`]
72//! * `LTRACEF(x...)` -> [`ltracef!`]
73//! * `LTRACEF_LEVEL(lvl, x...)` -> [`ltracef_level!`]
74
75#[doc(hidden)]
76pub struct KernelConsoleWriter;
77
78impl core::fmt::Write for KernelConsoleWriter {
79    #[cfg(not(test))]
80    fn write_str(&mut self, s: &str) -> core::fmt::Result {
81        // TODO(https://fxbug.dev/518017761): Update this to use the Rust libc
82        // crate when it is available.
83        unsafe extern "C" {
84            fn printf(format: *const core::ffi::c_char, ...) -> core::ffi::c_int;
85        }
86        // SAFETY: `"%.*s\0"` is a valid C format string requiring an integer length
87        // and a character buffer pointer. `s.len()` and `s.as_ptr()` provide a valid slice.
88        unsafe {
89            const FORMAT: &[u8; 5] = b"%.*s\0";
90            printf(
91                FORMAT.as_ptr() as *const core::ffi::c_char,
92                s.len() as core::ffi::c_int,
93                s.as_ptr() as *const core::ffi::c_char,
94            );
95        }
96        Ok(())
97    }
98
99    #[cfg(test)]
100    fn write_str(&mut self, s: &str) -> core::fmt::Result {
101        std::print!("{}", s);
102        Ok(())
103    }
104}
105
106#[doc(hidden)]
107#[inline(always)]
108pub fn print_trace_args(module_path: &str, line: u32, args: core::fmt::Arguments<'_>) {
109    use core::fmt::Write;
110    let mut writer = KernelConsoleWriter;
111    let _ = write!(writer, "{}:{}: ", module_path, line);
112    let _ = writer.write_fmt(args);
113}
114
115#[doc(hidden)]
116#[inline(always)]
117pub fn print_trace_location(module_path: &str, line: u32) {
118    use core::fmt::Write;
119    let mut writer = KernelConsoleWriter;
120    let _ = write!(writer, "{}:{}\n", module_path, line);
121}
122
123#[doc(hidden)]
124#[inline(always)]
125pub fn print_trace_action(module_path: &str, line: u32, action: &str) {
126    use core::fmt::Write;
127    let mut writer = KernelConsoleWriter;
128    let _ = write!(writer, "{}:{}: {}\n", module_path, line, action);
129}
130
131#[doc(hidden)]
132#[inline(always)]
133pub fn print_trace_action_named(name: &str, action: &str) {
134    use core::fmt::Write;
135    let mut writer = KernelConsoleWriter;
136    let _ = write!(writer, "{}: {}\n", name, action);
137}
138
139#[doc(hidden)]
140#[inline(always)]
141pub fn print_trace_obj<T: ?Sized>(module_path: &str, line: u32, obj: &T, action: &str) {
142    use core::fmt::Write;
143    let ptr = obj as *const T as *const ();
144    let mut writer = KernelConsoleWriter;
145    let _ = write!(writer, "{}:{}: {} {:p}\n", module_path, line, action, ptr);
146}
147
148#[doc(hidden)]
149#[inline(always)]
150pub fn print_trace_obj_named<T: ?Sized>(name: &str, obj: &T, action: &str) {
151    use core::fmt::Write;
152    let ptr = obj as *const T as *const ();
153    let mut writer = KernelConsoleWriter;
154    let _ = write!(writer, "{}: {} {:p}\n", name, action, ptr);
155}
156
157/// Prints function/module entry information (`{module}:{line}: entry` or `{name}: entry`).
158///
159/// Equivalent to C++ `TRACE_ENTRY`.
160///
161/// # Examples
162/// ```rust
163/// trace_entry!();           // Prints "<module>:<line>: entry"
164/// trace_entry!("Foo::bar"); // Prints "Foo::bar: entry"
165/// ```
166#[macro_export]
167macro_rules! trace_entry {
168    () => {
169        $crate::ltrace::print_trace_action(core::module_path!(), core::line!(), "entry")
170    };
171    ($name:expr) => {
172        $crate::ltrace::print_trace_action_named($name, "entry")
173    };
174}
175
176/// Prints function/module exit information (`{module}:{line}: exit` or `{name}: exit`).
177///
178/// Equivalent to C++ `TRACE_EXIT`.
179#[macro_export]
180macro_rules! trace_exit {
181    () => {
182        $crate::ltrace::print_trace_action(core::module_path!(), core::line!(), "exit")
183    };
184    ($name:expr) => {
185        $crate::ltrace::print_trace_action_named($name, "exit")
186    };
187}
188
189/// Prints function entry along with the object pointer (`self` or reference).
190///
191/// Equivalent to C++ `TRACE_ENTRY_OBJ`.
192///
193/// # Examples
194/// ```rust
195/// trace_entry_obj!(self);             // Prints "<module>:<line>: entry obj <ptr>"
196/// trace_entry_obj!("Foo::bar", self); // Prints "Foo::bar: entry obj <ptr>"
197/// ```
198#[macro_export]
199macro_rules! trace_entry_obj {
200    ($obj:expr) => {
201        $crate::ltrace::print_trace_obj(core::module_path!(), core::line!(), $obj, "entry obj")
202    };
203    ($name:expr, $obj:expr) => {
204        $crate::ltrace::print_trace_obj_named($name, $obj, "entry obj")
205    };
206}
207
208/// Prints function exit along with the object pointer (`self` or reference).
209///
210/// Equivalent to C++ `TRACE_EXIT_OBJ`.
211#[macro_export]
212macro_rules! trace_exit_obj {
213    ($obj:expr) => {
214        $crate::ltrace::print_trace_obj(core::module_path!(), core::line!(), $obj, "exit obj")
215    };
216    ($name:expr, $obj:expr) => {
217        $crate::ltrace::print_trace_obj_named($name, $obj, "exit obj")
218    };
219}
220
221/// Prints the current module path and line (`{module}:{line}`).
222///
223/// Equivalent to C++ `TRACE`.
224#[macro_export]
225macro_rules! trace {
226    () => {
227        $crate::ltrace::print_trace_location(core::module_path!(), core::line!())
228    };
229}
230
231/// Formats and prints a trace message prefixed with module path and line number.
232///
233/// Equivalent to C++ `TRACEF`.
234///
235/// # Examples
236/// ```rust
237/// tracef!("initialized with value {}\n", val);
238/// ```
239#[macro_export]
240macro_rules! tracef {
241    ($($arg:tt)*) => {
242        $crate::ltrace::print_trace_args(core::module_path!(), core::line!(), core::format_args!($($arg)*))
243    };
244}
245
246/// Prints function entry (`TRACE_ENTRY`) if `LOCAL_TRACE >= 1`.
247///
248/// Equivalent to C++ `LTRACE_ENTRY`.
249#[macro_export]
250macro_rules! ltrace_entry {
251    ($($arg:tt)*) => {
252        if LOCAL_TRACE >= 1u32 {
253            $crate::trace_entry!($($arg)*);
254        }
255    };
256}
257
258/// Prints function exit (`TRACE_EXIT`) if `LOCAL_TRACE >= 1`.
259///
260/// Equivalent to C++ `LTRACE_EXIT`.
261#[macro_export]
262macro_rules! ltrace_exit {
263    ($($arg:tt)*) => {
264        if LOCAL_TRACE >= 1u32 {
265            $crate::trace_exit!($($arg)*);
266        }
267    };
268}
269
270/// Prints function entry with object pointer (`TRACE_ENTRY_OBJ`) if `LOCAL_TRACE >= 1`.
271///
272/// Equivalent to C++ `LTRACE_ENTRY_OBJ`.
273#[macro_export]
274macro_rules! ltrace_entry_obj {
275    ($($arg:tt)*) => {
276        if LOCAL_TRACE >= 1u32 {
277            $crate::trace_entry_obj!($($arg)*);
278        }
279    };
280}
281
282/// Prints function exit with object pointer (`TRACE_EXIT_OBJ`) if `LOCAL_TRACE >= 1`.
283///
284/// Equivalent to C++ `LTRACE_EXIT_OBJ`.
285#[macro_export]
286macro_rules! ltrace_exit_obj {
287    ($($arg:tt)*) => {
288        if LOCAL_TRACE >= 1u32 {
289            $crate::trace_exit_obj!($($arg)*);
290        }
291    };
292}
293
294/// Prints current module path and line (`TRACE`) if `LOCAL_TRACE >= 1`.
295///
296/// Equivalent to C++ `LTRACE`.
297#[macro_export]
298macro_rules! ltrace {
299    () => {
300        if LOCAL_TRACE >= 1u32 {
301            $crate::trace!();
302        }
303    };
304    ($($stmt:tt)+) => {
305        if LOCAL_TRACE >= 1u32 {
306            $($stmt)+
307        }
308    };
309}
310
311/// Formats and prints a trace message (`TRACEF`) if `LOCAL_TRACE >= 1`.
312///
313/// Equivalent to C++ `LTRACEF`.
314///
315/// # Examples
316/// ```rust
317/// const LOCAL_TRACE: u32 = 0; // Edit to 1 when locally debugging this file
318///
319/// fn my_func(x: i32) {
320///     ltracef!("my_func called with x = {}\n", x);
321/// }
322/// ```
323#[macro_export]
324macro_rules! ltracef {
325    ($($arg:tt)*) => {
326        if LOCAL_TRACE >= 1u32 {
327            $crate::tracef!($($arg)*);
328        }
329    };
330}
331
332/// Prints current module path and line (`TRACE`) if `LOCAL_TRACE >= level`.
333#[macro_export]
334macro_rules! ltrace_level {
335    ($level:expr) => {
336        if LOCAL_TRACE >= $level {
337            $crate::trace!();
338        }
339    };
340}
341
342/// Formats and prints a trace message (`TRACEF`) if `LOCAL_TRACE >= level`.
343///
344/// Equivalent to C++ `LTRACEF_LEVEL`.
345///
346/// # Examples
347/// ```rust
348/// const LOCAL_TRACE: u32 = 1;
349///
350/// // Printed when LOCAL_TRACE >= 1:
351/// ltracef_level!(1, "basic info: {}\n", val);
352/// // Optimized out unless LOCAL_TRACE >= 2:
353/// ltracef_level!(2, "detailed info: {}\n", val);
354/// ```
355#[macro_export]
356macro_rules! ltracef_level {
357    ($level:expr, $($arg:tt)*) => {
358        if LOCAL_TRACE >= $level {
359            $crate::tracef!($($arg)*);
360        }
361    };
362}
363
364#[cfg(test)]
365mod tests {
366
367    #[test]
368    fn test_unconditional_macros() {
369        trace!();
370        trace_entry!();
371        trace_entry!("Test::foo");
372        trace_exit!();
373        trace_exit!("Test::foo");
374        let obj = 42;
375        trace_entry_obj!(&obj);
376        trace_entry_obj!("Test::foo", &obj);
377        trace_exit_obj!(&obj);
378        trace_exit_obj!("Test::foo", &obj);
379        tracef!("hello {}\n", 123);
380    }
381
382    #[test]
383    fn test_disabled_local_trace() {
384        const LOCAL_TRACE: u32 = 0;
385        ltrace!();
386        ltrace_entry!();
387        ltrace_entry!("Test::disabled");
388        ltrace_exit!();
389        ltrace_exit!("Test::disabled");
390        let obj = 42;
391        ltrace_entry_obj!(&obj);
392        ltrace_exit_obj!(&obj);
393        ltracef!("should not print: {}\n", 123);
394        ltracef_level!(1, "should not print: {}\n", 456);
395        let mut executed = false;
396        ltrace!(executed = true);
397        assert!(!executed);
398    }
399
400    #[test]
401    fn test_enabled_local_trace() {
402        const LOCAL_TRACE: u32 = 2;
403        ltrace!();
404        ltrace_entry!();
405        ltrace_entry!("Test::enabled");
406        ltrace_exit!();
407        ltrace_exit!("Test::enabled");
408        let obj = 42;
409        ltrace_entry_obj!(&obj);
410        ltrace_exit_obj!(&obj);
411        ltracef!("enabled level 1: {}\n", 123);
412        ltracef_level!(1, "enabled level 1: {}\n", 123);
413        ltracef_level!(2, "enabled level 2: {}\n", 456);
414        ltracef_level!(3, "should not print level 3: {}\n", 789);
415        let mut executed = false;
416        ltrace!(executed = true);
417        assert!(executed);
418    }
419}