stackdump_trace/platform/cortex_m/
mod.rs1use super::{Platform, UnwindResult};
4use crate::error::TraceError;
5use crate::{Frame, FrameType};
6use addr2line::object::{Object, ObjectSection, ObjectSymbol};
7use core::ops::Range;
8use gimli::{
9 BaseAddresses, CfaRule, DebugFrame, EndianSlice, LittleEndian, RegisterRule, RunTimeEndian,
10 UnwindContext, UnwindSection, UnwindTableRow,
11};
12use stackdump_core::device_memory::DeviceMemory;
13
14const THUMB_BIT: u32 = 1;
15const EXC_RETURN_MARKER: u32 = 0xFF00_0000;
16const EXC_RETURN_FTYPE_MASK: u32 = 1 << 4;
17
18pub struct CortexMPlatform<'data> {
19 debug_frame: DebugFrame<EndianSlice<'data, LittleEndian>>,
20 reset_vector_address_range: Range<u32>,
21 text_address_range: Range<u32>,
22 bases: BaseAddresses,
23 unwind_context: UnwindContext<EndianSlice<'data, LittleEndian>>,
24}
25
26impl<'data> CortexMPlatform<'data> {
27 fn apply_unwind_info(
28 device_memory: &mut DeviceMemory<<Self as Platform<'data>>::Word>,
29 unwind_info: UnwindTableRow<EndianSlice<LittleEndian>>,
30 ) -> Result<bool, TraceError> {
31 let updated = match unwind_info.cfa() {
32 CfaRule::RegisterAndOffset { register, offset } => {
33 let new_cfa = (device_memory.register(*register)? as i64 + *offset) as u32;
34 let old_cfa = device_memory.register(gimli::Arm::SP)?;
35 let changed = new_cfa != old_cfa;
36 *device_memory.register_mut(gimli::Arm::SP)? = new_cfa;
37 changed
38 }
39 CfaRule::Expression(_) => todo!("CfaRule::Expression"),
40 };
41
42 for (reg, rule) in unwind_info.registers() {
43 match rule {
44 RegisterRule::Undefined => unreachable!(),
45 RegisterRule::Offset(offset) => {
46 let cfa = device_memory.register(gimli::Arm::SP)?;
47 let addr = (i64::from(cfa) + offset) as u64;
48 let new_value = device_memory
49 .read_u32(addr, RunTimeEndian::Little)?
50 .ok_or(TraceError::MissingMemory(addr))?;
51 *device_memory.register_mut(*reg)? = new_value;
52 }
53 _ => unimplemented!(),
54 }
55 }
56
57 Ok(updated)
58 }
59
60 fn is_last_frame(
61 &self,
62 device_memory: &DeviceMemory<<Self as Platform<'data>>::Word>,
63 ) -> Result<bool, TraceError> {
64 Ok(device_memory.register(gimli::Arm::LR)? == 0
65 || (!self
66 .text_address_range
67 .contains(device_memory.register_ref(gimli::Arm::PC)?)
68 && device_memory.register(gimli::Arm::LR)? < EXC_RETURN_MARKER))
69 }
70
71 fn update_registers_with_exception_stack(
76 device_memory: &mut DeviceMemory<<Self as Platform<'data>>::Word>,
77 fpu: bool,
78 ) -> Result<(), TraceError> {
79 let current_sp = device_memory.register(gimli::Arm::SP)?;
80
81 fn read_stack_var(
82 device_memory: &DeviceMemory<u32>,
83 starting_sp: u32,
84 index: usize,
85 ) -> Result<u32, TraceError> {
86 device_memory
87 .read_u32(starting_sp as u64 + index as u64 * 4, RunTimeEndian::Little)?
88 .ok_or(TraceError::MissingMemory(
89 starting_sp as u64 + index as u64 * 4,
90 ))
91 }
92
93 *device_memory.register_mut(gimli::Arm::R0)? =
94 read_stack_var(device_memory, current_sp, 0)?;
95 *device_memory.register_mut(gimli::Arm::R1)? =
96 read_stack_var(device_memory, current_sp, 1)?;
97 *device_memory.register_mut(gimli::Arm::R2)? =
98 read_stack_var(device_memory, current_sp, 2)?;
99 *device_memory.register_mut(gimli::Arm::R3)? =
100 read_stack_var(device_memory, current_sp, 3)?;
101 *device_memory.register_mut(gimli::Arm::R12)? =
102 read_stack_var(device_memory, current_sp, 4)?;
103 *device_memory.register_mut(gimli::Arm::LR)? =
104 read_stack_var(device_memory, current_sp, 5)?;
105 *device_memory.register_mut(gimli::Arm::PC)? =
106 read_stack_var(device_memory, current_sp, 6)?;
107 *device_memory.register_mut(gimli::Arm::SP)? = device_memory.register(gimli::Arm::SP)?
111 + 8 * std::mem::size_of::<<Self as Platform>::Word>() as <Self as Platform>::Word;
112
113 if fpu {
114 *device_memory.register_mut(gimli::Arm::D0)? =
115 read_stack_var(device_memory, current_sp, 8)?;
116 *device_memory.register_mut(gimli::Arm::D1)? =
117 read_stack_var(device_memory, current_sp, 9)?;
118 *device_memory.register_mut(gimli::Arm::D2)? =
119 read_stack_var(device_memory, current_sp, 10)?;
120 *device_memory.register_mut(gimli::Arm::D3)? =
121 read_stack_var(device_memory, current_sp, 11)?;
122 *device_memory.register_mut(gimli::Arm::D4)? =
123 read_stack_var(device_memory, current_sp, 12)?;
124 *device_memory.register_mut(gimli::Arm::D5)? =
125 read_stack_var(device_memory, current_sp, 13)?;
126 *device_memory.register_mut(gimli::Arm::D6)? =
127 read_stack_var(device_memory, current_sp, 14)?;
128 *device_memory.register_mut(gimli::Arm::D7)? =
129 read_stack_var(device_memory, current_sp, 15)?;
130 *device_memory.register_mut(gimli::Arm::D8)? =
131 read_stack_var(device_memory, current_sp, 16)?;
132 *device_memory.register_mut(gimli::Arm::D9)? =
133 read_stack_var(device_memory, current_sp, 17)?;
134 *device_memory.register_mut(gimli::Arm::D10)? =
135 read_stack_var(device_memory, current_sp, 18)?;
136 *device_memory.register_mut(gimli::Arm::D11)? =
137 read_stack_var(device_memory, current_sp, 19)?;
138 *device_memory.register_mut(gimli::Arm::D12)? =
139 read_stack_var(device_memory, current_sp, 20)?;
140 *device_memory.register_mut(gimli::Arm::D13)? =
141 read_stack_var(device_memory, current_sp, 21)?;
142 *device_memory.register_mut(gimli::Arm::D14)? =
143 read_stack_var(device_memory, current_sp, 22)?;
144 *device_memory.register_mut(gimli::Arm::D15)? =
145 read_stack_var(device_memory, current_sp, 23)?;
146 *device_memory.register_mut(gimli::Arm::SP)? =
150 device_memory.register(gimli::Arm::SP)? + 17 * std::mem::size_of::<u32>() as u32;
151 }
152
153 Ok(())
154 }
155}
156
157impl<'data> Platform<'data> for CortexMPlatform<'data> {
158 type Word = u32;
159
160 fn create_context(elf: &addr2line::object::File<'data, &'data [u8]>) -> Result<Self, TraceError>
161 where
162 Self: Sized,
163 {
164 let debug_info_sector_data = elf
165 .section_by_name(".debug_frame")
166 .ok_or_else(|| TraceError::MissingElfSection(".debug_frame".into()))?
167 .data()?;
168 let mut debug_frame =
169 addr2line::gimli::DebugFrame::new(debug_info_sector_data, LittleEndian);
170 debug_frame.set_address_size(std::mem::size_of::<Self::Word>() as u8);
171
172 let vector_table_section = elf
173 .section_by_name(".vector_table")
174 .ok_or_else(|| TraceError::MissingElfSection(".vector_table".into()))?;
175 let vector_table = vector_table_section
176 .data()?
177 .chunks_exact(4)
178 .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
179 .collect::<Vec<_>>();
180 let reset_vector_address = vector_table[1];
181 let reset_vector_address_range = elf
182 .symbols()
183 .find(|sym| sym.address() as u32 == reset_vector_address)
184 .map(|reset_vector_symbol| {
185 reset_vector_symbol.address() as u32
186 ..reset_vector_symbol.address() as u32 + reset_vector_symbol.size() as u32
187 })
188 .unwrap_or(reset_vector_address..reset_vector_address);
189 let text_section = elf
190 .section_by_name(".text")
191 .ok_or_else(|| TraceError::MissingElfSection(".text".into()))?;
192 let text_address_range = (text_section.address() as u32)
193 ..(text_section.address() as u32 + text_section.size() as u32);
194
195 let bases = BaseAddresses::default();
196 let unwind_context = UnwindContext::new();
197
198 Ok(Self {
199 debug_frame,
200 reset_vector_address_range,
201 text_address_range,
202 bases,
203 unwind_context,
204 })
205 }
206
207 fn unwind(
208 &mut self,
209 device_memory: &mut DeviceMemory<Self::Word>,
210 previous_frame: Option<&mut Frame<Self::Word>>,
211 ) -> Result<super::UnwindResult<Self::Word>, TraceError> {
212 let unwind_info = self.debug_frame.unwind_info_for_address(
213 &self.bases,
214 &mut self.unwind_context,
215 device_memory.register(gimli::Arm::PC)? as u64,
216 DebugFrame::cie_from_offset,
217 );
218
219 let unwind_info = match unwind_info {
220 Ok(unwind_info) => unwind_info.clone(),
221 Err(_e) => {
222 return Ok(UnwindResult::Corrupted {error_frame: Some(Frame { function: "Unknown".into(), location: crate::Location { file: None, line: None, column: None }, frame_type: FrameType::Corrupted(format!("debug information for address {:#x} is missing. Likely fixes:
223 1. compile the Rust code with `debug = 1` or higher. This is configured in the `profile.{{release,bench}}` sections of Cargo.toml (`profile.{{dev,test}}` default to `debug = 2`)
224 2. use a recent version of the `cortex-m` crates (e.g. cortex-m 0.6.3 or newer). Check versions in Cargo.lock
225 3. if linking to C code, compile the C code with the `-g` flag", device_memory.register(gimli::Arm::PC)?)),
226 variables: Vec::new(), }) });
227 }
228 };
229
230 let stack_pointer_changed = match Self::apply_unwind_info(device_memory, unwind_info) {
232 Ok(stack_pointer_changed) => stack_pointer_changed,
233 Err(e) => {
234 return Ok(UnwindResult::Corrupted {
235 error_frame: Some(Frame {
236 function: "Unknown".into(),
237 location: crate::Location {
238 file: None,
239 line: None,
240 column: None,
241 },
242 frame_type: FrameType::Corrupted(e.to_string()),
243 variables: Vec::new(),
244 }),
245 });
246 }
247 };
248
249 if !stack_pointer_changed
253 && device_memory.register(gimli::Arm::LR)? & !THUMB_BIT
254 == device_memory.register(gimli::Arm::PC)? & !THUMB_BIT
255 {
256 return Ok(UnwindResult::Corrupted {
260 error_frame: Some(Frame {
261 function: "Unknown".into(),
262 location: crate::Location {
263 file: None,
264 line: None,
265 column: None,
266 },
267 frame_type: FrameType::Corrupted(
268 "CFA did not change and LR and PC are equal".into(),
269 ),
270 variables: Vec::new(),
271 }),
272 });
273 }
274
275 if device_memory.register(gimli::Arm::LR)? >= EXC_RETURN_MARKER {
278 let fpu = device_memory.register(gimli::Arm::LR)? & EXC_RETURN_FTYPE_MASK > 0;
282
283 if let Some(previous_frame) = previous_frame {
284 previous_frame.frame_type = FrameType::Exception;
285 }
286
287 match Self::update_registers_with_exception_stack(device_memory, fpu) {
288 Ok(()) => {}
289 Err(TraceError::MissingMemory(address)) => {
290 return Ok(UnwindResult::Corrupted {
291 error_frame: Some(Frame {
292 function: "Unknown".into(),
293 location: crate::Location {
294 file: None,
295 line: None,
296 column: None,
297 },
298 frame_type: FrameType::Corrupted(format!(
299 "Could not read address {:#10X} from the stack",
300 address
301 )),
302 variables: Vec::new(),
303 }),
304 });
305 }
306 Err(e) => return Err(e),
307 }
308 } else {
309 *device_memory.register_mut(gimli::Arm::PC)? = device_memory.register(gimli::Arm::LR)?
311 }
312
313 if self
315 .reset_vector_address_range
316 .contains(device_memory.register_ref(gimli::Arm::PC)?)
317 {
318 return Ok(UnwindResult::Finished);
321 }
322
323 if self.is_last_frame(device_memory)? {
324 Ok(UnwindResult::Finished)
325 } else {
326 if device_memory
328 .read_u32(
329 device_memory.register(gimli::Arm::SP)? as u64,
330 RunTimeEndian::Little,
331 )?
332 .is_none()
333 {
334 Ok(UnwindResult::Corrupted {error_frame:Some(Frame {
335 function: "Unknown".into(),
336 location: crate::Location { file: None, line: None, column: None },
337 frame_type: FrameType::Corrupted(
338 format!("The stack pointer ({:#08X}) is corrupted or the dump does not contain the full stack", device_memory
339 .register(gimli::Arm::SP)?),
340 ),
341 variables: Vec::new(),
342 })})
343 } else {
344 Ok(UnwindResult::Proceeded)
345 }
346 }
347 }
348}