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