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