Skip to main content

probe_rs_debug/
exception_handling.rs

1//! This module (and its children) contains the implementation of the [`ExceptionInterface`] for the various ARM core
2//! variants.
3
4use std::ops::ControlFlow;
5
6use probe_rs_target::CoreType;
7
8use crate::unwind_pc_without_debuginfo;
9use probe_rs::{
10    CoreRegister, InstructionSet, MemoryInterface, RegisterDataType, RegisterRole, RegisterValue,
11    UnwindRule,
12};
13
14use super::{DebugError, DebugInfo, DebugRegisters, StackFrame};
15
16pub(crate) mod armv6m;
17/// Where applicable, this defines shared logic for implementing exception handling across the various ARMv6-m and
18/// ARMv7-m [`crate::CoreType`]'s.
19pub(crate) mod armv6m_armv7m_shared;
20// NOTE: There is also a [`CoreType::Armv7em`] variant, but it is not currently used/implemented in probe-rs.
21pub(crate) mod armv7m;
22pub(crate) mod armv8m;
23pub(crate) mod riscv;
24pub(crate) mod xtensa;
25
26/// Creates a new exception interface for the [`CoreType`] at hand.
27pub fn exception_handler_for_core(core_type: CoreType) -> Box<dyn ExceptionInterface> {
28    use self::{armv6m, armv7m, armv8m};
29    match core_type {
30        CoreType::Armv6m => Box::new(armv6m::ArmV6MExceptionHandler),
31        CoreType::Armv7m | CoreType::Armv7em => Box::new(armv7m::ArmV7MExceptionHandler),
32        CoreType::Armv8m => Box::new(armv8m::ArmV8MExceptionHandler),
33        CoreType::Xtensa => Box::<xtensa::XtensaExceptionHandler>::default(),
34        CoreType::Riscv | CoreType::Riscv64 => Box::new(riscv::RiscvExceptionHandler),
35        CoreType::Armv7a | CoreType::Armv7r | CoreType::Armv8a => {
36            Box::new(UnimplementedExceptionHandler)
37        }
38    }
39}
40
41/// Placeholder for exception handling for cores where handling exceptions is not yet supported.
42pub struct UnimplementedExceptionHandler;
43
44impl ExceptionInterface for UnimplementedExceptionHandler {}
45
46/// A struct containing key information about an exception.
47/// The exception details are architecture specific, and the abstraction is handled in the
48/// architecture specific implementations of [`ExceptionInterface`].
49#[derive(PartialEq)]
50pub struct ExceptionInfo {
51    /// The exception number.
52    /// This is architecture specific and can be used to decode the architecture specific exception reason.
53    pub raw_exception: u32,
54    /// A human readable explanation for the exception.
55    pub description: String,
56    /// A populated [`StackFrame`] to represent the stack data in the exception handler.
57    pub handler_frame: StackFrame,
58}
59
60/// A generic interface to identify and decode exceptions during unwind processing.
61pub trait ExceptionInterface {
62    /// Using the `stackframe_registers` for a "called frame",
63    /// determine if the given frame was called from an exception handler,
64    /// and resolve the relevant details about the exception, including the reason for the exception,
65    /// and the stackframe registers for the frame that triggered the exception.
66    /// A return value of `Ok(None)` indicates that the given frame was called from within the current thread,
67    /// and the unwind should continue normally.
68    fn exception_details(
69        &self,
70        _memory: &mut dyn MemoryInterface,
71        _stackframe_registers: &DebugRegisters,
72        _debug_info: &DebugInfo,
73    ) -> Result<Option<ExceptionInfo>, DebugError> {
74        // For architectures where the exception handling has not been implemented in probe-rs,
75        // this will result in maintaining the current `unwind` behavior, i.e. unwinding will include up
76        // to the first frame that was called from an exception handler.
77        Ok(None)
78    }
79
80    /// Using the `stackframe_registers` for a "called frame", retrieve updated register values for the "calling frame".
81    fn calling_frame_registers(
82        &self,
83        _memory: &mut dyn MemoryInterface,
84        _stackframe_registers: &crate::DebugRegisters,
85        _raw_exception: u32,
86    ) -> Result<crate::DebugRegisters, DebugError> {
87        Err(DebugError::NotImplemented("calling frame registers"))
88    }
89
90    /// Retrieve the architecture specific exception number.
91    fn raw_exception(
92        &self,
93        _stackframe_registers: &crate::DebugRegisters,
94    ) -> Result<u32, DebugError> {
95        Err(DebugError::NotImplemented("raw exception"))
96    }
97
98    /// Convert the architecture specific exception number into a human readable description.
99    /// Where possible, the implementation may read additional registers from the core, to provide additional context.
100    fn exception_description(
101        &self,
102        _raw_exception: u32,
103        _memory: &mut dyn MemoryInterface,
104    ) -> Result<String, DebugError> {
105        Err(DebugError::NotImplemented("exception description"))
106    }
107
108    /// Unwind the stack without debug info.
109    ///
110    /// This method can be implemented to provide a stack trace using frame pointers, for example.
111    fn unwind_without_debuginfo(
112        &self,
113        unwind_registers: &mut DebugRegisters,
114        frame_pc: u64,
115        _stack_frames: &[StackFrame],
116        instruction_set: Option<InstructionSet>,
117        _memory: &mut dyn MemoryInterface,
118    ) -> ControlFlow<Option<DebugError>> {
119        unwind_pc_without_debuginfo(unwind_registers, frame_pc, instruction_set)
120    }
121
122    /// Compute the caller-frame value of `debug_register` when DWARF has no rule for it.
123    ///
124    /// `register_rule` is updated with a short description of the rule that was applied,
125    /// for inclusion in unwind trace logs.
126    ///
127    /// In many cases, the DWARF has `Undefined` rules for variables like frame pointer, program counter, etc.,
128    /// so the default implementation hard-codes ARM/RISC-V style heuristics here to make sure unwinding can continue.
129    /// If there is a valid rule, it will bypass these hardcoded ones.
130    fn unwind_undefined_register(
131        &self,
132        debug_register: &CoreRegister,
133        callee_frame_registers: &DebugRegisters,
134        unwind_cfa: Option<u64>,
135        _memory: &mut dyn MemoryInterface,
136        register_rule: &mut String,
137    ) -> Result<Option<RegisterValue>, DebugError> {
138        if debug_register.register_has_role(RegisterRole::FramePointer)
139            && debug_register.unwind_rule != UnwindRule::Preserve
140        {
141            // The frame pointer usually has an `Undefined` DWARF rule, in which
142            // case we fall back to FP=CFA. But if it is marked `Preserve` (it is
143            // callee-saved, e.g. R7 in the ARM ABI), the caller's value is simply
144            // whatever the callee left in it, so we must NOT overwrite it with the
145            // CFA. Fall through to the `Preserve` handling below instead.
146            *register_rule = "FP=CFA (dwarf Undefined)".to_string();
147            return Ok(cfa_as_register(debug_register, unwind_cfa));
148        }
149
150        if debug_register.register_has_role(RegisterRole::StackPointer) {
151            // NOTE: [ARMv7-M Architecture Reference Manual](https://developer.arm.com/documentation/ddi0403/ee), Section B.1.4.1: Treat bits [1:0] as `Should be Zero or Preserved`
152            // - Applying this logic to RISC-V has no adverse effects, since all incoming addresses are already 32-bit aligned.
153            *register_rule = "SP=CFA (dwarf Undefined)".to_string();
154            return Ok(cfa_as_register(debug_register, unwind_cfa));
155        }
156
157        if debug_register.register_has_role(RegisterRole::ReturnAddress) {
158            let current_pc = callee_frame_registers
159                .get_register_value_by_role(&RegisterRole::ProgramCounter)
160                .map_err(|_| {
161                    DebugError::Other(
162                        "UNWIND: Tried to unwind return address value where current program counter is unknown."
163                            .to_string(),
164                    )
165                })?;
166            let current_lr = callee_frame_registers
167                .get_register_by_role(&RegisterRole::ReturnAddress)
168                .ok()
169                .and_then(|lr| lr.value)
170                .ok_or_else(|| {
171                    DebugError::Other(
172                        "UNWIND: Tried to unwind return address value where current return address is unknown."
173                            .to_string(),
174                    )
175                })?;
176
177            let current_lr_value: u64 = current_lr.try_into()?;
178
179            return Ok(if current_pc == current_lr_value & !0b1 {
180                // If the previous PC is the same as the half-word aligned current LR,
181                // we have no way of inferring the previous frames LR until we have the PC.
182                *register_rule = "LR=Undefined (dwarf Undefined)".to_string();
183                None
184            } else {
185                // We can attempt to continue unwinding with the current LR value, e.g. inlined code.
186                *register_rule = "LR=Current LR (dwarf Undefined)".to_string();
187                Some(current_lr)
188            });
189        }
190
191        if debug_register.register_has_role(RegisterRole::ProgramCounter) {
192            unreachable!("The program counter is handled separately")
193        }
194
195        // If the register rule was not specified, then we either carry the previous value forward,
196        // or we clear the register value, depending on the architecture and register type.
197        Ok(match debug_register.unwind_rule {
198            UnwindRule::Preserve => {
199                *register_rule = "Preserve".to_string();
200                callee_frame_registers
201                    .get_register(debug_register.id)
202                    .and_then(|reg| reg.value)
203            }
204            UnwindRule::Clear => {
205                *register_rule = "Clear".to_string();
206                None
207            }
208            UnwindRule::SpecialRule => {
209                // When no DWARF rules are available, and it is not a special register like PC, SP, FP, etc.,
210                // we will clear the value. It is possible it might have its value set later if
211                // exception frame information is available.
212                *register_rule = "Clear (no unwind rules specified)".to_string();
213                None
214            }
215        })
216    }
217}
218
219fn cfa_as_register(
220    debug_register: &CoreRegister,
221    unwind_cfa: Option<u64>,
222) -> Option<RegisterValue> {
223    unwind_cfa.map(|cfa| {
224        if debug_register.data_type == RegisterDataType::UnsignedInteger(32) {
225            RegisterValue::U32(cfa as u32 & !0b11)
226        } else {
227            RegisterValue::U64(cfa & !0b11)
228        }
229    })
230}