Skip to content

Commit

Permalink
Refactored debugger to extract TUI abstraction. Added option to dump …
Browse files Browse the repository at this point in the history
…debugger context to file as json.
  • Loading branch information
piohei committed Mar 12, 2024
1 parent 9f6bb3b commit 7cdf9ff
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 79 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ pub async fn handle_traces(
.decoder(&decoder)
.sources(sources)
.build();
debugger.try_run()?;
debugger.try_run_tui()?;
} else {
print_traces(&mut result, &decoder).await?;
}
Expand Down
1 change: 1 addition & 0 deletions crates/debugger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ eyre.workspace = true
ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] }
revm.workspace = true
tracing.workspace = true
serde.workspace = true
File renamed without changes.
16 changes: 16 additions & 0 deletions crates/debugger/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::collections::{BTreeMap, HashMap};
use alloy_primitives::Address;
use foundry_common::compile::ContractSources;
use foundry_common::evm::Breakpoints;
use foundry_evm_core::debug::DebugNodeFlat;
use foundry_evm_core::utils::PcIcMap;

pub struct DebuggerContext {
pub debug_arena: Vec<DebugNodeFlat>,
pub identified_contracts: HashMap<Address, String>,
/// Source map of contract sources
pub contracts_sources: ContractSources,
/// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code)
pub pc_ic_maps: BTreeMap<String, (PcIcMap, PcIcMap)>,
pub breakpoints: Breakpoints,
}
77 changes: 77 additions & 0 deletions crates/debugger/src/debugger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! The TUI implementation.

use alloy_primitives::Address;
use eyre::Result;
use foundry_common::{compile::ContractSources, evm::Breakpoints};
use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap};
use revm::primitives::SpecId;
use std::collections::HashMap;
use std::path::PathBuf;


use crate::{DebuggerBuilder, ExitReason, FileDumper};
use crate::tui::TUI;
use crate::context::DebuggerContext;

/// The TUI debugger.
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<DebugNodeFlat>,
identified_contracts: HashMap<Address, String>,
contracts_sources: ContractSources,
breakpoints: Breakpoints,
) -> Self {
let pc_ic_maps = contracts_sources
.entries()
.filter_map(|(contract_name, (_, contract))| {
Some((
contract_name.clone(),
(
PcIcMap::new(SpecId::LATEST, contract.bytecode.bytes()?),
PcIcMap::new(SpecId::LATEST, contract.deployed_bytecode.bytes()?),
),
))
})
.collect();
Self { context: DebuggerContext { debug_arena, identified_contracts, contracts_sources, pc_ic_maps, 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 debug context 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()
}
}
125 changes: 125 additions & 0 deletions crates/debugger/src/file_dumper/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! The file dumper implementation

use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use alloy_primitives::Address;
use serde::Serialize;

use crate::context::DebuggerContext;
use eyre::Result;
use foundry_compilers::artifacts::ContractBytecodeSome;
use foundry_common::compile::ContractSources;
use foundry_common::fs::{write_json_file};
use foundry_evm_core::debug::DebugNodeFlat;
use foundry_evm_core::utils::PcIcMap;

/// 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(())
}
}

#[derive(Serialize)]
struct DebuggerDump<'a> {
calls: &'a Vec<DebugNodeFlat>,
identified_contracts: &'a HashMap<Address, String>,
/// Source map of contract sources
contracts_sources: ContractSourcesDump,
/// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code)
pc_ic_maps: PcIcMapDump,
}

impl <'a> DebuggerDump<'a> {
fn from(debugger_context: &'a DebuggerContext) -> DebuggerDump {
Self {
calls: &debugger_context.debug_arena,
identified_contracts: &debugger_context.identified_contracts,
contracts_sources: ContractSourcesDump::from(&debugger_context.contracts_sources),
pc_ic_maps: PcIcMapDump::from(&debugger_context.pc_ic_maps),
}
}
}

#[derive(Serialize)]
struct ContractSourcesDump {
#[serde(flatten)]
inner: HashMap<String, Vec<ContractSourcesItemDump>>,
}

#[derive(Serialize)]
struct ContractSourcesItemDump {
id: u32,
source: String,
#[serde(flatten)]
bytecode: ContractBytecodeSome,
}

impl ContractSourcesDump {
fn from(contract_sources: &ContractSources) -> ContractSourcesDump {
let mut inner: HashMap<String, Vec<ContractSourcesItemDump>> = HashMap::new();

for (name, ids) in &contract_sources.ids_by_name {
let list = inner.entry(name.clone()).or_insert(Default::default());
for id in ids {
if let Some((source, bytecode)) = contract_sources.sources_by_id.get(id) {
list.push(ContractSourcesItemDump{id: *id, source: source.clone(), bytecode: bytecode.clone()});
}
}
}

Self { inner }
}
}

#[derive(Serialize)]
struct PcIcMapDump {
#[serde(flatten)]
inner: HashMap<String, PcIcMapItemDump>,
}

impl PcIcMapDump {
fn from(pc_ic_map: &BTreeMap<String, (PcIcMap, PcIcMap)>) -> PcIcMapDump {
let mut inner = HashMap::new();

for (k, v) in pc_ic_map.iter() {
inner.insert(k.clone(), PcIcMapItemDump::from(v));
}

Self { inner }
}
}

#[derive(Serialize)]
struct PcIcMapItemDump {
deploy_code_map: HashMap<usize, usize>,
runtime_code_map: HashMap<usize, usize>,
}

impl PcIcMapItemDump {
fn from(pc_ic_map: &(PcIcMap, PcIcMap)) -> PcIcMapItemDump {
let mut deploy_code_map= HashMap::new();
let mut runtime_code_map = HashMap::new();

for (k, v) in pc_ic_map.0.inner.iter() {
deploy_code_map.insert(k.clone(), v.clone());
}

for (k, v) in pc_ic_map.1.inner.iter() {
runtime_code_map.insert(k.clone(), v.clone());
}

Self { deploy_code_map, runtime_code_map }
}
}
11 changes: 10 additions & 1 deletion crates/debugger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ extern crate tracing;
mod op;

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

pub use debugger::Debugger;
pub use builder::DebuggerBuilder;
pub use tui::{TUI, ExitReason};
pub use file_dumper::FileDumper;

20 changes: 10 additions & 10 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::{Debugger, ExitReason};
use crate::{ExitReason, context::DebuggerContext};
use alloy_primitives::Address;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use foundry_evm_core::debug::{DebugNodeFlat, DebugStep};
Expand Down Expand Up @@ -45,8 +45,8 @@ impl BufferKind {
}
}

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 @@ -64,10 +64,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 @@ -87,7 +87,7 @@ impl<'a> DebuggerContext<'a> {
}

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

pub(crate) fn debug_call(&self) -> &DebugNodeFlat {
Expand Down Expand Up @@ -131,7 +131,7 @@ impl<'a> DebuggerContext<'a> {
}
}

impl DebuggerContext<'_> {
impl TUIContext<'_> {
pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow<ExitReason> {
if self.last_index != self.draw_memory.inner_call_index {
self.gen_opcode_list();
Expand Down Expand Up @@ -307,7 +307,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
12 changes: 6 additions & 6 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::{BufferKind, DebuggerContext};
use super::context::{BufferKind, TUIContext};
use crate::op::OpcodeParam;
use alloy_primitives::U256;
use foundry_compilers::sourcemap::SourceElement;
Expand All @@ -16,7 +16,7 @@ use revm::interpreter::opcode;
use revm_inspectors::tracing::types::CallKind;
use std::{cmp, 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 @@ -330,17 +330,17 @@ impl DebuggerContext<'_> {

fn src_map(&self) -> Result<(SourceElement, &str), 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}"));
};

let Some(mut files_source_code) =
self.debugger.contracts_sources.get_sources(contract_name)
self.debugger_context.contracts_sources.get_sources(contract_name)
else {
return Err(format!("No source map index for contract {contract_name}"));
};

let Some((create_map, rt_map)) = self.debugger.pc_ic_maps.get(contract_name) else {
let Some((create_map, rt_map)) = self.debugger_context.pc_ic_maps.get(contract_name) else {
return Err(format!("No PC-IC maps for contract {contract_name}"));
};

Expand All @@ -366,7 +366,7 @@ impl DebuggerContext<'_> {
(index == file_id).then(|| (source_element.clone(), source_code)))
.or_else(|| {
// otherwise find the source code for the element's index
self.debugger
self.debugger_context
.contracts_sources
.sources_by_id
.get(&(source_element.index?))
Expand Down
Loading

0 comments on commit 7cdf9ff

Please sign in to comment.