Skip to main content

patina_debugger/
lib.rs

1//! Patina Debugger
2//!
3//! This crate provides a debugger implementation that will install itself in the
4//! exception handlers and communicate with debugger software using the GDB Remote
5//! protocol. The debugger is intended to be used in the boot phase cores.
6//!
7//! This crate is under construction and may be missing functionality, documentation,
8//! and testing.
9//!
10//! ## Getting Started
11//!
12//! For more details on using the debugger on a device, see the [readme](./Readme.md).
13//!
14//! ## Examples and Usage
15//!
16//! The debugger consists of the static access routines and the underlying debugger
17//! struct. The top level platform code should initialize the static `PatinaDebugger`
18//! struct with the appropriate serial transport and default configuration. The
19//! platform has the option of setting static configuration, or enabling the
20//! debugger in runtime code based on platform policy. During entry, the platform
21//! should use the `set_debugger` routine to set the global instance of the debugger.
22//!
23//! Core code should use the static routines to interact with the debugger. If the
24//! debugger is either not set or not enabled, the static routines will be no-ops.
25//!
26//! ```rust
27//! extern crate patina;
28//! # extern crate patina_internal_cpu;
29//! # use patina_internal_cpu::interrupts::{Interrupts, InterruptManager};
30//! # use patina::component::service::perf_timer::ArchTimerFunctionality;
31//!
32//! static DEBUGGER: patina_debugger::PatinaDebugger<patina::serial::uart::UartNull> =
33//!     patina_debugger::PatinaDebugger::new(patina::serial::uart::UartNull{})
34//!         .with_timeout(30); // Set initial break timeout to 30 seconds.
35//!
36//! fn entry() {
37//!
38//!     // Configure the debugger. This is used for dynamic configuration of the debugger.
39//!     DEBUGGER.enable(true);
40//!
41//!     // Set the global debugger instance. This can only be done once.
42//!     patina_debugger::set_debugger(&DEBUGGER);
43//!
44//!     // Setup a custom monitor command for this platform.
45//!     patina_debugger::add_monitor_command("my_command", "Description of my_command", |args, writer| {
46//!         // Parse the arguments from _args, which is a SplitWhitespace iterator.
47//!         let _ = write!(writer, "Executed my_command with args: {:?}", args);
48//!     });
49//!
50//!     // Call the core entry. The core can then initialize and access the debugger
51//!     // through the static routines.
52//!     start();
53//! }
54//!
55//! fn start() {
56//!     // Initialize the debugger. This will cause a debug break because of the
57//!     // initial break configuration set above.
58//!     patina_debugger::initialize(&mut Interrupts::default(), Some(&ExampleTimer));
59//!
60//!     // Notify the debugger of a module load.
61//!     patina_debugger::notify_module_load("module.efi", 0x420000, 0x10000);
62//!
63//!     // Poll the debugger for any pending interrupts.
64//!     patina_debugger::poll_debugger();
65//!
66//!     // Break into the debugger if the debugger is enabled and initialized.
67//!     patina_debugger::breakpoint();
68//!
69//!     // Cause a debug break unconditionally. This will crash the system
70//!     // if the debugger is not enabled or initialized. This should be used with extreme caution.
71//!     patina_debugger::breakpoint_unchecked();
72//! }
73//!
74//! # struct ExampleTimer;
75//! # impl ArchTimerFunctionality for ExampleTimer {
76//! #     fn cpu_count(&self) -> u64 {
77//! #         0
78//! #     }
79//! #
80//! #     fn perf_frequency(&self) -> u64 {
81//! #         1
82//! #     }
83//! # }
84//!
85//! ```
86//!
87//! The debugger can be further configured by using various functions on the
88//! initialization of the debugger struct. See the definition for [debugger::PatinaDebugger]
89//! for more details. Notably, if the device is using the same transport for
90//! logging and debugger, it is advisable to use `.without_log_init()`.
91//!
92//! ## Features
93//!
94//! `alloc` - Uses allocated buffers rather than static buffers for all memory. This provides additional functionality
95//! but prevents debugging prior to allocations being available. This is intended for use by the core crate, and not
96//! for platform use.
97//!
98//! ## License
99//!
100//! Copyright (C) Microsoft Corporation.
101//!
102//! SPDX-License-Identifier: Apache-2.0
103//!
104#![cfg_attr(not(test), no_std)]
105#![feature(coverage_attribute)]
106
107#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
108mod arch;
109#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
110mod dbg_target;
111#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
112mod debugger;
113mod memory;
114mod system;
115mod transport;
116
117#[cfg(any(feature = "alloc", test))]
118extern crate alloc;
119
120pub use debugger::PatinaDebugger;
121
122#[cfg(not(test))]
123use arch::{DebuggerArch, SystemArch};
124use patina::{component::service::perf_timer::ArchTimerFunctionality, serial::SerialIO};
125use patina_internal_cpu::interrupts::{ExceptionContext, InterruptManager};
126
127/// Global instance of the debugger.
128///
129/// This is only expected to be set once, and will be accessed through the static
130/// routines after that point. Because the debugger is expected to install itself
131/// in exception handlers and will have access to other static state for things
132/// like breakpoints, it is not safe to remove or replace it. For this reason,
133/// this uses the Once lock to provide these properties.
134///
135static DEBUGGER: spin::Once<&dyn Debugger> = spin::Once::new();
136
137/// Type for monitor command functions. This will be invoked by the debugger when
138/// the associated monitor command is invoked.
139///
140/// The first argument contains the whitespace separated arguments from the command.
141/// For example, if the command is `my_command arg1 arg2`, then `arg1` and `arg2` will
142/// be the first and second elements of the iterator respectively.
143///
144/// The second argument is a writer that should be used to write the output of the
145/// command. This can be done by directly invoking the [core::fmt::Write] trait methods
146/// or using the `write!` macro. `format!` should be avoided as it will allocate memory
147/// which shouldn't be done in debugger when possible.
148pub type MonitorCommandFn = dyn Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync;
149
150/// Trait for debugger interaction. This is required to allow for a global to the
151/// platform specific debugger implementation. For safety, these routines should
152/// only be invoked on the global instance of the debugger.
153trait Debugger: Sync {
154    #[cfg(test)]
155    fn test_initialize(&'static self, initialized: bool);
156
157    /// Initializes the debugger. Intended for core use only.
158    fn initialize(
159        &'static self,
160        interrupt_manager: &mut dyn InterruptManager,
161        timer: Option<&'static dyn ArchTimerFunctionality>,
162    );
163
164    /// Checks if the debugger is enabled.
165    fn enabled(&'static self) -> bool;
166
167    /// Checks if the debugger is initialized.
168    fn initialized(&'static self) -> bool;
169
170    /// Notifies the debugger of a module load.
171    fn notify_module_load(&'static self, module_name: &str, _address: usize, _length: usize);
172
173    /// Polls the debugger for any pending interrupts.
174    fn poll_debugger(&'static self);
175
176    #[cfg(feature = "alloc")]
177    fn add_monitor_command(
178        &'static self,
179        command: &'static str,
180        description: &'static str,
181        callback: alloc::boxed::Box<MonitorCommandFn>,
182    );
183}
184
185#[derive(Debug)]
186#[allow(dead_code)]
187enum DebugError {
188    /// The debugger lock could not be acquired. Usually indicating the debugger faulted.
189    Reentry,
190    /// The debugger configuration is locked. This indicates a failure during debugger configuration.
191    ConfigLocked,
192    /// The debugger was invoked without being fully initialized.
193    NotInitialized,
194    /// Failure from the GDB stub initialization.
195    GdbStubInit,
196    /// Failure from the GDB stub.
197    GdbStubError(gdbstub::stub::GdbStubError<(), patina::error::EfiError>),
198    /// Failure to reboot the system.
199    RebootFailure,
200    /// Failure in the transport layer.
201    TransportFailure,
202}
203
204/// Policy for how the debugger will handle logging on the system.
205pub enum DebuggerLoggingPolicy {
206    /// The debugger will suspend logging while broken in, but will not change the
207    /// logging state outside of the debugger. This may cause instability if the
208    /// debugger and logging share a transport.
209    SuspendLogging,
210    /// The debugger will disable all logging after a connection is made. This is
211    /// the safest option if the debugger and logging share a transport.
212    DisableLogging,
213    /// The debugger will not suspend logging while broken in and will allow log
214    /// messages from the debugger itself. This should only be used if the debugger
215    /// and logging transports are separate.
216    FullLogging,
217}
218
219/// Sets the global instance of the debugger.
220pub fn set_debugger<T: SerialIO>(debugger: &'static PatinaDebugger<T>) {
221    DEBUGGER.call_once(|| debugger);
222}
223
224/// Initializes the debugger. This will install the debugger into the exception
225/// handlers using the provided interrupt manager. This routine may invoke a debug
226/// break depending on configuration.
227#[coverage(off)] // Initializing the debugger requires integration testing infrastructure. Disabling coverage until this is completed.
228pub fn initialize(interrupt_manager: &mut dyn InterruptManager, timer: Option<&'static dyn ArchTimerFunctionality>) {
229    if let Some(debugger) = DEBUGGER.get() {
230        debugger.initialize(interrupt_manager, timer);
231    }
232}
233
234/// Invokes a debug break instruction if the debugger is enabled and initialized. This will cause
235/// the debugger to break in. If the debugger is not enabled and initialized, this routine will have no effect.
236pub fn breakpoint() {
237    if enabled() {
238        if initialized() {
239            breakpoint_unchecked();
240        } else {
241            log::error!("Debugger breakpoint invoked before debugger initialized, not breaking in!");
242        }
243    }
244}
245
246/// Invokes a debug break instruction unconditionally. If this routine is invoked when
247/// the debugger is not enabled and initialized, it will cause an unhandled exception.
248///
249/// ## Important
250///
251/// This should only be used in debug scenarios or when it is impossible to continue
252/// execution in the current state and an CPU exception must be raised.
253#[inline(always)]
254pub fn breakpoint_unchecked() {
255    #[cfg(not(test))]
256    SystemArch::breakpoint();
257    #[cfg(test)]
258    panic!("breakpoint_unchecked");
259}
260
261/// Notifies the debugger of a module load at the provided address and length.
262/// This should be invoked before the module has begun execution.
263pub fn notify_module_load(module_name: &str, address: usize, length: usize) {
264    if let Some(debugger) = DEBUGGER.get() {
265        debugger.notify_module_load(module_name, address, length);
266    }
267}
268
269/// Polls the debugger for any pending interrupts. The routine may cause a debug
270/// break.
271pub fn poll_debugger() {
272    if let Some(debugger) = DEBUGGER.get() {
273        debugger.poll_debugger();
274    }
275}
276
277/// Checks if the debugger is enabled.
278pub fn enabled() -> bool {
279    match DEBUGGER.get() {
280        Some(debugger) => debugger.enabled(),
281        None => false,
282    }
283}
284
285/// Checks if the debugger is initialized.
286pub fn initialized() -> bool {
287    match DEBUGGER.get() {
288        Some(debugger) => debugger.initialized(),
289        None => false,
290    }
291}
292
293/// Adds a monitor command to the debugger. This may be called before initialization,
294/// but should not be called before memory allocations are available. See [MonitorCommandFn]
295/// for more details on the callback function expectations.
296///
297/// ## Example
298///
299/// ```rust
300/// patina_debugger::add_monitor_command("my_command", "Description of my_command", |args, writer| {
301///     // Parse the arguments from _args, which is a SplitWhitespace iterator.
302///     let _ = write!(writer, "Executed my_command with args: {:?}", args);
303/// });
304/// ```
305///
306#[cfg(feature = "alloc")]
307pub fn add_monitor_command<F>(cmd: &'static str, description: &'static str, function: F)
308where
309    F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
310{
311    if let Some(debugger) = DEBUGGER.get() {
312        debugger.add_monitor_command(cmd, description, alloc::boxed::Box::new(function));
313    }
314}
315
316/// Adds a monitor command to the debugger. This may be called before initialization,
317/// but should not be called before memory allocations are available. See [MonitorCommandFn]
318/// for more details on the callback function expectations.
319///
320/// ## Example
321///
322/// ```rust
323/// patina_debugger::add_monitor_command("my_command", "Description of my_command", |args, writer| {
324///     // Parse the arguments from _args, which is a SplitWhitespace iterator.
325///     let _ = write!(writer, "Executed my_command with args: {:?}", args);
326/// });
327/// ```
328///
329#[cfg(not(feature = "alloc"))]
330pub fn add_monitor_command<F>(cmd: &'static str, _description: &'static str, _function: F)
331where
332    F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
333{
334    if let Some(_) = DEBUGGER.get() {
335        log::warn!("Dynamic monitor commands require the 'alloc' feature. Will not add command: {cmd}");
336    }
337}
338
339/// Exception information for the debugger.
340#[allow(dead_code)]
341struct ExceptionInfo {
342    /// The type of exception that occurred.
343    pub exception_type: ExceptionType,
344    /// The instruction pointer address.
345    pub instruction_pointer: u64,
346    /// The system context at the time of the exception.
347    pub context: ExceptionContext,
348}
349
350/// Exception type information.
351#[derive(PartialEq, Eq)]
352#[allow(dead_code)]
353enum ExceptionType {
354    /// A break due to a completed instruction step.
355    Step,
356    /// A break due to a breakpoint instruction.
357    Breakpoint,
358    /// A break due to an invalid memory access. The accessed address is provided.
359    AccessViolation(usize),
360    /// A general protection fault. Exception data is provided.
361    GeneralProtectionFault(u64),
362    /// A break due to an exception type not handled by the debugger. The exception type is provided.
363    Other(u64),
364}
365
366impl core::fmt::Display for ExceptionType {
367    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
368        match self {
369            ExceptionType::Step => write!(f, "Debug Step"),
370            ExceptionType::Breakpoint => write!(f, "Breakpoint"),
371            ExceptionType::AccessViolation(addr) => write!(f, "Access Violation at {addr:#X}"),
372            ExceptionType::GeneralProtectionFault(data) => {
373                write!(f, "General Protection Fault. Exception data: {data:#X}")
374            }
375            ExceptionType::Other(exception_type) => write!(f, "Unknown. Architecture code: {exception_type:#X}"),
376        }
377    }
378}
379
380#[coverage(off)]
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use serial_test::serial;
385
386    static DUMMY_DEBUGGER: PatinaDebugger<patina::serial::uart::UartNull> =
387        PatinaDebugger::new(patina::serial::uart::UartNull {});
388
389    fn reset() {
390        // Reset the global debugger for testing.
391        DUMMY_DEBUGGER.enable(false);
392        DUMMY_DEBUGGER.test_initialize(false);
393        if !DEBUGGER.is_completed() {
394            set_debugger(&DUMMY_DEBUGGER);
395        }
396    }
397
398    #[test]
399    #[serial(global_debugger)]
400    fn test_debug_break_not_enabled() {
401        reset();
402        // Ensure that invoking a debug break when the debugger is not enabled does not cause issues.
403        breakpoint();
404    }
405
406    #[test]
407    #[serial(global_debugger)]
408    fn test_debug_break_not_initialized() {
409        reset();
410        DUMMY_DEBUGGER.enable(true);
411        // Ensure that invoking a debug break when the debugger is not initialized does not cause issues.
412        breakpoint();
413    }
414
415    #[test]
416    #[should_panic(expected = "breakpoint_unchecked")]
417    #[serial(global_debugger)]
418    fn test_debug_break_enabled_and_initialized() {
419        reset();
420        // Enable the debugger.
421        DUMMY_DEBUGGER.enable(true);
422        DUMMY_DEBUGGER.test_initialize(true);
423        // Ensure that invoking a debug break when the debugger is enabled and initialized causes a panic.
424        breakpoint();
425    }
426}