diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 6bcd35d1c901..6f8c30939769 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -13,10 +13,38 @@ pub(crate) struct DrawMemory { // TODO pub(crate) current_startline: RefCell, pub(crate) inner_call_index: usize, - pub(crate) current_mem_startline: usize, + pub(crate) current_buf_startline: usize, pub(crate) current_stack_startline: usize, } +/// Used to keep track of which buffer is currently active to be drawn by the debugger. +#[derive(Debug, PartialEq)] +pub(crate) enum BufferKind { + Memory, + Calldata, + Returndata, +} + +impl BufferKind { + /// Helper to cycle through the active buffers. + pub(crate) fn next(&self) -> Self { + match self { + BufferKind::Memory => BufferKind::Calldata, + BufferKind::Calldata => BufferKind::Returndata, + BufferKind::Returndata => BufferKind::Memory, + } + } + + /// Helper to format the title of the active buffer pane + pub(crate) fn title(&self, size: usize) -> String { + match self { + BufferKind::Memory => format!("Memory (max expansion: {} bytes)", size), + BufferKind::Calldata => format!("Calldata (size: {} bytes)", size), + BufferKind::Returndata => format!("Returndata (size: {} bytes)", size), + } + } +} + pub(crate) struct DebuggerContext<'a> { pub(crate) debugger: &'a mut Debugger, @@ -29,8 +57,11 @@ pub(crate) struct DebuggerContext<'a> { pub(crate) last_index: usize, pub(crate) stack_labels: bool, - pub(crate) mem_utf: bool, + /// Whether to decode active buffer as utf8 or not. + pub(crate) buf_utf: bool, pub(crate) show_shortcuts: bool, + /// The currently active buffer (memory, calldata, returndata) to be drawn. + pub(crate) active_buffer: BufferKind, } impl<'a> DebuggerContext<'a> { @@ -45,8 +76,9 @@ impl<'a> DebuggerContext<'a> { last_index: 0, stack_labels: false, - mem_utf: false, + buf_utf: false, show_shortcuts: true, + active_buffer: BufferKind::Memory, } } @@ -89,6 +121,14 @@ impl<'a> DebuggerContext<'a> { fn opcode_list(&self) -> Vec { self.debug_steps().iter().map(DebugStep::pretty_opcode).collect() } + + fn active_buffer(&self) -> &[u8] { + match self.active_buffer { + BufferKind::Memory => &self.current_step().memory, + BufferKind::Calldata => &self.current_step().calldata, + BufferKind::Returndata => &self.current_step().returndata, + } + } } impl DebuggerContext<'_> { @@ -121,9 +161,9 @@ impl DebuggerContext<'_> { // Grab number of times to do it for _ in 0..buffer_as_number(&self.key_buffer, 1) { if event.modifiers.contains(KeyModifiers::CONTROL) { - let max_mem = (self.current_step().memory.len() / 32).saturating_sub(1); - if self.draw_memory.current_mem_startline < max_mem { - self.draw_memory.current_mem_startline += 1; + let max_buf = (self.active_buffer().len() / 32).saturating_sub(1); + if self.draw_memory.current_buf_startline < max_buf { + self.draw_memory.current_buf_startline += 1; } } else if self.current_step < self.opcode_list.len() - 1 { self.current_step += 1; @@ -147,8 +187,8 @@ impl DebuggerContext<'_> { KeyCode::Char('k') | KeyCode::Up => { for _ in 0..buffer_as_number(&self.key_buffer, 1) { if event.modifiers.contains(KeyModifiers::CONTROL) { - self.draw_memory.current_mem_startline = - self.draw_memory.current_mem_startline.saturating_sub(1); + self.draw_memory.current_buf_startline = + self.draw_memory.current_buf_startline.saturating_sub(1); } else if self.current_step > 0 { self.current_step -= 1; } else if self.draw_memory.inner_call_index > 0 { @@ -165,6 +205,10 @@ impl DebuggerContext<'_> { } self.key_buffer.clear(); } + KeyCode::Char('b') => { + self.active_buffer = self.active_buffer.next(); + self.draw_memory.current_buf_startline = 0; + } // Go to top of file KeyCode::Char('g') => { self.draw_memory.inner_call_index = 0; @@ -248,7 +292,7 @@ impl DebuggerContext<'_> { // toggle stack labels KeyCode::Char('t') => self.stack_labels = !self.stack_labels, // toggle memory utf8 decoding - KeyCode::Char('m') => self.mem_utf = !self.mem_utf, + KeyCode::Char('m') => self.buf_utf = !self.buf_utf, // toggle help notice KeyCode::Char('h') => self.show_shortcuts = !self.show_shortcuts, KeyCode::Char( @@ -284,7 +328,7 @@ impl DebuggerContext<'_> { self.current_step -= 1; } else if self.draw_memory.inner_call_index > 0 { self.draw_memory.inner_call_index -= 1; - self.draw_memory.current_mem_startline = 0; + self.draw_memory.current_buf_startline = 0; self.draw_memory.current_stack_startline = 0; self.current_step = self.debug_steps().len() - 1; } @@ -294,7 +338,7 @@ impl DebuggerContext<'_> { self.current_step += 1; } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { self.draw_memory.inner_call_index += 1; - self.draw_memory.current_mem_startline = 0; + self.draw_memory.current_buf_startline = 0; self.draw_memory.current_stack_startline = 0; self.current_step = 0; } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 7a834392986d..fbee1c6a0031 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -1,6 +1,6 @@ //! TUI draw implementation. -use super::context::DebuggerContext; +use super::context::{BufferKind, DebuggerContext}; use crate::op::OpcodeParam; use alloy_primitives::U256; use foundry_compilers::sourcemap::SourceElement; @@ -77,7 +77,7 @@ impl DebuggerContext<'_> { /// |-----------------------------| /// | stack | /// |-----------------------------| - /// | mem | + /// | buf | /// |-----------------------------| /// | | /// | src | @@ -120,7 +120,7 @@ impl DebuggerContext<'_> { self.draw_src(f, src_pane); self.draw_op_list(f, op_pane); self.draw_stack(f, stack_pane); - self.draw_memory(f, memory_pane); + self.draw_buffer(f, memory_pane); } /// Draws the layout in horizontal mode. @@ -130,7 +130,7 @@ impl DebuggerContext<'_> { /// | op | stack | /// |-----------------|-----------| /// | | | - /// | src | mem | + /// | src | buf | /// | | | /// |-----------------|-----------| /// ``` @@ -180,12 +180,12 @@ impl DebuggerContext<'_> { self.draw_src(f, src_pane); self.draw_op_list(f, op_pane); self.draw_stack(f, stack_pane); - self.draw_memory(f, memory_pane); + self.draw_buffer(f, memory_pane); } fn draw_footer(&self, f: &mut Frame<'_>, area: Rect) { - let l1 = "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end"; - let l2 = "[t]: stack labels | [m]: memory decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll memory | [']: goto breakpoint | [h] toggle help"; + let l1 = "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end | [b]: cycle memory/calldata/returndata buffers"; + let l2 = "[t]: stack labels | [m]: buffer decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll buffer | [']: goto breakpoint | [h] toggle help"; let dimmed = Style::new().add_modifier(Modifier::DIM); let lines = vec![Line::from(Span::styled(l1, dimmed)), Line::from(Span::styled(l2, dimmed))]; @@ -500,11 +500,15 @@ impl DebuggerContext<'_> { f.render_widget(paragraph, area); } - fn draw_memory(&self, f: &mut Frame<'_>, area: Rect) { + fn draw_buffer(&self, f: &mut Frame<'_>, area: Rect) { let step = self.current_step(); - let memory = &step.memory; + let buf = match self.active_buffer { + BufferKind::Memory => &step.memory, + BufferKind::Calldata => &step.calldata, + BufferKind::Returndata => &step.returndata, + }; - let min_len = hex_digits(memory.len()); + let min_len = hex_digits(buf.len()); // Color memory region based on read/write. let mut offset = None; @@ -513,16 +517,22 @@ impl DebuggerContext<'_> { if let Instruction::OpCode(op) = step.instruction { let stack_len = step.stack.len(); if stack_len > 0 { - let (read_offset, read_size, write_offset, write_size) = - get_memory_access(op, &step.stack); - if read_offset.is_some() { - offset = read_offset; - size = read_size; - color = Some(Color::Cyan); - } else if write_offset.is_some() { - offset = write_offset; - size = write_size; - color = Some(Color::Red); + if let Some(accesses) = get_buffer_accesses(op, &step.stack) { + if let Some(read_access) = accesses.read { + if read_access.0 == self.active_buffer { + offset = Some(read_access.1.offset); + size = Some(read_access.1.size); + color = Some(Color::Cyan); + } + } else if let Some(write_access) = accesses.write { + // TODO: with MCOPY, it will be possible to both read from and write to the + // memory buffer with the same opcode + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Red); + } + } } } } @@ -532,34 +542,37 @@ impl DebuggerContext<'_> { let prev_step = self.current_step - 1; let prev_step = &self.debug_steps()[prev_step]; if let Instruction::OpCode(op) = prev_step.instruction { - let (_, _, write_offset, write_size) = get_memory_access(op, &prev_step.stack); - if write_offset.is_some() { - offset = write_offset; - size = write_size; - color = Some(Color::Green); + if let Some(write_access) = + get_buffer_accesses(op, &prev_step.stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Green); + } } } } let height = area.height as usize; - let end_line = self.draw_memory.current_mem_startline + height; + let end_line = self.draw_memory.current_buf_startline + height; - let text: Vec = memory + let text: Vec = buf .chunks(32) .enumerate() - .skip(self.draw_memory.current_mem_startline) + .skip(self.draw_memory.current_buf_startline) .take_while(|(i, _)| *i < end_line) - .map(|(i, mem_word)| { + .map(|(i, buf_word)| { let mut spans = Vec::with_capacity(1 + 32 * 2 + 1 + 32 / 4 + 1); - // Memory index. + // Buffer index. spans.push(Span::styled( format!("{:0min_len$x}| ", i * 32), Style::new().fg(Color::White), )); // Word hex bytes. - hex_bytes_spans(mem_word, &mut spans, |j, _| { + hex_bytes_spans(buf_word, &mut spans, |j, _| { let mut byte_color = Color::White; if let (Some(offset), Some(size), Some(color)) = (offset, size, color) { let idx = i * 32 + j; @@ -573,9 +586,9 @@ impl DebuggerContext<'_> { Style::new().fg(byte_color) }); - if self.mem_utf { + if self.buf_utf { spans.push(Span::raw("|")); - for utf in mem_word.chunks(4) { + for utf in buf_word.chunks(4) { if let Ok(utf_str) = std::str::from_utf8(utf) { spans.push(Span::raw(utf_str.replace('\0', "."))); } else { @@ -590,7 +603,7 @@ impl DebuggerContext<'_> { }) .collect(); - let title = format!("Memory (max expansion: {} bytes)", memory.len()); + let title = self.active_buffer.title(buf.len()); let block = Block::default().title(title).borders(Borders::ALL); let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); f.render_widget(paragraph, area); @@ -628,30 +641,48 @@ impl<'a> SourceLines<'a> { } } -/// The memory_access variable stores the index on the stack that indicates the memory +/// Container for buffer access information. +struct BufferAccess { + offset: usize, + size: usize, +} + +/// Container for read and write buffer access information. +struct BufferAccesses { + /// The read buffer kind and access information. + read: Option<(BufferKind, BufferAccess)>, + /// The only mutable buffer is the memory buffer, so don't store the buffer kind. + write: Option, +} + +/// The memory_access variable stores the index on the stack that indicates the buffer /// offset/size accessed by the given opcode: -/// (read memory offset, read memory size, write memory offset, write memory size) +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) /// >= 1: the stack index /// 0: no memory access /// -1: a fixed size of 32 bytes /// -2: a fixed size of 1 byte -/// The return value is a tuple about accessed memory region by the given opcode: -/// (read memory offset, read memory size, write memory offset, write memory size) -fn get_memory_access( - op: u8, - stack: &[U256], -) -> (Option, Option, Option, Option) { - let memory_access = match op { - opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => (1, 2, 0, 0), - opcode::CALLDATACOPY | opcode::CODECOPY | opcode::RETURNDATACOPY => (0, 0, 1, 3), - opcode::EXTCODECOPY => (0, 0, 2, 4), - opcode::MLOAD => (1, -1, 0, 0), - opcode::MSTORE => (0, 0, 1, -1), - opcode::MSTORE8 => (0, 0, 1, -2), - opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => (1, 2, 0, 0), - opcode::CREATE | opcode::CREATE2 => (2, 3, 0, 0), - opcode::CALL | opcode::CALLCODE => (4, 5, 0, 0), - opcode::DELEGATECALL | opcode::STATICCALL => (3, 4, 0, 0), +/// The return value is a tuple about accessed buffer region by the given opcode: +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) +fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { + let buffer_access = match op { + opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CALLDATACOPY => (Some((BufferKind::Calldata, 2, 3)), Some((1, 3))), + opcode::RETURNDATACOPY => (Some((BufferKind::Returndata, 2, 3)), Some((1, 3))), + opcode::CALLDATALOAD => (Some((BufferKind::Calldata, 1, -1)), None), + opcode::CODECOPY => (None, Some((1, 3))), + opcode::EXTCODECOPY => (None, Some((2, 4))), + opcode::MLOAD => (Some((BufferKind::Memory, 1, -1)), None), + opcode::MSTORE => (None, Some((1, -1))), + opcode::MSTORE8 => (None, Some((1, -2))), + opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CREATE | opcode::CREATE2 => (Some((BufferKind::Memory, 2, 3)), None), + opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), + opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), _ => Default::default(), }; @@ -670,13 +701,20 @@ fn get_memory_access( _ => panic!("invalid stack index"), }; - let (read_offset, read_size, write_offset, write_size) = ( - get_size(memory_access.0), - get_size(memory_access.1), - get_size(memory_access.2), - get_size(memory_access.3), - ); - (read_offset, read_size, write_offset, write_size) + if buffer_access.0.is_some() || buffer_access.1.is_some() { + let (read, write) = buffer_access; + let read_access = read.and_then(|b| { + let (buffer, offset, size) = b; + Some((buffer, BufferAccess { offset: get_size(offset)?, size: get_size(size)? })) + }); + let write_access = write.and_then(|b| { + let (offset, size) = b; + Some(BufferAccess { offset: get_size(offset)?, size: get_size(size)? }) + }); + Some(BufferAccesses { read: read_access, write: write_access }) + } else { + None + } } fn hex_bytes_spans(bytes: &[u8], spans: &mut Vec>, f: impl Fn(usize, u8) -> Style) { diff --git a/crates/evm/core/src/debug.rs b/crates/evm/core/src/debug.rs index 9981955fda90..d6f459339c17 100644 --- a/crates/evm/core/src/debug.rs +++ b/crates/evm/core/src/debug.rs @@ -169,6 +169,10 @@ pub struct DebugStep { pub stack: Vec, /// Memory *prior* to running the associated opcode pub memory: Vec, + /// Calldata *prior* to running the associated opcode + pub calldata: Vec, + /// Returndata *prior* to running the associated opcode + pub returndata: Vec, /// Opcode to be executed pub instruction: Instruction, /// Optional bytes that are being pushed onto the stack @@ -187,6 +191,8 @@ impl Default for DebugStep { Self { stack: vec![], memory: Default::default(), + calldata: Default::default(), + returndata: Default::default(), instruction: Instruction::OpCode(revm::interpreter::opcode::INVALID), push_bytes: None, pc: 0, diff --git a/crates/evm/evm/src/inspectors/debugger.rs b/crates/evm/evm/src/inspectors/debugger.rs index a43bb2a42b96..bb327b36e5be 100644 --- a/crates/evm/evm/src/inspectors/debugger.rs +++ b/crates/evm/evm/src/inspectors/debugger.rs @@ -75,6 +75,8 @@ impl Inspector for Debugger { pc, stack: interpreter.stack().data().clone(), memory: interpreter.shared_memory.context_memory().to_vec(), + calldata: interpreter.contract().input.to_vec(), + returndata: interpreter.return_data_buffer.to_vec(), instruction: Instruction::OpCode(op), push_bytes, total_gas_used,