Repository: probe-rs/probe-rs
Pull request #3278 (2025-05-02 20:22:04)

Resolve `DW_AT_type = DebugInfoRef`
This PR implements handling of `DW_AT_type = DebugInfoRef` which is a rare case where a variable's debuginfo is encoded in a different compilation unit.


Patch:
diff --git a/changelog/added-debug-info-ref.md b/changelog/added-debug-info-ref.md
new file mode 100644
index 00000000..804f5f00
--- /dev/null
+++ b/changelog/added-debug-info-ref.md
@@ -0,0 +1,1 @@
+Debugger now handles DW_AT_type DebugInfoRef
\ No newline at end of file
diff --git a/probe-rs-debug/src/debug_info.rs b/probe-rs-debug/src/debug_info.rs
index 35b52957..d07f266d 100644
--- a/probe-rs-debug/src/debug_info.rs
+++ b/probe-rs-debug/src/debug_info.rs
@@ -10,8 +10,7 @@ use crate::{
     SourceLocation, VerifiedBreakpoint, registers, stack_frame::StackFrameInfo, unit_info::RangeExt,
 };
 use gimli::{
-    BaseAddresses, DebugFrame, DebugInfoOffset, RunTimeEndian, UnwindContext, UnwindSection,
-    UnwindTableRow,
+    BaseAddresses, DebugFrame, RunTimeEndian, UnwindContext, UnwindSection, UnwindTableRow,
 };
 use object::read::{Object, ObjectSection};
 use probe_rs::{Error, MemoryInterface, RegisterDataType, RegisterRole, RegisterValue, UnwindRule};
@@ -917,25 +916,6 @@ impl DebugInfo {
         )))
     }
 
-    /// Get the DIE at the given offset into the debug info section.
-    pub(crate) fn get_die_at_offset(&self, offset: DebugInfoOffset) -> Result<Die, DebugError> {
-        for unit_info in &self.unit_infos {
-            if let Some(unit_offset) = offset.to_unit_offset(&unit_info.unit.header) {
-                return unit_info.unit.entry(unit_offset).map_err(|error| {
-                    DebugError::Other(format!(
-                        "Error reading DIE at debug info offset {:#x} : {}",
-                        offset.0, error
-                    ))
-                });
-            }
-        }
-
-        Err(DebugError::Other(format!(
-            "DIE at debug info offset {:#010x} not found",
-            offset.0
-        )))
-    }
-
     /// Look up the DIE reference for the given attribute, if it exists.
     pub(crate) fn resolve_die_reference<'debug_info, 'unit_info>(
         &'debug_info self,
@@ -946,27 +926,56 @@ impl DebugInfo {
     where
         'unit_info: 'debug_info,
     {
-        let value = die.attr_value(attribute).ok().flatten()?;
+        let attr = die.attr(attribute).ok().flatten()?;
 
-        match value {
-            gimli::AttributeValue::UnitRef(unit_ref) => unit_info.unit.entry(unit_ref).ok(),
-            gimli::AttributeValue::DebugInfoRef(debug_info_ref) => {
-                self.get_die_at_offset(debug_info_ref).ok()
-            }
-            other_value => {
-                tracing::warn!(
-                    "Unsupported {:?} value: {other_value:?}",
-                    attribute.static_string(),
-                );
-                None
-            }
-        }
+        self.resolve_die_reference_with_unit(&attr, unit_info)
+            .ok()
+            .map(|(_, die)| die)
     }
 
     /// The program binary's (and core's) endianness.
     pub fn endianness(&self) -> RunTimeEndian {
         self.endianness
     }
+
+    /// Returns the UnitInfo and DIE for the given attribute.
+    pub(crate) fn resolve_die_reference_with_unit<'debug_info, 'unit_info>(
+        &'debug_info self,
+        attr: &gimli::Attribute<GimliReader>,
+        unit_info: &'unit_info UnitInfo,
+    ) -> Result<(&'debug_info UnitInfo, Die<'debug_info, 'debug_info>), DebugError>
+    where
+        'unit_info: 'debug_info,
+    {
+        match attr.value() {
+            gimli::AttributeValue::UnitRef(unit_ref) => {
+                Ok((unit_info, unit_info.unit.entry(unit_ref)?))
+            }
+            gimli::AttributeValue::DebugInfoRef(offset) => {
+                for unit_info in &self.unit_infos {
+                    let Some(unit_offset) = offset.to_unit_offset(&unit_info.unit.header) else {
+                        continue;
+                    };
+
+                    let entry = unit_info.unit.entry(unit_offset).map_err(|error| {
+                        DebugError::Other(format!(
+                            "Error reading DIE at debug info offset {:#x} : {}",
+                            offset.0, error
+                        ))
+                    })?;
+                    return Ok((unit_info, entry));
+                }
+
+                Err(DebugError::Other(format!(
+                    "Unable to find unit info for debug info offset {:#x}",
+                    offset.0
+                )))
+            }
+            other_attribute_value => Err(DebugError::Other(format!(
+                "Unimplemented attribute value {other_attribute_value:?}"
+            ))),
+        }
+    }
 }
 
 /// Uses the [`TypedPathBuf::normalize`] function to normalize both paths before comparing them
diff --git a/probe-rs-debug/src/unit_info.rs b/probe-rs-debug/src/unit_info.rs
index 31c60287..17434319 100644
--- a/probe-rs-debug/src/unit_info.rs
+++ b/probe-rs-debug/src/unit_info.rs
@@ -429,23 +429,21 @@ impl UnitInfo {
         frame_info: StackFrameInfo<'_>,
         cache: &mut VariableCache,
     ) -> Result<(), DebugError> {
-        match attr.value() {
-            gimli::AttributeValue::UnitRef(unit_ref) => {
-                // Reference to a type, or an entry to another type or a type modifier which will point to another type.
-                // Before we resolve that type tree, we need to resolve the current node's memory location.
-                // This is because the memory location of the type nodes and child variables often inherit this value.
-                self.process_memory_location(
-                    debug_info,
-                    attributes_entry,
-                    parent_variable,
-                    child_variable,
-                    memory,
-                    frame_info,
-                )?;
+        // Reference to a type, or an entry to another type or a type modifier which will point to another type.
+        // Before we resolve that type tree, we need to resolve the current node's memory location.
+        // This is because the memory location of the type nodes and child variables often inherit this value.
+        self.process_memory_location(
+            debug_info,
+            attributes_entry,
+            parent_variable,
+            child_variable,
+            memory,
+            frame_info,
+        )?;
 
-                // Now resolve the referenced tree node for the type.
-                let referenced_type_tree_node = self.unit.entry(unit_ref)?;
-                self.extract_type(
+        match debug_info.resolve_die_reference_with_unit(attr, self) {
+            Ok((unit_info, referenced_type_tree_node)) => {
+                unit_info.extract_type(
                     debug_info,
                     &referenced_type_tree_node,
                     parent_variable,
@@ -455,9 +453,9 @@ impl UnitInfo {
                     frame_info,
                 )?;
             }
-            other_attribute_value => {
+            Err(error) => {
                 child_variable.set_value(VariableValue::Error(format!(
-                    "Unimplemented: Attribute Value for DW_AT_type {other_attribute_value:?}"
+                    "Failed to process DW_AT_type: {error:?}"
                 )));
             }
         }


Test patch:
diff --git a/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap b/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
index b2c0a959..02cd5c0d 100644
--- a/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
+++ b/probe-rs-debug/src/snapshots/probe_rs_debug__debug_info__test__esp32s3_esp_hal_panic.snap
@@ -5975,12 +5975,56 @@ expression: stack_frames
                   children:
                     - name:
                         Named: inner
-                      type_name: Unknown
-                      value: "< Unimplemented: Attribute Value for DW_AT_type DebugInfoRef(DebugInfoOffset(5879)) >"
-                - name:
-                    Named: not_send
-                  type_name: Unknown
-                  value: "< Unimplemented: Attribute Value for DW_AT_type DebugInfoRef(DebugInfoOffset(6875)) >"
+                      type_name:
+                        Struct: Executor
+                      value: Executor @ 0x00000000
+                      children:
+                        - name:
+                            Named: inner
+                          type_name:
+                            Struct: SyncExecutor
+                          value: SyncExecutor @ 0x00000000
+                          children:
+                            - name:
+                                Named: run_queue
+                              type_name:
+                                Struct: RunQueue
+                              value: RunQueue @ 0x00000000
+                              children:
+                                - name:
+                                    Named: head
+                                  type_name:
+                                    Struct: "AtomicPtr<embassy_executor::raw::TaskHeader>"
+                                  value: "AtomicPtr<embassy_executor::raw::TaskHeader> @ 0x00000000"
+                                  children:
+                                    - name:
+                                        Named: p
+                                      type_name:
+                                        Struct: "UnsafeCell<*mut embassy_executor::raw::TaskHeader>"
+                                      value: "UnsafeCell<*mut embassy_executor::raw::TaskHeader> @ 0x00000000"
+                                      children:
+                                        - name:
+                                            Named: value
+                                          type_name:
+                                            Pointer: "*mut embassy_executor::raw::TaskHeader"
+                                          value: "*mut embassy_executor::raw::TaskHeader @ 0x00000000"
+                                          children:
+                                            - name:
+                                                Named: "*value"
+                                              type_name:
+                                                Struct: TaskHeader
+                                              value: TaskHeader
+                            - name:
+                                Named: pender
+                              type_name:
+                                Struct: Pender
+                              value: Pender @ 0x00000004
+                              children:
+                                - name:
+                                    Named: __0
+                                  type_name:
+                                    Pointer: "*mut ()"
+                                  value: "*mut () @ 0x00000004"
         - name:
             Named: init
           type_name: