diff --git a/src/debug/debugger.rs b/src/debug/debugger.rs index f9eecf1846..8e0cc4e5a8 100644 --- a/src/debug/debugger.rs +++ b/src/debug/debugger.rs @@ -16,6 +16,12 @@ impl Breakpoint { Self { contract, pc } } + pub fn script(pc: Word) -> Self { + let contract = Default::default(); + + Self::new(contract, pc) + } + pub const fn contract(&self) -> &ContractAddress { &self.contract } @@ -50,6 +56,13 @@ impl DebugEval { _ => false, } } + + pub const fn breakpoint(&self) -> Option<&Breakpoint> { + match self { + Self::Breakpoint(b) => Some(b), + _ => None, + } + } } #[derive(Debug, Default, Clone)] @@ -59,7 +72,10 @@ pub struct Debugger { } impl Debugger { - pub fn set_breakpoint(&mut self, contract: ContractAddress, pc: Word) { + pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) { + let contract = *breakpoint.contract(); + let pc = breakpoint.pc(); + self.breakpoints .get_mut(&contract) .map(|set| set.insert(pc)) @@ -73,8 +89,10 @@ impl Debugger { }); } - pub fn remove_breakpoint(&mut self, contract: &ContractAddress, pc: Word) { - self.breakpoints.get_mut(contract).map(|set| set.remove(&pc)); + pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) { + self.breakpoints + .get_mut(breakpoint.contract()) + .map(|set| set.remove(&breakpoint.pc())); } pub fn eval_state(&mut self, contract: ContractAddress, pc: Word) -> DebugEval { diff --git a/src/interpreter/debug.rs b/src/interpreter/debug.rs index 61c60abb16..b9440ec907 100644 --- a/src/interpreter/debug.rs +++ b/src/interpreter/debug.rs @@ -1,31 +1,21 @@ use super::{Interpreter, ProgramState}; use crate::consts::*; -use crate::debug::DebugEval; - -use fuel_asm::Word; -use fuel_tx::ContractAddress; +use crate::debug::{Breakpoint, DebugEval}; impl Interpreter { - pub fn set_breakpoint(&mut self, contract: Option, pc: Word) { - let contract = contract.unwrap_or_default(); - - self.debugger.set_breakpoint(contract, pc) + pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) { + self.debugger.set_breakpoint(breakpoint) } - pub fn remove_breakpoint(&mut self, contract: Option, pc: Word) { - let contract = contract.unwrap_or_default(); - - self.debugger.remove_breakpoint(&contract, pc) + pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) { + self.debugger.remove_breakpoint(breakpoint) } pub fn eval_debugger_state(&mut self) -> DebugEval { let debugger = &mut self.debugger; let contract = self.frames.last().map(|f| f.to()).copied(); - let pc = match &contract { - Some(_) => self.registers[REG_PC].saturating_sub(self.registers[REG_IS]), - None => self.registers[REG_PC], - }; + let pc = self.registers[REG_PC].saturating_sub(self.registers[REG_IS]); // Default contract address maps to unset contract target let contract = contract.unwrap_or_default(); diff --git a/src/interpreter/executors.rs b/src/interpreter/executors.rs index f3251af2da..b255468c81 100644 --- a/src/interpreter/executors.rs +++ b/src/interpreter/executors.rs @@ -225,16 +225,23 @@ where #[cfg(feature = "debug")] pub fn resume(&mut self) -> Result { - match self + let state = self .debugger_last_state() - .ok_or(ExecuteError::DebugStateNotInitialized)? - { + .ok_or(ExecuteError::DebugStateNotInitialized)?; + + let state = match state { ProgramState::Return(w) => Ok(ProgramState::Return(w)), ProgramState::RunProgram(_) => self.run_program(), ProgramState::VerifyPredicate(_) => unimplemented!(), + }?; + + if state.is_debug() { + self.debugger_set_last_state(state.clone()); } + + Ok(state) } pub fn run_program(&mut self) -> Result { @@ -249,6 +256,8 @@ where .map(Opcode::from_bytes_unchecked) .ok_or(ExecuteError::ProgramOverflow)?; + println!("LEAK {:?}", op); + match self.execute(op)? { ExecuteState::Return(r) => { return Ok(ProgramState::Return(r)); @@ -324,6 +333,7 @@ where #[cfg(feature = "debug")] { let debug = self.eval_debugger_state(); + println!("EVAL {:?}", debug); if !debug.should_continue() { return Ok(debug.into()); } diff --git a/src/lib.rs b/src/lib.rs index f98376df3d..e937308182 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,15 @@ pub mod interpreter; pub mod prelude { pub use crate::data::{InterpreterStorage, MemoryStorage, Storage}; pub use crate::debug::Debugger; - pub use crate::interpreter::{Call, CallFrame, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange}; + pub use crate::interpreter::{ + Call, CallFrame, Contract, ExecuteError, Interpreter, LogEvent, MemoryRange, ProgramState, + }; pub use fuel_asm::{Immediate06, Immediate12, Immediate18, Immediate24, Opcode, RegisterId, Word}; pub use fuel_tx::{ bytes::{Deserializable, SerializableVec, SizedBytes}, Address, Color, ContractAddress, Hash, Input, Output, Salt, Transaction, ValidationError, Witness, }; + + #[cfg(feature = "debug")] + pub use crate::debug::{Breakpoint, DebugEval}; } diff --git a/tests/interpreter/debug.rs b/tests/interpreter/debug.rs new file mode 100644 index 0000000000..95756aaa80 --- /dev/null +++ b/tests/interpreter/debug.rs @@ -0,0 +1,61 @@ +use super::program_to_bytes; +use fuel_vm::consts::*; +use fuel_vm::prelude::*; + +#[test] +fn breakpoint_script() { + let storage = MemoryStorage::default(); + let mut vm = Interpreter::with_storage(storage); + + let gas_price = 10; + let gas_limit = 1_000_000; + let maturity = 100; + + let script = vec![ + Opcode::ADDI(0x10, REG_ZERO, 8), + Opcode::ADDI(0x11, REG_ZERO, 16), + Opcode::ADDI(0x12, REG_ZERO, 32), + Opcode::ADDI(0x13, REG_ZERO, 64), + Opcode::ADDI(0x14, REG_ZERO, 128), + Opcode::RET(0x10), + ]; + let script = program_to_bytes(script.as_slice()); + + let tx = Transaction::script(gas_price, gas_limit, maturity, script, vec![], vec![], vec![], vec![]); + + vm.init(tx).expect("Failed to init VM!"); + + let suite = vec![ + ( + Breakpoint::script(0), + vec![(0x10, 0), (0x11, 0), (0x12, 0), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(8), + vec![(0x10, 8), (0x11, 16), (0x12, 0), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(12), + vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 0), (0x14, 0)], + ), + ( + Breakpoint::script(20), + vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 64), (0x14, 128)], + ), + ]; + + suite.iter().for_each(|(b, _)| vm.set_breakpoint(*b)); + let state = vm.run().expect("Failed to execute script!"); + + suite.into_iter().fold(state, |state, (breakpoint, registers)| { + let debug = state.debug_ref().expect("Expected breakpoint"); + let b = debug.breakpoint().expect("State without expected breakpoint"); + + assert_eq!(&breakpoint, b); + registers.into_iter().for_each(|(r, w)| { + assert_eq!(w, vm.registers()[r]); + }); + + vm.resume().expect("Failed to resume") + }); +} diff --git a/tests/interpreter/mod.rs b/tests/interpreter/mod.rs index 836e76241c..2ade1ed20a 100644 --- a/tests/interpreter/mod.rs +++ b/tests/interpreter/mod.rs @@ -8,6 +8,9 @@ mod flow; mod memory; mod predicate; +#[cfg(feature = "debug")] +mod debug; + pub use super::common; pub fn program_to_bytes(program: &[Opcode]) -> Vec {