stackdump_trace/platform/
mod.rs

1use crate::{error::TraceError, type_value_tree::TypeValueTree, Frame, FrameType, Location};
2use addr2line::object::{Object, ObjectSection, SectionKind};
3use funty::Fundamental;
4use gimli::{DebugInfoOffset, EndianRcSlice, RunTimeEndian};
5use stackdump_core::{device_memory::DeviceMemory, memory_region::VecMemoryRegion};
6use std::collections::HashMap;
7
8pub mod cortex_m;
9
10/// The result of an unwinding procedure
11pub enum UnwindResult<ADDR: funty::Integral> {
12    /// The unwinding is done up to the start of the program
13    Finished,
14    /// The unwinding can't continue because the stack is corrupted
15    Corrupted {
16        /// An optional frame that explains the corruption
17        error_frame: Option<Frame<ADDR>>,
18    },
19    /// The unwinding took another step and is not yet finished
20    Proceeded,
21}
22
23pub trait Platform<'data> {
24    type Word: funty::Integral;
25
26    fn create_context(
27        elf: &addr2line::object::File<'data, &'data [u8]>,
28    ) -> Result<Self, TraceError>
29    where
30        Self: Sized;
31
32    /// Unwind the stack of the platform to the previous exception if possible
33    ///
34    /// The device memory is mutated so that it is brought back to the state it was before the previous exception.
35    ///
36    /// Based on the unwinding, new information about the previous frame can be discovered.
37    /// In that case, that frame can be updated with that info.
38    fn unwind(
39        &mut self,
40        device_memory: &mut DeviceMemory<Self::Word>,
41        previous_frame: Option<&mut Frame<Self::Word>>,
42    ) -> Result<UnwindResult<Self::Word>, TraceError>;
43}
44
45/// Create the stacktrace for the given platform.
46///
47/// - device_memory: All the captured memory of the device.
48/// It is not necessary to include any data that is present in the elf file because that will automatically be added.
49/// It is required to have a decent chunk of the stack present. If not all of the stack is present,
50/// then eventually the tracing procedure will find a corrupt frame.
51/// The standard set of registers is also required to be present.
52/// - elf_data: The raw bytes of the elf file.
53/// This must be the exact same elf file as the one the device was running. Even a recompilation of the exact same code can change the debug info.
54pub fn trace<'data, P: Platform<'data>>(
55    mut device_memory: DeviceMemory<P::Word>,
56    elf_data: &'data [u8],
57) -> Result<Vec<Frame<P::Word>>, TraceError>
58where
59    <P::Word as funty::Numeric>::Bytes: bitvec::view::BitView<Store = u8>,
60{
61    // Parse the elf data
62    let elf = addr2line::object::File::parse(elf_data)?;
63
64    // Add all relevant memory sections present in the elf file to the device memory
65    for section in elf.sections().filter(|section| {
66        matches!(
67            section.kind(),
68            SectionKind::Text | SectionKind::ReadOnlyData | SectionKind::ReadOnlyString
69        )
70    }) {
71        device_memory.add_memory_region(VecMemoryRegion::new(
72            section.address(),
73            section.uncompressed_data()?.to_vec(),
74        ));
75    }
76
77    let endian = if elf.is_little_endian() {
78        gimli::RunTimeEndian::Little
79    } else {
80        gimli::RunTimeEndian::Big
81    };
82
83    fn load_section<'data: 'file, 'file, O, Endian>(
84        id: gimli::SectionId,
85        file: &'file O,
86        endian: Endian,
87    ) -> Result<gimli::EndianRcSlice<Endian>, TraceError>
88    where
89        O: addr2line::object::Object<'data, 'file>,
90        Endian: gimli::Endianity,
91    {
92        let data = file
93            .section_by_name(id.name())
94            .and_then(|section| section.uncompressed_data().ok())
95            .unwrap_or(std::borrow::Cow::Borrowed(&[]));
96        Ok(gimli::EndianRcSlice::new(std::rc::Rc::from(&*data), endian))
97    }
98
99    let dwarf = gimli::Dwarf::load(|id| load_section(id, &elf, endian))?;
100
101    // Create the vector we'll be adding our found frames to
102    let mut frames = Vec::new();
103
104    // To find the frames, we need the addr2line context which does a lot of the work for us
105    let addr2line_context =
106        addr2line::Context::from_dwarf(gimli::Dwarf::load(|id| load_section(id, &elf, endian))?)?;
107
108    // To unwind, we need the platform context
109    let mut platform_context = P::create_context(&elf)?;
110
111    let mut type_cache = Default::default();
112
113    // Now we need to keep looping until we unwound to the start of the program
114    loop {
115        // Get the frames of the current state
116        match add_current_frames::<P>(
117            &device_memory,
118            &addr2line_context,
119            &mut frames,
120            &mut type_cache,
121        ) {
122            Ok(_) => {}
123            Err(e @ TraceError::DwarfUnitNotFound { pc: _ }) => {
124                frames.push(Frame {
125                    function: "Unknown".into(),
126                    location: Location::default(),
127                    frame_type: FrameType::Corrupted(e.to_string()),
128                    variables: Vec::default(),
129                });
130                break;
131            }
132            Err(e) => return Err(e),
133        }
134
135        // Try to unwind
136        match platform_context.unwind(&mut device_memory, frames.last_mut())? {
137            UnwindResult::Finished => {
138                frames.push(Frame {
139                    function: "RESET".into(),
140                    location: crate::Location {
141                        file: None,
142                        line: None,
143                        column: None,
144                    },
145                    frame_type: FrameType::Function,
146                    variables: Vec::new(),
147                });
148                break;
149            }
150            UnwindResult::Corrupted {
151                error_frame: Some(error_frame),
152            } => {
153                frames.push(error_frame);
154                break;
155            }
156            UnwindResult::Corrupted { error_frame: None } => {
157                break;
158            }
159            UnwindResult::Proceeded => {
160                continue;
161            }
162        }
163    }
164
165    // We're done with the stack data, but we can also decode the static variables and make a frame out of that
166    let static_variables =
167        crate::variables::find_static_variables(&dwarf, &device_memory, &mut type_cache)?;
168    let static_frame = Frame {
169        function: "Static".into(),
170        location: Location {
171            file: None,
172            line: None,
173            column: None,
174        },
175        frame_type: FrameType::Static,
176        variables: static_variables,
177    };
178    frames.push(static_frame);
179
180    // We're done
181    Ok(frames)
182}
183
184fn add_current_frames<'a, P: Platform<'a>>(
185    device_memory: &DeviceMemory<P::Word>,
186    addr2line_context: &addr2line::Context<EndianRcSlice<RunTimeEndian>>,
187    frames: &mut Vec<Frame<P::Word>>,
188    type_cache: &mut HashMap<DebugInfoOffset, Result<TypeValueTree<P::Word>, TraceError>>,
189) -> Result<(), TraceError>
190where
191    <P::Word as funty::Numeric>::Bytes: bitvec::view::BitView<Store = u8>,
192{
193    // Find the frames of the current register context
194    let mut context_frames = addr2line_context
195        .find_frames(device_memory.register(gimli::Arm::PC)?.as_u64())
196        .skip_all_loads()?;
197
198    // Get the debug compilation unit of the current register context
199    let (dwarf, unit) = addr2line_context
200        .find_dwarf_and_unit(device_memory.register(gimli::Arm::PC)?.as_u64())
201        .skip_all_loads()
202        .ok_or(TraceError::DwarfUnitNotFound {
203            pc: device_memory.register(gimli::Arm::PC)?.as_u64(),
204        })?;
205
206    // Get the abbreviations of the unit
207    let abbreviations = dwarf.abbreviations(&unit.header)?;
208
209    // Loop through the found frames and add them
210    let mut added_frames = 0;
211    while let Some(context_frame) = context_frames.next()? {
212        let (file, line, column) = context_frame
213            .location
214            .map(|l| {
215                (
216                    l.file.map(|f| f.to_string()),
217                    l.line.map(|line| line as _),
218                    l.column.map(|column| column as _),
219                )
220            })
221            .unwrap_or_default();
222
223        let mut variables = Vec::new();
224
225        if let Some(die_offset) = context_frame.dw_die_offset {
226            let mut entries = match unit.header.entries_tree(&abbreviations, Some(die_offset)) {
227                Ok(entries) => entries,
228                Err(_) => {
229                    continue;
230                }
231            };
232
233            if let Ok(entry_root) = entries.root() {
234                variables = crate::variables::find_variables_in_function(
235                    dwarf,
236                    unit,
237                    &abbreviations,
238                    device_memory,
239                    entry_root,
240                    type_cache,
241                )?;
242            }
243        }
244
245        frames.push(Frame {
246            function: context_frame
247                .function
248                .and_then(|f| f.demangle().ok().map(|f| f.into_owned()))
249                .unwrap_or_else(|| "UNKNOWN".into()),
250            location: crate::Location { file, line, column },
251            frame_type: FrameType::InlineFunction,
252            variables,
253        });
254
255        added_frames += 1;
256    }
257
258    if added_frames > 0 {
259        // The last frame of `find_frames` is always a real function. All frames before are inline functions.
260        frames.last_mut().unwrap().frame_type = FrameType::Function;
261    }
262
263    Ok(())
264}