Skip to content

Commit 7cdf9ff

Browse files
committed
Refactored debugger to extract TUI abstraction. Added option to dump debugger context to file as json.
1 parent 9f6bb3b commit 7cdf9ff

File tree

15 files changed

+292
-79
lines changed

15 files changed

+292
-79
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/src/utils/cmd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ pub async fn handle_traces(
414414
.decoder(&decoder)
415415
.sources(sources)
416416
.build();
417-
debugger.try_run()?;
417+
debugger.try_run_tui()?;
418418
} else {
419419
print_traces(&mut result, &decoder).await?;
420420
}

crates/debugger/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ eyre.workspace = true
2323
ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] }
2424
revm.workspace = true
2525
tracing.workspace = true
26+
serde.workspace = true
File renamed without changes.

crates/debugger/src/context.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::collections::{BTreeMap, HashMap};
2+
use alloy_primitives::Address;
3+
use foundry_common::compile::ContractSources;
4+
use foundry_common::evm::Breakpoints;
5+
use foundry_evm_core::debug::DebugNodeFlat;
6+
use foundry_evm_core::utils::PcIcMap;
7+
8+
pub struct DebuggerContext {
9+
pub debug_arena: Vec<DebugNodeFlat>,
10+
pub identified_contracts: HashMap<Address, String>,
11+
/// Source map of contract sources
12+
pub contracts_sources: ContractSources,
13+
/// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code)
14+
pub pc_ic_maps: BTreeMap<String, (PcIcMap, PcIcMap)>,
15+
pub breakpoints: Breakpoints,
16+
}

crates/debugger/src/debugger.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! The TUI implementation.
2+
3+
use alloy_primitives::Address;
4+
use eyre::Result;
5+
use foundry_common::{compile::ContractSources, evm::Breakpoints};
6+
use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap};
7+
use revm::primitives::SpecId;
8+
use std::collections::HashMap;
9+
use std::path::PathBuf;
10+
11+
12+
use crate::{DebuggerBuilder, ExitReason, FileDumper};
13+
use crate::tui::TUI;
14+
use crate::context::DebuggerContext;
15+
16+
/// The TUI debugger.
17+
pub struct Debugger {
18+
context: DebuggerContext,
19+
}
20+
21+
impl Debugger {
22+
/// Creates a new debugger builder.
23+
#[inline]
24+
pub fn builder() -> DebuggerBuilder {
25+
DebuggerBuilder::new()
26+
}
27+
28+
/// Creates a new debugger.
29+
pub fn new(
30+
debug_arena: Vec<DebugNodeFlat>,
31+
identified_contracts: HashMap<Address, String>,
32+
contracts_sources: ContractSources,
33+
breakpoints: Breakpoints,
34+
) -> Self {
35+
let pc_ic_maps = contracts_sources
36+
.entries()
37+
.filter_map(|(contract_name, (_, contract))| {
38+
Some((
39+
contract_name.clone(),
40+
(
41+
PcIcMap::new(SpecId::LATEST, contract.bytecode.bytes()?),
42+
PcIcMap::new(SpecId::LATEST, contract.deployed_bytecode.bytes()?),
43+
),
44+
))
45+
})
46+
.collect();
47+
Self { context: DebuggerContext { debug_arena, identified_contracts, contracts_sources, pc_ic_maps, breakpoints } }
48+
}
49+
50+
/// Starts the debugger TUI. Terminates the current process on failure or user exit.
51+
pub fn run_tui_exit(mut self) -> ! {
52+
let code = match self.try_run_tui() {
53+
Ok(ExitReason::CharExit) => 0,
54+
Err(e) => {
55+
println!("{e}");
56+
1
57+
}
58+
};
59+
std::process::exit(code)
60+
}
61+
62+
/// Starts the debugger TUI.
63+
pub fn try_run_tui(&mut self) -> Result<ExitReason> {
64+
eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");
65+
66+
let mut tui = TUI::new(&mut self.context);
67+
tui.try_run()
68+
}
69+
70+
/// Dumps debug context to file.
71+
pub fn dump_to_file(&mut self, path: &PathBuf) -> Result<()> {
72+
eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");
73+
74+
let mut file_dumper = FileDumper::new(path, &mut self.context);
75+
file_dumper.run()
76+
}
77+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! The file dumper implementation
2+
3+
use std::collections::{BTreeMap, HashMap};
4+
use std::path::PathBuf;
5+
use alloy_primitives::Address;
6+
use serde::Serialize;
7+
8+
use crate::context::DebuggerContext;
9+
use eyre::Result;
10+
use foundry_compilers::artifacts::ContractBytecodeSome;
11+
use foundry_common::compile::ContractSources;
12+
use foundry_common::fs::{write_json_file};
13+
use foundry_evm_core::debug::DebugNodeFlat;
14+
use foundry_evm_core::utils::PcIcMap;
15+
16+
/// The file dumper
17+
pub struct FileDumper<'a> {
18+
path: &'a PathBuf,
19+
debugger_context: &'a mut DebuggerContext,
20+
}
21+
22+
impl<'a> FileDumper<'a> {
23+
pub fn new(path: &'a PathBuf, debugger_context: &'a mut DebuggerContext, ) -> Self {
24+
Self { path, debugger_context }
25+
}
26+
27+
pub fn run(&mut self) -> Result<()> {
28+
let data = DebuggerDump::from(self.debugger_context);
29+
write_json_file(self.path, &data).unwrap();
30+
Ok(())
31+
}
32+
}
33+
34+
#[derive(Serialize)]
35+
struct DebuggerDump<'a> {
36+
calls: &'a Vec<DebugNodeFlat>,
37+
identified_contracts: &'a HashMap<Address, String>,
38+
/// Source map of contract sources
39+
contracts_sources: ContractSourcesDump,
40+
/// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code)
41+
pc_ic_maps: PcIcMapDump,
42+
}
43+
44+
impl <'a> DebuggerDump<'a> {
45+
fn from(debugger_context: &'a DebuggerContext) -> DebuggerDump {
46+
Self {
47+
calls: &debugger_context.debug_arena,
48+
identified_contracts: &debugger_context.identified_contracts,
49+
contracts_sources: ContractSourcesDump::from(&debugger_context.contracts_sources),
50+
pc_ic_maps: PcIcMapDump::from(&debugger_context.pc_ic_maps),
51+
}
52+
}
53+
}
54+
55+
#[derive(Serialize)]
56+
struct ContractSourcesDump {
57+
#[serde(flatten)]
58+
inner: HashMap<String, Vec<ContractSourcesItemDump>>,
59+
}
60+
61+
#[derive(Serialize)]
62+
struct ContractSourcesItemDump {
63+
id: u32,
64+
source: String,
65+
#[serde(flatten)]
66+
bytecode: ContractBytecodeSome,
67+
}
68+
69+
impl ContractSourcesDump {
70+
fn from(contract_sources: &ContractSources) -> ContractSourcesDump {
71+
let mut inner: HashMap<String, Vec<ContractSourcesItemDump>> = HashMap::new();
72+
73+
for (name, ids) in &contract_sources.ids_by_name {
74+
let list = inner.entry(name.clone()).or_insert(Default::default());
75+
for id in ids {
76+
if let Some((source, bytecode)) = contract_sources.sources_by_id.get(id) {
77+
list.push(ContractSourcesItemDump{id: *id, source: source.clone(), bytecode: bytecode.clone()});
78+
}
79+
}
80+
}
81+
82+
Self { inner }
83+
}
84+
}
85+
86+
#[derive(Serialize)]
87+
struct PcIcMapDump {
88+
#[serde(flatten)]
89+
inner: HashMap<String, PcIcMapItemDump>,
90+
}
91+
92+
impl PcIcMapDump {
93+
fn from(pc_ic_map: &BTreeMap<String, (PcIcMap, PcIcMap)>) -> PcIcMapDump {
94+
let mut inner = HashMap::new();
95+
96+
for (k, v) in pc_ic_map.iter() {
97+
inner.insert(k.clone(), PcIcMapItemDump::from(v));
98+
}
99+
100+
Self { inner }
101+
}
102+
}
103+
104+
#[derive(Serialize)]
105+
struct PcIcMapItemDump {
106+
deploy_code_map: HashMap<usize, usize>,
107+
runtime_code_map: HashMap<usize, usize>,
108+
}
109+
110+
impl PcIcMapItemDump {
111+
fn from(pc_ic_map: &(PcIcMap, PcIcMap)) -> PcIcMapItemDump {
112+
let mut deploy_code_map= HashMap::new();
113+
let mut runtime_code_map = HashMap::new();
114+
115+
for (k, v) in pc_ic_map.0.inner.iter() {
116+
deploy_code_map.insert(k.clone(), v.clone());
117+
}
118+
119+
for (k, v) in pc_ic_map.1.inner.iter() {
120+
runtime_code_map.insert(k.clone(), v.clone());
121+
}
122+
123+
Self { deploy_code_map, runtime_code_map }
124+
}
125+
}

crates/debugger/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,13 @@ extern crate tracing;
1010
mod op;
1111

1212
mod tui;
13-
pub use tui::{Debugger, DebuggerBuilder, ExitReason};
13+
mod file_dumper;
14+
mod debugger;
15+
mod builder;
16+
mod context;
17+
18+
pub use debugger::Debugger;
19+
pub use builder::DebuggerBuilder;
20+
pub use tui::{TUI, ExitReason};
21+
pub use file_dumper::FileDumper;
22+

crates/debugger/src/tui/context.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Debugger context and event handler implementation.
22
3-
use crate::{Debugger, ExitReason};
3+
use crate::{ExitReason, context::DebuggerContext};
44
use alloy_primitives::Address;
55
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
66
use foundry_evm_core::debug::{DebugNodeFlat, DebugStep};
@@ -45,8 +45,8 @@ impl BufferKind {
4545
}
4646
}
4747

48-
pub(crate) struct DebuggerContext<'a> {
49-
pub(crate) debugger: &'a mut Debugger,
48+
pub(crate) struct TUIContext<'a> {
49+
pub(crate) debugger_context: &'a mut DebuggerContext,
5050

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

67-
impl<'a> DebuggerContext<'a> {
68-
pub(crate) fn new(debugger: &'a mut Debugger) -> Self {
69-
DebuggerContext {
70-
debugger,
67+
impl<'a> TUIContext<'a> {
68+
pub(crate) fn new(debugger_context: &'a mut DebuggerContext) -> Self {
69+
TUIContext {
70+
debugger_context,
7171

7272
key_buffer: String::with_capacity(64),
7373
current_step: 0,
@@ -87,7 +87,7 @@ impl<'a> DebuggerContext<'a> {
8787
}
8888

8989
pub(crate) fn debug_arena(&self) -> &[DebugNodeFlat] {
90-
&self.debugger.debug_arena
90+
&self.debugger_context.debug_arena
9191
}
9292

9393
pub(crate) fn debug_call(&self) -> &DebugNodeFlat {
@@ -131,7 +131,7 @@ impl<'a> DebuggerContext<'a> {
131131
}
132132
}
133133

134-
impl DebuggerContext<'_> {
134+
impl TUIContext<'_> {
135135
pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow<ExitReason> {
136136
if self.last_index != self.draw_memory.inner_call_index {
137137
self.gen_opcode_list();
@@ -307,7 +307,7 @@ impl DebuggerContext<'_> {
307307
fn handle_breakpoint(&mut self, c: char) {
308308
// Find the location of the called breakpoint in the whole debug arena (at this address with
309309
// this pc)
310-
if let Some((caller, pc)) = self.debugger.breakpoints.get(&c) {
310+
if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) {
311311
for (i, node) in self.debug_arena().iter().enumerate() {
312312
if node.address == *caller {
313313
if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) {

crates/debugger/src/tui/draw.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! TUI draw implementation.
22
3-
use super::context::{BufferKind, DebuggerContext};
3+
use super::context::{BufferKind, TUIContext};
44
use crate::op::OpcodeParam;
55
use alloy_primitives::U256;
66
use foundry_compilers::sourcemap::SourceElement;
@@ -16,7 +16,7 @@ use revm::interpreter::opcode;
1616
use revm_inspectors::tracing::types::CallKind;
1717
use std::{cmp, collections::VecDeque, fmt::Write, io};
1818

19-
impl DebuggerContext<'_> {
19+
impl TUIContext<'_> {
2020
/// Draws the TUI layout and subcomponents to the given terminal.
2121
pub(crate) fn draw(&self, terminal: &mut super::DebuggerTerminal) -> io::Result<()> {
2222
terminal.draw(|f| self.draw_layout(f)).map(drop)
@@ -330,17 +330,17 @@ impl DebuggerContext<'_> {
330330

331331
fn src_map(&self) -> Result<(SourceElement, &str), String> {
332332
let address = self.address();
333-
let Some(contract_name) = self.debugger.identified_contracts.get(address) else {
333+
let Some(contract_name) = self.debugger_context.identified_contracts.get(address) else {
334334
return Err(format!("Unknown contract at address {address}"));
335335
};
336336

337337
let Some(mut files_source_code) =
338-
self.debugger.contracts_sources.get_sources(contract_name)
338+
self.debugger_context.contracts_sources.get_sources(contract_name)
339339
else {
340340
return Err(format!("No source map index for contract {contract_name}"));
341341
};
342342

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

@@ -366,7 +366,7 @@ impl DebuggerContext<'_> {
366366
(index == file_id).then(|| (source_element.clone(), source_code)))
367367
.or_else(|| {
368368
// otherwise find the source code for the element's index
369-
self.debugger
369+
self.debugger_context
370370
.contracts_sources
371371
.sources_by_id
372372
.get(&(source_element.index?))

0 commit comments

Comments
 (0)