stackdump_trace/platform/
mod.rs1use 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
10pub enum UnwindResult<ADDR: funty::Integral> {
12 Finished,
14 Corrupted {
16 error_frame: Option<Frame<ADDR>>,
18 },
19 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 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
45pub 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 let elf = addr2line::object::File::parse(elf_data)?;
63
64 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 let mut frames = Vec::new();
103
104 let addr2line_context =
106 addr2line::Context::from_dwarf(gimli::Dwarf::load(|id| load_section(id, &elf, endian))?)?;
107
108 let mut platform_context = P::create_context(&elf)?;
110
111 let mut type_cache = Default::default();
112
113 loop {
115 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 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 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 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 let mut context_frames = addr2line_context
195 .find_frames(device_memory.register(gimli::Arm::PC)?.as_u64())
196 .skip_all_loads()?;
197
198 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 let abbreviations = dwarf.abbreviations(&unit.header)?;
208
209 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 frames.last_mut().unwrap().frame_type = FrameType::Function;
261 }
262
263 Ok(())
264}