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}