1use std::{collections::VecDeque, rc::Rc, sync::Arc};
2
3use miden_assembly::{DefaultSourceManager, SourceManager};
4use miden_assembly_syntax::diagnostics::{IntoDiagnostic, Report};
5use miden_core::{program::Program, serde::Deserializable};
6use miden_processor::{
7 Felt, StackInputs,
8 advice::{AdviceInputs, AdviceMutation},
9 mast::MastForest,
10};
11
12use crate::{
13 config::DebuggerConfig,
14 debug::{Breakpoint, BreakpointType, ReadMemoryExpr, ResolvedLocation, resolve_variable_value},
15 exec::{DebugExecutor, Executor},
16 input::InputFile,
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq)]
21pub enum DebugMode {
22 Program,
24 Transaction,
26 Remote,
28}
29
30fn clone_advice_mutation(mutation: &AdviceMutation) -> AdviceMutation {
31 match mutation {
32 AdviceMutation::ExtendStack { values } => AdviceMutation::ExtendStack {
33 values: values.clone(),
34 },
35 AdviceMutation::ExtendMap { other } => AdviceMutation::ExtendMap {
36 other: other.clone(),
37 },
38 AdviceMutation::ExtendMerkleStore { infos } => AdviceMutation::ExtendMerkleStore {
39 infos: infos.clone(),
40 },
41 AdviceMutation::ExtendPrecompileRequests { data } => {
42 AdviceMutation::ExtendPrecompileRequests { data: data.clone() }
43 }
44 }
45}
46
47fn clone_event_replay_queue(event_replay: &[Vec<AdviceMutation>]) -> VecDeque<Vec<AdviceMutation>> {
48 event_replay
49 .iter()
50 .map(|batch| batch.iter().map(clone_advice_mutation).collect())
51 .collect()
52}
53
54pub struct State {
55 pub source_manager: Arc<dyn SourceManager>,
56 pub config: Box<DebuggerConfig>,
57 pub input_mode: InputMode,
58 pub breakpoints: Vec<Breakpoint>,
59 pub breakpoints_hit: Vec<Breakpoint>,
60 pub next_breakpoint_id: u8,
61 pub stopped: bool,
62 pub debug_mode: DebugMode,
63 session: SessionState,
64}
65
66#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
67pub enum InputMode {
68 #[default]
69 Normal,
70 #[allow(dead_code)]
71 Insert,
72 Command,
73}
74
75struct LocalState {
76 executor: DebugExecutor,
77 execution_failed: Option<miden_processor::ExecutionError>,
78}
79
80#[cfg(feature = "dap")]
81struct RemoteState {
82 client: crate::exec::DapClient,
83 executor: DebugExecutor,
84 addr: String,
85 synced_bp_files: std::collections::BTreeSet<String>,
88}
89
90enum SessionState {
91 Local(Box<LocalState>),
92 #[cfg(feature = "dap")]
93 Remote(Box<RemoteState>),
94}
95
96#[cfg(feature = "dap")]
97struct RemoteSnapshot {
98 callstack: crate::debug::CallStack,
99 current_stack: Vec<Felt>,
100 cycle: usize,
101}
102
103#[cfg(feature = "dap")]
104impl RemoteState {
105 fn connect(addr: &str, source_manager: &Arc<dyn SourceManager>) -> Result<Self, Report> {
106 use std::{cell::RefCell, collections::BTreeSet, rc::Rc};
107
108 use miden_debug_engine::debug::DebugVarTracker;
109 use miden_processor::{ContextId, FastProcessor};
110
111 use crate::exec::DebuggerHost;
112
113 let mut client = crate::exec::DapClient::connect(addr).map_err(Report::msg)?;
114 let ui_state = client.handshake().map_err(Report::msg)?;
115 let snapshot = convert_ui_state(&ui_state, source_manager);
116
117 let debug_vars = DebugVarTracker::new(Rc::new(RefCell::new(Default::default())));
118 let executor = DebugExecutor {
119 processor: FastProcessor::new(StackInputs::default()),
120 host: DebuggerHost::new(source_manager.clone()),
121 resume_ctx: None,
122 current_stack: snapshot.current_stack,
123 current_op: None,
124 current_asmop: None,
125 stack_outputs: Default::default(),
126 contexts: BTreeSet::new(),
127 root_context: ContextId::root(),
128 current_context: ContextId::root(),
129 callstack: snapshot.callstack,
130 current_proc: None,
131 debug_vars,
132 last_debug_var_count: 0,
133 recent: VecDeque::new(),
134 cycle: snapshot.cycle,
135 stopped: false,
136 };
137
138 Ok(Self {
139 client,
140 executor,
141 addr: addr.to_string(),
142 synced_bp_files: std::collections::BTreeSet::new(),
143 })
144 }
145
146 fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
147 self.client.read_memory(expr)
148 }
149
150 fn sync_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
151 use std::collections::BTreeMap;
152
153 let mut by_file: BTreeMap<String, Vec<i64>> = BTreeMap::new();
155 let mut func_names: Vec<String> = Vec::new();
157
158 for bp in breakpoints {
159 match &bp.ty {
160 BreakpointType::Line { pattern, line } => {
161 by_file.entry(pattern.as_str().to_string()).or_default().push(*line as i64);
162 }
163 BreakpointType::Called(pattern) | BreakpointType::File(pattern) => {
164 func_names.push(pattern.as_str().to_string());
165 }
166 _ => {}
167 }
168 }
169
170 let stale_files: Vec<String> = self
173 .synced_bp_files
174 .iter()
175 .filter(|f| !by_file.contains_key(f.as_str()))
176 .cloned()
177 .collect();
178 for file in &stale_files {
179 let _ = self.client.set_breakpoints(file, &[]);
180 }
181
182 for (file, lines) in &by_file {
184 let _ = self.client.set_breakpoints(file, lines);
185 }
186
187 let _ = self.client.set_function_breakpoints(&func_names);
189
190 self.synced_bp_files = by_file.into_keys().collect();
192 }
193
194 fn resume(&mut self, breakpoints: &[Breakpoint]) -> Result<crate::exec::DapStopReason, String> {
195 self.sync_breakpoints(breakpoints);
197
198 let has_step = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Step));
199 let has_next = breakpoints
200 .iter()
201 .any(|bp| matches!(bp.ty, BreakpointType::Next | BreakpointType::NextLine));
202 let has_finish = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Finish));
203
204 if has_step {
205 self.client.step_in()
206 } else if has_next {
207 self.client.step_over()
208 } else if has_finish {
209 self.client.step_out()
210 } else {
211 self.client.continue_()
212 }
213 }
214
215 fn refresh_executor(
216 &mut self,
217 source_manager: &Arc<dyn SourceManager>,
218 pushed: &crate::exec::DapUiState,
219 ) {
220 let snapshot = convert_ui_state(pushed, source_manager);
226 self.executor.current_stack = snapshot.current_stack;
227 self.executor.callstack = snapshot.callstack;
228 self.executor.cycle = snapshot.cycle;
229 }
230
231 fn reconnect(&mut self, source_manager: &Arc<dyn SourceManager>) -> Result<(), Report> {
232 let timeout = std::time::Duration::from_secs(30);
233 let mut new_client =
234 crate::exec::DapClient::connect_with_retry(&self.addr, timeout).map_err(Report::msg)?;
235 let ui_state = new_client.handshake().map_err(Report::msg)?;
236 let snapshot = convert_ui_state(&ui_state, source_manager);
237
238 self.client = new_client;
239 self.executor.current_stack = snapshot.current_stack;
240 self.executor.callstack = snapshot.callstack;
241 self.executor.cycle = snapshot.cycle;
242 Ok(())
243 }
244}
245
246impl State {
247 fn new_local(
248 source_manager: Arc<dyn SourceManager>,
249 config: Box<DebuggerConfig>,
250 debug_mode: DebugMode,
251 local: LocalState,
252 ) -> Self {
253 Self {
254 source_manager,
255 config,
256 input_mode: InputMode::Normal,
257 breakpoints: vec![],
258 breakpoints_hit: vec![],
259 next_breakpoint_id: 0,
260 stopped: true,
261 debug_mode,
262 session: SessionState::Local(Box::new(local)),
263 }
264 }
265
266 pub fn new(config: Box<DebuggerConfig>) -> Result<Self, Report> {
267 let source_manager = Arc::new(DefaultSourceManager::default());
268 let mut inputs = config.inputs.clone().unwrap_or_default();
269 if !config.args.is_empty() {
270 let args = config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
272 inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
273 }
274 let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
275 let package = load_package(&config)?;
276
277 let mut libs = Vec::with_capacity(config.link_libraries.len());
279 for link_library in config.link_libraries.iter() {
280 log::debug!(target: "state", "loading link library {}", link_library.name());
281 let lib = link_library.load(&config, source_manager.clone())?;
282 libs.push(lib.clone());
283 }
284
285 if let Some(toolchain_dir) = config.toolchain_dir() {
287 libs.extend(load_sysroot_libs(&toolchain_dir)?);
288 }
289
290 let mut executor = Executor::new(args.clone());
292 for lib in libs.iter() {
293 executor.register_library_dependency(lib.clone());
294 executor.with_library(lib.clone());
295 }
296
297 let dependencies = package.manifest.dependencies();
299 executor.with_dependencies(dependencies)?;
300 executor.with_advice_inputs(inputs.advice_inputs);
301
302 let program = package.unwrap_program();
303 let executor = executor.into_debug(&program, source_manager.clone());
304
305 Ok(Self::new_local(
306 source_manager,
307 config,
308 DebugMode::Program,
309 LocalState {
310 executor,
311 execution_failed: None,
312 },
313 ))
314 }
315
316 pub fn new_for_transaction(
322 program: Arc<Program>,
323 stack_inputs: StackInputs,
324 advice_inputs: AdviceInputs,
325 source_manager: Arc<dyn SourceManager>,
326 mast_forests: Vec<Arc<MastForest>>,
327 event_replay: Vec<Vec<AdviceMutation>>,
328 ) -> Result<Self, Report> {
329 let args = stack_inputs.iter().copied().rev().collect::<Vec<_>>();
330
331 let mut executor = Executor::new(args);
333 executor.with_advice_inputs(advice_inputs);
334 let debug_executor = executor.into_debug_with_replay(
335 &program,
336 source_manager.clone(),
337 mast_forests,
338 clone_event_replay_queue(&event_replay),
339 );
340
341 Ok(Self::new_local(
342 source_manager,
343 Box::default(),
344 DebugMode::Transaction,
345 LocalState {
346 executor: debug_executor,
347 execution_failed: None,
348 },
349 ))
350 }
351
352 pub fn reload(&mut self) -> Result<(), Report> {
353 if self.debug_mode == DebugMode::Transaction {
354 return Err(Report::msg("reload is not supported in transaction debug mode"));
355 }
356 if self.debug_mode == DebugMode::Remote {
357 #[cfg(feature = "dap")]
358 {
359 let source_manager = self.source_manager.clone();
360 let SessionState::Remote(remote) = &mut self.session else {
361 return Err(Report::msg("no remote debug session"));
362 };
363 let result = remote.client.restart_phase2().map_err(Report::msg)?;
364 match result {
365 crate::exec::DapStopReason::Restarting => {
366 remote.reconnect(&source_manager)?;
367 }
368 crate::exec::DapStopReason::Stopped(snapshot) => {
369 remote.refresh_executor(&source_manager, &snapshot);
371 }
372 crate::exec::DapStopReason::Terminated => {
373 return Err(Report::msg("server terminated without restart signal"));
374 }
375 }
376 self.breakpoints_hit.clear();
377 self.stopped = true;
378 return Ok(());
379 }
380 #[cfg(not(feature = "dap"))]
381 return Err(Report::msg("remote debug mode requires the `dap` feature"));
382 }
383
384 log::debug!("reloading program");
385 let package = load_package(&self.config)?;
386
387 let mut inputs = self.config.inputs.clone().unwrap_or_default();
388 if !self.config.args.is_empty() {
389 let args = self.config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
391 inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
392 }
393 let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
394
395 let mut libs = Vec::with_capacity(self.config.link_libraries.len());
397 for link_library in self.config.link_libraries.iter() {
398 let lib = link_library.load(&self.config, self.source_manager.clone())?;
399 libs.push(lib.clone());
400 }
401
402 if let Some(toolchain_dir) = self.config.toolchain_dir() {
404 libs.extend(load_sysroot_libs(&toolchain_dir)?);
405 }
406
407 let mut executor = Executor::new(args.clone());
409 for lib in libs.iter() {
410 executor.register_library_dependency(lib.clone());
411 executor.with_library(lib.clone());
412 }
413
414 let dependencies = package.manifest.dependencies();
416 executor.with_dependencies(dependencies)?;
417 executor.with_advice_inputs(inputs.advice_inputs);
418
419 let program = package.unwrap_program();
420 let executor = executor.into_debug(&program, self.source_manager.clone());
421
422 self.session = SessionState::Local(Box::new(LocalState {
423 executor,
424 execution_failed: None,
425 }));
426 self.breakpoints_hit.clear();
427 let breakpoints = core::mem::take(&mut self.breakpoints);
428 self.breakpoints.reserve(breakpoints.len());
429 self.next_breakpoint_id = 0;
430 self.stopped = true;
431 for bp in breakpoints {
432 self.create_breakpoint(bp.ty);
433 }
434 Ok(())
435 }
436
437 pub fn create_breakpoint(&mut self, ty: BreakpointType) {
438 let id = self.next_breakpoint_id();
439 let creation_cycle = self.executor().cycle;
440 log::trace!("created breakpoint with id {id} at cycle {creation_cycle}");
441 if matches!(ty, BreakpointType::Finish)
442 && let Some(frame) = self.executor_mut().callstack.current_frame_mut()
443 {
444 frame.break_on_exit();
445 }
446 self.breakpoints.push(Breakpoint {
447 id,
448 creation_cycle,
449 ty,
450 });
451 }
452
453 fn next_breakpoint_id(&mut self) -> u8 {
454 let mut candidate = self.next_breakpoint_id;
455 let initial = candidate;
456 let mut next = candidate.wrapping_add(1);
457 loop {
458 assert_ne!(initial, next, "unable to allocate a breakpoint id: too many breakpoints");
459 if self
460 .breakpoints
461 .iter()
462 .chain(self.breakpoints_hit.iter())
463 .any(|bp| bp.id == candidate)
464 {
465 candidate = next;
466 next = candidate.wrapping_add(1);
467 continue;
468 }
469 self.next_breakpoint_id = next;
470 break candidate;
471 }
472 }
473
474 pub fn executor(&self) -> &DebugExecutor {
475 match &self.session {
476 SessionState::Local(local) => &local.executor,
477 #[cfg(feature = "dap")]
478 SessionState::Remote(remote) => &remote.executor,
479 }
480 }
481
482 pub fn executor_mut(&mut self) -> &mut DebugExecutor {
483 match &mut self.session {
484 SessionState::Local(local) => &mut local.executor,
485 #[cfg(feature = "dap")]
486 SessionState::Remote(remote) => &mut remote.executor,
487 }
488 }
489
490 pub fn current_procedure(&self) -> Option<Rc<str>> {
491 let live_proc = self
492 .executor()
493 .current_asmop
494 .as_ref()
495 .map(|op| Rc::from(op.context_name()))
496 .or_else(|| self.executor().current_proc.clone());
497 let frame_proc =
498 self.executor().callstack.current_frame().and_then(|frame| frame.procedure(""));
499 live_proc.or(frame_proc)
500 }
501
502 pub fn current_location(&self) -> Option<ResolvedLocation> {
503 self.executor()
504 .callstack
505 .current_frame()
506 .and_then(|frame| frame.recent().back())
507 .and_then(|detail| detail.resolve(&*self.source_manager))
508 .cloned()
509 }
510
511 pub fn current_display_location(&self) -> Option<ResolvedLocation> {
512 self.executor()
513 .callstack
514 .current_frame()
515 .and_then(|frame| frame.last_resolved(&*self.source_manager))
516 .cloned()
517 }
518
519 pub fn is_next_source_line(
520 start_proc: Option<&str>,
521 start_loc: Option<&ResolvedLocation>,
522 current_proc: Option<&str>,
523 current_loc: Option<&ResolvedLocation>,
524 ) -> bool {
525 let same_proc = match (start_proc, current_proc) {
526 (Some(start), Some(current)) => start == current,
527 (Some(_), None) => false,
528 _ => true,
529 };
530 if !same_proc {
531 return false;
532 }
533
534 match (start_loc, current_loc) {
535 (Some(start), Some(current)) => {
536 start.source_file.uri().as_str() == current.source_file.uri().as_str()
537 && start.line != current.line
538 }
539 (None, Some(_)) => true,
540 _ => false,
541 }
542 }
543
544 pub fn execution_failed(&self) -> Option<&miden_processor::ExecutionError> {
545 match &self.session {
546 SessionState::Local(local) => local.execution_failed.as_ref(),
547 #[cfg(feature = "dap")]
548 SessionState::Remote(_) => None,
549 }
550 }
551
552 pub fn set_execution_failed(&mut self, error: miden_processor::ExecutionError) {
553 match &mut self.session {
554 SessionState::Local(local) => local.execution_failed = Some(error),
555 #[cfg(feature = "dap")]
556 SessionState::Remote(_) => {
557 panic!("cannot record local execution failure while in remote mode")
558 }
559 }
560 }
561}
562
563macro_rules! write_with_format_type {
564 ($out:ident, $read_expr:ident, $value:expr) => {
565 match $read_expr.format {
566 crate::debug::FormatType::Decimal => write!(&mut $out, "{}", $value).unwrap(),
567 crate::debug::FormatType::Hex => write!(&mut $out, "{:0x}", $value).unwrap(),
568 crate::debug::FormatType::Binary => write!(&mut $out, "{:0b}", $value).unwrap(),
569 }
570 };
571}
572
573impl State {
574 pub fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
575 use core::fmt::Write;
576
577 use miden_assembly_syntax::ast::types::Type;
578
579 use crate::debug::FormatType;
580
581 #[cfg(feature = "dap")]
582 if self.debug_mode == DebugMode::Remote {
583 let SessionState::Remote(remote) = &mut self.session else {
584 return Err("no remote debug session".into());
585 };
586 return remote.read_memory(expr);
587 }
588
589 #[cfg(not(feature = "dap"))]
590 if self.debug_mode == DebugMode::Remote {
591 return Err("remote debug mode requires the `dap` feature".into());
592 }
593
594 let executor = self.executor();
595 let cycle = miden_processor::trace::RowIndex::from(executor.cycle);
596 let context = executor.current_context;
597 let memory = executor.processor.memory();
598 let read_element = |addr: u32| -> Option<Felt> {
599 memory.read_element(context, Felt::new(addr as u64)).ok()
600 };
601 let mut output = String::new();
602 if expr.count > 1 {
603 return Err("-count with value > 1 is not yet implemented".into());
604 } else if matches!(expr.ty, Type::Felt) {
605 if !expr.addr.is_element_aligned() {
606 return Err(
607 "read failed: type 'felt' must be aligned to an element boundary".into()
608 );
609 }
610 let felt = read_element(expr.addr.addr).unwrap_or(Felt::ZERO);
611 write_with_format_type!(output, expr, felt.as_canonical_u64());
612 } else if matches!(
613 expr.ty,
614 Type::Array(ref array_ty) if array_ty.element_type() == &Type::Felt && array_ty.len() == 4
615 ) {
616 if !expr.addr.is_word_aligned() {
617 return Err("read failed: type 'word' must be aligned to a word boundary".into());
618 }
619 let word = memory
620 .read_word(context, Felt::new(expr.addr.addr as u64), cycle)
621 .unwrap_or_default();
622 output.push('[');
623 for (i, elem) in word.iter().enumerate() {
624 if i > 0 {
625 output.push_str(", ");
626 }
627 write_with_format_type!(output, expr, elem.as_canonical_u64());
628 }
629 output.push(']');
630 } else {
631 if !expr.addr.is_element_aligned() {
632 return Err("invalid read: unaligned reads are not supported yet".into());
633 }
634
635 const U32_MASK: u64 = u32::MAX as u64;
636 let size = expr.ty.size_in_bytes();
637 let size_in_felts = expr.ty.size_in_felts();
638 let mut bytes = Vec::with_capacity(size);
639 let mut needed = size;
640 for i in 0..size_in_felts {
641 let addr = expr.addr.addr.checked_add(i as u32).ok_or_else(|| {
642 "invalid read: attempted to read beyond end of linear memory".to_string()
643 })?;
644 let elem = read_element(addr).unwrap_or_default();
645 let elem_bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_le_bytes();
646 let take = core::cmp::min(needed, 4);
647 bytes.extend(&elem_bytes[..take]);
648 needed -= take;
649 }
650
651 match &expr.ty {
652 Type::I1 => match expr.format {
653 FormatType::Decimal => write!(&mut output, "{}", bytes[0] != 0).unwrap(),
654 FormatType::Hex => {
655 write!(&mut output, "{:#0x}", (bytes[0] != 0) as u8).unwrap()
656 }
657 FormatType::Binary => {
658 write!(&mut output, "{:#0b}", (bytes[0] != 0) as u8).unwrap()
659 }
660 },
661 Type::I8 => write_with_format_type!(output, expr, bytes[0] as i8),
662 Type::U8 => write_with_format_type!(output, expr, bytes[0]),
663 Type::I16 => {
664 write_with_format_type!(output, expr, i16::from_le_bytes([bytes[0], bytes[1]]))
665 }
666 Type::U16 => {
667 write_with_format_type!(output, expr, u16::from_le_bytes([bytes[0], bytes[1]]))
668 }
669 Type::I32 => write_with_format_type!(
670 output,
671 expr,
672 i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
673 ),
674 Type::U32 => write_with_format_type!(
675 output,
676 expr,
677 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
678 ),
679 ty @ (Type::I64 | Type::U64) => {
680 let val = u64::from_le_bytes(bytes[..8].try_into().unwrap());
681 if matches!(ty, Type::I64) {
682 write_with_format_type!(output, expr, val as i64)
683 } else {
684 write_with_format_type!(output, expr, val)
685 }
686 }
687 ty => {
688 return Err(format!(
689 "support for reads of type '{ty}' are not implemented yet"
690 ));
691 }
692 }
693 }
694
695 Ok(output)
696 }
697
698 pub fn format_variables(&self, show_all: bool) -> String {
703 use core::fmt::Write;
704
705 let executor = self.executor();
706 let debug_vars = &executor.debug_vars;
707
708 if !debug_vars.has_variables() {
709 return "No debug variables tracked".to_string();
710 }
711
712 let mut output = String::new();
713 let stack = executor.current_stack.clone();
714 let context = executor.current_context;
715
716 let read_mem = |addr: u32| -> Option<Felt> {
718 executor.processor.memory().read_element(context, Felt::new(addr as u64)).ok()
719 };
720
721 let current_source = if show_all {
722 None
723 } else {
724 self.current_display_location()
725 };
726
727 for var_snapshot in debug_vars.current_variables() {
728 let name = var_snapshot.info.name();
729
730 if !show_all && is_compiler_generated_name(name) {
731 continue;
732 }
733
734 if let (Some(current), Some(var_loc)) =
735 (current_source.as_ref(), var_snapshot.info.location())
736 && (var_loc.uri.as_str() != current.source_file.uri().as_str()
737 || var_loc.line.to_u32() > current.line)
738 {
739 continue;
740 }
741
742 if !output.is_empty() {
743 output.push_str(", ");
744 }
745
746 let location = var_snapshot.info.value_location();
747
748 let value = resolve_variable_value(location, &stack, read_mem, |offset| {
749 let fmp_addr = miden_core::FMP_ADDR.as_canonical_u64() as u32;
751 let fmp = read_mem(fmp_addr)?;
752 let addr = (fmp.as_canonical_u64() as i64 + offset as i64) as u32;
753 read_mem(addr)
754 });
755
756 match value {
757 Some(felt) => {
758 write!(&mut output, "{name}={}", felt.as_canonical_u64()).unwrap();
759 }
760 None => {
761 write!(&mut output, "{name}={location}").unwrap();
762 }
763 }
764 }
765
766 if output.is_empty() {
767 "No source-level variables (use ':vars all' to show compiler locals)".to_string()
768 } else {
769 output
770 }
771 }
772}
773
774fn is_compiler_generated_name(name: &str) -> bool {
777 name.strip_prefix("local")
778 .is_some_and(|suffix| !suffix.is_empty() && suffix.chars().all(|c| c.is_ascii_digit()))
779}
780
781#[cfg(feature = "dap")]
785impl State {
786 pub fn new_for_dap(addr: &str) -> Result<Self, Report> {
791 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
792 let remote = RemoteState::connect(addr, &source_manager)?;
793
794 Ok(Self {
795 source_manager,
796 config: Box::default(),
797 input_mode: InputMode::Normal,
798 breakpoints: vec![],
799 breakpoints_hit: vec![],
800 next_breakpoint_id: 0,
801 stopped: true,
802 debug_mode: DebugMode::Remote,
803 session: SessionState::Remote(Box::new(remote)),
804 })
805 }
806
807 pub fn step_remote(&mut self) -> Result<crate::exec::DapStopReason, Report> {
808 let source_manager = self.source_manager.clone();
809 let SessionState::Remote(remote) = &mut self.session else {
810 return Err(Report::msg("no remote debug session"));
811 };
812 let result = remote.resume(&self.breakpoints).map_err(Report::msg)?;
813
814 self.breakpoints.retain(|bp| !bp.is_one_shot());
815
816 match &result {
817 crate::exec::DapStopReason::Stopped(snapshot) => {
818 remote.refresh_executor(&source_manager, snapshot);
819 self.stopped = true;
820 }
821 crate::exec::DapStopReason::Terminated => {
822 remote.executor.stopped = true;
823 self.stopped = true;
824 }
825 crate::exec::DapStopReason::Restarting => {
826 return Err(Report::msg("unexpected Phase 2 restart signal during step"));
827 }
828 }
829
830 Ok(result)
831 }
832}
833
834#[cfg(feature = "dap")]
837fn convert_ui_state(
838 snapshot: &crate::exec::DapUiState,
839 source_manager: &Arc<dyn SourceManager>,
840) -> RemoteSnapshot {
841 use crate::debug::{CallFrame, CallStack};
842
843 let call_frames: Vec<CallFrame> = snapshot
844 .callstack
845 .iter()
846 .map(|frame| {
847 let resolved = resolve_remote_frame(frame, source_manager);
848 CallFrame::from_remote(Some(frame.name.clone()), resolved)
849 })
850 .collect();
851
852 let current_stack = snapshot.current_stack.iter().copied().map(Felt::new).collect();
853
854 RemoteSnapshot {
855 callstack: CallStack::from_remote_frames(call_frames),
856 current_stack,
857 cycle: snapshot.cycle,
858 }
859}
860
861#[cfg(feature = "dap")]
863fn resolve_remote_frame(
864 frame: &crate::exec::DapUiFrame,
865 source_manager: &Arc<dyn SourceManager>,
866) -> Option<crate::debug::ResolvedLocation> {
867 use std::path::Path;
868
869 use miden_debug_types::{SourceManagerExt, SourceSpan};
870
871 let path_str = frame.source_path.as_ref()?;
872 let path = Path::new(path_str);
873 let source_file = source_manager.load_file(path).ok()?;
874 let line = frame.line.max(1) as u32;
875 let col = frame.column.max(1) as u32;
876
877 let content = source_file.content();
879 let line_index = miden_debug_types::LineIndex::from(line.saturating_sub(1));
880 let range = content.line_range(line_index)?;
881 let span = SourceSpan::new(source_file.id(), range);
882
883 Some(crate::debug::ResolvedLocation {
884 source_file,
885 line,
886 col,
887 span,
888 })
889}
890
891fn load_sysroot_libs(
900 toolchain_dir: &std::path::Path,
901) -> Result<Vec<Arc<miden_assembly_syntax::Library>>, Report> {
902 let mut libs = Vec::new();
903
904 let entries = match std::fs::read_dir(toolchain_dir) {
905 Ok(entries) => entries,
906 Err(_) => {
907 log::debug!(target: "state", "could not read sysroot directory: {}", toolchain_dir.display());
908 return Ok(libs);
909 }
910 };
911
912 for entry in entries {
913 let entry = entry.into_diagnostic()?;
914 let path = entry.path();
915 let Some(ext) = path.extension() else {
916 continue;
917 };
918
919 if ext == "masp" {
920 log::debug!(target: "state", "loading library from sysroot: {}", path.display());
921 let bytes = std::fs::read(&path).into_diagnostic()?;
922 let package = miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
923 Report::msg(format!("failed to load package '{}': {e}", path.display()))
924 })?;
925 libs.push(package.mast.clone());
926 } else if ext == "masl" {
927 log::debug!(target: "state", "loading library from sysroot: {}", path.display());
928 let bytes = std::fs::read(&path).into_diagnostic()?;
929 let lib = miden_assembly_syntax::Library::read_from_bytes(&bytes).map_err(|e| {
930 Report::msg(format!("failed to load library '{}': {e}", path.display()))
931 })?;
932 libs.push(Arc::new(lib));
933 }
934 }
935
936 if libs.is_empty() {
937 log::debug!(target: "state", "no libraries found in sysroot: {}", toolchain_dir.display());
938 }
939
940 Ok(libs)
941}
942
943fn load_package(config: &DebuggerConfig) -> Result<Arc<miden_mast_package::Package>, Report> {
944 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
945 let package = match input {
946 InputFile::Real(path) => {
947 let bytes = std::fs::read(path).into_diagnostic()?;
948 miden_mast_package::Package::read_from_bytes(&bytes)
949 .map(Arc::new)
950 .map_err(|e| {
951 Report::msg(format!(
952 "failed to load Miden package from {}: {e}",
953 path.display()
954 ))
955 })?
956 }
957 InputFile::Stdin(bytes) => miden_mast_package::Package::read_from_bytes(bytes)
958 .map(Arc::new)
959 .map_err(|e| Report::msg(format!("failed to load Miden package from stdin: {e}")))?,
960 };
961
962 if let Some(entry) = config.entrypoint.as_ref() {
963 let id = entry
965 .parse::<miden_assembly::ast::QualifiedProcedureName>()
966 .map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?;
967 if !package.is_library() {
968 return Err(Report::msg("cannot use --entrypoint with executable packages"));
969 }
970
971 package.make_executable(&id).map(Arc::new)
972 } else {
973 Ok(package)
974 }
975}