pub mod breakpoints;
pub mod core;
pub mod execution;
pub mod interface;
pub mod session;
pub mod state;
pub mod watch;
pub use core::{
Breakpoint, BreakpointId, BreakpointLocation, CallFrame, ContinueResult, DebugCommand,
DebugCommandResult, DebugSessionResult, DebugState, DebugStatistics, DebugValue,
DebuggerConfig, DisassemblyInstruction, DisassemblyView, EvaluationResult, ExecutionLocation,
ExecutionStep, InspectionResult, InspectionTarget, InstructionExecutionResult, MemoryView,
NodeExecutionResult, NodeMetadata, StepResult, TypeInfo, UiMode, Watch, WatchId, WatchUpdate,
};
pub use breakpoints::BreakpointManager;
pub use execution::{DebugExecutionEngine, OperationStatistics};
pub use interface::DebuggerInterface;
pub use session::DebugSession;
pub use state::{CallStack, CallStackSummary, MemoryRegion, MemoryState, MemoryStats};
pub use watch::{ExpressionEvaluator, WatchManager};
use crate::{ir::IrModule, ComputationGraph, JitError, JitResult};
use std::sync::{Arc, Mutex};
pub struct JitDebugger {
config: DebuggerConfig,
session: Option<Arc<Mutex<DebugSession>>>,
breakpoints: BreakpointManager,
watch_manager: WatchManager,
call_stack: CallStack,
execution_engine: DebugExecutionEngine,
ui_interface: DebuggerInterface,
}
impl JitDebugger {
pub fn new(config: DebuggerConfig) -> Self {
Self {
breakpoints: BreakpointManager::new(),
watch_manager: WatchManager::new(),
call_stack: CallStack::new(),
execution_engine: DebugExecutionEngine::new(config.clone()),
ui_interface: DebuggerInterface::new(config.clone()),
session: None,
config,
}
}
pub fn debug_graph(&mut self, graph: ComputationGraph) -> JitResult<DebugSessionResult> {
let session = DebugSession::new(graph, self.config.clone());
let session_arc = Arc::new(Mutex::new(session));
self.session = Some(session_arc.clone());
self.interactive_debug_loop(session_arc)
}
pub fn debug_ir(&mut self, ir_module: IrModule) -> JitResult<DebugSessionResult> {
let session = DebugSession::from_ir(ir_module, self.config.clone());
let session_arc = Arc::new(Mutex::new(session));
self.session = Some(session_arc.clone());
self.interactive_debug_loop(session_arc)
}
fn interactive_debug_loop(
&mut self,
session: Arc<Mutex<DebugSession>>,
) -> JitResult<DebugSessionResult> {
let mut command_history = Vec::new();
let mut continue_execution = true;
self.ui_interface.show_welcome_message();
while continue_execution {
let current_state = {
let session_guard = session.lock().expect("lock should not be poisoned");
session_guard.get_current_state()
};
self.ui_interface.display_current_state(¤t_state);
let command = self.ui_interface.get_user_command()?;
command_history.push(command.clone());
match self.process_debug_command(command, session.clone())? {
DebugCommandResult::Continue => {}
DebugCommandResult::Exit => continue_execution = false,
DebugCommandResult::ExecutionComplete => {
self.ui_interface.show_execution_complete();
continue_execution = false;
}
}
}
let session_guard = session.lock().expect("lock should not be poisoned");
Ok(DebugSessionResult {
execution_trace: session_guard.get_execution_trace(),
final_state: session_guard.get_current_state(),
command_history,
statistics: session_guard.get_statistics(),
})
}
fn process_debug_command(
&mut self,
command: DebugCommand,
session: Arc<Mutex<DebugSession>>,
) -> JitResult<DebugCommandResult> {
match command {
DebugCommand::Step => {
let mut session_guard = session.lock().expect("lock should not be poisoned");
session_guard.step()?;
Ok(DebugCommandResult::Continue)
}
DebugCommand::StepOver => {
let mut session_guard = session.lock().expect("lock should not be poisoned");
session_guard.step_over()?;
Ok(DebugCommandResult::Continue)
}
DebugCommand::StepInto => {
let mut session_guard = session.lock().expect("lock should not be poisoned");
session_guard.step_into()?;
Ok(DebugCommandResult::Continue)
}
DebugCommand::StepOut => {
let mut session_guard = session.lock().expect("lock should not be poisoned");
session_guard.step_out()?;
Ok(DebugCommandResult::Continue)
}
DebugCommand::Continue => {
let mut session_guard = session.lock().expect("lock should not be poisoned");
let result = session_guard.continue_execution()?;
match result {
ContinueResult::Breakpoint => Ok(DebugCommandResult::Continue),
ContinueResult::Completed => Ok(DebugCommandResult::ExecutionComplete),
}
}
DebugCommand::SetBreakpoint { location } => {
self.breakpoints.set_breakpoint(location.clone())?;
self.ui_interface.show_breakpoint_set(location);
Ok(DebugCommandResult::Continue)
}
DebugCommand::RemoveBreakpoint { id } => {
self.breakpoints.remove_breakpoint(id)?;
self.ui_interface.show_breakpoint_removed(id);
Ok(DebugCommandResult::Continue)
}
DebugCommand::ListBreakpoints => {
let breakpoints = self.breakpoints.list_breakpoints();
self.ui_interface.show_breakpoints(&breakpoints);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Watch { expression } => {
let watch_id = self.watch_manager.add_watch(expression.clone())?;
self.ui_interface.show_watch_added(watch_id, &expression);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Unwatch { id } => {
self.watch_manager.remove_watch(id)?;
self.ui_interface.show_watch_removed(id);
Ok(DebugCommandResult::Continue)
}
DebugCommand::ListWatches => {
let watches = self.watch_manager.list_watches();
self.ui_interface.show_watches(&watches);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Inspect { target } => {
let session_guard = session.lock().expect("lock should not be poisoned");
let inspection_result = session_guard.inspect_target(&target)?;
self.ui_interface.show_inspection_result(&inspection_result);
Ok(DebugCommandResult::Continue)
}
DebugCommand::CallStack => {
let session_guard = session.lock().expect("lock should not be poisoned");
let call_stack = session_guard.get_call_stack();
self.ui_interface.show_call_stack(&call_stack);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Locals => {
let session_guard = session.lock().expect("lock should not be poisoned");
let locals = session_guard.get_local_variables();
self.ui_interface.show_local_variables(&locals);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Memory { address } => {
let session_guard = session.lock().expect("lock should not be poisoned");
let memory_view = session_guard.get_memory_view(address)?;
self.ui_interface.show_memory_view(&memory_view);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Disassemble { location } => {
let session_guard = session.lock().expect("lock should not be poisoned");
let disassembly = session_guard.disassemble_at(location)?;
self.ui_interface.show_disassembly(&disassembly);
Ok(DebugCommandResult::Continue)
}
DebugCommand::Help => {
self.ui_interface.show_help();
Ok(DebugCommandResult::Continue)
}
DebugCommand::Quit => Ok(DebugCommandResult::Exit),
}
}
pub fn set_breakpoint(&mut self, location: BreakpointLocation) -> JitResult<BreakpointId> {
self.breakpoints.set_breakpoint(location)
}
pub fn remove_breakpoint(&mut self, id: BreakpointId) -> JitResult<()> {
self.breakpoints.remove_breakpoint(id)
}
pub fn add_watch(&mut self, expression: String) -> JitResult<WatchId> {
self.watch_manager.add_watch(expression)
}
pub fn remove_watch(&mut self, id: WatchId) -> JitResult<()> {
self.watch_manager.remove_watch(id)
}
pub fn get_current_state(&self) -> Option<DebugState> {
if let Some(session) = &self.session {
let session_guard = session.lock().expect("lock should not be poisoned");
Some(session_guard.get_current_state())
} else {
None
}
}
pub fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
if let Some(session) = &self.session {
let session_guard = session.lock().expect("lock should not be poisoned");
session_guard.evaluate_expression(expression)
} else {
Err(JitError::RuntimeError(
"No active debug session".to_string(),
))
}
}
pub fn breakpoints(&self) -> &BreakpointManager {
&self.breakpoints
}
pub fn breakpoints_mut(&mut self) -> &mut BreakpointManager {
&mut self.breakpoints
}
pub fn watch_manager(&self) -> &WatchManager {
&self.watch_manager
}
pub fn watch_manager_mut(&mut self) -> &mut WatchManager {
&mut self.watch_manager
}
pub fn interface(&self) -> &DebuggerInterface {
&self.ui_interface
}
pub fn interface_mut(&mut self) -> &mut DebuggerInterface {
&mut self.ui_interface
}
pub fn execution_engine(&self) -> &DebugExecutionEngine {
&self.execution_engine
}
pub fn execution_engine_mut(&mut self) -> &mut DebugExecutionEngine {
&mut self.execution_engine
}
pub fn config(&self) -> &DebuggerConfig {
&self.config
}
pub fn update_config(&mut self, config: DebuggerConfig) {
self.config = config.clone();
self.execution_engine.update_config(config.clone());
self.ui_interface.update_config(config);
}
pub fn has_active_session(&self) -> bool {
self.session.is_some()
}
pub fn clear_session(&mut self) {
self.session = None;
}
pub fn get_session_statistics(&self) -> Option<DebugStatistics> {
if let Some(session) = &self.session {
let session_guard = session.lock().expect("lock should not be poisoned");
Some(session_guard.get_statistics())
} else {
None
}
}
}
pub fn create_debugger() -> JitDebugger {
JitDebugger::new(DebuggerConfig::default())
}
pub fn create_debugger_with_config(config: DebuggerConfig) -> JitDebugger {
JitDebugger::new(config)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debugger_creation() {
let debugger = create_debugger();
assert!(!debugger.has_active_session());
assert_eq!(debugger.config().max_trace_length, 10000);
}
#[test]
fn test_debugger_with_custom_config() {
let config = DebuggerConfig {
max_trace_length: 5000,
enable_memory_view: false,
..DebuggerConfig::default()
};
let debugger = create_debugger_with_config(config);
assert_eq!(debugger.config().max_trace_length, 5000);
assert!(!debugger.config().enable_memory_view);
}
#[test]
fn test_breakpoint_management() {
let mut debugger = create_debugger();
let location = BreakpointLocation::GraphNode(crate::NodeId::new(0));
let bp_id = debugger
.set_breakpoint(location)
.expect("breakpoint setting should succeed");
assert_eq!(debugger.breakpoints().count(), 1);
assert!(debugger.remove_breakpoint(bp_id).is_ok());
assert_eq!(debugger.breakpoints().count(), 0);
}
#[test]
fn test_watch_management() {
let mut debugger = create_debugger();
let watch_id = debugger
.add_watch("test_expression".to_string())
.expect("operation should succeed");
assert_eq!(debugger.watch_manager().count(), 1);
assert!(debugger.remove_watch(watch_id).is_ok());
assert_eq!(debugger.watch_manager().count(), 0);
}
#[test]
fn test_configuration_update() {
let mut debugger = create_debugger();
let new_config = DebuggerConfig {
max_trace_length: 8000,
..DebuggerConfig::default()
};
debugger.update_config(new_config.clone());
assert_eq!(debugger.config().max_trace_length, 8000);
}
#[test]
fn test_session_management() {
let debugger = create_debugger();
assert!(!debugger.has_active_session());
assert!(debugger.get_current_state().is_none());
assert!(debugger.get_session_statistics().is_none());
}
#[test]
fn test_expression_evaluation_without_session() {
let debugger = create_debugger();
let result = debugger.evaluate_expression("test");
assert!(result.is_err());
}
}