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}