1use super::{
2 DebugError, DebugRegisters, StackFrame, VariableCache,
3 exception_handling::ExceptionInterface,
4 function_die::{Die, FunctionDie},
5 get_object_reference,
6 unit_info::UnitInfo,
7 variable::*,
8};
9use crate::{SourceLocation, VerifiedBreakpoint, stack_frame::StackFrameInfo, unit_info::RangeExt};
10use gimli::{
11 BaseAddresses, DebugFrame, RunTimeEndian, UnwindContext, UnwindSection, UnwindTableRow,
12 read::RegisterRule,
13};
14use object::read::{Object, ObjectSection};
15use probe_rs::{
16 CoreRegister, Error, InstructionSet, MemoryInterface, RegisterDataType, RegisterRole,
17 RegisterValue, UnwindRule,
18};
19use std::{
20 borrow, cmp::Ordering, num::NonZeroU64, ops::ControlFlow, path::Path, rc::Rc, str::from_utf8,
21};
22use typed_path::{TypedPath, TypedPathBuf};
23
24pub(crate) type GimliReader = gimli::EndianReader<RunTimeEndian, std::rc::Rc<[u8]>>;
25pub(crate) type GimliReaderOffset =
26 <gimli::EndianReader<RunTimeEndian, Rc<[u8]>> as gimli::Reader>::Offset;
27
28pub(crate) type GimliAttribute = gimli::Attribute<GimliReader>;
29
30pub(crate) type DwarfReader = gimli::read::EndianRcSlice<RunTimeEndian>;
31
32pub struct DebugInfo {
34 pub(crate) dwarf: gimli::Dwarf<DwarfReader>,
35 pub(crate) frame_section: gimli::DebugFrame<DwarfReader>,
36 pub(crate) locations_section: gimli::LocationLists<DwarfReader>,
37 pub(crate) address_section: gimli::DebugAddr<DwarfReader>,
38 pub(crate) debug_line_section: gimli::DebugLine<DwarfReader>,
39
40 pub(crate) unit_infos: Vec<UnitInfo>,
41 pub(crate) endianness: gimli::RunTimeEndian,
42
43 pub(crate) addr2line: Option<addr2line::Loader>,
44}
45
46impl DebugInfo {
47 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<DebugInfo, DebugError> {
49 let data = std::fs::read(path.as_ref())?;
50
51 let mut this = DebugInfo::from_raw(&data)?;
52 this.addr2line = addr2line::Loader::new(path).ok();
53 Ok(this)
54 }
55
56 pub fn from_raw(data: &[u8]) -> Result<Self, DebugError> {
58 let object = object::File::parse(data)?;
59
60 let endianness = if object.is_little_endian() {
61 RunTimeEndian::Little
62 } else {
63 RunTimeEndian::Big
64 };
65
66 let load_section = |id: gimli::SectionId| -> Result<DwarfReader, gimli::Error> {
68 let data = object
69 .section_by_name(id.name())
70 .and_then(|section| section.uncompressed_data().ok())
71 .unwrap_or_else(|| borrow::Cow::Borrowed(&[][..]));
72
73 Ok(gimli::read::EndianRcSlice::new(
74 Rc::from(&*data),
75 endianness,
76 ))
77 };
78
79 let dwarf_cow = gimli::Dwarf::load(&load_section)?;
81
82 use gimli::Section;
83 let mut frame_section = gimli::DebugFrame::load(load_section)?;
84 let address_section = gimli::DebugAddr::load(load_section)?;
85 let debug_loc = gimli::DebugLoc::load(load_section)?;
86 let debug_loc_lists = gimli::DebugLocLists::load(load_section)?;
87 let locations_section = gimli::LocationLists::new(debug_loc, debug_loc_lists);
88 let debug_line_section = gimli::DebugLine::load(load_section)?;
89
90 let mut unit_infos = Vec::new();
91
92 let mut iter = dwarf_cow.units();
93
94 while let Ok(Some(header)) = iter.next() {
95 if let Ok(unit) = dwarf_cow.unit(header) {
96 frame_section.set_address_size(unit.encoding().address_size);
103
104 unit_infos.push(UnitInfo::new(unit));
105 };
106 }
107
108 Ok(DebugInfo {
109 dwarf: dwarf_cow,
110 frame_section,
111 locations_section,
112 address_section,
113 debug_line_section,
114 unit_infos,
115 endianness,
116 addr2line: None,
117 })
118 }
119
120 pub fn get_source_location(&self, address: u64) -> Option<SourceLocation> {
122 for unit_info in &self.unit_infos {
123 let unit = &unit_info.unit;
124
125 let mut ranges = match self.dwarf.unit_ranges(unit) {
126 Ok(ranges) => ranges,
127 Err(error) => {
128 tracing::warn!(
129 "No valid source code ranges found for unit {:?}: {:?}",
130 unit.dwo_name(),
131 error
132 );
133 continue;
134 }
135 };
136
137 while let Ok(Some(range)) = ranges.next() {
138 if !(range.begin <= address && address < range.end) {
139 continue;
140 }
141 let ilnp = unit.line_program.as_ref()?.clone();
143
144 let (program, sequences) = match ilnp.sequences() {
145 Ok(value) => value,
146 Err(error) => {
147 tracing::warn!(
148 "No valid source code ranges found for address {}: {:?}",
149 address,
150 error
151 );
152 continue;
153 }
154 };
155
156 let mut target_seq = None;
158
159 for seq in sequences {
160 if seq.start <= address && address < seq.end {
161 target_seq = Some(seq);
162 break;
163 }
164 }
165
166 let Some(target_seq) = target_seq.as_ref() else {
167 continue;
168 };
169
170 let mut previous_row: Option<gimli::LineRow> = None;
171
172 let mut rows = program.resume_from(target_seq);
173
174 while let Ok(Some((_, row))) = rows.next_row() {
175 match row.address().cmp(&address) {
176 Ordering::Greater => {
177 if let Some(previous_row) = previous_row
182 && let Some(path) =
183 self.find_file_and_directory(unit, previous_row.file_index())
184 {
185 tracing::debug!("{:#010x} - {:?}", address, previous_row.isa());
186 return Some(SourceLocation {
187 line: previous_row.line().map(NonZeroU64::get),
188 column: Some(previous_row.column().into()),
189 path,
190 address: Some(previous_row.address()),
191 });
192 }
193 }
194 Ordering::Less => {}
195 Ordering::Equal => {
196 if let Some(path) = self.find_file_and_directory(unit, row.file_index())
197 {
198 tracing::debug!("{:#010x} - {:?}", address, row.isa());
199
200 return Some(SourceLocation {
201 line: row.line().map(NonZeroU64::get),
202 column: Some(row.column().into()),
203 path,
204 address: Some(row.address()),
205 });
206 }
207 }
208 }
209 previous_row = Some(*row);
210 }
211 }
212 }
213 None
214 }
215
216 pub fn create_static_scope_cache(&self) -> VariableCache {
222 VariableCache::new_static_cache()
223 }
224
225 pub(crate) fn create_function_scope_cache(
227 &self,
228 die_cursor_state: &FunctionDie,
229 unit_info: &UnitInfo,
230 ) -> Result<VariableCache, DebugError> {
231 let function_variable_cache = VariableCache::new_dwarf_cache(
232 die_cursor_state.function_die.offset(),
233 VariableName::LocalScopeRoot,
234 unit_info,
235 )?;
236
237 Ok(function_variable_cache)
238 }
239
240 #[tracing::instrument(level = "trace", skip_all, fields(parent_variable = ?parent_variable.variable_key()))]
242 pub fn cache_deferred_variables(
243 &self,
244 cache: &mut VariableCache,
245 memory: &mut dyn MemoryInterface,
246 parent_variable: &mut Variable,
247 frame_info: StackFrameInfo<'_>,
248 ) -> Result<(), DebugError> {
249 if !parent_variable.is_valid() {
250 return Ok(());
252 }
253
254 if cache.has_children(parent_variable) {
256 return Ok(());
257 }
258
259 match parent_variable.variable_node_type {
260 VariableNodeType::TypeOffset(header_offset, unit_offset)
261 | VariableNodeType::DirectLookup(header_offset, unit_offset) => {
262 let Some(unit_info) = self
263 .unit_infos
264 .iter()
265 .find(|unit_info| unit_info.unit.header.offset() == header_offset.into())
266 else {
267 return Err(DebugError::Other(
268 "Failed to find unit info for offset lookup.".to_string(),
269 ));
270 };
271
272 let mut type_tree = unit_info.unit.entries_tree(Some(unit_offset))?;
274 let parent_node = type_tree.root()?;
275
276 unit_info.process_tree(
277 self,
278 parent_node,
279 parent_variable,
280 memory,
281 cache,
282 frame_info,
283 )?;
284 }
285 VariableNodeType::UnitsLookup => {
286 if self.unit_infos.is_empty() {
287 return Err(DebugError::Other("Missing unit infos".to_string()));
289 }
290
291 for unit_info in self.unit_infos.iter() {
293 let mut entries = unit_info.unit.entries();
294
295 let (_, unit_node) = entries.next_dfs()?.unwrap();
298 let unit_offset = unit_node.offset();
299
300 let mut type_tree = unit_info.unit.entries_tree(Some(unit_offset))?;
301 let parent_node = type_tree.root()?;
302
303 unit_info.process_tree(
304 self,
305 parent_node,
306 parent_variable,
307 memory,
308 cache,
309 frame_info,
310 )?;
311 }
312 }
313 _ => {
314 }
316 }
317 Ok(())
318 }
319
320 fn get_stackframe_from_symbols(
322 &self,
323 address: u64,
324 unwind_registers: &DebugRegisters,
325 ) -> Result<Vec<StackFrame>, DebugError> {
326 let Some(ref addr2line) = self.addr2line else {
327 return Ok(vec![]);
328 };
329 let Some(fn_name) = addr2line.find_symbol(address) else {
330 return Ok(vec![]);
331 };
332
333 let mut fn_name = fn_name.to_string();
334 for lang in [
335 gimli::DW_LANG_Rust,
336 gimli::DW_LANG_C_plus_plus,
337 gimli::DW_LANG_C_plus_plus_03,
338 gimli::DW_LANG_C_plus_plus_11,
339 gimli::DW_LANG_C_plus_plus_14,
340 ] {
341 if let Some(demangle) = addr2line::demangle(&fn_name, lang) {
342 fn_name = demangle;
343 break;
344 }
345 }
346
347 Ok(vec![StackFrame {
348 id: get_object_reference(),
349 function_name: format!(
350 "{fn_name} @ {address:#0width$x}>",
351 width = (unwind_registers.get_address_size_bytes() * 2 + 2)
352 ),
353 source_location: None,
354 registers: unwind_registers.clone(),
355 pc: RegisterValue::from(address),
356 frame_base: None,
357 is_inlined: false,
358 local_variables: None,
359 canonical_frame_address: None,
360 }])
361 }
362
363 pub(crate) fn get_stackframe_info(
367 &self,
368 memory: &mut impl MemoryInterface,
369 address: u64,
370 cfa: Option<u64>,
371 unwind_registers: &DebugRegisters,
372 ) -> Result<Vec<StackFrame>, DebugError> {
373 let unknown_function = || {
376 format!(
377 "<unknown function @ {address:#0width$x}>",
378 width = (unwind_registers.get_address_size_bytes() * 2 + 2)
379 )
380 };
381
382 let Ok((unit_info, functions)) = self.get_function_dies(address) else {
383 return self.get_stackframe_from_symbols(address, unwind_registers);
385 };
386 if functions.is_empty() {
387 return self.get_stackframe_from_symbols(address, unwind_registers);
389 }
390
391 let frame_base = functions[0].frame_base(
394 self,
395 memory,
396 StackFrameInfo {
397 registers: unwind_registers,
398 frame_base: None,
399 canonical_frame_address: cfa,
400 },
401 )?;
402
403 let mut frames = Vec::new();
404
405 for function_pair in functions.windows(2) {
408 let function_die = &function_pair[0];
409 let next_function = &function_pair[1];
410
411 let function_name = function_die
412 .function_name(self)
413 .unwrap_or_else(unknown_function);
414
415 tracing::debug!("UNWIND: Function name: {}", function_name);
416
417 assert!(next_function.is_inline());
418
419 let address_size = unit_info.unit.header.address_size() as u64;
421
422 let Some(next_function_low_pc) = next_function.low_pc() else {
423 tracing::warn!(
424 "UNWIND: Unknown starting address for inlined function {}.",
425 function_name
426 );
427 continue;
428 };
429
430 if !(next_function_low_pc > address_size && next_function_low_pc < u32::MAX as u64) {
431 tracing::warn!("UNWIND: Unknown call site for inlined function {function_name}.");
432 continue;
433 }
434
435 let inlined_call_site = RegisterValue::from(next_function_low_pc);
437
438 tracing::debug!(
439 "UNWIND: Callsite for inlined function {:?}",
440 next_function.function_name(self)
441 );
442
443 let inlined_caller_source_location = next_function.inline_call_location(self);
444
445 tracing::debug!("UNWIND: Call site: {inlined_caller_source_location:?}");
446
447 let local_variables = self
451 .create_function_scope_cache(function_die, unit_info)
452 .inspect_err(|error| {
453 tracing::error!("Could not resolve function variables. {error}. Continuing...");
454 })
455 .ok();
456
457 frames.push(StackFrame {
458 id: get_object_reference(),
459 function_name,
460 source_location: inlined_caller_source_location,
461 registers: unwind_registers.clone(),
462 pc: inlined_call_site,
463 frame_base,
464 is_inlined: function_die.is_inline(),
465 local_variables,
466 canonical_frame_address: cfa,
467 });
468 }
469
470 #[expect(clippy::unwrap_used)]
473 let last_function = functions.last().unwrap();
474
475 let function_name = last_function
476 .function_name(self)
477 .unwrap_or_else(unknown_function);
478
479 let function_location = self.get_source_location(address);
480
481 let local_variables =
484 self.create_function_scope_cache(last_function, unit_info)
485 .map_or_else(
486 |error| {
487 tracing::error!(
488 "Could not resolve function variables. {error}. Continuing...",
489 );
490 None
491 },
492 Some,
493 );
494
495 frames.push(StackFrame {
496 id: get_object_reference(),
497 function_name,
498 source_location: function_location,
499 registers: unwind_registers.clone(),
500 pc: match unwind_registers.get_address_size_bytes() {
501 4 => RegisterValue::U32(address as u32),
502 8 => RegisterValue::U64(address),
503 _ => RegisterValue::from(address),
504 },
505 frame_base,
506 is_inlined: last_function.is_inline(),
507 local_variables,
508 canonical_frame_address: cfa,
509 });
510
511 Ok(frames)
512 }
513
514 pub fn unwind(
542 &self,
543 core: &mut impl MemoryInterface,
544 initial_registers: DebugRegisters,
545 exception_handler: &dyn ExceptionInterface,
546 instruction_set: Option<InstructionSet>,
547 max_stack_frame_count: usize,
548 ) -> Result<Vec<StackFrame>, Error> {
549 self.unwind_impl(
550 initial_registers,
551 core,
552 exception_handler,
553 instruction_set,
554 max_stack_frame_count,
555 )
556 }
557
558 pub(crate) fn unwind_impl(
559 &self,
560 initial_registers: DebugRegisters,
561 memory: &mut impl MemoryInterface,
562 exception_handler: &dyn ExceptionInterface,
563 instruction_set: Option<InstructionSet>,
564 max_stack_frame_count: usize,
565 ) -> Result<Vec<StackFrame>, Error> {
566 let mut stack_frames = Vec::<StackFrame>::new();
567
568 let mut unwind_context = Box::new(gimli::UnwindContext::new());
569
570 let mut unwind_registers = initial_registers;
571
572 'unwind: while let Some(frame_pc_register_value) =
574 unwind_registers.get_program_counter().and_then(|pc| {
575 if pc.is_zero() | pc.is_max_value() {
576 None
577 } else {
578 pc.value
579 }
580 })
581 {
582 if stack_frames.len() >= max_stack_frame_count {
583 tracing::warn!("Stopped unwinding the stack after {max_stack_frame_count} frames");
584 break;
585 }
586 let frame_pc = frame_pc_register_value.try_into().map_err(|error| {
587 let message = format!("Cannot convert register value for program counter to a 64-bit integer value: {error:?}");
588 Error::Register(message)
589 })?;
590
591 tracing::trace!(
595 "UNWIND: Will generate `StackFrame` for function at address (PC) {frame_pc_register_value:#}"
596 );
597 let unwind_info = get_unwind_info(&mut unwind_context, &self.frame_section, frame_pc);
598
599 let cfa = unwind_info
601 .as_ref()
602 .ok()
603 .and_then(|unwind_info| determine_cfa(&unwind_registers, unwind_info).ok())
604 .flatten();
605
606 let cached_stack_frames =
608 match self.get_stackframe_info(memory, frame_pc, cfa, &unwind_registers) {
609 Ok(cached_stack_frames) => cached_stack_frames,
610 Err(e) => {
611 tracing::error!("UNWIND: Unable to complete `StackFrame` information: {e}");
612 break;
614 }
615 };
616
617 if !cached_stack_frames.is_empty() {
621 for frame in cached_stack_frames.into_iter().rev() {
622 if frame.is_inlined {
623 tracing::trace!(
624 "UNWIND: Found inlined function - name={}, pc={}",
625 frame.function_name,
626 frame.pc
627 );
628 }
629 stack_frames.push(frame);
630 }
631 } else {
632 stack_frames.push(StackFrame {
635 id: get_object_reference(),
636 function_name: format!(
637 "<unknown function @ {:#0width$x}>",
638 frame_pc,
639 width = (unwind_registers.get_address_size_bytes() * 2 + 2)
640 ),
641 source_location: self.get_source_location(frame_pc),
642 registers: unwind_registers.clone(),
643 pc: frame_pc_register_value,
644 frame_base: None,
645 is_inlined: false,
646 local_variables: None,
647 canonical_frame_address: None,
648 });
649 };
650
651 tracing::trace!("UNWIND - Preparing to unwind the registers for the previous frame.");
653
654 let callee_frame_registers = unwind_registers.clone();
657
658 let unwind_info = match unwind_info {
661 Ok(unwind_info) => {
662 tracing::trace!("UNWIND: Found unwind info for address {frame_pc:#010x}");
663 unwind_info
664 }
665 Err(err) => {
666 tracing::trace!(
667 "UNWIND: Unable to find unwind info for address {frame_pc:#010x}: {err}"
668 );
669 if let ControlFlow::Break(error) = exception_handler.unwind_without_debuginfo(
670 &mut unwind_registers,
671 frame_pc,
672 &stack_frames,
673 instruction_set,
674 memory,
675 ) {
676 if let Some(error) = error {
677 tracing::error!("{:?}", &error);
679 if let Some(first_frame) = stack_frames.first_mut() {
680 first_frame.function_name =
681 format!("{} : ERROR : {error}", first_frame.function_name);
682 };
683 }
684 break 'unwind;
685 }
686
687 if callee_frame_registers == unwind_registers {
688 tracing::debug!("No change, preventing infinite loop");
689 break;
690 }
691 continue 'unwind;
692 }
693 };
694
695 for debug_register in unwind_registers.0.iter_mut() {
697 if debug_register
699 .core_register
700 .register_has_role(RegisterRole::ProgramCounter)
701 {
702 continue;
703 }
704
705 match unwind_register(
706 debug_register,
707 &callee_frame_registers,
708 unwind_info,
709 cfa,
710 memory,
711 ) {
712 Err(error) => {
713 tracing::error!("{:?}", &error);
714 if let Some(first_frame) = stack_frames.last_mut() {
715 first_frame.function_name =
716 format!("{} : ERROR: {error}", first_frame.function_name);
717 };
718 break 'unwind;
719 }
720 Ok(val) => {
721 debug_register.value = val;
722 }
723 };
724 }
725
726 if unwind_registers
734 .get_return_address()
735 .is_some_and(|ra| ra.value.is_some())
736 {
737 match exception_handler.exception_details(memory, &unwind_registers, self) {
738 Ok(Some(exception_info)) => {
739 tracing::trace!(
740 "UNWIND: Stack unwind reached an exception handler {}",
741 exception_info.description
742 );
743 unwind_registers = exception_info.handler_frame.registers.clone();
744 stack_frames.push(exception_info.handler_frame);
745 continue 'unwind;
747 }
748 Ok(None) => {
749 tracing::trace!(
750 "UNWIND: No exception context found. Stack unwind will continue."
751 );
752 }
753 Err(e) => {
754 let message = format!(
755 "UNWIND: Error while checking for exception context. The stack trace will not include the calling frames. : {e:?}"
756 );
757 tracing::warn!("{message}");
758 stack_frames.push(StackFrame {
759 id: get_object_reference(),
760 function_name: message,
761 source_location: None,
762 registers: unwind_registers.clone(),
763 pc: frame_pc_register_value,
764 frame_base: None,
765 is_inlined: false,
766 local_variables: None,
767 canonical_frame_address: None,
768 });
769 break 'unwind;
770 }
771 };
772 }
773
774 let unwound_return_address = unwind_registers
775 .get_register_by_role(&RegisterRole::ReturnAddress)
776 .ok()
777 .and_then(|reg| reg.value);
778
779 let program_counter = unwind_registers.get_program_counter_mut().unwrap();
780
781 let Ok(current_pc) =
782 callee_frame_registers.get_register_value_by_role(&RegisterRole::ProgramCounter)
783 else {
784 let error = "UNWIND: Tried to unwind return address value where current program counter is unknown.";
785 tracing::error!("{error}");
786 if let Some(first_frame) = stack_frames.last_mut() {
787 first_frame.function_name =
788 format!("{} : ERROR: {error}", first_frame.function_name);
789 };
790 break 'unwind;
791 };
792 let mut register_rule_string = "PC=(unwound LR) (dwarf Undefined)".to_string();
795 program_counter.value = unwound_return_address.and_then(|return_address| {
796 unwind_program_counter_register(
797 return_address,
798 current_pc,
799 instruction_set,
800 &mut register_rule_string,
801 )
802 });
803
804 tracing::trace!(
805 "UNWIND - {:>10}: Caller: {}\tCallee: {}\tRule: {}",
806 program_counter.get_register_name(),
807 program_counter.value.unwrap_or_default(),
808 callee_frame_registers
809 .get_register(program_counter.core_register.id)
810 .and_then(|reg| reg.value)
811 .unwrap_or_default(),
812 register_rule_string,
813 );
814
815 if callee_frame_registers == unwind_registers {
816 tracing::debug!("No change, preventing infinite loop");
817 break;
818 }
819 }
820
821 Ok(stack_frames)
822 }
823
824 #[tracing::instrument(skip_all)]
828 pub fn get_breakpoint_location(
829 &self,
830 path: TypedPath,
831 line: u64,
832 column: Option<u64>,
833 ) -> Result<VerifiedBreakpoint, DebugError> {
834 tracing::debug!(
835 "Looking for breakpoint location for {}:{}:{}",
836 path.display(),
837 line,
838 column
839 .map(|c| c.to_string())
840 .unwrap_or_else(|| "-".to_owned())
841 );
842 VerifiedBreakpoint::for_source_location(self, path, line, column)
843 }
844
845 pub(crate) fn get_path(
848 &self,
849 unit: &gimli::read::Unit<DwarfReader>,
850 file_index: u64,
851 ) -> Option<TypedPathBuf> {
852 let line_program = unit.line_program.as_ref()?;
853 let header = line_program.header();
854 let Some(file_entry) = header.file(file_index) else {
855 tracing::warn!(
856 "Unable to extract file entry for file_index {:?}.",
857 file_index
858 );
859 return None;
860 };
861 let file_name_attr_string = self.dwarf.attr_string(unit, file_entry.path_name()).ok()?;
862 let name_path = from_utf8(&file_name_attr_string).ok()?;
863
864 let dir_name_attr_string = file_entry
865 .directory(header)
866 .and_then(|dir| self.dwarf.attr_string(unit, dir).ok());
867
868 let dir_path = dir_name_attr_string.and_then(|dir_name| {
869 from_utf8(&dir_name)
870 .ok()
871 .map(|p| TypedPath::derive(p).to_path_buf())
872 });
873
874 let mut combined_path = match dir_path {
875 Some(dir_path) => dir_path.join(name_path),
876 None => TypedPath::derive(name_path).to_path_buf(),
877 };
878
879 if combined_path.is_relative() {
880 let comp_dir = unit
881 .comp_dir
882 .as_ref()
883 .map(|dir| from_utf8(dir))
884 .transpose()
885 .ok()?
886 .map(TypedPath::derive);
887 if let Some(comp_dir) = comp_dir {
888 combined_path = comp_dir.join(&combined_path);
889 }
890 }
891
892 Some(combined_path)
893 }
894
895 pub(crate) fn find_file_and_directory(
896 &self,
897 unit: &gimli::read::Unit<DwarfReader>,
898 file_index: u64,
899 ) -> Option<TypedPathBuf> {
900 let combined_path = self.get_path(unit, file_index)?;
901
902 Some(combined_path)
903 }
904
905 pub(crate) fn compile_unit_info(
907 &self,
908 address: u64,
909 ) -> Result<&super::unit_info::UnitInfo, DebugError> {
910 for header in &self.unit_infos {
911 match self.dwarf.unit_ranges(&header.unit) {
912 Ok(mut ranges) => {
913 while let Ok(Some(range)) = ranges.next() {
914 if range.contains(address) {
915 return Ok(header);
916 }
917 }
918 }
919 Err(_) => continue,
920 };
921 }
922 Err(DebugError::WarnAndContinue {
923 message: format!(
924 "No debug information available for the instruction at {address:#010x}. Please consider using instruction level stepping."
925 ),
926 })
927 }
928
929 pub(crate) fn get_function_dies(
934 &self,
935 address: u64,
936 ) -> Result<(&UnitInfo, Vec<FunctionDie<'_>>), DebugError> {
937 for unit_info in &self.unit_infos {
938 let function_dies = unit_info.get_function_dies(self, address)?;
939
940 if !function_dies.is_empty() {
941 return Ok((unit_info, function_dies));
942 }
943 }
944 Err(DebugError::Other(format!(
945 "No function DIE's at address {address:#x}."
946 )))
947 }
948
949 pub(crate) fn resolve_die_reference<'debug_info, 'unit_info>(
951 &'debug_info self,
952 attribute: gimli::DwAt,
953 die: &Die,
954 unit_info: &'unit_info UnitInfo,
955 ) -> Option<Die<'debug_info, 'debug_info>>
956 where
957 'unit_info: 'debug_info,
958 {
959 let attr = die.attr(attribute).ok().flatten()?;
960
961 self.resolve_die_reference_with_unit(&attr, unit_info)
962 .ok()
963 .map(|(_, die)| die)
964 }
965
966 pub fn endianness(&self) -> RunTimeEndian {
968 self.endianness
969 }
970
971 pub(crate) fn resolve_die_reference_with_unit<'debug_info, 'unit_info>(
973 &'debug_info self,
974 attr: &gimli::Attribute<GimliReader>,
975 unit_info: &'unit_info UnitInfo,
976 ) -> Result<(&'debug_info UnitInfo, Die<'debug_info, 'debug_info>), DebugError>
977 where
978 'unit_info: 'debug_info,
979 {
980 match attr.value() {
981 gimli::AttributeValue::UnitRef(unit_ref) => {
982 Ok((unit_info, unit_info.unit.entry(unit_ref)?))
983 }
984 gimli::AttributeValue::DebugInfoRef(offset) => {
985 for unit_info in &self.unit_infos {
986 let Some(unit_offset) = offset.to_unit_offset(&unit_info.unit.header) else {
987 continue;
988 };
989
990 let entry = unit_info.unit.entry(unit_offset).map_err(|error| {
991 DebugError::Other(format!(
992 "Error reading DIE at debug info offset {:#x} : {}",
993 offset.0, error
994 ))
995 })?;
996 return Ok((unit_info, entry));
997 }
998
999 Err(DebugError::Other(format!(
1000 "Unable to find unit info for debug info offset {:#x}",
1001 offset.0
1002 )))
1003 }
1004 other_attribute_value => Err(DebugError::Other(format!(
1005 "Unimplemented attribute value {other_attribute_value:?}"
1006 ))),
1007 }
1008 }
1009}
1010
1011pub(crate) fn canonical_path_eq(primary_path: TypedPath, secondary_path: TypedPath) -> bool {
1013 primary_path.normalize() == secondary_path.normalize()
1014}
1015
1016pub fn get_unwind_info<'a>(
1018 unwind_context: &'a mut UnwindContext<GimliReaderOffset>,
1019 frame_section: &DebugFrame<DwarfReader>,
1020 frame_program_counter: u64,
1021) -> Result<&'a gimli::UnwindTableRow<GimliReaderOffset>, DebugError> {
1022 let transform_error = |error| {
1023 DebugError::Other(format!(
1024 "UNWIND: Error reading FrameDescriptorEntry at PC={frame_program_counter:x} : {error}"
1025 ))
1026 };
1027
1028 let unwind_bases = BaseAddresses::default();
1029
1030 let frame_descriptor_entry = frame_section
1031 .fde_for_address(
1032 &unwind_bases,
1033 frame_program_counter,
1034 DebugFrame::cie_from_offset,
1035 )
1036 .map_err(transform_error)?;
1037
1038 frame_descriptor_entry
1039 .unwind_info_for_address(
1040 frame_section,
1041 &unwind_bases,
1042 unwind_context,
1043 frame_program_counter,
1044 )
1045 .map_err(transform_error)
1046}
1047
1048pub fn determine_cfa<R: gimli::ReaderOffset>(
1050 unwind_registers: &DebugRegisters,
1051 unwind_info: &UnwindTableRow<R>,
1052) -> Result<Option<u64>, Error> {
1053 let gimli::CfaRule::RegisterAndOffset { register, offset } = unwind_info.cfa() else {
1054 unimplemented!()
1055 };
1056
1057 let reg_val = unwind_registers
1058 .get_register_by_dwarf_id(register.0)
1059 .and_then(|register| register.value);
1060
1061 let cfa = match reg_val {
1062 None => {
1063 tracing::error!(
1064 "UNWIND: `StackFrameIterator` unable to determine the unwind CFA: Missing value of register {}",
1065 register.0
1066 );
1067 None
1068 }
1069
1070 Some(reg_val) if reg_val.is_zero() => {
1071 tracing::trace!(
1074 "UNWIND: Stack unwind complete - The FP register value unwound to a value of zero."
1075 );
1076 None
1077 }
1078
1079 Some(reg_val) => {
1080 let unwind_cfa = add_to_address(
1081 reg_val.try_into()?,
1082 *offset,
1083 unwind_registers.get_address_size_bytes(),
1084 );
1085 tracing::trace!(
1086 "UNWIND - CFA : {:#010x}\tRule: {:?}",
1087 unwind_cfa,
1088 unwind_info.cfa()
1089 );
1090 Some(unwind_cfa)
1091 }
1092 };
1093
1094 Ok(cfa)
1095}
1096
1097pub fn unwind_pc_without_debuginfo(
1099 unwind_registers: &mut DebugRegisters,
1100 _frame_pc: u64,
1101 instruction_set: Option<InstructionSet>,
1102) -> ControlFlow<Option<DebugError>> {
1103 let callee_frame_registers = unwind_registers.clone();
1110 let unwound_return_address: Option<RegisterValue> = unwind_registers
1111 .get_return_address()
1112 .and_then(|lr| lr.value);
1113
1114 if let Some(calling_pc) = unwind_registers.get_program_counter_mut() {
1116 let mut register_rule_string = "PC=(unwound LR) (dwarf Undefined)".to_string();
1117
1118 let Ok(current_pc) =
1119 callee_frame_registers.get_register_value_by_role(&RegisterRole::ProgramCounter)
1120 else {
1121 return ControlFlow::Break(
1122 Some(Error::Other(
1123 "UNWIND: Tried to unwind return address value where current program counter is unknown.".to_string()
1124 ).into())
1125 );
1126 };
1127 calling_pc.value = unwound_return_address.and_then(|return_address| {
1130 unwind_program_counter_register(
1131 return_address,
1132 current_pc,
1133 instruction_set,
1134 &mut register_rule_string,
1135 )
1136 });
1137 }
1138
1139 ControlFlow::Continue(())
1140}
1141
1142pub fn unwind_register(
1144 debug_register: &super::DebugRegister,
1145 callee_frame_registers: &DebugRegisters,
1147 unwind_info: &gimli::UnwindTableRow<GimliReaderOffset>,
1148 unwind_cfa: Option<u64>,
1149 memory: &mut dyn MemoryInterface,
1150) -> Result<Option<RegisterValue>, Error> {
1151 let register_rule = debug_register
1153 .dwarf_id
1154 .map(|register_position| unwind_info.register(gimli::Register(register_position)))
1155 .unwrap_or(RegisterRule::Undefined);
1156
1157 unwind_register_using_rule(
1158 debug_register.core_register,
1159 callee_frame_registers,
1160 unwind_cfa,
1161 memory,
1162 register_rule,
1163 )
1164}
1165
1166fn unwind_register_using_rule(
1167 debug_register: &CoreRegister,
1168 callee_frame_registers: &DebugRegisters,
1169 unwind_cfa: Option<u64>,
1170 memory: &mut dyn MemoryInterface,
1171 register_rule: gimli::RegisterRule<usize>,
1172) -> Result<Option<RegisterValue>, Error> {
1173 use gimli::read::RegisterRule;
1174
1175 let mut register_rule_string = format!("{register_rule:?}");
1176
1177 let new_value = match register_rule {
1178 RegisterRule::Undefined => {
1179 match &debug_register {
1182 fp if fp.register_has_role(RegisterRole::FramePointer) => {
1183 register_rule_string = "FP=CFA (dwarf Undefined)".to_string();
1184 unwind_cfa.map(|unwind_cfa| {
1185 if fp.data_type == RegisterDataType::UnsignedInteger(32) {
1186 RegisterValue::U32(unwind_cfa as u32 & !0b11)
1187 } else {
1188 RegisterValue::U64(unwind_cfa & !0b11)
1189 }
1190 })
1191 }
1192 sp if sp.register_has_role(RegisterRole::StackPointer) => {
1193 register_rule_string = "SP=CFA (dwarf Undefined)".to_string();
1196 unwind_cfa.map(|unwind_cfa| {
1197 if sp.data_type == RegisterDataType::UnsignedInteger(32) {
1198 RegisterValue::U32(unwind_cfa as u32 & !0b11)
1199 } else {
1200 RegisterValue::U64(unwind_cfa & !0b11)
1201 }
1202 })
1203 }
1204 lr if lr.register_has_role(RegisterRole::ReturnAddress) => {
1205 let Ok(current_pc) = callee_frame_registers
1206 .get_register_value_by_role(&RegisterRole::ProgramCounter)
1207 else {
1208 return Err(
1209 Error::Other(
1210 "UNWIND: Tried to unwind return address value where current program counter is unknown.".to_string()
1211 )
1212 );
1213 };
1214 let Some(current_lr) = callee_frame_registers
1215 .get_register_by_role(&RegisterRole::ReturnAddress)
1216 .ok()
1217 .and_then(|lr| lr.value)
1218 else {
1219 return Err(
1220 Error::Other(
1221 "UNWIND: Tried to unwind return address value where current return address is unknown.".to_string()
1222 )
1223 );
1224 };
1225
1226 let current_lr_value: u64 = current_lr.try_into()?;
1227
1228 if current_pc == current_lr_value & !0b1 {
1229 register_rule_string = "LR=Undefined (dwarf Undefined)".to_string();
1232 None
1233 } else {
1234 register_rule_string = "LR=Current LR (dwarf Undefined)".to_string();
1236 Some(current_lr)
1237 }
1238 }
1239 pc if pc.register_has_role(RegisterRole::ProgramCounter) => {
1240 unreachable!("The program counter is handled separately")
1241 }
1242 other_register => {
1243 match other_register.unwind_rule {
1246 UnwindRule::Preserve => {
1247 register_rule_string = "Preserve".to_string();
1248 callee_frame_registers
1249 .get_register(other_register.id)
1250 .and_then(|reg| reg.value)
1251 }
1252 UnwindRule::Clear => {
1253 register_rule_string = "Clear".to_string();
1254 None
1255 }
1256 UnwindRule::SpecialRule => {
1257 register_rule_string = "Clear (no unwind rules specified)".to_string();
1261 None
1262 }
1263 }
1264 }
1265 }
1266 }
1267
1268 RegisterRule::SameValue => callee_frame_registers
1269 .get_register(debug_register.id)
1270 .and_then(|reg| reg.value),
1271
1272 RegisterRule::Offset(address_offset) => {
1273 let Some(unwind_cfa) = unwind_cfa else {
1275 return Err(Error::Other(
1276 "UNWIND: Tried to unwind `RegisterRule` at CFA = None.".to_string(),
1277 ));
1278 };
1279 let address_size = callee_frame_registers.get_address_size_bytes();
1280 let previous_frame_register_address =
1281 add_to_address(unwind_cfa, address_offset, address_size);
1282
1283 register_rule_string = format!("CFA {register_rule:?}");
1284
1285 let result = match address_size {
1287 4 => {
1288 let mut buff = [0u8; 4];
1289 memory
1290 .read(previous_frame_register_address, &mut buff)
1291 .map(|_| RegisterValue::U32(u32::from_le_bytes(buff)))
1292 }
1293 8 => {
1294 let mut buff = [0u8; 8];
1295 memory
1296 .read(previous_frame_register_address, &mut buff)
1297 .map(|_| RegisterValue::U64(u64::from_le_bytes(buff)))
1298 }
1299 _ => {
1300 return Err(Error::Other(format!(
1301 "UNWIND: Address size {address_size} not supported."
1302 )));
1303 }
1304 };
1305
1306 match result {
1307 Ok(register_value) => Some(register_value),
1308 Err(error) => {
1309 tracing::error!(
1310 "UNWIND: Rule: Offset {} from address {:#010x}",
1311 address_offset,
1312 unwind_cfa
1313 );
1314
1315 return Err(Error::Other(format!(
1316 "UNWIND: Failed to read value for register {} from address {} ({} bytes): {}",
1317 debug_register,
1318 RegisterValue::from(previous_frame_register_address),
1319 4,
1320 error
1321 )));
1322 }
1323 }
1324 }
1325 _ => unimplemented!(),
1327 };
1328
1329 tracing::trace!(
1330 "UNWIND - {:>10}: Caller: {}\tCallee: {}\tRule: {}",
1331 debug_register,
1332 new_value.unwrap_or_default(),
1333 callee_frame_registers
1334 .get_register(debug_register.id)
1335 .and_then(|reg| reg.value)
1336 .unwrap_or_default(),
1337 register_rule_string,
1338 );
1339 Ok(new_value)
1340}
1341
1342fn unwind_program_counter_register(
1344 return_address: RegisterValue,
1345 current_pc: u64,
1346 instruction_set: Option<InstructionSet>,
1347 register_rule_string: &mut String,
1348) -> Option<RegisterValue> {
1349 if return_address.is_max_value() || return_address.is_zero() {
1350 tracing::debug!(
1351 "No reliable return address is available, so we cannot determine the program counter to unwind the previous frame."
1352 );
1353 return None;
1354 }
1355
1356 match return_address {
1357 RegisterValue::U32(return_address) => {
1358 match instruction_set {
1359 Some(InstructionSet::Thumb2) => {
1360 *register_rule_string =
1365 "PC=(unwound (LR - 2) & !0b1) (dwarf Undefined)".to_string();
1366 Some(RegisterValue::U32((return_address - 2) & !0b1))
1367 }
1368 Some(InstructionSet::RV32C) => {
1369 *register_rule_string = "PC=(unwound x1 - 2) (dwarf Undefined)".to_string();
1370 Some(RegisterValue::U32(return_address - 2))
1371 }
1372 Some(InstructionSet::RV32) => {
1373 *register_rule_string = "PC=(unwound x1 - 4) (dwarf Undefined)".to_string();
1374 Some(RegisterValue::U32(return_address - 4))
1375 }
1376 Some(InstructionSet::Xtensa) => {
1377 let upper_bits = (current_pc as u32) & 0xC000_0000;
1378 *register_rule_string = "PC=(unwound x0 - 3) (dwarf Undefined)".to_string();
1379 Some(RegisterValue::U32(
1380 (return_address & 0x3FFF_FFFF | upper_bits) - 3,
1381 ))
1382 }
1383 _ => Some(RegisterValue::U32(return_address)),
1384 }
1385 }
1386 RegisterValue::U64(return_address) => Some(RegisterValue::U64(return_address)),
1387 RegisterValue::U128(_) => {
1388 tracing::warn!("128 bit address space not supported");
1389 None
1390 }
1391 }
1392}
1393
1394fn add_to_address(address: u64, offset: i64, address_size_in_bytes: usize) -> u64 {
1402 match address_size_in_bytes {
1403 4 => {
1404 if offset >= 0 {
1405 (address as u32)
1406 .checked_add(offset as u32)
1407 .map(u64::from)
1408 .unwrap_or(0x0)
1409 } else {
1410 (address as u32).saturating_sub(offset.unsigned_abs() as u32) as u64
1411 }
1412 }
1413 8 => {
1414 if offset >= 0 {
1415 address.checked_add(offset as u64).unwrap_or(0x0)
1416 } else {
1417 address.saturating_sub(offset.unsigned_abs())
1418 }
1419 }
1420 _ => {
1421 panic!(
1422 "UNWIND: Address size {address_size_in_bytes} not supported. Please report this as a bug."
1423 );
1424 }
1425 }
1426}
1427
1428#[cfg(test)]
1429mod test {
1430 use crate::{
1431 DebugInfo, DebugRegister, DebugRegisters,
1432 exception_handling::{
1433 armv6m::ArmV6MExceptionHandler, armv7m::ArmV7MExceptionHandler,
1434 exception_handler_for_core,
1435 },
1436 stack_frame::{StackFrameInfo, TestFormatter},
1437 };
1438
1439 use gimli::RegisterRule;
1440 use probe_rs::{
1441 CoreDump, RegisterValue,
1442 architecture::arm::core::registers::cortex_m::{self, CORTEX_M_CORE_REGISTERS},
1443 test::MockMemory,
1444 };
1445 use std::path::{Path, PathBuf};
1446 use test_case::test_case;
1447
1448 use super::unwind_register_using_rule;
1449
1450 fn get_path_for_test_files(relative_file: &str) -> PathBuf {
1452 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1453 path.push("tests");
1454 path.push(relative_file);
1455 path
1456 }
1457
1458 fn load_test_elf_as_debug_info(elf_file: &str) -> DebugInfo {
1461 let path = get_path_for_test_files(elf_file);
1462 DebugInfo::from_file(&path).unwrap_or_else(|err: crate::DebugError| {
1463 panic!("Failed to open file {}: {:?}", path.display(), err)
1464 })
1465 }
1466
1467 #[test]
1468 fn unwinding_first_instruction_after_exception() {
1469 let debug_info = load_test_elf_as_debug_info("exceptions");
1470
1471 let values: Vec<_> = [
1495 0x00000001, 0x2001ffcf, 0x20000044, 0x20000044, 0x00000000, 0x00000000, 0x00000000, 0x2001fff0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2001ffd0, 0xfffffff9, 0x00000182, 0x2001ffd0, 0x00000000, 0x2100000b, ]
1515 .into_iter()
1516 .enumerate()
1517 .map(|(id, r)| DebugRegister {
1518 dwarf_id: Some(id as u16),
1519 core_register: CORTEX_M_CORE_REGISTERS.core_register(id),
1520 value: Some(RegisterValue::U32(r)),
1521 })
1522 .collect();
1523
1524 let regs = DebugRegisters(values);
1525
1526 let expected_regs = regs.clone();
1527
1528 let mut mocked_mem = MockMemory::new();
1529
1530 mocked_mem.add_word_range(
1545 0x2001_ffd0,
1546 &[
1547 0x00000001, 0x2001ffcf, 0x20000044, 0x20000044, 0x00000000, 0x0000017f, 0x00000180,
1548 0x21000000, 0x2001fff8, 0x00000161, 0x00000000, 0x0000013d,
1549 ],
1550 );
1551
1552 let exception_handler = Box::new(ArmV6MExceptionHandler {});
1553
1554 let frames = debug_info
1555 .unwind_impl(
1556 regs,
1557 &mut mocked_mem,
1558 exception_handler.as_ref(),
1559 Some(probe_rs_target::InstructionSet::Thumb2),
1560 500,
1561 )
1562 .unwrap();
1563
1564 let first_frame = &frames[0];
1565
1566 assert_eq!(first_frame.pc, RegisterValue::U32(0x00000182));
1567
1568 assert_eq!(
1569 first_frame.function_name,
1570 "__cortex_m_rt_SVCall_trampoline".to_string()
1571 );
1572
1573 assert_eq!(first_frame.registers, expected_regs);
1574
1575 let next_frame = &frames[1];
1576 assert_eq!(next_frame.function_name, "SVC");
1577 assert_eq!(next_frame.pc, RegisterValue::U32(0x0000017f));
1578
1579 }
1617
1618 #[test]
1619 fn unwinding_in_exception_handler() {
1620 let debug_info = load_test_elf_as_debug_info("exceptions");
1621
1622 let values: Vec<_> = [
1645 0x00000001, 0x2001ff9f, 0x20000047, 0x20000047, 0x00000000, 0x00000000, 0x00000000, 0x2001ffc0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2001ffc0, 0x0000042f, 0x000001a4, 0x2001ffc0, 0x00000000, 0x2100000b, ]
1665 .into_iter()
1666 .enumerate()
1667 .map(|(id, r)| DebugRegister {
1668 dwarf_id: Some(id as u16),
1669 core_register: CORTEX_M_CORE_REGISTERS.core_register(id),
1670 value: Some(RegisterValue::U32(r)),
1671 })
1672 .collect();
1673
1674 let regs = DebugRegisters(values);
1675
1676 let mut dummy_mem = MockMemory::new();
1677
1678 dummy_mem.add_word_range(
1697 0x2001_ffc0,
1698 &[
1699 0x2001ffc8, 0x0000018b, 0x2001fff0, 0xfffffff9, 0x00000001, 0x2001ffcf, 0x20000044,
1700 0x20000044, 0x00000000, 0x0000017f, 0x00000180, 0x21000000, 0x2001fff8, 0x00000161,
1701 0x00000000, 0x0000013d,
1702 ],
1703 );
1704
1705 let exception_handler = Box::new(ArmV6MExceptionHandler {});
1706
1707 let frames = debug_info
1708 .unwind_impl(
1709 regs,
1710 &mut dummy_mem,
1711 exception_handler.as_ref(),
1712 Some(probe_rs_target::InstructionSet::Thumb2),
1713 500,
1714 )
1715 .unwrap();
1716
1717 assert_eq!(frames[0].pc, RegisterValue::U32(0x000001a4));
1718
1719 assert_eq!(
1720 frames[1].function_name,
1721 "__cortex_m_rt_SVCall_trampoline".to_string()
1722 );
1723
1724 assert_eq!(frames[1].pc, RegisterValue::U32(0x00000188)); assert_eq!(
1732 frames[1]
1733 .registers
1734 .get_register(probe_rs::RegisterId(7))
1735 .and_then(|r| r.value),
1736 Some(RegisterValue::U32(0x2001ffc8))
1737 );
1738
1739 let printed_backtrace = frames
1740 .into_iter()
1741 .map(|f| TestFormatter(&f).to_string())
1742 .collect::<Vec<String>>()
1743 .join("");
1744
1745 insta::assert_snapshot!(printed_backtrace);
1746 }
1747
1748 #[test]
1749 fn unwinding_in_exception_trampoline() {
1750 let debug_info = load_test_elf_as_debug_info("exceptions");
1751
1752 let values: Vec<_> = [
1775 0x00000001, 0x2001ffcf, 0x20000044, 0x20000044, 0x00000000, 0x00000000, 0x00000000, 0x2001ffc8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2001ffc8, 0x0000018B, 0x0000018A, 0x2001ffc8, 0x00000000, 0x2100000b, ]
1795 .into_iter()
1796 .enumerate()
1797 .map(|(id, r)| DebugRegister {
1798 dwarf_id: Some(id as u16),
1799 core_register: CORTEX_M_CORE_REGISTERS.core_register(id),
1800 value: Some(RegisterValue::U32(r)),
1801 })
1802 .collect();
1803
1804 let regs = DebugRegisters(values);
1805
1806 let mut dummy_mem = MockMemory::new();
1807
1808 dummy_mem.add_word_range(
1825 0x2001_ffc8,
1826 &[
1827 0x2001fff0, 0xfffffff9, 0x00000001, 0x2001ffcf, 0x20000044, 0x20000044, 0x00000000,
1828 0x0000017f, 0x00000180, 0x21000000, 0x2001fff8, 0x00000161, 0x00000000, 0x0000013d,
1829 ],
1830 );
1831
1832 let exception_handler = Box::new(ArmV6MExceptionHandler {});
1833
1834 let frames = debug_info
1835 .unwind_impl(
1836 regs,
1837 &mut dummy_mem,
1838 exception_handler.as_ref(),
1839 Some(probe_rs_target::InstructionSet::Thumb2),
1840 500,
1841 )
1842 .unwrap();
1843
1844 let printed_backtrace = frames
1845 .into_iter()
1846 .map(|f| TestFormatter(&f).to_string())
1847 .collect::<Vec<String>>()
1848 .join("");
1849
1850 insta::assert_snapshot!(printed_backtrace);
1851 }
1852
1853 #[test]
1854 fn unwinding_inlined() {
1855 let debug_info = load_test_elf_as_debug_info("inlined-functions");
1856
1857 let values: Vec<_> = [
1881 0xfffffecc, 0x00000001, 0x00000000, 0x40008140, 0x000f4240, 0xfffffec0, 0x00000000, 0x20003ff0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5000050c, 0x20003ff0, 0x00200000, 0x000002e4, 0x20003ff0, 0x00000000, 0x61000000, ]
1901 .into_iter()
1902 .enumerate()
1903 .map(|(id, r)| DebugRegister {
1904 dwarf_id: Some(id as u16),
1905 core_register: CORTEX_M_CORE_REGISTERS.core_register(id),
1906 value: Some(RegisterValue::U32(r)),
1907 })
1908 .collect();
1909
1910 let regs = DebugRegisters(values);
1911
1912 let mut dummy_mem = MockMemory::new();
1913
1914 dummy_mem.add_word_range(
1921 0x2000_3ff0,
1922 &[0x20003ff8, 0x00000161, 0x00000000, 0x0000013d],
1923 );
1924
1925 let exception_handler = Box::new(ArmV7MExceptionHandler);
1926
1927 let frames = debug_info
1928 .unwind_impl(
1929 regs,
1930 &mut dummy_mem,
1931 exception_handler.as_ref(),
1932 Some(probe_rs_target::InstructionSet::Thumb2),
1933 500,
1934 )
1935 .unwrap();
1936
1937 let printed_backtrace = frames
1938 .into_iter()
1939 .map(|f| TestFormatter(&f).to_string())
1940 .collect::<Vec<String>>()
1941 .join("");
1942
1943 insta::assert_snapshot!(printed_backtrace);
1944 }
1945
1946 #[test]
1947 fn test_print_stacktrace() {
1948 let elf = Path::new("./tests/gpio-hal-blinky/elf");
1949 let coredump = include_bytes!("../tests/gpio-hal-blinky/coredump");
1950
1951 let mut adapter = CoreDump::load_raw(coredump).unwrap();
1952 let debug_info = DebugInfo::from_file(elf).unwrap();
1953
1954 let initial_registers = DebugRegisters::from_coredump(&adapter);
1955 let exception_handler = exception_handler_for_core(adapter.core_type());
1956 let instruction_set = adapter.instruction_set();
1957
1958 let stack_frames = debug_info
1959 .unwind(
1960 &mut adapter,
1961 initial_registers,
1962 exception_handler.as_ref(),
1963 Some(instruction_set),
1964 1000,
1965 )
1966 .unwrap();
1967
1968 let printed_backtrace = stack_frames
1969 .into_iter()
1970 .map(|f| TestFormatter(&f).to_string())
1971 .collect::<Vec<String>>()
1972 .join("");
1973
1974 insta::assert_snapshot!(printed_backtrace);
1975 }
1976
1977 #[test_case("RP2040_full_unwind"; "full_unwind Armv6-m using RP2040")]
1978 #[test_case("RP2040_svcall"; "svcall Armv6-m using RP2040")]
1979 #[test_case("RP2040_systick"; "systick Armv6-m using RP2040")]
1980 #[test_case("nRF52833_xxAA_full_unwind"; "full_unwind Armv7-m using nRF52833_xxAA")]
1981 #[test_case("nRF52833_xxAA_svcall"; "svcall Armv7-m using nRF52833_xxAA")]
1982 #[test_case("nRF52833_xxAA_systick"; "systick Armv7-m using nRF52833_xxAA")]
1983 #[test_case("nRF52833_xxAA_hardfault_from_usagefault"; "hardfault_from_usagefault Armv7-m using nRF52833_xxAA")]
1984 #[test_case("nRF52833_xxAA_hardfault_from_busfault"; "hardfault_from_busfault Armv7-m using nRF52833_xxAA")]
1985 #[test_case("nRF52833_xxAA_hardfault_in_systick"; "hardfault_in_systick Armv7-m using nRF52833_xxAA")]
1986 #[test_case("atsamd51p19a"; "Armv7-em from C source code")]
1987 #[test_case("esp32c3_full_unwind"; "full_unwind RISC-V32E using esp32c3")]
1988 #[test_case("esp32s3_esp_hal_panic"; "Xtensa unwinding on an esp32s3 in a panic handler")]
1989 #[test_case("esp32c6_coredump_elf"; "Unwind using a RISC-V coredump in ELF format")]
1990 #[test_case("esp32s3_coredump_elf"; "Unwind using an Xtensa coredump in ELF format")]
1991 fn full_unwind(test_name: &str) {
1992 let debug_info =
1993 load_test_elf_as_debug_info(format!("debug-unwind-tests/{test_name}.elf").as_str());
1994
1995 let coredump_path = coredump_path(format!("debug-unwind-tests/{test_name}"));
1996 let mut adapter = CoreDump::load(&coredump_path).unwrap();
1997
1998 let snapshot_name = test_name.to_string();
1999
2000 let initial_registers = DebugRegisters::from_coredump(&adapter);
2001 let exception_handler = exception_handler_for_core(adapter.core_type());
2002 let instruction_set = adapter.instruction_set();
2003
2004 let mut stack_frames = debug_info
2005 .unwind(
2006 &mut adapter,
2007 initial_registers,
2008 exception_handler.as_ref(),
2009 Some(instruction_set),
2010 1000,
2011 )
2012 .unwrap();
2013
2014 for frame in stack_frames.iter_mut() {
2016 let mut variable_caches = Vec::new();
2017 if let Some(local_variables) = &mut frame.local_variables {
2018 variable_caches.push(local_variables);
2019 }
2020 for variable_cache in variable_caches {
2021 variable_cache.recurse_deferred_variables(
2023 &debug_info,
2024 &mut adapter,
2025 10,
2026 StackFrameInfo {
2027 registers: &frame.registers,
2028 frame_base: frame.frame_base,
2029 canonical_frame_address: frame.canonical_frame_address,
2030 },
2031 );
2032 }
2033 }
2034
2035 insta::assert_yaml_snapshot!(snapshot_name, stack_frames);
2038 }
2039
2040 #[test_case("RP2040_full_unwind"; "Armv6-m using RP2040")]
2041 #[test_case("nRF52833_xxAA_full_unwind"; "Armv7-m using nRF52833_xxAA")]
2042 #[test_case("atsamd51p19a"; "Armv7-em from C source code")]
2043 fn static_variables(chip_name: &str) {
2045 let debug_info =
2048 load_test_elf_as_debug_info(format!("debug-unwind-tests/{chip_name}.elf").as_str());
2049
2050 let coredump_path = coredump_path(format!("debug-unwind-tests/{chip_name}"));
2051 let mut adapter = CoreDump::load(&coredump_path).unwrap();
2052
2053 let initial_registers = DebugRegisters::from_coredump(&adapter);
2054
2055 let snapshot_name = format!("{chip_name}_static_variables");
2056
2057 let mut static_variables = debug_info.create_static_scope_cache();
2058
2059 static_variables.recurse_deferred_variables(
2060 &debug_info,
2061 &mut adapter,
2062 10,
2063 StackFrameInfo {
2064 registers: &initial_registers,
2065 frame_base: None,
2066 canonical_frame_address: None,
2067 },
2068 );
2069 insta::assert_yaml_snapshot!(snapshot_name, static_variables);
2072 }
2073
2074 fn coredump_path(base: String) -> PathBuf {
2075 let possible_coredump_paths = [
2076 get_path_for_test_files(format!("{base}.coredump").as_str()),
2077 get_path_for_test_files(format!("{base}_coredump.elf").as_str()),
2078 ];
2079
2080 possible_coredump_paths
2081 .iter()
2082 .find(|path| path.exists())
2083 .unwrap_or_else(|| {
2084 panic!(
2085 "No coredump found for chip {base}. Expected one of: {possible_coredump_paths:?}"
2086 )
2087 })
2088 .clone()
2089 }
2090
2091 #[test]
2092 fn unwind_same_value() {
2093 let rule = gimli::RegisterRule::SameValue;
2094
2095 let mut callee_frame_registers = DebugRegisters::default();
2096 let debug_register = CORTEX_M_CORE_REGISTERS.core_registers().next().unwrap();
2097
2098 let expected_value = Some(RegisterValue::U32(0x1234));
2099
2100 callee_frame_registers.0.push(DebugRegister {
2101 core_register: debug_register,
2102 dwarf_id: Some(0),
2103 value: expected_value,
2104 });
2105
2106 let mut memory = MockMemory::new();
2107
2108 let value = unwind_register_using_rule(
2109 debug_register,
2110 &callee_frame_registers,
2111 None,
2112 &mut memory,
2113 rule,
2114 )
2115 .unwrap();
2116
2117 assert_eq!(value, expected_value);
2118 }
2119
2120 #[test]
2121 fn unwind_offset() {
2122 let cfa = 0x1000;
2123 let offset = 4;
2124 let rule = gimli::RegisterRule::Offset(offset as i64);
2125 let expected_value = 0xcafe;
2126
2127 let expected_register_value = Some(RegisterValue::U32(expected_value));
2128
2129 let mut memory = MockMemory::new();
2130 memory.add_word_range(cfa + offset, &[expected_value]);
2131
2132 let mut callee_frame_registers = DebugRegisters::default();
2133 let debug_register = CORTEX_M_CORE_REGISTERS.core_registers().next().unwrap();
2134
2135 callee_frame_registers.0.push(DebugRegister {
2136 core_register: debug_register,
2137 dwarf_id: Some(0),
2138 value: None,
2139 });
2140
2141 callee_frame_registers.0.push(DebugRegister {
2143 core_register: &cortex_m::PC,
2144 dwarf_id: Some(15),
2145 value: Some(RegisterValue::U32(0x0)),
2146 });
2147
2148 let value = unwind_register_using_rule(
2149 debug_register,
2150 &callee_frame_registers,
2151 Some(cfa),
2152 &mut memory,
2153 rule,
2154 )
2155 .unwrap();
2156
2157 assert_eq!(value, expected_register_value);
2158 }
2159
2160 #[test]
2161 fn unwind_undefined_for_frame_pointer() {
2162 let mut callee_frame_registers = DebugRegisters::default();
2163 callee_frame_registers.0.push(DebugRegister {
2164 core_register: &cortex_m::FP,
2165 dwarf_id: Some(7),
2166 value: Some(RegisterValue::U32(0x100)),
2167 });
2168
2169 callee_frame_registers.0.push(DebugRegister {
2171 core_register: &cortex_m::PC,
2172 dwarf_id: Some(15),
2173 value: Some(RegisterValue::U32(0x0)),
2174 });
2175
2176 let cfa = 0x200;
2177
2178 let mut memory = MockMemory::new();
2179
2180 let value = unwind_register_using_rule(
2181 &cortex_m::FP,
2182 &callee_frame_registers,
2183 Some(cfa),
2184 &mut memory,
2185 RegisterRule::Undefined,
2186 )
2187 .unwrap();
2188
2189 assert_eq!(value, Some(RegisterValue::U32(0x200)));
2192 }
2193}