Skip to main content

starnix_logging/
logging.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use starnix_task_command::TaskCommand;
6use starnix_uapi::errors::Errno;
7use starnix_uapi::{pid_t, tid_t};
8use std::cell::RefCell;
9use std::fmt;
10
11// This needs to be available to the macros in this module without clients having to depend on
12// log themselves.
13#[doc(hidden)]
14pub use log as __log;
15
16pub use log::kv::{ToValue, Value};
17pub use log::{Level, Record, logger};
18
19/// Used to track the current thread's logical context.
20enum TaskDebugInfo {
21    /// The thread with this set is used for internal logic within the starnix kernel.
22    Kernel,
23    /// The thread with this set is used to service syscalls for a specific user thread, and this
24    /// describes the user thread's identity.
25    User { pid: pid_t, tid: tid_t, command: TaskCommand, leader_command: TaskCommand },
26    /// Unknown info. This happens when trying to log while in the destructor of a thread local
27    /// variable.
28    Unknown,
29}
30
31impl TaskDebugInfo {
32    pub fn leader_command(&self) -> TaskCommand {
33        match self {
34            Self::Kernel => TaskCommand::new(b"kthreadd"),
35            Self::User { leader_command, .. } => leader_command.clone(),
36            Self::Unknown => TaskCommand::new(b"<unknown>"),
37        }
38    }
39}
40
41thread_local! {
42    /// When a thread in this kernel is started, it is a kthread by default. Once the thread
43    /// becomes aware of the user-level task it is executing, this thread-local should be set to
44    /// include that info.
45    static CURRENT_TASK_INFO: RefCell<TaskDebugInfo> = const { RefCell::new(TaskDebugInfo::Kernel) } ;
46}
47
48impl fmt::Display for TaskDebugInfo {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Kernel => write!(f, "kthread"),
52            Self::User { pid, tid, command, .. } => write!(f, "{pid}:{tid}[{command}]"),
53            Self::Unknown => write!(f, "unknown"),
54        }
55    }
56}
57
58/// Helper type for logging macros that implements `Display` by reading the current thread's
59/// `TaskDebugInfo` on demand, avoiding the need for an enclosing closure.
60#[doc(hidden)]
61#[derive(Clone, Copy, Debug)]
62pub struct CurrentTaskInfo;
63
64impl fmt::Display for CurrentTaskInfo {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        with_current_task_info(|task| fmt::Display::fmt(task, f))
67    }
68}
69
70#[inline]
71pub const fn trace_debug_logs_enabled() -> bool {
72    // Allow trace and debug logs if we are in a debug (non-release) build
73    // or feature `trace_and_debug_logs_in_release` is enabled.
74    cfg!(debug_assertions) || cfg!(feature = "trace_and_debug_logs_in_release")
75}
76
77#[macro_export]
78macro_rules! log_trace {
79    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => {
80        if $crate::trace_debug_logs_enabled() {
81            $crate::__log::trace!(
82                tag:% = $crate::CurrentTaskInfo,
83                $($key $(:$capture)* $(= $value)*),+;
84                $($arg)*
85            );
86        }
87    };
88    ($($arg:tt)*) => {
89        if $crate::trace_debug_logs_enabled() {
90            $crate::__log::trace!(tag:% = $crate::CurrentTaskInfo; $($arg)*)
91        }
92    };
93}
94
95#[macro_export]
96macro_rules! log_syscall {
97    ($current_task:expr, $($arg:tt)*) => {
98        if $crate::trace_debug_logs_enabled() {
99            $crate::log!(
100                $current_task.task.thread_group.syscall_log_level(),
101                $($arg)*
102            )
103        }
104    };
105}
106
107#[macro_export]
108macro_rules! log_debug {
109    ($($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => {
110        if $crate::trace_debug_logs_enabled() {
111            $crate::__log::debug!(
112                tag:% = $crate::CurrentTaskInfo,
113                $($key $(:$capture)* $(= $value)*),+;
114                $($arg)*
115            );
116        }
117    };
118    ($($arg:tt)*) => {
119        if $crate::trace_debug_logs_enabled() {
120            $crate::__log::debug!(tag:% = $crate::CurrentTaskInfo; $($arg)*)
121        }
122    };
123}
124
125#[macro_export]
126macro_rules! log_info {
127    ($($arg:tt)*) => {
128        $crate::log!($crate::__log::Level::Info, $($arg)*)
129    };
130}
131
132#[macro_export]
133macro_rules! log_warn {
134    ($($arg:tt)*) => {
135        $crate::log!($crate::__log::Level::Warn, $($arg)*)
136    };
137}
138
139#[macro_export]
140macro_rules! log_error {
141    ($($arg:tt)*) => {
142        $crate::log!($crate::__log::Level::Error, $($arg)*)
143    };
144}
145
146#[macro_export]
147macro_rules! log {
148    ($lvl:expr, $($key:tt $(:$capture:tt)? $(= $value:expr)?),+; $($arg:tt)+) => {
149        $crate::__log::log!(
150            $lvl,
151            tag:% = $crate::CurrentTaskInfo,
152            $($key $(:$capture)* $(= $value)*),+;
153            $($arg)*
154        )
155    };
156    ($lvl:expr, $($arg:tt)+) => {
157        $crate::__log::log!($lvl, tag:% = $crate::CurrentTaskInfo; $($arg)*)
158    };
159}
160
161// Call this when you get an error that should "never" happen, i.e. if it does that means the
162// kernel was updated to produce some other error after this match was written.
163#[track_caller]
164pub fn impossible_error(status: zx::Status) -> Errno {
165    panic!("encountered impossible error: {status}");
166}
167
168pub fn set_zx_name(obj: &impl zx::AsHandleRef, name: impl AsRef<[u8]>) {
169    match obj.as_handle_ref().set_name(&zx::Name::from_bytes_lossy(name.as_ref())) {
170        // ZX_ERR_BAD_STATE occurs if the target thread has exited or is in the
171        // DYING/DEAD state. Allow it since the thread is in the process of tearing down.
172        Ok(()) | Err(zx::Status::BAD_STATE) => {}
173        Err(status) => {
174            impossible_error(status);
175        }
176    }
177}
178
179pub fn with_zx_name<O: zx::AsHandleRef>(obj: O, name: impl AsRef<[u8]>) -> O {
180    set_zx_name(&obj, name);
181    obj
182}
183
184/// Set the context for log messages from this thread. Should only be called when a thread has been
185/// created to execute a user-level task, and should only be called once at the start of that
186/// thread's execution.
187pub fn set_current_task_info(
188    command: TaskCommand,
189    leader_command: TaskCommand,
190    pid: pid_t,
191    tid: tid_t,
192) {
193    CURRENT_TASK_INFO.with(|task_info| {
194        *task_info.borrow_mut() = TaskDebugInfo::User { pid, tid, command, leader_command };
195    });
196}
197
198/// Access this thread's task info for debugging. Intended for use internally by Starnix's log
199/// macros.
200///
201/// *Do not use this for kernel logic.* If you need access to the current pid/tid/etc for the
202/// purposes of writing kernel logic beyond logging for debugging purposes, those should be accessed
203/// through the `CurrentTask` type as an argument explicitly passed to your function.
204#[doc(hidden)]
205pub fn with_current_task_info<T>(mut f: impl FnMut(&dyn fmt::Display) -> T) -> T {
206    match CURRENT_TASK_INFO.try_with(|task_info| f(&task_info.borrow())) {
207        Ok(value) => value,
208        Err(_) => f(&TaskDebugInfo::Unknown),
209    }
210}
211
212pub(crate) fn get_current_leader_command() -> flyweights::FlyByteStr {
213    match CURRENT_TASK_INFO.try_with(|task_info| task_info.borrow().leader_command()) {
214        Ok(value) => value.into(),
215        Err(_) => flyweights::FlyByteStr::new(b"<unknown>"),
216    }
217}
218
219/// A filter for syscall logging.
220#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221pub struct SyscallLogFilter {
222    match_string: String,
223}
224
225impl SyscallLogFilter {
226    pub fn new(match_string: String) -> Self {
227        Self { match_string }
228    }
229
230    pub fn matches(&self, command: &TaskCommand) -> bool {
231        let matcher = self.match_string.as_bytes();
232        command.as_bytes().windows(matcher.len()).any(|w| w == matcher)
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    unsafe extern "C" {
241        fn zx_thread_self() -> zx::sys::zx_handle_t;
242    }
243
244    #[test]
245    fn test_set_zx_name_on_terminated_thread() {
246        let mut terminated_thread = None;
247        std::thread::scope(|s| {
248            s.spawn(|| {
249                terminated_thread = Some(
250                    #[allow(clippy::undocumented_unsafe_blocks)]
251                    unsafe {
252                        let thread = zx::Unowned::<zx::Thread>::from_raw_handle(zx_thread_self());
253                        thread.duplicate_handle(zx::Rights::SAME_RIGHTS)
254                    }
255                    .unwrap(),
256                );
257            });
258        });
259        let terminated_thread = terminated_thread.expect("failed to obtain thread handle");
260        let _ = terminated_thread
261            .wait_one(zx::Signals::THREAD_TERMINATED, zx::MonotonicInstant::INFINITE);
262
263        // The scoped thread has terminated and is in ZX_THREAD_STATE_DEAD.
264        // In the Zircon microkernel, ThreadDispatcher::set_name returns ZX_ERR_BAD_STATE
265        // because core_thread_ is nullptr.
266        // Before fix: set_zx_name calls impossible_error(BAD_STATE) and panics.
267        // After fix: set_zx_name tolerates ZX_ERR_BAD_STATE and returns Ok(()).
268        set_zx_name(&terminated_thread, b"dead-thread-name");
269    }
270
271    #[test]
272    #[should_panic(expected = "encountered impossible error: BAD_HANDLE")]
273    #[allow(clippy::undocumented_unsafe_blocks)]
274    fn test_set_zx_name_invalid_handle_panics() {
275        let invalid =
276            unsafe { zx::Unowned::<zx::Thread>::from_raw_handle(zx::sys::ZX_HANDLE_INVALID) };
277        set_zx_name(&invalid, b"any-name");
278    }
279}