stackdump_trace/platform/cortex_m/
mod.rs

1//! Trace implementation for the cortex m target
2
3use 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    /// Assumes we are at an exception point in the stack unwinding.
78    /// Reads the registers that were stored on the stack and updates our current register representation with it.
79    ///
80    /// Returns Ok if everything went fine or an error with an address if the stack could not be read
81    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        // At stack place 7 is the PSR register, but we don't need that, so we skip it
114
115        // Adjust the sp with the size of what we've read
116        *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            // At stack place 24 is the fpscr register, but we don't need that, so we skip it
153
154            // Adjust the sp with the size of what we've read
155            *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        // We can update the stackpointer and other registers to the previous frame by applying the unwind info
237        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        // We're not at the last frame. What's the reason?
256
257        // Do we have a corrupted stack?
258        if !stack_pointer_changed
259            && device_memory.register(gimli::Arm::LR)? & !THUMB_BIT
260                == device_memory.register(gimli::Arm::PC)? & !THUMB_BIT
261        {
262            // The stack pointer didn't change and our LR points to our current PC
263            // If we unwound further we'd get the same frame again so we better stop
264
265            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        // Stack is not corrupted, but unwinding is not done
283        // Are we returning from an exception? (EXC_RETURN)
284        if device_memory.register(gimli::Arm::LR)? >= EXC_RETURN_MARKER {
285            // Yes, so the registers were pushed to the stack and we need to get them back
286
287            // Check the value to know if there are fpu registers to read
288            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            // No exception, so follow the LR back, but one instruction back.
316            // Sometimes a function will have a `bl` instruction at the end.
317            // An example is noreturn functions.
318            // LR always points to the next instruction, which means that it doesn't have to be in the same function as from where the branch originated.
319            // https://lkml.kernel.org/lkml/20240305175846.qnyiru7uaa7itqba@treble/
320            *device_memory.register_mut(gimli::Arm::PC)? =
321                device_memory.register(gimli::Arm::LR)? - 2;
322        }
323
324        // Have we reached the reset vector?
325        if self
326            .reset_vector_address_range
327            .contains(device_memory.register_ref(gimli::Arm::PC)?)
328        {
329            // Yes, let's make that a frame as well
330            // We'll also make an assumption that there's no frames before reset
331            return Ok(UnwindResult::Finished);
332        }
333
334        if self.is_last_frame(device_memory)? {
335            Ok(UnwindResult::Finished)
336        } else {
337            // Is our stack pointer in a weird place?
338            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}