rudy_dwarf/
expressions.rs

1//! DWARF expression evaluation for location information
2
3use anyhow::{Context, Result};
4
5use crate::{file::Expression, Die, DwarfDb};
6
7/// Get location expression from a DIE entry
8#[salsa::tracked]
9pub fn get_location_expr<'db>(
10    db: &'db dyn DwarfDb,
11    entry: Die,
12    attr: gimli::DwAt,
13) -> Option<Expression> {
14    let location = match entry.get_attr(db, attr) {
15        Ok(l) => l,
16        Err(e) => {
17            tracing::warn!(
18                "Failed to get location attribute for entry: {}: {e}",
19                entry.print(db)
20            );
21            return None;
22        }
23    };
24
25    let gimli::AttributeValue::Exprloc(expr) = location else {
26        tracing::error!("Location not an expression for entry: {}", entry.print(db));
27        return None;
28    };
29
30    Some(expr)
31}
32
33pub trait ExpressionContext {
34    fn get_register(&self, register: u16) -> Result<u64>;
35    fn get_stack_pointer(&self) -> Result<u64>;
36}
37
38/// Get function frame base register
39fn get_function_frame_base(
40    db: &dyn DwarfDb,
41    function_entry: Die,
42    context: &dyn ExpressionContext,
43) -> anyhow::Result<u64> {
44    let Some(loc_exp) = get_location_expr(db, function_entry, gimli::DW_AT_frame_base) else {
45        anyhow::bail!("Failed to get location expression for function");
46    };
47    // evaluation the expression
48    let unit_ref = function_entry.unit_ref(db)?;
49
50    let mut eval = loc_exp.evaluation(unit_ref.encoding());
51    let mut result = eval.evaluate()?;
52    let result = loop {
53        match result {
54            gimli::EvaluationResult::Complete => {
55                // evaluation complete -- break the loop
56                break eval.result();
57            }
58            gimli::EvaluationResult::RequiresCallFrameCfa => {
59                let sp = context.get_stack_pointer()?;
60                result = eval.resume_with_call_frame_cfa(sp).with_context(|| {
61                    format!("Failed to resume evaluation with call frame CFA with sp: {sp:#x}")
62                })?;
63            }
64            r => {
65                todo!("handle incomplete evaluation: {r:?}");
66            }
67        }
68    };
69
70    debug_assert_eq!(result.len(), 1, "got: {result:#?}");
71
72    let result = result[0].clone();
73
74    match result.location {
75        // We expect the location to be an address
76        gimli::Location::Address { address } => {
77            tracing::debug!("frame base address: {address:#x}");
78            Ok(address)
79        }
80        gimli::Location::Register { register, .. } => {
81            let reg_value = context.get_register(register.0)?;
82            tracing::debug!("frame base register value: {reg_value:#x}");
83            Ok(reg_value)
84        }
85        loc => Err(anyhow::anyhow!(
86            "Unexpected location type for frame base: {loc:?}"
87        )),
88    }
89}
90
91/// Resolve data location for a variable using DWARF expressions
92pub fn resolve_data_location(
93    db: &dyn DwarfDb,
94    function: Die,
95    base_address: u64,
96    variable_entry_id: Die,
97    context: &dyn ExpressionContext,
98) -> Result<Option<u64>> {
99    let Some(expr) = get_location_expr(db, variable_entry_id, gimli::DW_AT_location) else {
100        return Ok(None);
101    };
102
103    let unit_ref = function.unit_ref(db)?;
104
105    // evaluation the expression
106
107    let mut eval = expr.evaluation(unit_ref.encoding());
108    let mut result = eval.evaluate()?;
109    let result = loop {
110        match result {
111            gimli::EvaluationResult::Complete => {
112                // evaluation complete -- break the loop
113                break eval.result();
114            }
115            gimli::EvaluationResult::RequiresFrameBase => {
116                // get the frame base from the enclosing function
117                let frame_base = get_function_frame_base(db, function, context)?;
118                result = eval.resume_with_frame_base(frame_base)?;
119            }
120            gimli::EvaluationResult::RequiresRegister { register, .. } => {
121                let reg = register.0;
122                let reg_value = context.get_register(reg)?;
123                tracing::debug!("register value: {reg} = {reg_value:#x}");
124                result = eval.resume_with_register(gimli::Value::Generic(reg_value))?;
125            }
126            gimli::EvaluationResult::RequiresRelocatedAddress(addr) => {
127                // We have an address that is relative to where
128                // the data is loaded an need to shift it appropriately
129                let relocated_addr = base_address + addr;
130
131                tracing::debug!("relocated address: {addr:#x} -> {relocated_addr:#x}",);
132                result = eval.resume_with_relocated_address(relocated_addr)?;
133            }
134            r => {
135                todo!("handle incomplete evaluation: {r:?}");
136            }
137        }
138    };
139
140    // let mut data_buffer = vec![];
141    if let [piece] = &result[..] {
142        tracing::debug!("single piece: {piece:#?}");
143        match &piece.location {
144            gimli::Location::Address { address } => {
145                tracing::debug!("address: {address:#x}");
146                Ok(Some(*address))
147            }
148            loc => {
149                todo!("handle location: {loc:#?}");
150            }
151        }
152    } else {
153        todo!("support multiple pieces: {result:#?}");
154    }
155}