fuel_vm/interpreter/
debug.rs

1use super::Interpreter;
2use crate::prelude::*;
3use fuel_asm::RegId;
4
5impl<M, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal>
6where
7    Tx: ExecutableTransaction,
8{
9    /// Get single-stepping mode
10    pub const fn single_stepping(&self) -> bool {
11        self.debugger.single_stepping()
12    }
13
14    /// Set single-stepping mode
15    pub fn set_single_stepping(&mut self, single_stepping: bool) {
16        self.debugger.set_single_stepping(single_stepping)
17    }
18
19    /// Clear all set breakpoints.
20    pub fn clear_breakpoints(&mut self) {
21        self.debugger.clear_breakpoints();
22    }
23
24    /// Set a new breakpoint for the provided location.
25    pub fn set_breakpoint(&mut self, breakpoint: Breakpoint) {
26        self.debugger.set_breakpoint(breakpoint)
27    }
28
29    /// Overwrite all breakpoints with a new set of breakpoints.
30    pub fn overwrite_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
31        self.debugger.clear_breakpoints();
32        for bp in breakpoints {
33            self.debugger.set_breakpoint(*bp);
34        }
35    }
36
37    /// Remove a previously set breakpoint.
38    pub fn remove_breakpoint(&mut self, breakpoint: &Breakpoint) {
39        self.debugger.remove_breakpoint(breakpoint)
40    }
41
42    pub(crate) fn eval_debugger_state(&mut self) -> DebugEval {
43        let debugger = &mut self.debugger;
44
45        let contract = self.frames.last().map(CallFrame::to);
46        let pc = self.registers[RegId::PC].saturating_sub(self.registers[RegId::IS]);
47
48        debugger.eval_state(contract, pc)
49    }
50
51    pub(crate) fn debugger_set_last_state(&mut self, state: ProgramState) {
52        self.debugger.set_last_state(state)
53    }
54
55    pub(crate) const fn debugger_last_state(&self) -> &Option<ProgramState> {
56        self.debugger.last_state()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use alloc::{
63        vec,
64        vec::Vec,
65    };
66
67    use super::Interpreter;
68    use crate::prelude::*;
69    use fuel_asm::RegId;
70
71    #[test]
72    fn breakpoint_script() {
73        use fuel_asm::op;
74        use fuel_tx::ConsensusParameters;
75
76        let mut vm = Interpreter::<_, _, _>::with_memory_storage();
77
78        let gas_limit = 1_000_000;
79        let gas_price = 0;
80        let height = Default::default();
81
82        let script = [
83            op::addi(0x10, RegId::ZERO, 8),
84            op::addi(0x11, RegId::ZERO, 16),
85            op::addi(0x12, RegId::ZERO, 32),
86            op::addi(0x13, RegId::ZERO, 64),
87            op::addi(0x14, RegId::ZERO, 128),
88            op::ret(0x10),
89        ]
90        .into_iter()
91        .collect();
92
93        let consensus_params = ConsensusParameters::standard();
94
95        let tx = TransactionBuilder::script(script, vec![])
96            .script_gas_limit(gas_limit)
97            .add_fee_input()
98            .finalize()
99            .into_checked(height, &consensus_params)
100            .expect("failed to generate checked tx")
101            .into_ready(
102                gas_price,
103                consensus_params.gas_costs(),
104                consensus_params.fee_params(),
105                None,
106            )
107            .unwrap();
108
109        let suite = vec![
110            (
111                Breakpoint::script(0),
112                vec![(0x10, 0), (0x11, 0), (0x12, 0), (0x13, 0), (0x14, 0)],
113            ),
114            (
115                Breakpoint::script(2),
116                vec![(0x10, 8), (0x11, 16), (0x12, 0), (0x13, 0), (0x14, 0)],
117            ),
118            (
119                Breakpoint::script(3),
120                vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 0), (0x14, 0)],
121            ),
122            (
123                Breakpoint::script(5),
124                vec![(0x10, 8), (0x11, 16), (0x12, 32), (0x13, 64), (0x14, 128)],
125            ),
126        ];
127
128        suite.iter().for_each(|(b, _)| vm.set_breakpoint(*b));
129
130        let state = vm
131            .transact(tx)
132            .map(ProgramState::from)
133            .expect("Failed to execute script!");
134
135        suite
136            .into_iter()
137            .fold(state, |state, (breakpoint, registers)| {
138                let debug = state.debug_ref().expect("Expected breakpoint");
139                let b = debug
140                    .breakpoint()
141                    .expect("State without expected breakpoint");
142
143                assert_eq!(&breakpoint, b);
144                registers.into_iter().for_each(|(r, w)| {
145                    assert_eq!(w, vm.registers()[r]);
146                });
147
148                vm.resume().expect("Failed to resume")
149            });
150    }
151
152    #[test]
153    fn single_stepping() {
154        use fuel_asm::op;
155        use fuel_tx::ConsensusParameters;
156
157        let mut vm = Interpreter::<_, _, _>::with_memory_storage();
158
159        let gas_limit = 1_000_000;
160        let height = Default::default();
161        let gas_price = 0;
162
163        // Repeats the middle two instructions five times
164        let script = [
165            op::addi(0x10, RegId::ZERO, 5),
166            op::addi(0x11, 0x11, 1),
167            op::jnei(0x10, 0x11, 1),
168            op::ret(0x10),
169        ]
170        .into_iter()
171        .collect();
172
173        let consensus_params = ConsensusParameters::standard();
174
175        let tx = TransactionBuilder::script(script, vec![])
176            .script_gas_limit(gas_limit)
177            .add_fee_input()
178            .finalize()
179            .into_checked(height, &consensus_params)
180            .expect("failed to generate checked tx")
181            .into_ready(
182                gas_price,
183                consensus_params.gas_costs(),
184                consensus_params.fee_params(),
185                None,
186            )
187            .unwrap();
188
189        vm.set_single_stepping(true);
190
191        let mut state = vm
192            .transact(tx)
193            .map(ProgramState::from)
194            .expect("Failed to execute script!");
195
196        let mut stops = Vec::new();
197
198        while let Some(debug) = state.debug_ref() {
199            let b = debug
200                .breakpoint()
201                .expect("State without expected breakpoint");
202
203            stops.push(b.pc());
204
205            state = vm.resume().expect("Failed to resume");
206        }
207
208        assert_eq!(stops, vec![0, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 12]);
209    }
210
211    #[test]
212    fn resume_without_debug() {
213        use fuel_asm::op;
214        use fuel_tx::ConsensusParameters;
215
216        let mut vm = Interpreter::<_, _, _>::with_memory_storage();
217        vm.resume()
218            .expect_err("Expected error when resuming without debug");
219
220        let gas_limit = 1_000_000;
221        let height = Default::default();
222        let gas_price = 0;
223
224        // Just returns
225        let script: Vec<u8> = [op::ret(0x10)].into_iter().collect();
226
227        let consensus_params = ConsensusParameters::standard();
228
229        let tx = TransactionBuilder::script(script, vec![])
230            .script_gas_limit(gas_limit)
231            .add_fee_input()
232            .finalize()
233            .into_checked(height, &consensus_params)
234            .expect("failed to generate checked tx")
235            .into_ready(
236                gas_price,
237                consensus_params.gas_costs(),
238                consensus_params.fee_params(),
239                None,
240            )
241            .unwrap();
242
243        let _ = vm
244            .transact(tx)
245            .map(ProgramState::from)
246            .expect("Failed to execute script!");
247
248        vm.resume()
249            .expect_err("Expected error when resuming without debug");
250    }
251}