Skip to main content

probe_rs_debug/
lib.rs

1//! Debugging support for probe-rs
2//!
3//! The `debug` module contains various debug functionality, which can be
4//! used to implement a debugger based on `probe-rs`.
5
6/// Debug information which is parsed from DWARF debugging information.
7pub mod debug_info;
8/// Stepping through a program during debug, at various granularities.
9pub mod debug_step;
10/// References to the DIE (debug information entry) of functions.
11pub mod function_die;
12/// Programming languages
13pub(crate) mod language;
14/// Target Register definitions, expanded from [`crate::core::registers::CoreRegister`] to include unwind specific information.
15pub mod registers;
16/// The source statement information used while identifying haltpoints for debug stepping and breakpoints.
17pub(crate) mod source_instructions;
18/// The stack frame information used while unwinding the stack from a specific program counter.
19pub mod stack_frame;
20/// Information about a Unit in the debug information.
21pub mod unit_info;
22/// Variable information used during debug.
23pub mod variable;
24/// The hierarchical cache of all variables for a given scope.
25pub mod variable_cache;
26
27pub(crate) mod exception_handling;
28
29pub use self::{
30    debug_info::*, debug_step::SteppingMode, exception_handling::exception_handler_for_core,
31    registers::*, source_instructions::SourceLocation, source_instructions::VerifiedBreakpoint,
32    stack_frame::StackFrame, variable::*, variable_cache::VariableCache,
33};
34
35use probe_rs::{Core, MemoryInterface};
36
37use gimli::DebuggingInformationEntry;
38use gimli::EvaluationResult;
39use gimli::{AttributeValue, RunTimeEndian};
40use serde::Serialize;
41use typed_path::TypedPathBuf;
42
43use std::num::ParseIntError;
44use std::{
45    io,
46    num::NonZeroU32,
47    str::Utf8Error,
48    sync::atomic::{AtomicU32, Ordering},
49    vec,
50};
51
52/// A simplified type alias of the [`gimli::EndianReader`] type.
53pub type EndianReader = gimli::EndianReader<RunTimeEndian, std::rc::Rc<[u8]>>;
54
55/// An error occurred while debugging the target.
56#[derive(Debug, thiserror::Error)]
57pub enum DebugError {
58    /// An IO error occurred when accessing debug data.
59    #[error("IO Error while accessing debug data")]
60    Io(#[from] io::Error),
61    /// An error occurred while accessing debug data.
62    #[error("Error accessing debug data")]
63    DebugData(#[from] object::read::Error),
64    /// Something failed while parsing debug data.
65    #[error("Error parsing debug data")]
66    Parse(#[from] gimli::read::Error),
67    /// Non-UTF8 data was found in the debug data.
68    #[error("Non-UTF8 data found in debug data")]
69    NonUtf8(#[from] Utf8Error),
70    /// A probe-rs error occurred.
71    #[error("Error using the probe")]
72    Probe(#[from] probe_rs::Error),
73    /// A char could not be created from the given string.
74    #[error(transparent)]
75    CharConversion(#[from] std::char::CharTryFromError),
76    /// An int could not be created from the given string.
77    #[error(transparent)]
78    IntConversion(#[from] std::num::TryFromIntError),
79    /// Non-terminal Errors encountered while unwinding the stack, e.g. Could not resolve the value of a variable in the stack.
80    /// These are distinct from other errors because they do not interrupt processing.
81    /// Instead, the cause of incomplete results are reported back/explained to the user, and the stack continues to unwind.
82    #[error("{message}")]
83    WarnAndContinue {
84        /// A message that can be displayed to the user to help them understand the reason for the incomplete results.
85        message: String,
86    },
87
88    /// Required functionality is not implemented
89    #[error("Not implemented: {0}")]
90    NotImplemented(&'static str),
91
92    /// Some other error occurred.
93    #[error("{0}")]
94    Other(String),
95}
96
97/// A copy of [`gimli::ColumnType`] which uses [`u64`] instead of [`NonZeroU64`](std::num::NonZeroU64).
98#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
99pub enum ColumnType {
100    /// The `LeftEdge` means that the statement begins at the start of the new line.
101    LeftEdge,
102    /// A column number, whose range begins at 1.
103    Column(u64),
104}
105
106impl From<gimli::ColumnType> for ColumnType {
107    fn from(column: gimli::ColumnType) -> Self {
108        match column {
109            gimli::ColumnType::LeftEdge => ColumnType::LeftEdge,
110            gimli::ColumnType::Column(c) => ColumnType::Column(c.get()),
111        }
112    }
113}
114
115impl From<u64> for ColumnType {
116    fn from(column: u64) -> Self {
117        match column {
118            0 => ColumnType::LeftEdge,
119            _ => ColumnType::Column(column),
120        }
121    }
122}
123
124/// Object reference as defined in the DAP standard.
125#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum ObjectRef {
127    /// Valid object reference (> 0)
128    Valid(NonZeroU32),
129    /// Invalid object reference (<= 0)
130    #[default]
131    Invalid,
132}
133
134impl PartialOrd for ObjectRef {
135    fn partial_cmp(&self, other: &ObjectRef) -> Option<std::cmp::Ordering> {
136        Some(self.cmp(other))
137    }
138}
139
140impl Ord for ObjectRef {
141    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
142        i64::from(*self).cmp(&i64::from(*other))
143    }
144}
145
146impl From<ObjectRef> for i64 {
147    fn from(value: ObjectRef) -> Self {
148        match value {
149            ObjectRef::Valid(v) => v.get() as i64,
150            ObjectRef::Invalid => 0,
151        }
152    }
153}
154
155impl From<i64> for ObjectRef {
156    fn from(value: i64) -> Self {
157        if value > 0 {
158            ObjectRef::Valid(NonZeroU32::try_from(value as u32).unwrap())
159        } else {
160            ObjectRef::Invalid
161        }
162    }
163}
164
165impl std::str::FromStr for ObjectRef {
166    type Err = ParseIntError;
167
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        let value = s.parse::<i64>()?;
170        Ok(ObjectRef::from(value))
171    }
172}
173
174static CACHE_KEY: AtomicU32 = AtomicU32::new(1);
175/// Generate a unique key that can be used to assign id's to StackFrame and Variable structs.
176pub fn get_object_reference() -> ObjectRef {
177    let key = CACHE_KEY.fetch_add(1, Ordering::SeqCst);
178    ObjectRef::Valid(NonZeroU32::new(key).unwrap())
179}
180
181/// If file information is available, it returns `Some(directory:PathBuf, file_name:String)`, otherwise `None`.
182fn extract_file(
183    debug_info: &DebugInfo,
184    unit: &gimli::Unit<GimliReader>,
185    attribute_value: AttributeValue<GimliReader>,
186) -> Option<TypedPathBuf> {
187    match attribute_value {
188        AttributeValue::FileIndex(index) => {
189            if let Some(path) = debug_info.find_file_and_directory(unit, index) {
190                Some(path)
191            } else {
192                tracing::warn!("Unable to extract file or path from {:?}.", attribute_value);
193                None
194            }
195        }
196        other => {
197            tracing::warn!(
198                "Unable to extract file information from attribute value {:?}: Not implemented.",
199                other
200            );
201            None
202        }
203    }
204}
205
206/// If a DW_AT_byte_size attribute exists, return the u64 value, otherwise (including errors) return None
207fn extract_byte_size(node_die: &DebuggingInformationEntry<GimliReader>) -> Option<u64> {
208    match node_die.attr(gimli::DW_AT_byte_size) {
209        Some(byte_size_attr) => match byte_size_attr.value() {
210            AttributeValue::Udata(byte_size) => Some(byte_size),
211            AttributeValue::Data1(byte_size) => Some(byte_size as u64),
212            AttributeValue::Data2(byte_size) => Some(byte_size as u64),
213            AttributeValue::Data4(byte_size) => Some(byte_size as u64),
214            AttributeValue::Data8(byte_size) => Some(byte_size),
215            other => {
216                tracing::warn!("Unimplemented: DW_AT_byte_size value: {other:?}");
217                None
218            }
219        },
220        None => None,
221    }
222}
223
224fn extract_line(attribute_value: AttributeValue<GimliReader>) -> Option<u64> {
225    match attribute_value {
226        AttributeValue::Udata(line) => Some(line),
227        _ => None,
228    }
229}
230
231#[expect(clippy::unwrap_used)]
232pub(crate) fn _print_all_attributes(
233    core: &mut Core<'_>,
234    stackframe_cfa: Option<u64>,
235    dwarf: &gimli::Dwarf<DwarfReader>,
236    unit: &gimli::Unit<DwarfReader>,
237    tag: &gimli::DebuggingInformationEntry<DwarfReader>,
238    print_depth: usize,
239) {
240    for attr in tag.attrs() {
241        for _ in 0..print_depth {
242            print!("\t");
243        }
244        print!("{}: ", attr.name());
245
246        match attr.value() {
247            AttributeValue::Addr(a) => println!("{a:#010x}"),
248            AttributeValue::DebugStrRef(str_ref) => {
249                let val = dwarf.string(str_ref).unwrap();
250                println!("{}", std::str::from_utf8(&val).unwrap());
251            }
252            AttributeValue::Exprloc(e) => {
253                let mut evaluation = e.evaluation(unit.encoding());
254
255                // go for evaluation
256                let mut result = evaluation.evaluate().unwrap();
257
258                while let Some(next) = iterate(result, core, &mut evaluation, stackframe_cfa) {
259                    result = next;
260                }
261
262                let result = evaluation.result();
263
264                println!("Expression: {:x?}", result[0]);
265            }
266            AttributeValue::LocationListsRef(_) => println!("LocationList"),
267            AttributeValue::DebugLocListsBase(_) => println!(" LocationList"),
268            AttributeValue::DebugLocListsIndex(_) => println!(" LocationList"),
269            _ => println!("print_all_attributes {:?}", attr.value()),
270        }
271    }
272}
273
274#[expect(dead_code)]
275fn iterate(
276    result: EvaluationResult<DwarfReader>,
277    core: &mut Core,
278    evaluation: &mut gimli::Evaluation<DwarfReader>,
279    stackframe_cfa: Option<u64>,
280) -> Option<EvaluationResult<DwarfReader>> {
281    let resume_result = match result {
282        EvaluationResult::Complete => return None,
283        EvaluationResult::RequiresMemory { address, size, .. } => {
284            let mut buff = vec![0u8; size as usize];
285            core.read(address, &mut buff)
286                .expect("Failed to read memory");
287
288            let value = match size {
289                1 => gimli::Value::U8(buff[0]),
290                2 => gimli::Value::U16(u16::from_be_bytes([buff[0], buff[1]])),
291                4 => gimli::Value::U32(u32::from_be_bytes([buff[0], buff[1], buff[2], buff[3]])),
292                x => unimplemented!("Requested memory with size {x}, which is not supported yet."),
293            };
294
295            evaluation.resume_with_memory(value)
296        }
297        EvaluationResult::RequiresFrameBase => {
298            evaluation.resume_with_frame_base(stackframe_cfa.unwrap())
299        }
300        EvaluationResult::RequiresRegister {
301            register,
302            base_type,
303        } => {
304            let raw_value = core
305                .read_core_reg::<u64>(register.0)
306                .expect("Failed to read memory");
307
308            if base_type != gimli::UnitOffset(0) {
309                unimplemented!(
310                    "Support for units in RequiresRegister request is not yet implemented."
311                )
312            }
313            evaluation.resume_with_register(gimli::Value::Generic(raw_value))
314        }
315        EvaluationResult::RequiresRelocatedAddress(address_index) => {
316            // Use the address_index as an offset from 0, so just pass it into the next step.
317            evaluation.resume_with_relocated_address(address_index)
318        }
319        x => {
320            println!("print_all_attributes {x:?}");
321            // x
322            todo!()
323        }
324    };
325
326    Some(resume_result.unwrap())
327}