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}