probe-rs 0.13.0

A collection of on chip debugging tools to communicate with microchips.
Documentation
use super::debug_info::DebugInfo;
use super::DebugError;
use crate::core::Core;
use crate::CoreStatus;

/// Stepping granularity for stepping through a program during debug.
#[derive(Debug)]
pub enum SteppingMode {
    /// Advance one machine instruction at a time.
    StepInstruction,
    /// Step Over the current statement, and halt at the start of the next statement.
    OverStatement,
    /// DWARF doesn't encode when a statement contains a call to a non-inlined function.
    /// - The best-effort approach is to step a single instruction, and then find the first valid breakpoint address.
    /// - The worst case is that the user might have to perform the step into action more than once before the debugger properly steps into a function at the specified address.
    IntoStatement,
    /// Step to the calling statement, immediately after the current function returns.
    OutOfStatement,
}

impl SteppingMode {
    /// Determine the program counter location where the SteppingMode is aimed, and step to it.
    /// Return the new CoreStatus and program_counter value.
    ///
    /// Implementation Notes for stepping at statement granularity:
    /// - If a hardware breakpoint is available, we will set it at the desired location, run to it, and release it.
    /// - If no hardware breakpoints are available, we will do repeated instruction steps until we reach the desired location.
    ///
    /// Usage Note:
    /// - Currently, no special provision is made for the effect of user defined breakpoints in interrupts that get triggered before this function completes.
    pub fn step(
        &self,
        core: &mut Core<'_>,
        debug_info: &DebugInfo,
    ) -> Result<(CoreStatus, u64), DebugError> {
        let mut core_status = core
            .status()
            .map_err(|error| DebugError::Other(anyhow::anyhow!(error)))
            .map_err(|error| DebugError::Other(anyhow::anyhow!(error)))?;
        let (mut program_counter, mut return_address) = match core_status {
            CoreStatus::Halted(_) => (
                core.read_core_reg(core.registers().program_counter())?,
                core.read_core_reg(core.registers().return_address())?,
            ),
            _ => {
                return Err(DebugError::Other(anyhow::anyhow!(
                    "Core must be halted before stepping."
                )))
            }
        };

        // First deal with the two special cases.
        match self {
            SteppingMode::StepInstruction => {
                program_counter = core.step()?.pc;
                core_status = core.status()?;
                return Ok((core_status, program_counter));
            }
            SteppingMode::IntoStatement => {
                // Step a single instruction, then proceed to the next step.
                program_counter = core.step()?.pc;
            }
            _ => {
                // We will deal with the rest in the next step.
            }
        }

        let mut target_address: Option<u64> = None;
        // Sometimes the target program_counter is at a location where the debug_info program row data does not contain valid statements for halt points.
        // When DebugError::NoValidHaltLocation happens, we will step to the next instruction and try again(until we can reasonably expect to have passed out of an epilogue), before giving up.
        for _ in 0..10 {
            match debug_info.get_halt_locations(program_counter, Some(return_address)) {
                Ok(program_row_data) => {
                    match self {
                        SteppingMode::OverStatement => {
                            target_address = program_row_data.next_statement_address
                        }
                        SteppingMode::OutOfStatement => {
                            if program_row_data.step_out_address.is_none() {
                                return Err(DebugError::NoValidHaltLocation {
                                    message: "Cannot step out of a non-returning function"
                                        .to_string(),
                                    pc_at_error: program_counter as u64,
                                });
                            } else {
                                target_address = program_row_data.step_out_address
                            }
                        }
                        SteppingMode::IntoStatement => {
                            // We have already stepped a single instruction, now use the next available breakpoint.
                            target_address = program_row_data.first_halt_address
                        }
                        _ => {
                            // We've already covered SteppingMode::StepInstruction
                        }
                    }
                    // If we get here, we don't have to retry anymore.
                    break;
                }
                Err(error) => match error {
                    DebugError::NoValidHaltLocation {
                        message,
                        pc_at_error,
                    } => {
                        // Step on target instruction, and then try again.
                        log::debug!(
                            "Incomplete stepping information @{:#010X}: {}",
                            pc_at_error,
                            message
                        );
                        program_counter = core.step()?.pc;
                        return_address = core.read_core_reg(core.registers().return_address())?;
                        continue;
                    }
                    other_error => return Err(other_error),
                },
            }
        }
        match target_address {
            Some(target_address) => {
                log::debug!(
                    "Preparing to step ({:20?}) from: {:#010X} to: {:#010X}",
                    self,
                    program_counter,
                    target_address
                );

                if target_address == program_counter as u64 {
                    // For simple functions that complete in a single statement.
                    program_counter = core.step()?.pc;
                } else if core.set_hw_breakpoint(target_address).is_ok() {
                    core.run()?;
                    core.clear_hw_breakpoint(target_address)?;
                    core_status = match core.status() {
                        Ok(core_status) => {
                            match core_status {
                                CoreStatus::Halted(_) => {
                                    program_counter =
                                        core.read_core_reg(core.registers().program_counter())?
                                }
                                other => {
                                    log::error!(
                                        "Core should be halted after stepping but is: {:?}",
                                        &other
                                    );
                                    program_counter = 0;
                                }
                            };
                            core_status
                        }
                        Err(error) => return Err(DebugError::Probe(error)),
                    };
                } else {
                    while target_address != core.step()?.pc {
                        // Single step the core until we get to the target_address;
                        // TODO: In theory, this could go on for a long time. Should we consider NOT allowing this kind of stepping if there are no breakpoints available?
                    }
                    core_status = core.status()?;
                    program_counter = target_address;
                }
            }
            None => {
                return Err(DebugError::NoValidHaltLocation {
                    message: "Unable to determine target address for this step request."
                        .to_string(),
                    pc_at_error: program_counter as u64,
                });
            }
        }
        Ok((core_status, program_counter))
    }
}