1use std::{collections::VecDeque, 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},
15 exec::{DebugExecutor, ExecutionTrace, 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_trace: ExecutionTrace,
78 execution_failed: Option<miden_processor::ExecutionError>,
79}
80
81#[cfg(feature = "dap")]
82struct RemoteState {
83 client: crate::exec::DapClient,
84 executor: DebugExecutor,
85 addr: String,
86 synced_bp_files: std::collections::BTreeSet<String>,
89}
90
91enum SessionState {
92 Local(Box<LocalState>),
93 #[cfg(feature = "dap")]
94 Remote(Box<RemoteState>),
95}
96
97#[cfg(feature = "dap")]
98struct RemoteSnapshot {
99 callstack: crate::debug::CallStack,
100 current_stack: Vec<Felt>,
101 cycle: usize,
102}
103
104#[cfg(feature = "dap")]
105impl RemoteState {
106 fn connect(addr: &str, source_manager: &Arc<dyn SourceManager>) -> Result<Self, Report> {
107 use std::collections::BTreeSet;
108
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 processor = FastProcessor::new(StackInputs::default());
118 let host = DebuggerHost::new(source_manager.clone());
119 let executor = DebugExecutor {
120 processor,
121 host,
122 resume_ctx: None,
123 current_stack: snapshot.current_stack,
124 current_op: None,
125 current_asmop: None,
126 stack_outputs: Default::default(),
127 contexts: BTreeSet::new(),
128 root_context: ContextId::root(),
129 current_context: ContextId::root(),
130 callstack: snapshot.callstack,
131 recent: VecDeque::new(),
132 cycle: snapshot.cycle,
133 stopped: false,
134 };
135
136 Ok(Self {
137 client,
138 executor,
139 addr: addr.to_string(),
140 synced_bp_files: std::collections::BTreeSet::new(),
141 })
142 }
143
144 fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
145 self.client.read_memory(expr)
146 }
147
148 fn sync_breakpoints(&mut self, breakpoints: &[Breakpoint]) {
149 use std::collections::BTreeMap;
150
151 let mut by_file: BTreeMap<String, Vec<i64>> = BTreeMap::new();
153 let mut func_names: Vec<String> = Vec::new();
155
156 for bp in breakpoints {
157 match &bp.ty {
158 BreakpointType::Line { pattern, line } => {
159 by_file.entry(pattern.as_str().to_string()).or_default().push(*line as i64);
160 }
161 BreakpointType::Called(pattern) | BreakpointType::File(pattern) => {
162 func_names.push(pattern.as_str().to_string());
163 }
164 _ => {}
165 }
166 }
167
168 let stale_files: Vec<String> = self
171 .synced_bp_files
172 .iter()
173 .filter(|f| !by_file.contains_key(f.as_str()))
174 .cloned()
175 .collect();
176 for file in &stale_files {
177 let _ = self.client.set_breakpoints(file, &[]);
178 }
179
180 for (file, lines) in &by_file {
182 let _ = self.client.set_breakpoints(file, lines);
183 }
184
185 let _ = self.client.set_function_breakpoints(&func_names);
187
188 self.synced_bp_files = by_file.into_keys().collect();
190 }
191
192 fn resume(&mut self, breakpoints: &[Breakpoint]) -> Result<crate::exec::DapStopReason, String> {
193 self.sync_breakpoints(breakpoints);
195
196 let has_step = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Step));
197 let has_next = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Next));
198 let has_finish = breakpoints.iter().any(|bp| matches!(bp.ty, BreakpointType::Finish));
199
200 if has_step {
201 self.client.step_in()
202 } else if has_next {
203 self.client.step_over()
204 } else if has_finish {
205 self.client.step_out()
206 } else {
207 self.client.continue_()
208 }
209 }
210
211 fn refresh_executor(
212 &mut self,
213 source_manager: &Arc<dyn SourceManager>,
214 pushed: &crate::exec::DapUiState,
215 ) {
216 let snapshot = convert_ui_state(pushed, source_manager);
222 self.executor.current_stack = snapshot.current_stack;
223 self.executor.callstack = snapshot.callstack;
224 self.executor.cycle = snapshot.cycle;
225 }
226
227 fn reconnect(&mut self, source_manager: &Arc<dyn SourceManager>) -> Result<(), Report> {
228 let timeout = std::time::Duration::from_secs(30);
229 let mut new_client =
230 crate::exec::DapClient::connect_with_retry(&self.addr, timeout).map_err(Report::msg)?;
231 let ui_state = new_client.handshake().map_err(Report::msg)?;
232 let snapshot = convert_ui_state(&ui_state, source_manager);
233
234 self.client = new_client;
235 self.executor.current_stack = snapshot.current_stack;
236 self.executor.callstack = snapshot.callstack;
237 self.executor.cycle = snapshot.cycle;
238 Ok(())
239 }
240}
241
242impl State {
243 fn new_local(
244 source_manager: Arc<dyn SourceManager>,
245 config: Box<DebuggerConfig>,
246 debug_mode: DebugMode,
247 local: LocalState,
248 ) -> Self {
249 Self {
250 source_manager,
251 config,
252 input_mode: InputMode::Normal,
253 breakpoints: vec![],
254 breakpoints_hit: vec![],
255 next_breakpoint_id: 0,
256 stopped: true,
257 debug_mode,
258 session: SessionState::Local(Box::new(local)),
259 }
260 }
261
262 pub fn new(config: Box<DebuggerConfig>) -> Result<Self, Report> {
263 let source_manager = Arc::new(DefaultSourceManager::default());
264 let mut inputs = config.inputs.clone().unwrap_or_default();
265 if !config.args.is_empty() {
266 let args = config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
268 inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
269 }
270 let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
271 let package = load_package(&config)?;
272
273 let mut libs = Vec::with_capacity(config.link_libraries.len());
275 for link_library in config.link_libraries.iter() {
276 log::debug!(target: "state", "loading link library {}", link_library.name());
277 let lib = link_library.load(&config, source_manager.clone())?;
278 libs.push(lib.clone());
279 }
280
281 if let Some(toolchain_dir) = config.toolchain_dir() {
283 libs.extend(load_sysroot_libs(&toolchain_dir)?);
284 }
285
286 let mut executor = Executor::new(args.clone());
288 for lib in libs.iter() {
289 executor.register_library_dependency(lib.clone());
290 executor.with_library(lib.clone());
291 }
292
293 let dependencies = package.manifest.dependencies();
295 executor.with_dependencies(dependencies)?;
296 executor.with_advice_inputs(inputs.advice_inputs.clone());
297
298 let program = package.unwrap_program();
299 let executor = executor.into_debug(&program, source_manager.clone());
300
301 let mut trace_executor = Executor::new(args);
303 for lib in libs.iter() {
304 trace_executor.register_library_dependency(lib.clone());
305 trace_executor.with_library(lib.clone());
306 }
307 let dependencies = package.manifest.dependencies();
308 trace_executor.with_dependencies(dependencies)?;
309 trace_executor.with_advice_inputs(inputs.advice_inputs.clone());
310
311 let execution_trace = trace_executor.capture_trace(&program, source_manager.clone());
312
313 Ok(Self::new_local(
314 source_manager,
315 config,
316 DebugMode::Program,
317 LocalState {
318 executor,
319 execution_trace,
320 execution_failed: None,
321 },
322 ))
323 }
324
325 pub fn new_for_transaction(
331 program: Arc<Program>,
332 stack_inputs: StackInputs,
333 advice_inputs: AdviceInputs,
334 source_manager: Arc<dyn SourceManager>,
335 mast_forests: Vec<Arc<MastForest>>,
336 event_replay: Vec<Vec<AdviceMutation>>,
337 ) -> Result<Self, Report> {
338 let args = stack_inputs.iter().copied().rev().collect::<Vec<_>>();
339
340 let mut executor = Executor::new(args.clone());
342 executor.with_advice_inputs(advice_inputs.clone());
343 let debug_executor = executor.into_debug_with_replay(
344 &program,
345 source_manager.clone(),
346 mast_forests.clone(),
347 clone_event_replay_queue(&event_replay),
348 );
349
350 let mut trace_executor = Executor::new(args);
352 trace_executor.with_advice_inputs(advice_inputs);
353 let trace_debug = trace_executor.into_debug_with_replay(
354 &program,
355 source_manager.clone(),
356 mast_forests,
357 clone_event_replay_queue(&event_replay),
358 );
359
360 let execution_trace = run_to_trace(trace_debug);
362
363 Ok(Self::new_local(
364 source_manager,
365 Box::default(),
366 DebugMode::Transaction,
367 LocalState {
368 executor: debug_executor,
369 execution_trace,
370 execution_failed: None,
371 },
372 ))
373 }
374
375 pub fn reload(&mut self) -> Result<(), Report> {
376 if self.debug_mode == DebugMode::Transaction {
377 return Err(Report::msg("reload is not supported in transaction debug mode"));
378 }
379 if self.debug_mode == DebugMode::Remote {
380 #[cfg(feature = "dap")]
381 {
382 let source_manager = self.source_manager.clone();
383 let SessionState::Remote(remote) = &mut self.session else {
384 return Err(Report::msg("no remote debug session"));
385 };
386 let result = remote.client.restart_phase2().map_err(Report::msg)?;
387 match result {
388 crate::exec::DapStopReason::Restarting => {
389 remote.reconnect(&source_manager)?;
390 }
391 crate::exec::DapStopReason::Stopped(snapshot) => {
392 remote.refresh_executor(&source_manager, &snapshot);
394 }
395 crate::exec::DapStopReason::Terminated => {
396 return Err(Report::msg("server terminated without restart signal"));
397 }
398 }
399 self.breakpoints_hit.clear();
400 self.stopped = true;
401 return Ok(());
402 }
403 #[cfg(not(feature = "dap"))]
404 return Err(Report::msg("remote debug mode requires the `dap` feature"));
405 }
406
407 log::debug!("reloading program");
408 let package = load_package(&self.config)?;
409
410 let mut inputs = self.config.inputs.clone().unwrap_or_default();
411 if !self.config.args.is_empty() {
412 let args = self.config.args.iter().rev().map(|felt| felt.0).collect::<Vec<_>>();
414 inputs.inputs = StackInputs::new(&args).into_diagnostic()?;
415 }
416 let args = inputs.inputs.iter().copied().collect::<Vec<_>>();
417
418 let mut libs = Vec::with_capacity(self.config.link_libraries.len());
420 for link_library in self.config.link_libraries.iter() {
421 let lib = link_library.load(&self.config, self.source_manager.clone())?;
422 libs.push(lib.clone());
423 }
424
425 if let Some(toolchain_dir) = self.config.toolchain_dir() {
427 libs.extend(load_sysroot_libs(&toolchain_dir)?);
428 }
429
430 let mut executor = Executor::new(args.clone());
432 for lib in libs.iter() {
433 executor.register_library_dependency(lib.clone());
434 executor.with_library(lib.clone());
435 }
436
437 let dependencies = package.manifest.dependencies();
439 executor.with_dependencies(dependencies)?;
440 executor.with_advice_inputs(inputs.advice_inputs.clone());
441
442 let program = package.unwrap_program();
443 let executor = executor.into_debug(&program, self.source_manager.clone());
444
445 let mut trace_executor = Executor::new(args);
447 for lib in libs.iter() {
448 trace_executor.register_library_dependency(lib.clone());
449 trace_executor.with_library(lib.clone());
450 }
451 let dependencies = package.manifest.dependencies();
452 trace_executor.with_dependencies(dependencies)?;
453 trace_executor.with_advice_inputs(core::mem::take(&mut inputs.advice_inputs));
454 let execution_trace = trace_executor.capture_trace(&program, self.source_manager.clone());
455
456 self.session = SessionState::Local(Box::new(LocalState {
457 executor,
458 execution_trace,
459 execution_failed: None,
460 }));
461 self.breakpoints_hit.clear();
462 let breakpoints = core::mem::take(&mut self.breakpoints);
463 self.breakpoints.reserve(breakpoints.len());
464 self.next_breakpoint_id = 0;
465 self.stopped = true;
466 for bp in breakpoints {
467 self.create_breakpoint(bp.ty);
468 }
469 Ok(())
470 }
471
472 pub fn create_breakpoint(&mut self, ty: BreakpointType) {
473 let id = self.next_breakpoint_id();
474 let creation_cycle = self.executor().cycle;
475 log::trace!("created breakpoint with id {id} at cycle {creation_cycle}");
476 if matches!(ty, BreakpointType::Finish)
477 && let Some(frame) = self.executor_mut().callstack.current_frame_mut()
478 {
479 frame.break_on_exit();
480 }
481 self.breakpoints.push(Breakpoint {
482 id,
483 creation_cycle,
484 ty,
485 });
486 }
487
488 fn next_breakpoint_id(&mut self) -> u8 {
489 let mut candidate = self.next_breakpoint_id;
490 let initial = candidate;
491 let mut next = candidate.wrapping_add(1);
492 loop {
493 assert_ne!(initial, next, "unable to allocate a breakpoint id: too many breakpoints");
494 if self
495 .breakpoints
496 .iter()
497 .chain(self.breakpoints_hit.iter())
498 .any(|bp| bp.id == candidate)
499 {
500 candidate = next;
501 next = candidate.wrapping_add(1);
502 continue;
503 }
504 self.next_breakpoint_id = next;
505 break candidate;
506 }
507 }
508
509 pub fn executor(&self) -> &DebugExecutor {
510 match &self.session {
511 SessionState::Local(local) => &local.executor,
512 #[cfg(feature = "dap")]
513 SessionState::Remote(remote) => &remote.executor,
514 }
515 }
516
517 pub fn executor_mut(&mut self) -> &mut DebugExecutor {
518 match &mut self.session {
519 SessionState::Local(local) => &mut local.executor,
520 #[cfg(feature = "dap")]
521 SessionState::Remote(remote) => &mut remote.executor,
522 }
523 }
524
525 pub fn execution_failed(&self) -> Option<&miden_processor::ExecutionError> {
526 match &self.session {
527 SessionState::Local(local) => local.execution_failed.as_ref(),
528 #[cfg(feature = "dap")]
529 SessionState::Remote(_) => None,
530 }
531 }
532
533 pub fn set_execution_failed(&mut self, error: miden_processor::ExecutionError) {
534 match &mut self.session {
535 SessionState::Local(local) => local.execution_failed = Some(error),
536 #[cfg(feature = "dap")]
537 SessionState::Remote(_) => {
538 panic!("cannot record local execution failure while in remote mode")
539 }
540 }
541 }
542
543 fn local_session(&self) -> &LocalState {
544 match &self.session {
545 SessionState::Local(local) => local,
546 #[cfg(feature = "dap")]
547 SessionState::Remote(_) => panic!("local session requested while in remote mode"),
548 }
549 }
550}
551
552macro_rules! write_with_format_type {
553 ($out:ident, $read_expr:ident, $value:expr) => {
554 match $read_expr.format {
555 crate::debug::FormatType::Decimal => write!(&mut $out, "{}", $value).unwrap(),
556 crate::debug::FormatType::Hex => write!(&mut $out, "{:0x}", $value).unwrap(),
557 crate::debug::FormatType::Binary => write!(&mut $out, "{:0b}", $value).unwrap(),
558 }
559 };
560}
561
562impl State {
563 pub fn read_memory(&mut self, expr: &ReadMemoryExpr) -> Result<String, String> {
564 use core::fmt::Write;
565
566 use miden_assembly_syntax::ast::types::Type;
567
568 use crate::debug::FormatType;
569
570 #[cfg(feature = "dap")]
571 if self.debug_mode == DebugMode::Remote {
572 let SessionState::Remote(remote) = &mut self.session else {
573 return Err("no remote debug session".into());
574 };
575 return remote.read_memory(expr);
576 }
577
578 #[cfg(not(feature = "dap"))]
579 if self.debug_mode == DebugMode::Remote {
580 return Err("remote debug mode requires the `dap` feature".into());
581 }
582
583 let cycle = miden_processor::trace::RowIndex::from(self.executor().cycle);
584 let context = self.executor().current_context;
585 let local = self.local_session();
586 let mut output = String::new();
587 if expr.count > 1 {
588 return Err("-count with value > 1 is not yet implemented".into());
589 } else if matches!(expr.ty, Type::Felt) {
590 if !expr.addr.is_element_aligned() {
591 return Err(
592 "read failed: type 'felt' must be aligned to an element boundary".into()
593 );
594 }
595 let felt = local
596 .execution_trace
597 .read_memory_element_in_context(expr.addr.addr, context, cycle)
598 .unwrap_or(Felt::ZERO);
599 write_with_format_type!(output, expr, felt.as_canonical_u64());
600 } else if matches!(
601 expr.ty,
602 Type::Array(ref array_ty) if array_ty.element_type() == &Type::Felt && array_ty.len() == 4
603 ) {
604 if !expr.addr.is_word_aligned() {
605 return Err("read failed: type 'word' must be aligned to a word boundary".into());
606 }
607 let word = local.execution_trace.read_memory_word(expr.addr.addr).unwrap_or_default();
608 output.push('[');
609 for (i, elem) in word.iter().enumerate() {
610 if i > 0 {
611 output.push_str(", ");
612 }
613 write_with_format_type!(output, expr, elem.as_canonical_u64());
614 }
615 output.push(']');
616 } else {
617 let bytes = local
618 .execution_trace
619 .read_bytes_for_type(expr.addr, &expr.ty, context, cycle)
620 .map_err(|err| format!("invalid read: {err}"))?;
621 match &expr.ty {
622 Type::I1 => match expr.format {
623 FormatType::Decimal => write!(&mut output, "{}", bytes[0] != 0).unwrap(),
624 FormatType::Hex => {
625 write!(&mut output, "{:#0x}", (bytes[0] != 0) as u8).unwrap()
626 }
627 FormatType::Binary => {
628 write!(&mut output, "{:#0b}", (bytes[0] != 0) as u8).unwrap()
629 }
630 },
631 Type::I8 => write_with_format_type!(output, expr, bytes[0] as i8),
632 Type::U8 => write_with_format_type!(output, expr, bytes[0]),
633 Type::I16 => {
634 write_with_format_type!(output, expr, i16::from_le_bytes([bytes[0], bytes[1]]))
635 }
636 Type::U16 => {
637 write_with_format_type!(output, expr, u16::from_le_bytes([bytes[0], bytes[1]]))
638 }
639 Type::I32 => write_with_format_type!(
640 output,
641 expr,
642 i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
643 ),
644 Type::U32 => write_with_format_type!(
645 output,
646 expr,
647 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
648 ),
649 ty @ (Type::I64 | Type::U64) => {
650 let val = u64::from_le_bytes(bytes[..8].try_into().unwrap());
651 if matches!(ty, Type::I64) {
652 write_with_format_type!(output, expr, val as i64)
653 } else {
654 write_with_format_type!(output, expr, val)
655 }
656 }
657 ty => {
658 return Err(format!(
659 "support for reads of type '{ty}' are not implemented yet"
660 ));
661 }
662 }
663 }
664
665 Ok(output)
666 }
667}
668
669#[cfg(feature = "dap")]
673impl State {
674 pub fn new_for_dap(addr: &str) -> Result<Self, Report> {
679 let source_manager: Arc<dyn SourceManager> = Arc::new(DefaultSourceManager::default());
680 let remote = RemoteState::connect(addr, &source_manager)?;
681
682 Ok(Self {
683 source_manager,
684 config: Box::default(),
685 input_mode: InputMode::Normal,
686 breakpoints: vec![],
687 breakpoints_hit: vec![],
688 next_breakpoint_id: 0,
689 stopped: true,
690 debug_mode: DebugMode::Remote,
691 session: SessionState::Remote(Box::new(remote)),
692 })
693 }
694
695 pub fn step_remote(&mut self) -> Result<crate::exec::DapStopReason, Report> {
696 let source_manager = self.source_manager.clone();
697 let SessionState::Remote(remote) = &mut self.session else {
698 return Err(Report::msg("no remote debug session"));
699 };
700 let result = remote.resume(&self.breakpoints).map_err(Report::msg)?;
701
702 self.breakpoints.retain(|bp| !bp.is_one_shot());
703
704 match &result {
705 crate::exec::DapStopReason::Stopped(snapshot) => {
706 remote.refresh_executor(&source_manager, snapshot);
707 self.stopped = true;
708 }
709 crate::exec::DapStopReason::Terminated => {
710 remote.executor.stopped = true;
711 self.stopped = true;
712 }
713 crate::exec::DapStopReason::Restarting => {
714 return Err(Report::msg("unexpected Phase 2 restart signal during step"));
715 }
716 }
717
718 Ok(result)
719 }
720}
721
722#[cfg(feature = "dap")]
725fn convert_ui_state(
726 snapshot: &crate::exec::DapUiState,
727 source_manager: &Arc<dyn SourceManager>,
728) -> RemoteSnapshot {
729 use crate::debug::{CallFrame, CallStack};
730
731 let call_frames: Vec<CallFrame> = snapshot
732 .callstack
733 .iter()
734 .map(|frame| {
735 let resolved = resolve_remote_frame(frame, source_manager);
736 CallFrame::from_remote(Some(frame.name.clone()), resolved)
737 })
738 .collect();
739
740 let current_stack = snapshot.current_stack.iter().copied().map(Felt::new).collect();
741
742 RemoteSnapshot {
743 callstack: CallStack::from_remote_frames(call_frames),
744 current_stack,
745 cycle: snapshot.cycle,
746 }
747}
748
749#[cfg(feature = "dap")]
751fn resolve_remote_frame(
752 frame: &crate::exec::DapUiFrame,
753 source_manager: &Arc<dyn SourceManager>,
754) -> Option<crate::debug::ResolvedLocation> {
755 use std::path::Path;
756
757 use miden_debug_types::{SourceManagerExt, SourceSpan};
758
759 let path_str = frame.source_path.as_ref()?;
760 let path = Path::new(path_str);
761 let source_file = source_manager.load_file(path).ok()?;
762 let line = frame.line.max(1) as u32;
763 let col = frame.column.max(1) as u32;
764
765 let content = source_file.content();
767 let line_index = miden_debug_types::LineIndex::from(line.saturating_sub(1));
768 let range = content.line_range(line_index)?;
769 let span = SourceSpan::new(source_file.id(), range);
770
771 Some(crate::debug::ResolvedLocation {
772 source_file,
773 line,
774 col,
775 span,
776 })
777}
778
779fn load_sysroot_libs(
788 toolchain_dir: &std::path::Path,
789) -> Result<Vec<Arc<miden_assembly_syntax::Library>>, Report> {
790 let mut libs = Vec::new();
791
792 let entries = match std::fs::read_dir(toolchain_dir) {
793 Ok(entries) => entries,
794 Err(_) => {
795 log::debug!(target: "state", "could not read sysroot directory: {}", toolchain_dir.display());
796 return Ok(libs);
797 }
798 };
799
800 for entry in entries {
801 let entry = entry.into_diagnostic()?;
802 let path = entry.path();
803 let Some(ext) = path.extension() else {
804 continue;
805 };
806
807 if ext == "masp" {
808 log::debug!(target: "state", "loading library from sysroot: {}", path.display());
809 let bytes = std::fs::read(&path).into_diagnostic()?;
810 let package = miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
811 Report::msg(format!("failed to load package '{}': {e}", path.display()))
812 })?;
813 libs.push(package.mast.clone());
814 } else if ext == "masl" {
815 log::debug!(target: "state", "loading library from sysroot: {}", path.display());
816 let bytes = std::fs::read(&path).into_diagnostic()?;
817 let lib = miden_assembly_syntax::Library::read_from_bytes(&bytes).map_err(|e| {
818 Report::msg(format!("failed to load library '{}': {e}", path.display()))
819 })?;
820 libs.push(Arc::new(lib));
821 }
822 }
823
824 if libs.is_empty() {
825 log::debug!(target: "state", "no libraries found in sysroot: {}", toolchain_dir.display());
826 }
827
828 Ok(libs)
829}
830
831fn run_to_trace(mut executor: DebugExecutor) -> ExecutionTrace {
833 loop {
834 if executor.stopped {
835 break;
836 }
837 match executor.step() {
838 Ok(_) => continue,
839 Err(_) => break,
840 }
841 }
842 executor.into_execution_trace()
843}
844
845fn load_package(config: &DebuggerConfig) -> Result<Arc<miden_mast_package::Package>, Report> {
846 let input = config.input.as_ref().ok_or_else(|| Report::msg("no input file specified"))?;
847 let package = match input {
848 InputFile::Real(path) => {
849 let bytes = std::fs::read(path).into_diagnostic()?;
850 miden_mast_package::Package::read_from_bytes(&bytes)
851 .map(Arc::new)
852 .map_err(|e| {
853 Report::msg(format!(
854 "failed to load Miden package from {}: {e}",
855 path.display()
856 ))
857 })?
858 }
859 InputFile::Stdin(bytes) => miden_mast_package::Package::read_from_bytes(bytes)
860 .map(Arc::new)
861 .map_err(|e| Report::msg(format!("failed to load Miden package from stdin: {e}")))?,
862 };
863
864 if let Some(entry) = config.entrypoint.as_ref() {
865 let id = entry
867 .parse::<miden_assembly::ast::QualifiedProcedureName>()
868 .map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?;
869 if !package.is_library() {
870 return Err(Report::msg("cannot use --entrypoint with executable packages"));
871 }
872
873 package.make_executable(&id).map(Arc::new)
874 } else {
875 Ok(package)
876 }
877}