use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::dmp::TriageCrashInfo;
use crate::error::{Error, Result};
use crate::gdb::RegisterMap;
use crate::target::Target;
use crate::types::VirtAddr;
#[derive(Clone, Debug)]
pub struct DebugLine {
pub seq: u64,
pub timestamp_ms: u64,
pub text: String,
}
#[derive(Clone, Debug, Default)]
pub struct DebugOutputPage {
pub lines: Vec<DebugLine>,
pub next_seq: u64,
pub dropped: bool,
}
#[derive(Clone)]
pub struct DebugLog {
inner: Arc<Mutex<DebugLogInner>>,
}
struct DebugLogInner {
lines: VecDeque<DebugLine>,
partial: String,
next_seq: u64,
capacity: usize,
}
impl DebugLog {
pub fn new(capacity: usize) -> Self {
Self {
inner: Arc::new(Mutex::new(DebugLogInner {
lines: VecDeque::new(),
partial: String::new(),
next_seq: 0,
capacity: capacity.max(1),
})),
}
}
pub fn record(&self, bytes: &[u8]) {
let text = String::from_utf8_lossy(bytes);
let now = now_ms();
let mut inner = self.inner.lock().unwrap();
inner.push_text(&text, now);
}
pub fn read_since(&self, since_seq: u64) -> DebugOutputPage {
let inner = self.inner.lock().unwrap();
let dropped = inner
.lines
.front()
.is_some_and(|first| since_seq < first.seq);
let lines = inner
.lines
.iter()
.filter(|line| line.seq >= since_seq)
.cloned()
.collect();
DebugOutputPage {
lines,
next_seq: inner.next_seq,
dropped,
}
}
}
impl DebugLogInner {
fn push_text(&mut self, text: &str, now_ms: u64) {
for ch in text.chars() {
if ch == '\n' {
let mut line = std::mem::take(&mut self.partial);
if line.ends_with('\r') {
line.pop();
}
self.push_line(line, now_ms);
} else {
self.partial.push(ch);
}
}
}
fn push_line(&mut self, text: String, now_ms: u64) {
let seq = self.next_seq;
self.next_seq += 1;
self.lines.push_back(DebugLine {
seq,
timestamp_ms: now_ms,
text,
});
while self.lines.len() > self.capacity {
self.lines.pop_front();
}
}
}
fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BugcheckInfo {
pub code: u32,
pub parameters: [u64; 4],
pub driver: Option<String>,
}
pub struct StopEvent {
pub thread_id: Option<String>,
pub exception_code: Option<u32>,
pub program_counter: Option<u64>,
pub is_bugcheck: bool,
pub bugcheck: Option<BugcheckInfo>,
pub target_reloaded: bool,
pub target_kernel_base_hint: Option<VirtAddr>,
pub assisted_breakin: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HwBreakpointAccess {
Execute,
Write,
ReadWrite,
}
impl HwBreakpointAccess {
pub fn label(self) -> &'static str {
match self {
Self::Execute => "execute",
Self::Write => "write",
Self::ReadWrite => "read/write",
}
}
pub fn letter(self) -> char {
match self {
Self::Execute => 'e',
Self::Write => 'w',
Self::ReadWrite => 'r',
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WatchpointAccess {
Write,
ReadWrite,
}
impl WatchpointAccess {
pub fn label(self) -> &'static str {
match self {
Self::Write => "write",
Self::ReadWrite => "read/write",
}
}
pub fn name(self) -> &'static str {
match self {
Self::Write => "write",
Self::ReadWrite => "read_write",
}
}
}
impl std::str::FromStr for WatchpointAccess {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
match value {
"write" => Ok(Self::Write),
"read_write" | "read/write" => Ok(Self::ReadWrite),
_ => Err(Error::Rsp(format!(
"invalid watchpoint access '{value}' (use 'write' or 'read_write')"
))),
}
}
}
impl From<WatchpointAccess> for HwBreakpointAccess {
fn from(access: WatchpointAccess) -> Self {
match access {
WatchpointAccess::Write => Self::Write,
WatchpointAccess::ReadWrite => Self::ReadWrite,
}
}
}
pub const HW_BREAKPOINT_SLOTS: u8 = 4;
pub fn validate_hw_breakpoint(access: HwBreakpointAccess, len: u8, addr: u64) -> Result<()> {
if matches!(access, HwBreakpointAccess::Execute) && len != 1 {
return Err(Error::Rsp(
"execute hardware breakpoints must be 1 byte".into(),
));
}
if !matches!(len, 1 | 2 | 4 | 8) {
return Err(Error::Rsp(format!(
"invalid hardware breakpoint length {len} (use 1, 2, 4, or 8)"
)));
}
if !addr.is_multiple_of(len as u64) {
return Err(Error::Rsp(format!(
"hardware breakpoint address {addr:#x} must be {len}-byte aligned"
)));
}
Ok(())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugCapability {
MemoryIntrospection,
ExecutionControl,
InterruptTarget,
SingleStep,
ReadRegisters,
WriteRegisters,
ThreadList,
ThreadSelection,
KernelBreakpoints,
UserModeBreakpoints,
Watchpoints,
TargetReloadDetection,
KernelBaseHint,
BugcheckDetection,
BugcheckDetails,
DebugOutput,
}
impl DebugCapability {
pub fn label(self) -> &'static str {
match self {
Self::MemoryIntrospection => "memory introspection",
Self::ExecutionControl => "execution control",
Self::InterruptTarget => "target interrupt",
Self::SingleStep => "single step",
Self::ReadRegisters => "register read",
Self::WriteRegisters => "register write",
Self::ThreadList => "context enumeration",
Self::ThreadSelection => "context selection",
Self::KernelBreakpoints => "kernel breakpoints",
Self::UserModeBreakpoints => "usermode breakpoints",
Self::Watchpoints => "data watchpoints",
Self::TargetReloadDetection => "target reload detection",
Self::KernelBaseHint => "kernel base hint",
Self::BugcheckDetection => "bugcheck stop detection",
Self::BugcheckDetails => "bugcheck details",
Self::DebugOutput => "debug output",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BackendCapability {
pub capability: DebugCapability,
pub supported: bool,
}
impl BackendCapability {
pub fn supported(capability: DebugCapability) -> Self {
Self {
capability,
supported: true,
}
}
pub fn unsupported(capability: DebugCapability) -> Self {
Self {
capability,
supported: false,
}
}
}
pub trait DebugBackend {
fn register_map(&self) -> &RegisterMap;
fn name(&self) -> &'static str {
"dbg"
}
fn read_registers(&mut self) -> Result<Vec<u8>>;
fn write_registers(&mut self, data: &[u8]) -> Result<()>;
fn set_breakpoint(&mut self, addr: u64) -> Result<()>;
fn remove_breakpoint(&mut self, addr: u64) -> Result<()>;
fn supports_user_mode_breakpoints(&self) -> bool {
false
}
fn supports_watchpoints(&self) -> bool {
false
}
fn set_hardware_breakpoint(
&mut self,
_slot: u8,
_addr: u64,
_access: HwBreakpointAccess,
_len: u8,
) -> Result<()> {
Err(Error::NotSupported)
}
fn clear_hardware_breakpoint(&mut self, _slot: u8) -> Result<()> {
Err(Error::NotSupported)
}
fn optional_capabilities(&self) -> Vec<BackendCapability> {
vec![
BackendCapability {
capability: DebugCapability::UserModeBreakpoints,
supported: self.supports_user_mode_breakpoints(),
},
BackendCapability {
capability: DebugCapability::Watchpoints,
supported: self.supports_watchpoints(),
},
BackendCapability::unsupported(DebugCapability::TargetReloadDetection),
BackendCapability::unsupported(DebugCapability::KernelBaseHint),
BackendCapability::unsupported(DebugCapability::BugcheckDetection),
BackendCapability::unsupported(DebugCapability::BugcheckDetails),
BackendCapability::unsupported(DebugCapability::DebugOutput),
]
}
fn capabilities(&self) -> Vec<BackendCapability> {
let mut capabilities = vec![
BackendCapability::supported(DebugCapability::MemoryIntrospection),
BackendCapability::supported(DebugCapability::ExecutionControl),
BackendCapability::supported(DebugCapability::InterruptTarget),
BackendCapability::supported(DebugCapability::SingleStep),
BackendCapability::supported(DebugCapability::ReadRegisters),
BackendCapability::supported(DebugCapability::WriteRegisters),
BackendCapability::supported(DebugCapability::ThreadList),
BackendCapability::supported(DebugCapability::ThreadSelection),
BackendCapability::supported(DebugCapability::KernelBreakpoints),
];
capabilities.extend(self.optional_capabilities());
capabilities
}
fn note_breakpoint_installed(&mut self, _addr: u64) {}
fn note_breakpoint_uninstalled(&mut self, _addr: u64) {}
fn initialize_from_target(&mut self, _target: &Target) {}
fn triage_crash_info(&self) -> Option<&TriageCrashInfo> {
None
}
fn note_target_rediscovery_pending(&mut self) {}
fn note_target_rediscovery_complete(&mut self) {}
fn target_kernel_base_hint(&mut self) -> Result<Option<VirtAddr>> {
Ok(None)
}
fn continue_execution(&mut self) -> Result<()>;
fn step(&mut self) -> Result<()>;
fn interrupt(&mut self) -> Result<StopEvent>;
fn wait_for_stop(&mut self) -> Result<StopEvent>;
fn try_wait_for_stop(&mut self, timeout: Duration) -> Result<Option<StopEvent>>;
fn thread_list(&mut self) -> Result<Vec<String>>;
fn set_current_thread(&mut self, thread_id: &str) -> Result<()>;
fn stopped_thread_id(&mut self) -> Result<String>;
fn is_running(&self) -> bool;
fn has_pending_stop(&self) -> bool {
false
}
fn prepare_for_exit(&mut self, leave_running: bool) -> Result<()> {
if leave_running && !self.is_running() {
self.continue_execution()?;
}
Ok(())
}
fn read_debug_output(&self, _since_seq: u64) -> DebugOutputPage {
DebugOutputPage::default()
}
fn take_modules_changed(&mut self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_log_splits_lines_and_strips_crlf() {
let log = DebugLog::new(16);
log.record(b"DriverEntry failed\r\nhello ");
log.record(b"world\n");
let page = log.read_since(0);
let texts: Vec<&str> = page.lines.iter().map(|l| l.text.as_str()).collect();
assert_eq!(texts, vec!["DriverEntry failed", "hello world"]);
assert_eq!(page.next_seq, 2);
assert!(!page.dropped);
}
#[test]
fn debug_log_buffers_unterminated_partial() {
let log = DebugLog::new(16);
log.record(b"no newline yet");
assert!(log.read_since(0).lines.is_empty());
log.record(b"\n");
assert_eq!(log.read_since(0).lines.len(), 1);
}
#[test]
fn debug_log_cursor_returns_only_new_lines() {
let log = DebugLog::new(16);
log.record(b"one\ntwo\n");
let first = log.read_since(0);
assert_eq!(first.lines.len(), 2);
log.record(b"three\n");
let next = log.read_since(first.next_seq);
let texts: Vec<&str> = next.lines.iter().map(|l| l.text.as_str()).collect();
assert_eq!(texts, vec!["three"]);
assert_eq!(next.next_seq, 3);
}
#[test]
fn debug_log_evicts_oldest_and_flags_dropped() {
let log = DebugLog::new(2);
log.record(b"a\nb\nc\n");
let page = log.read_since(0);
let texts: Vec<&str> = page.lines.iter().map(|l| l.text.as_str()).collect();
assert_eq!(texts, vec!["b", "c"]);
assert!(page.dropped);
assert!(!log.read_since(1).dropped);
}
#[test]
fn validate_hw_execute_must_be_one_byte() {
assert!(validate_hw_breakpoint(HwBreakpointAccess::Execute, 1, 0x1003).is_ok());
assert!(validate_hw_breakpoint(HwBreakpointAccess::Execute, 4, 0x1000).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::Execute, 2, 0x1000).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::Execute, 8, 0x1000).is_err());
}
#[test]
fn validate_hw_data_widths_when_aligned() {
for access in [HwBreakpointAccess::Write, HwBreakpointAccess::ReadWrite] {
assert!(validate_hw_breakpoint(access, 1, 0x1003).is_ok());
assert!(validate_hw_breakpoint(access, 2, 0x1000).is_ok());
assert!(validate_hw_breakpoint(access, 4, 0x1000).is_ok());
assert!(validate_hw_breakpoint(access, 8, 0x2000).is_ok());
}
}
#[test]
fn validate_hw_rejects_invalid_lengths() {
for len in [0u8, 3, 5, 16] {
assert!(validate_hw_breakpoint(HwBreakpointAccess::Write, len, 0x1000).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::ReadWrite, len, 0x1000).is_err());
}
}
#[test]
fn validate_hw_requires_length_alignment() {
assert!(validate_hw_breakpoint(HwBreakpointAccess::Write, 4, 0x1002).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::Write, 2, 0x1001).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::ReadWrite, 8, 0x1004).is_err());
assert!(validate_hw_breakpoint(HwBreakpointAccess::Write, 1, 0x1001).is_ok());
assert!(validate_hw_breakpoint(HwBreakpointAccess::ReadWrite, 1, 0x1003).is_ok());
}
#[test]
fn semantic_watchpoint_access_parses() {
assert_eq!(
"write".parse::<WatchpointAccess>().unwrap(),
WatchpointAccess::Write
);
assert_eq!(
"read_write".parse::<WatchpointAccess>().unwrap(),
WatchpointAccess::ReadWrite
);
assert_eq!(
"read/write".parse::<WatchpointAccess>().unwrap(),
WatchpointAccess::ReadWrite
);
assert!("read".parse::<WatchpointAccess>().is_err());
}
}