tasru/
extract.rs

1use crate::GimliReader;
2use gimli::{Endianity, EvaluationResult, Location};
3
4#[derive(Debug)]
5pub enum ExtractError {
6    WarnAndContinue { message: String },
7    GimliError(gimli::Error),
8    UnknownVariable,
9}
10
11impl From<gimli::Error> for ExtractError {
12    fn from(value: gimli::Error) -> Self {
13        ExtractError::GimliError(value)
14    }
15}
16
17/// The result of `UnitInfo::evaluate_expression()` can be the value of a variable, or a memory location.
18#[derive(Debug)]
19pub(crate) enum ExpressionResult {
20    #[allow(dead_code)]
21    Value(u64),
22    Location(VariableLocation),
23}
24
25// /// A [Variable] will have either a valid value, or some reason why a value could not be constructed.
26// /// - If we encounter expected errors, they will be displayed to the user as defined below.
27// /// - If we encounter unexpected errors, they will be treated as proper errors and will propagated
28// ///   to the calling process as an `Err()`
29// #[derive(Clone, Debug, PartialEq, Eq, Default)]
30// pub enum VariableValue {
31//     /// A valid value of this variable
32//     Valid(String),
33//     /// Notify the user that we encountered a problem correctly resolving the variable.
34//     /// - The variable will be visible to the user, as will the other field of the variable.
35//     /// - The contained warning message will be displayed to the user.
36//     /// - The debugger will not attempt to resolve additional fields or children of this variable.
37//     Error(String),
38//     /// The value has not been set. This could be because ...
39//     /// - It is too early in the process to have discovered its value, or ...
40//     /// - The variable cannot have a stored value, e.g. a `struct`. In this case, please use
41//     ///   `Variable::get_value` to infer a human readable value from the value of the struct's fields.
42//     #[default]
43//     Empty,
44// }
45
46// impl std::fmt::Display for VariableValue {
47//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48//         match self {
49//             VariableValue::Valid(value) => value.fmt(f),
50//             VariableValue::Error(error) => write!(f, "< {error} >"),
51//             VariableValue::Empty => write!(
52//                 f,
53//                 "Value not set. Please use Variable::get_value() to infer a human readable variable value"
54//             ),
55//         }
56//     }
57// }
58
59// impl VariableValue {
60//     /// Returns `true` if the variable resolver did not encounter an error, `false` otherwise.
61//     pub fn is_valid(&self) -> bool {
62//         !matches!(self, VariableValue::Error(_))
63//     }
64
65//     /// Returns `true` if no value or error is present, `false` otherwise.
66//     pub fn is_empty(&self) -> bool {
67//         matches!(self, VariableValue::Empty)
68//     }
69// }
70
71#[derive(Debug, Clone, PartialEq, Eq, Default)]
72pub enum VariableLocation {
73    /// Location of the variable is not known. This means that it has not been evaluated yet.
74    #[default]
75    Unknown,
76    /// The variable does not have a location currently, probably due to optimisations.
77    Unavailable,
78    /// The variable can be found in memory, at this address.
79    Address(u64),
80    /// The value of the variable is directly available.
81    Value,
82    /// There was an error evaluating the variable location.
83    Error(String),
84    /// Support for handling the location of this variable is not (yet) implemented.
85    Unsupported(String),
86}
87
88impl VariableLocation {
89    /// Return the memory address, if available. Otherwise an error is returned.
90    pub fn memory_address(&self) -> Result<u64, ExtractError> {
91        match self {
92            VariableLocation::Address(address) => Ok(*address),
93            other => Err(ExtractError::WarnAndContinue {
94                message: format!("Variable does not have a memory location: location={other:?}"),
95            }),
96        }
97    }
98
99    /// Check if the location is valid, ie. not an error, unsupported, or unavailable.
100    pub fn valid(&self) -> bool {
101        match self {
102            VariableLocation::Address(_) | VariableLocation::Value | VariableLocation::Unknown => {
103                true
104            }
105            _other => false,
106        }
107    }
108}
109
110impl std::fmt::Display for VariableLocation {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            VariableLocation::Unknown => "<unknown value>".fmt(f),
114            VariableLocation::Unavailable => "<value not available>".fmt(f),
115            VariableLocation::Address(address) => write!(f, "{address:#010X}"),
116            VariableLocation::Value => "<not applicable - statically stored value>".fmt(f),
117            VariableLocation::Error(error) => error.fmt(f),
118            VariableLocation::Unsupported(reason) => reason.fmt(f),
119        }
120    }
121}
122
123// /// If a DW_AT_byte_size attribute exists, return the u64 value, otherwise (including errors) return None
124// pub(crate) fn extract_byte_size(node_die: &DebuggingInformationEntry<GimliReader>) -> Option<u64> {
125//     match node_die.attr(gimli::DW_AT_byte_size) {
126//         Ok(Some(byte_size_attr)) => match byte_size_attr.value() {
127//             AttributeValue::Udata(byte_size) => Some(byte_size),
128//             AttributeValue::Data1(byte_size) => Some(byte_size as u64),
129//             AttributeValue::Data2(byte_size) => Some(byte_size as u64),
130//             AttributeValue::Data4(byte_size) => Some(byte_size as u64),
131//             AttributeValue::Data8(byte_size) => Some(byte_size),
132//             other => {
133//                 eprintln!("Unimplemented: DW_AT_byte_size value: {other:?}");
134//                 None
135//             }
136//         },
137//         Ok(None) => None,
138//         Err(error) => {
139//             eprintln!(
140//                 "Failed to extract byte_size: {error:?} for debug_entry {:?}",
141//                 node_die.tag().static_string()
142//             );
143//             None
144//         }
145//     }
146// }
147
148// pub(crate) fn extract_line(attribute_value: AttributeValue<GimliReader>) -> Option<u64> {
149//     match attribute_value {
150//         AttributeValue::Udata(line) => Some(line),
151//         _ => None,
152//     }
153// }
154
155// /// If file information is available, it returns `Some(directory:PathBuf, file_name:String)`, otherwise `None`.
156// pub(crate) fn extract_file(
157//     // _debug_info: &DebugInfo,
158//     _unit: &gimli::Unit<GimliReader>,
159//     _attribute_value: AttributeValue<GimliReader>,
160// ) -> Option<(TypedPathBuf, String)> {
161//     // match attribute_value {
162//     //     AttributeValue::FileIndex(index) => {
163//     //         if let Some((Some(file), Some(path))) = debug_info.find_file_and_directory(unit, index)
164//     //         {
165//     //             Some((path, file))
166//     //         } else {
167//     //             eprintln!("Unable to extract file or path from {:?}.", attribute_value);
168//     //             None
169//     //         }
170//     //     }
171//     //     other => {
172//     //         eprintln!(
173//     //             "Unable to extract file information from attribute value {:?}: Not implemented.",
174//     //             other
175//     //         );
176//     //         None
177//     //     }
178//     // }
179//     None
180// }
181
182/// Tries to get the result of a DWARF expression in the form of a Piece.
183pub(crate) fn expression_to_piece<ENDIAN: Endianity>(
184    expression: gimli::Expression<GimliReader<ENDIAN>>,
185    encoding: gimli::Encoding,
186) -> Result<Vec<gimli::Piece<GimliReader<ENDIAN>, usize>>, ExtractError> {
187    let mut evaluation = expression.evaluation(encoding);
188    let mut result = evaluation.evaluate()?;
189
190    loop {
191        result = match result {
192            EvaluationResult::Complete => return Ok(evaluation.result()),
193            // EvaluationResult::RequiresMemory { address, size, .. } => {
194            //     read_memory(size, memory, address, &mut evaluation)?
195            // }
196            // EvaluationResult::RequiresFrameBase => {
197            //     provide_frame_base(frame_info.frame_base, &mut evaluation)?
198            // }
199            // EvaluationResult::RequiresRegister {
200            //     register,
201            //     base_type,
202            // } => provide_register(frame_info.registers, register, base_type, &mut evaluation)?,
203            EvaluationResult::RequiresRelocatedAddress(address_index) => {
204                // The address_index as an offset from 0, so just pass it into the next step.
205                evaluation.resume_with_relocated_address(address_index)?
206            }
207            // EvaluationResult::RequiresCallFrameCfa => {
208            //     provide_cfa(frame_info.canonical_frame_address, &mut evaluation)?
209            // }
210            unimplemented_expression => {
211                return Err(ExtractError::WarnAndContinue {
212                    message: format!(
213                        "Unimplemented: Expressions that include {unimplemented_expression:?} are not currently supported."
214                    ),
215                });
216            }
217        }
218    }
219}
220
221/// Evaluate a [`gimli::Expression`] as a valid memory location.
222/// Return values are implemented as follows:
223/// - `Result<_, ExtractError>`: This happens when we encounter an error we did not expect, and will propagate upwards until the debugger request is failed. NOT GRACEFUL, and should be avoided.
224/// - `Result<ExpressionResult::Value(),_>`: The value is statically stored in the binary, and can be returned, and has no relevant memory location.
225/// - `Result<ExpressionResult::Location(),_>`: One of the variants of VariableLocation, and needs to be interpreted for handling the 'expected' errors we encounter during evaluation.
226pub(crate) fn evaluate_expression<ENDIAN: Endianity>(
227    expression: gimli::Expression<GimliReader<ENDIAN>>,
228    encoding: gimli::Encoding,
229) -> Result<ExpressionResult, ExtractError> {
230    fn evaluate_address(address: u64) -> ExpressionResult {
231        let location = if address >= u32::MAX as u64
232        /*&& !memory.supports_native_64bit_access()*/
233        {
234            VariableLocation::Error(format!(
235                "The memory location for this variable value ({:#010X}) is invalid. Please report this as a bug.",
236                address
237            ))
238        } else {
239            VariableLocation::Address(address)
240        };
241        ExpressionResult::Location(location)
242    }
243
244    let pieces = expression_to_piece(expression, encoding)?;
245
246    if pieces.is_empty() {
247        return Ok(ExpressionResult::Location(VariableLocation::Error(
248            "Error: expr_to_piece() returned 0 results".to_string(),
249        )));
250    }
251    if pieces.len() > 1 {
252        return Ok(ExpressionResult::Location(VariableLocation::Error(
253            "<unsupported memory implementation>".to_string(),
254        )));
255    }
256
257    let result = match &pieces[0].location {
258        Location::Empty => {
259            // This means the value was optimized away.
260            ExpressionResult::Location(VariableLocation::Unavailable)
261        }
262        Location::Address { address: 0 } => {
263            let error = "The value of this variable may have been optimized out of the debug info, by the compiler.".to_string();
264            ExpressionResult::Location(VariableLocation::Error(error))
265        }
266        Location::Address { address } => evaluate_address(*address),
267        Location::Value { value } => value.to_u64(u64::MAX).map(ExpressionResult::Value)?,
268        // Location::Register { register } => {
269        //     if let Some(address) = frame_info
270        //         .registers
271        //         .get_register_by_dwarf_id(register.0)
272        //         .and_then(|register| register.value)
273        //     {
274        //         match address.try_into() {
275        //             Ok(address) => evaluate_address(address),
276        //             Err(error) => ExpressionResult::Location(VariableLocation::Error(format!(
277        //                 "Error: Cannot convert register value to location address: {error:?}"
278        //             ))),
279        //         }
280        //     } else {
281        //         ExpressionResult::Location(VariableLocation::Error(format!(
282        //             "Error: Cannot resolve register: {register:?}"
283        //         )))
284        //     }
285        // }
286        l => ExpressionResult::Location(VariableLocation::Error(format!(
287            "Unimplemented: extract_location() found a location type: {:.100}",
288            format!("{l:?}")
289        ))),
290    };
291
292    Ok(result)
293}
294
295// /// - Find the location using either DW_AT_location, DW_AT_data_member_location, or DW_AT_frame_base attribute.
296// ///
297// /// Return values are implemented as follows:
298// /// - `Result<_, ExtractError>`: This happens when we encounter an error we did not expect, and will propagate upwards until the debugger request is failed. **NOT GRACEFUL**, and should be avoided.
299// /// - `Result<ExpressionResult::Value(),_>`: The value is statically stored in the binary, and can be returned, and has no relevant memory location.
300// /// - `Result<ExpressionResult::Location(),_>`: One of the variants of VariableLocation, and needs to be interpreted for handling the 'expected' errors we encounter during evaluation.
301// pub(crate) fn extract_location(
302//     debug_info: &DebugInfo,
303//     node_die: &gimli::DebuggingInformationEntry<GimliReader>,
304//     parent_location: &VariableLocation,
305//     memory: &mut dyn MemoryInterface,
306//     frame_info: StackFrameInfo<'_>,
307// ) -> Result<ExpressionResult, ExtractError> {
308//     trait ResultExt {
309//         /// Turns UnwindIncompleteResults into Unavailable locations
310//         fn convert_incomplete(self) -> Result<ExpressionResult, ExtractError>;
311//     }
312
313//     impl ResultExt for Result<ExpressionResult, ExtractError> {
314//         fn convert_incomplete(self) -> Result<ExpressionResult, ExtractError> {
315//             match self {
316//                 Ok(result) => Ok(result),
317//                 Err(ExtractError::WarnAndContinue { message }) => {
318//                     tracing::warn!("UnwindIncompleteResults: {:?}", message);
319//                     Ok(ExpressionResult::Location(VariableLocation::Unavailable))
320//                 }
321//                 e => e,
322//             }
323//         }
324//     }
325
326//     let mut attrs = node_die.attrs();
327//     while let Ok(Some(attr)) = attrs.next() {
328//         let result = match attr.name() {
329//                 gimli::DW_AT_location
330//                 | gimli::DW_AT_frame_base
331//                 | gimli::DW_AT_data_member_location => match attr.value() {
332//                     gimli::AttributeValue::Exprloc(expression) => evaluate_expression(memory, expression, frame_info)
333//                         .convert_incomplete()?,
334
335//                     gimli::AttributeValue::Udata(offset_from_location) => {
336//                         let location = if let VariableLocation::Address(address) = parent_location {
337//                             let Some(location) = address.checked_add(offset_from_location) else {
338//                                 return Err(ExtractError::WarnAndContinue {
339//                                     message: "Overflow calculating variable address"
340//                                         .to_string(),
341//                                 });
342//                             };
343
344//                             VariableLocation::Address(location)
345//                         } else {
346//                             parent_location.clone()
347//                         };
348
349//                         ExpressionResult::Location(location)
350//                     }
351
352//                     gimli::AttributeValue::LocationListsRef(location_list_offset) => self
353//                         .evaluate_location_list_ref(
354//                             debug_info,
355//                             location_list_offset,
356//                             frame_info,
357//                             memory,
358//                         )
359//                         .convert_incomplete()?,
360
361//                     other_attribute_value => {
362//                         ExpressionResult::Location(VariableLocation::Unsupported(format!(
363//                             "Unimplemented: extract_location() Could not extract location from: {:.100}",
364//                             format!("{other_attribute_value:?}")
365//                         )))
366//                     }
367//                 },
368
369//                 gimli::DW_AT_address_class => {
370//                     let location = match attr.value() {
371//                         gimli::AttributeValue::AddressClass(gimli::DwAddr(0)) => {
372//                             // We pass on the location of the parent, which will later to be used along with DW_AT_data_member_location to calculate the location of this variable.
373//                             parent_location.clone()
374//                         }
375//                         gimli::AttributeValue::AddressClass(address_class) => {
376//                             VariableLocation::Unsupported(format!(
377//                                 "Unimplemented: extract_location() found unsupported DW_AT_address_class(gimli::DwAddr({address_class:?}))"
378//                             ))
379//                         }
380//                         other_attribute_value => {
381//                             VariableLocation::Unsupported(format!(
382//                                 "Unimplemented: extract_location() found invalid DW_AT_address_class: {:.100}",
383//                                 format!("{other_attribute_value:?}")
384//                             ))
385//                         }
386//                     };
387
388//                     ExpressionResult::Location(location)
389//                 }
390
391//                 _other_attributes => {
392//                     // These will be handled elsewhere.
393//                     continue;
394//                 }
395//             };
396
397//         return Ok(result);
398//     }
399
400//     // If we get here, we did not find a location attribute, then leave the value as Unknown.
401//     Ok(ExpressionResult::Location(VariableLocation::Unknown))
402// }
403
404// pub(crate) fn extract_location(
405//     attrs: &[gimli::Attribute<GimliReader>],
406//     unit: gimli::UnitRef<GimliReader>,
407// ) -> Result<ExpressionResult, ExtractError> {
408//     trait ResultExt {
409//         /// Turns UnwindIncompleteResults into Unavailable locations
410//         fn convert_incomplete(self) -> Result<ExpressionResult, ExtractError>;
411//     }
412
413//     impl ResultExt for Result<ExpressionResult, ExtractError> {
414//         fn convert_incomplete(self) -> Result<ExpressionResult, ExtractError> {
415//             match self {
416//                 Ok(result) => Ok(result),
417//                 Err(ExtractError::WarnAndContinue { .. }) => {
418//                     // tracing::warn!("UnwindIncompleteResults: {:?}", message);
419//                     Ok(ExpressionResult::Location(VariableLocation::Unavailable))
420//                 }
421//                 e => e,
422//             }
423//         }
424//     }
425
426//     for attr in attrs {
427//         let result = match attr.name() {
428//             gimli::DW_AT_location | gimli::DW_AT_frame_base | gimli::DW_AT_data_member_location => {
429//                 match attr.value() {
430//                     gimli::AttributeValue::Exprloc(expression) => {
431//                         evaluate_expression(expression, unit.unit.encoding())
432//                             .convert_incomplete()?
433//                     }
434
435//                     // gimli::AttributeValue::Udata(offset_from_location) => {
436//                     //     let location = if let VariableLocation::Address(address) = parent_location {
437//                     //         let Some(location) = address.checked_add(offset_from_location) else {
438//                     //             return Err(ExtractError::WarnAndContinue {
439//                     //                 message: "Overflow calculating variable address".to_string(),
440//                     //             });
441//                     //         };
442
443//                     //         VariableLocation::Address(location)
444//                     //     } else {
445//                     //         parent_location.clone()
446//                     //     };
447
448//                     //     ExpressionResult::Location(location)
449//                     // }
450
451//                     // gimli::AttributeValue::LocationListsRef(location_list_offset) => self
452//                     //     .evaluate_location_list_ref(
453//                     //         debug_info,
454//                     //         location_list_offset,
455//                     //         frame_info,
456//                     //         memory,
457//                     //     )
458//                     //     .convert_incomplete()?,
459//                     other_attribute_value => {
460//                         ExpressionResult::Location(VariableLocation::Unsupported(format!(
461//                     "Unimplemented: extract_location() Could not extract location from: {:.100}",
462//                     format!("{other_attribute_value:?}")
463//                 )))
464//                     }
465//                 }
466//             }
467
468//             gimli::DW_AT_address_class => {
469//                 let location = match attr.value() {
470//                 // gimli::AttributeValue::AddressClass(gimli::DwAddr(0)) => {
471//                 //     // We pass on the location of the parent, which will later to be used along with DW_AT_data_member_location to calculate the location of this variable.
472//                 //     parent_location.clone()
473//                 // }
474//                 gimli::AttributeValue::AddressClass(address_class) => {
475//                     VariableLocation::Unsupported(format!(
476//                         "Unimplemented: extract_location() found unsupported DW_AT_address_class(gimli::DwAddr({address_class:?}))"
477//                     ))
478//                 }
479//                 other_attribute_value => {
480//                     VariableLocation::Unsupported(format!(
481//                         "Unimplemented: extract_location() found invalid DW_AT_address_class: {:.100}",
482//                         format!("{other_attribute_value:?}")
483//                     ))
484//                 }
485//             };
486
487//                 ExpressionResult::Location(location)
488//             }
489
490//             _other_attributes => {
491//                 // These will be handled elsewhere.
492//                 continue;
493//             }
494//         };
495//         return Ok(result);
496//     }
497//     Ok(ExpressionResult::Location(VariableLocation::Unknown))
498// }