#![cfg_attr(not(test), no_std)]
#![feature(coverage_attribute)]
#[coverage(off)] mod arch;
#[coverage(off)] mod dbg_target;
#[coverage(off)] mod debugger;
mod memory;
mod system;
mod transport;
#[cfg(any(feature = "alloc", test))]
extern crate alloc;
pub use debugger::PatinaDebugger;
#[cfg(not(test))]
use arch::{DebuggerArch, SystemArch};
use patina::{component::service::perf_timer::ArchTimerFunctionality, serial::SerialIO};
use patina_internal_cpu::interrupts::{ExceptionContext, InterruptManager};
static DEBUGGER: spin::Once<&dyn Debugger> = spin::Once::new();
pub type MonitorCommandFn = dyn Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync;
trait Debugger: Sync {
#[cfg(test)]
fn test_initialize(&'static self, initialized: bool);
fn initialize(
&'static self,
interrupt_manager: &mut dyn InterruptManager,
timer: Option<&'static dyn ArchTimerFunctionality>,
);
fn enabled(&'static self) -> bool;
fn initialized(&'static self) -> bool;
fn notify_module_load(&'static self, module_name: &str, _address: usize, _length: usize);
fn poll_debugger(&'static self);
#[cfg(feature = "alloc")]
fn add_monitor_command(
&'static self,
command: &'static str,
description: Option<&'static str>,
callback: alloc::boxed::Box<MonitorCommandFn>,
);
}
#[derive(Debug)]
#[allow(dead_code)]
enum DebugError {
Reentry,
ConfigLocked,
NotInitialized,
GdbStubInit,
GdbStubError(gdbstub::stub::GdbStubError<(), patina::error::EfiError>),
RebootFailure,
TransportFailure,
}
pub enum DebuggerLoggingPolicy {
SuspendLogging,
DisableLogging,
FullLogging,
}
pub fn set_debugger<T: SerialIO>(debugger: &'static PatinaDebugger<T>) {
DEBUGGER.call_once(|| debugger);
}
#[coverage(off)] pub fn initialize(interrupt_manager: &mut dyn InterruptManager, timer: Option<&'static dyn ArchTimerFunctionality>) {
if let Some(debugger) = DEBUGGER.get() {
debugger.initialize(interrupt_manager, timer);
}
}
pub fn breakpoint() {
if enabled() {
if initialized() {
breakpoint_unchecked();
} else {
log::error!("Debugger breakpoint invoked before debugger initialized, not breaking in!");
}
}
}
#[inline(always)]
pub fn breakpoint_unchecked() {
#[cfg(not(test))]
SystemArch::breakpoint();
#[cfg(test)]
panic!("breakpoint_unchecked");
}
pub fn notify_module_load(module_name: &str, address: usize, length: usize) {
if let Some(debugger) = DEBUGGER.get() {
debugger.notify_module_load(module_name, address, length);
}
}
pub fn poll_debugger() {
if let Some(debugger) = DEBUGGER.get() {
debugger.poll_debugger();
}
}
pub fn enabled() -> bool {
match DEBUGGER.get() {
Some(debugger) => debugger.enabled(),
None => false,
}
}
pub fn initialized() -> bool {
match DEBUGGER.get() {
Some(debugger) => debugger.initialized(),
None => false,
}
}
#[cfg(feature = "alloc")]
pub fn add_monitor_command<F>(cmd: &'static str, description: Option<&'static str>, function: F)
where
F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
{
if let Some(debugger) = DEBUGGER.get() {
debugger.add_monitor_command(cmd, description, alloc::boxed::Box::new(function));
}
}
#[cfg(not(feature = "alloc"))]
pub fn add_monitor_command<F>(cmd: &'static str, _description: Option<&'static str>, _function: F)
where
F: Fn(&mut core::str::SplitWhitespace<'_>, &mut dyn core::fmt::Write) + Send + Sync + 'static,
{
if let Some(_) = DEBUGGER.get() {
log::warn!("Dynamic monitor commands require the 'alloc' feature. Will not add command: {cmd}");
}
}
#[allow(dead_code)]
struct ExceptionInfo {
pub exception_type: ExceptionType,
pub instruction_pointer: u64,
pub context: ExceptionContext,
}
#[derive(PartialEq, Eq)]
#[allow(dead_code)]
enum ExceptionType {
Step,
Breakpoint,
AccessViolation(usize),
GeneralProtectionFault(u64),
Other(u64),
}
impl core::fmt::Display for ExceptionType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ExceptionType::Step => write!(f, "Debug Step"),
ExceptionType::Breakpoint => write!(f, "Breakpoint"),
ExceptionType::AccessViolation(addr) => write!(f, "Access Violation at {addr:#X}"),
ExceptionType::GeneralProtectionFault(data) => {
write!(f, "General Protection Fault. Exception data: {data:#X}")
}
ExceptionType::Other(exception_type) => write!(f, "Unknown. Architecture code: {exception_type:#X}"),
}
}
}
#[coverage(off)]
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
static DUMMY_DEBUGGER: PatinaDebugger<patina::serial::uart::UartNull> =
PatinaDebugger::new(patina::serial::uart::UartNull {});
fn reset() {
DUMMY_DEBUGGER.enable(false);
DUMMY_DEBUGGER.test_initialize(false);
if !DEBUGGER.is_completed() {
set_debugger(&DUMMY_DEBUGGER);
}
}
#[test]
#[serial(global_debugger)]
fn test_debug_break_not_enabled() {
reset();
breakpoint();
}
#[test]
#[serial(global_debugger)]
fn test_debug_break_not_initialized() {
reset();
DUMMY_DEBUGGER.enable(true);
breakpoint();
}
#[test]
#[should_panic(expected = "breakpoint_unchecked")]
#[serial(global_debugger)]
fn test_debug_break_enabled_and_initialized() {
reset();
DUMMY_DEBUGGER.enable(true);
DUMMY_DEBUGGER.test_initialize(true);
breakpoint();
}
}