Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug file dump #7375

Merged
merged 7 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ pub async fn handle_traces(
.decoder(&decoder)
.sources(sources)
.build();
debugger.try_run()?;
debugger.try_run_tui()?;
} else {
print_traces(&mut result, &decoder, verbose).await?;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! TUI debugger builder.
//! Debugger builder.

use crate::{node::flatten_call_trace, DebugNode, Debugger};
use alloy_primitives::{map::AddressHashMap, Address};
Expand Down
12 changes: 12 additions & 0 deletions crates/debugger/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::DebugNode;
use alloy_primitives::map::AddressHashMap;
use foundry_common::evm::Breakpoints;
use foundry_evm_traces::debug::ContractSources;

pub struct DebuggerContext {
pub debug_arena: Vec<DebugNode>,
pub identified_contracts: AddressHashMap<String>,
/// Source map of contract sources
pub contracts_sources: ContractSources,
pub breakpoints: Breakpoints,
}
67 changes: 67 additions & 0 deletions crates/debugger/src/debugger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Debugger implementation.

use crate::{
context::DebuggerContext, tui::TUI, DebugNode, DebuggerBuilder, ExitReason, FileDumper,
};
use alloy_primitives::map::AddressHashMap;
use eyre::Result;
use foundry_common::evm::Breakpoints;
use foundry_evm_traces::debug::ContractSources;
use std::path::PathBuf;

pub struct Debugger {
context: DebuggerContext,
}

impl Debugger {
/// Creates a new debugger builder.
#[inline]
pub fn builder() -> DebuggerBuilder {
DebuggerBuilder::new()
}

/// Creates a new debugger.
pub fn new(
debug_arena: Vec<DebugNode>,
identified_contracts: AddressHashMap<String>,
contracts_sources: ContractSources,
breakpoints: Breakpoints,
) -> Self {
Self {
context: DebuggerContext {
debug_arena,
identified_contracts,
contracts_sources,
breakpoints,
},
}
}

/// Starts the debugger TUI. Terminates the current process on failure or user exit.
pub fn run_tui_exit(mut self) -> ! {
let code = match self.try_run_tui() {
Ok(ExitReason::CharExit) => 0,
Err(e) => {
println!("{e}");
1
}
};
std::process::exit(code)
}

/// Starts the debugger TUI.
pub fn try_run_tui(&mut self) -> Result<ExitReason> {
eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");

let mut tui = TUI::new(&mut self.context);
tui.try_run()
}

/// Dumps debugger data to file.
pub fn dump_to_file(&mut self, path: &PathBuf) -> Result<()> {
eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");

let mut file_dumper = FileDumper::new(path, &mut self.context);
file_dumper.run()
}
}
169 changes: 169 additions & 0 deletions crates/debugger/src/file_dumper/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! The file dumper implementation

use crate::{context::DebuggerContext, DebugNode};
use alloy_primitives::Address;
use eyre::Result;
use foundry_common::fs::write_json_file;
use foundry_compilers::{
artifacts::sourcemap::{Jump, SourceElement},
multi::MultiCompilerLanguage,
};
use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData};
use serde::Serialize;
use std::{collections::HashMap, ops::Deref, path::PathBuf};

/// The file dumper
pub struct FileDumper<'a> {
path: &'a PathBuf,
debugger_context: &'a mut DebuggerContext,
}

impl<'a> FileDumper<'a> {
pub fn new(path: &'a PathBuf, debugger_context: &'a mut DebuggerContext) -> Self {
Self { path, debugger_context }
}

pub fn run(&mut self) -> Result<()> {
let data = DebuggerDump::from(self.debugger_context);
write_json_file(self.path, &data).unwrap();
Ok(())
}
}

impl DebuggerDump {
fn from(debugger_context: &DebuggerContext) -> Self {
Self {
contracts: ContractsDump::new(debugger_context),
debug_arena: debugger_context.debug_arena.clone(),
}
}
}

#[derive(Serialize)]
struct DebuggerDump {
contracts: ContractsDump,
debug_arena: Vec<DebugNode>,
}

#[derive(Serialize)]
pub struct SourceElementDump {
offset: u32,
length: u32,
index: i32,
jump: u32,
modifier_depth: u32,
}

#[derive(Serialize)]
struct ContractsDump {
// Map of call address to contract name
identified_contracts: HashMap<Address, String>,
sources: ContractsSourcesDump,
}

#[derive(Serialize)]
struct ContractsSourcesDump {
sources_by_id: HashMap<String, HashMap<u32, SourceDataDump>>,
artifacts_by_name: HashMap<String, Vec<ArtifactDataDump>>,
}

#[derive(Serialize)]
struct SourceDataDump {
source: String,
language: MultiCompilerLanguage,
path: PathBuf,
}

#[derive(Serialize)]
struct ArtifactDataDump {
pub source_map: Option<Vec<SourceElementDump>>,
pub source_map_runtime: Option<Vec<SourceElementDump>>,
pub pc_ic_map: Option<HashMap<usize, usize>>,
pub pc_ic_map_runtime: Option<HashMap<usize, usize>>,
pub build_id: String,
pub file_id: u32,
}

impl ContractsDump {
pub fn new(debugger_context: &DebuggerContext) -> Self {
Self {
identified_contracts: debugger_context
.identified_contracts
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect(),
sources: ContractsSourcesDump::new(&debugger_context.contracts_sources),
}
}
}

impl ContractsSourcesDump {
pub fn new(contracts_sources: &ContractSources) -> Self {
Self {
sources_by_id: contracts_sources
.sources_by_id
.iter()
.map(|(name, inner_map)| {
(
name.clone(),
inner_map
.iter()
.map(|(id, source_data)| (*id, SourceDataDump::new(source_data)))
.collect(),
)
})
.collect(),
artifacts_by_name: contracts_sources
.artifacts_by_name
.iter()
.map(|(name, data)| {
(name.clone(), data.iter().map(ArtifactDataDump::new).collect())
})
.collect(),
}
}
}

impl SourceDataDump {
pub fn new(v: &SourceData) -> Self {
Self { source: v.source.deref().clone(), language: v.language, path: v.path.clone() }
}
}

impl SourceElementDump {
pub fn new(v: &SourceElement) -> Self {
Self {
offset: v.offset(),
length: v.length(),
index: v.index_i32(),
jump: match v.jump() {
Jump::In => 0,
Jump::Out => 1,
Jump::Regular => 2,
},
modifier_depth: v.modifier_depth(),
}
}
}

impl ArtifactDataDump {
pub fn new(v: &ArtifactData) -> Self {
Self {
source_map: v
.source_map
.clone()
.map(|source_map| source_map.iter().map(SourceElementDump::new).collect()),
source_map_runtime: v
.source_map_runtime
.clone()
.map(|source_map| source_map.iter().map(SourceElementDump::new).collect()),
pc_ic_map: v.pc_ic_map.clone().map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()),
pc_ic_map_runtime: v
.pc_ic_map_runtime
.clone()
.map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()),
build_id: v.build_id.clone(),
file_id: v.file_id,
}
}
}
12 changes: 10 additions & 2 deletions crates/debugger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! # foundry-debugger
//!
//! Interactive Solidity TUI debugger.
//! Interactive Solidity TUI debugger and debugger data file dumper

#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
Expand All @@ -10,8 +10,16 @@ extern crate tracing;

mod op;

mod builder;
mod context;
mod debugger;
mod file_dumper;
mod tui;
pub use tui::{Debugger, DebuggerBuilder, ExitReason};

mod node;
pub use node::DebugNode;

pub use builder::DebuggerBuilder;
pub use debugger::Debugger;
pub use file_dumper::FileDumper;
pub use tui::{ExitReason, TUI};
23 changes: 12 additions & 11 deletions crates/debugger/src/tui/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Debugger context and event handler implementation.

use crate::{DebugNode, Debugger, ExitReason};
use crate::{context::DebuggerContext, DebugNode, ExitReason};
use alloy_primitives::{hex, Address};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use foundry_evm_core::buffer::BufferKind;
Expand All @@ -16,8 +16,8 @@ pub(crate) struct DrawMemory {
pub(crate) current_stack_startline: usize,
}

pub(crate) struct DebuggerContext<'a> {
pub(crate) debugger: &'a mut Debugger,
pub(crate) struct TUIContext<'a> {
pub(crate) debugger_context: &'a mut DebuggerContext,

/// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations.
pub(crate) key_buffer: String,
Expand All @@ -35,10 +35,10 @@ pub(crate) struct DebuggerContext<'a> {
pub(crate) active_buffer: BufferKind,
}

impl<'a> DebuggerContext<'a> {
pub(crate) fn new(debugger: &'a mut Debugger) -> Self {
DebuggerContext {
debugger,
impl<'a> TUIContext<'a> {
pub(crate) fn new(debugger_context: &'a mut DebuggerContext) -> Self {
TUIContext {
debugger_context,

key_buffer: String::with_capacity(64),
current_step: 0,
Expand All @@ -58,7 +58,7 @@ impl<'a> DebuggerContext<'a> {
}

pub(crate) fn debug_arena(&self) -> &[DebugNode] {
&self.debugger.debug_arena
&self.debugger_context.debug_arena
}

pub(crate) fn debug_call(&self) -> &DebugNode {
Expand Down Expand Up @@ -87,7 +87,8 @@ impl<'a> DebuggerContext<'a> {

fn gen_opcode_list(&mut self) {
self.opcode_list.clear();
let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps;
let debug_steps =
&self.debugger_context.debug_arena[self.draw_memory.inner_call_index].steps;
for step in debug_steps {
self.opcode_list.push(pretty_opcode(step));
}
Expand All @@ -109,7 +110,7 @@ impl<'a> DebuggerContext<'a> {
}
}

impl DebuggerContext<'_> {
impl TUIContext<'_> {
pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow<ExitReason> {
let ret = match event {
Event::Key(event) => self.handle_key_event(event),
Expand Down Expand Up @@ -259,7 +260,7 @@ impl DebuggerContext<'_> {
fn handle_breakpoint(&mut self, c: char) {
// Find the location of the called breakpoint in the whole debug arena (at this address with
// this pc)
if let Some((caller, pc)) = self.debugger.breakpoints.get(&c) {
if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) {
for (i, node) in self.debug_arena().iter().enumerate() {
if node.address == *caller {
if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) {
Expand Down
8 changes: 4 additions & 4 deletions crates/debugger/src/tui/draw.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! TUI draw implementation.

use super::context::DebuggerContext;
use super::context::TUIContext;
use crate::op::OpcodeParam;
use foundry_compilers::artifacts::sourcemap::SourceElement;
use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind};
Expand All @@ -15,7 +15,7 @@ use ratatui::{
use revm_inspectors::tracing::types::CallKind;
use std::{collections::VecDeque, fmt::Write, io};

impl DebuggerContext<'_> {
impl TUIContext<'_> {
/// Draws the TUI layout and subcomponents to the given terminal.
pub(crate) fn draw(&self, terminal: &mut super::DebuggerTerminal) -> io::Result<()> {
terminal.draw(|f| self.draw_layout(f)).map(drop)
Expand Down Expand Up @@ -343,11 +343,11 @@ impl DebuggerContext<'_> {
/// Returns source map, source code and source name of the current line.
fn src_map(&self) -> Result<(SourceElement, &SourceData), String> {
let address = self.address();
let Some(contract_name) = self.debugger.identified_contracts.get(address) else {
let Some(contract_name) = self.debugger_context.identified_contracts.get(address) else {
return Err(format!("Unknown contract at address {address}"));
};

self.debugger
self.debugger_context
.contracts_sources
.find_source_mapping(
contract_name,
Expand Down
Loading
Loading