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.
67//!     patina_debugger::breakpoint();
68//!
69//!     // Cause a debug break unconditionally. This will crash the system
70//!     // if the debugger is not enabled. 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//! `windbg_workarounds` - (Default) Enables workarounds for Windbg compatibility.
95//!
96//! `alloc` - Uses allocated buffers rather than static buffers for all memory. This provides additional functionality
97//! but prevents debugging prior to allocations being available.
98//!
99//! ## License
100//!
101//! Copyright (C) Microsoft Corporation.
102//!
103//! SPDX-License-Identifier: Apache-2.0
104//!
105#![cfg_attr(not(test), no_std)]
106#![feature(coverage_attribute)]
107
108#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
109mod arch;
110#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
111mod dbg_target;
112#[coverage(off)] // The debugger needs integration test infrastructure. Disabling coverage until this is completed.
113mod debugger;
114mod memory;
115mod system;
116mod transport;
117
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.
147pub type MonitorCommandFn = dyn Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync;
148
149/// Trait for debugger interaction. This is required to allow for a global to the
150/// platform specific debugger implementation. For safety, these routines should
151/// only be invoked on the global instance of the debugger.
152trait Debugger: Sync {
153    /// Initializes the debugger. Intended for core use only.
154    fn initialize(
155        &'static self,
156        interrupt_manager: &mut dyn InterruptManager,
157        timer: Option<&'static dyn ArchTimerFunctionality>,
158    );
159
160    /// Checks if the debugger is enabled.
161    fn enabled(&'static self) -> bool;
162
163    /// Notifies the debugger of a module load.
164    fn notify_module_load(&'static self, module_name: &str, _address: usize, _length: usize);
165
166    /// Polls the debugger for any pending interrupts.
167    fn poll_debugger(&'static self);
168
169    fn add_monitor_command(
170        &'static self,
171        command: &'static str,
172        description: &'static str,
173        callback: alloc::boxed::Box<MonitorCommandFn>,
174    );
175}
176
177#[derive(Debug)]
178#[allow(dead_code)]
179enum DebugError {
180    /// The debugger lock could not be acquired. Usually indicating the debugger faulted.
181    Reentry,
182    /// The debugger configuration is locked. This indicates a failure during debugger configuration.
183    ConfigLocked,
184    /// The debugger was invoked without being fuly initialized.
185    NotInitialized,
186    /// Failure from the GDB stub initialization.
187    GdbStubInit,
188    /// Failure from the GDB stub.
189    GdbStubError(gdbstub::stub::GdbStubError<(), patina::error::EfiError>),
190    /// Failure to reboot the system.
191    RebootFailure,
192    /// Failure in the transport layer.
193    TransportFailure,
194}
195
196/// Policy for how the debugger will handle logging on the system.
197pub enum DebuggerLoggingPolicy {
198    /// The debugger will suspend logging while broken in, but will not change the
199    /// logging state outside of the debugger. This may cause instability if the
200    /// debugger and logging share a transport.
201    SuspendLogging,
202    /// The debugger will disable all logging after a connection is made. This is
203    /// the safest option if the debugger and logging share a transport.
204    DisableLogging,
205    /// The debugger will not suspend logging while broken in and will allow log
206    /// messages from the debugger itself. This should only be used if the debugger
207    /// and logging transports are separate.
208    FullLogging,
209}
210
211/// Sets the global instance of the debugger.
212pub fn set_debugger<T: SerialIO>(debugger: &'static PatinaDebugger<T>) {
213    DEBUGGER.call_once(|| debugger);
214}
215
216/// Initializes the debugger. This will install the debugger into the exception
217/// handlers using the provided interrupt manager. This routine may invoke a debug
218/// break depending on configuration.
219#[coverage(off)] // Initializing the debugger requires integration testing infrastructure. Disabling coverage until this is completed.
220pub fn initialize(interrupt_manager: &mut dyn InterruptManager, timer: Option<&'static dyn ArchTimerFunctionality>) {
221    if let Some(debugger) = DEBUGGER.get() {
222        debugger.initialize(interrupt_manager, timer);
223    }
224}
225
226/// Invokes a debug break instruction if the debugger is enabled. This will cause
227/// the debugger to break in, if enabled. If the debugger is not enabled, this
228/// routine will have no effect.
229pub fn breakpoint() {
230    if enabled() {
231        breakpoint_unchecked();
232    }
233}
234
235/// Invokes a debug break instruction unconditionally. If this routine is invoked when
236/// the debugger is not enabled, it will cause an unhandled exception.
237///
238/// ## Important
239///
240/// This should only be used in debug scenarios or when it is impossible to continue
241/// execution in the current state and an CPU exception must be raised.
242#[inline(always)]
243pub fn breakpoint_unchecked() {
244    #[cfg(not(test))]
245    SystemArch::breakpoint();
246    #[cfg(test)]
247    panic!("breakpoint_unchecked");
248}
249
250/// Notifies the debugger of a module load at the provided address and length.
251/// This should be invoked before the module has begun execution.
252pub fn notify_module_load(module_name: &str, address: usize, length: usize) {
253    if let Some(debugger) = DEBUGGER.get() {
254        debugger.notify_module_load(module_name, address, length);
255    }
256}
257
258/// Polls the debugger for any pending interrupts. The routine may cause a debug
259/// break.
260pub fn poll_debugger() {
261    if let Some(debugger) = DEBUGGER.get() {
262        debugger.poll_debugger();
263    }
264}
265
266/// Checks if the debugger is enabled.
267pub fn enabled() -> bool {
268    match DEBUGGER.get() {
269        Some(debugger) => debugger.enabled(),
270        None => false,
271    }
272}
273
274/// Adds a monitor command to the debugger. This may be called before initialization,
275/// but should not be called before memory allocations are available. See [MonitorCommandFn]
276/// for more details on the callback function expectations.
277///
278/// ## Example
279///
280/// ```rust
281/// patina_debugger::add_monitor_command("my_command", "Description of my_command", |args, writer| {
282///     // Parse the arguments from _args, which is a SplitWhitespace iterator.
283///     let _ = write!(writer, "Executed my_command with args: {:?}", args);
284/// });
285/// ```
286///
287#[cfg(feature = "alloc")]
288pub fn add_monitor_command<F>(cmd: &'static str, description: &'static str, function: F)
289where
290    F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
291{
292    if let Some(debugger) = DEBUGGER.get() {
293        debugger.add_monitor_command(cmd, description, alloc::boxed::Box::new(function));
294    }
295}
296
297/// Adds a monitor command to the debugger. This may be called before initialization,
298/// but should not be called before memory allocations are available. See [MonitorCommandFn]
299/// for more details on the callback function expectations.
300///
301/// ## Example
302///
303/// ```rust
304/// patina_debugger::add_monitor_command("my_command", "Description of my_command", |args, writer| {
305///     // Parse the arguments from _args, which is a SplitWhitespace iterator.
306///     let _ = write!(writer, "Executed my_command with args: {:?}", args);
307/// });
308/// ```
309///
310#[cfg(not(feature = "alloc"))]
311pub fn add_monitor_command<F>(cmd: &'static str, description: &'static str, function: F)
312where
313    F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
314{
315    if let Some(debugger) = DEBUGGER.get() {
316        log::warn!(
317            "Monitor commands are only supported with the 'alloc' feature enabled. Will not add command: {command}"
318        );
319    }
320}
321
322/// Exception information for the debugger.
323#[allow(dead_code)]
324struct ExceptionInfo {
325    /// The type of exception that occurred.
326    pub exception_type: ExceptionType,
327    /// The instruction pointer address.
328    pub instruction_pointer: u64,
329    /// The system context at the time of the exception.
330    pub context: ExceptionContext,
331}
332
333/// Exception type information.
334#[derive(PartialEq, Eq)]
335#[allow(dead_code)]
336enum ExceptionType {
337    /// A break due to a completed instruction step.
338    Step,
339    /// A break due to a breakpoint instruction.
340    Breakpoint,
341    /// A break due to an invalid memory access. The accessed address is provided.
342    AccessViolation(usize),
343    /// A general protection fault. Exception data is provided.
344    GeneralProtectionFault(u64),
345    /// A break due to an exception type not handled by the debugger. The exception type is provided.
346    Other(u64),
347}
348
349impl core::fmt::Display for ExceptionType {
350    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351        match self {
352            ExceptionType::Step => write!(f, "Debug Step"),
353            ExceptionType::Breakpoint => write!(f, "Breakpoint"),
354            ExceptionType::AccessViolation(addr) => write!(f, "Access Violation at {addr:#X}"),
355            ExceptionType::GeneralProtectionFault(data) => {
356                write!(f, "General Protection Fault. Exception data: {data:#X}")
357            }
358            ExceptionType::Other(exception_type) => write!(f, "Unknown. Architecture code: {exception_type:#X}"),
359        }
360    }
361}
362
363#[coverage(off)]
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use serial_test::serial;
368
369    static DUMMY_DEBUGGER: PatinaDebugger<patina::serial::uart::UartNull> =
370        PatinaDebugger::new(patina::serial::uart::UartNull {});
371
372    fn reset() {
373        // Reset the global debugger for testing.
374        DUMMY_DEBUGGER.enable(false);
375        if !DEBUGGER.is_completed() {
376            set_debugger(&DUMMY_DEBUGGER);
377        }
378    }
379
380    #[test]
381    #[serial(global_debugger)]
382    fn test_debug_break_not_enabled() {
383        reset();
384        // Ensure that invoking a debug break when the debugger is not enabled does not cause issues.
385        breakpoint();
386    }
387
388    #[test]
389    #[should_panic(expected = "breakpoint_unchecked")]
390    #[serial(global_debugger)]
391    fn test_debug_break_enabled() {
392        reset();
393        // Enable the debugger.
394        DUMMY_DEBUGGER.enable(true);
395
396        // Ensure that invoking a debug break when the debugger is enabled causes a panic.
397        breakpoint();
398    }
399}