Skip to main content

ghostscope_dwarf/core/
evaluation.rs

1//! DWARF expression planning primitives
2//!
3//! This module keeps the expression op IR shared by DWARF lowering, semantic
4//! plans, CFI recovery, and compiler lowering. `RawExpressionResult` is a
5//! transient detail of DWARF expression lowering; semantic variable planning
6//! should traffic in `ParsedLocation`/`VariableLocation` instead.
7//!
8//! Design principles:
9//! 1. Preserve whether an expression describes a value or a location
10//! 2. Pre-compute as much as possible at compile time
11//! 3. Keep register and memory dependencies explicit for later lowering
12
13use std::collections::BTreeMap;
14use std::fmt;
15
16/// Raw, short-lived result of evaluating a DWARF expression.
17#[derive(Debug, Clone, PartialEq)]
18pub(crate) enum RawExpressionResult {
19    /// Direct value - expression result is the variable value (no memory read needed)
20    DirectValue(DirectValueResult),
21
22    /// Memory location - expression result is an address that needs to be dereferenced
23    MemoryLocation(LocationResult),
24
25    /// Variable is optimized out (no location/value available)
26    Optimized,
27
28    /// Composite location (multiple pieces) - for split variables
29    #[allow(dead_code)]
30    Composite(Vec<PieceResult>),
31}
32
33/// Direct value results - expression produces the variable value directly
34#[derive(Debug, Clone, PartialEq)]
35pub(crate) enum DirectValueResult {
36    /// Literal constant from DWARF expression (DW_OP_lit*, DW_OP_const*)
37    Constant(i64),
38
39    /// Link-time absolute address that must be rebased to a runtime address
40    /// before use (for example, DW_OP_implicit_pointer targeting static storage).
41    AbsoluteAddress(u64),
42
43    /// Implicit value embedded in DWARF (DW_OP_implicit_value)
44    ImplicitValue(Vec<u8>),
45
46    /// Register contains the variable value directly (DW_OP_reg*)
47    RegisterValue(u16),
48
49    /// Computed value from expression (DW_OP_stack_value)
50    /// This is a full expression that computes the value
51    ComputedValue {
52        /// Expression steps (stack-based computation)
53        steps: Vec<PlanExprOp>,
54        /// Expected result type size
55        result_size: MemoryAccessSize,
56    },
57}
58
59/// Memory location results - expression produces an address to be read via bpf_probe_read_user
60#[derive(Debug, Clone, PartialEq)]
61pub(crate) enum LocationResult {
62    /// Absolute memory address (DW_OP_addr)
63    Address(u64),
64
65    /// Register-based address with optional offset (DW_OP_breg*)
66    /// The register value will be read from pt_regs in eBPF
67    RegisterAddress {
68        register: u16, // DWARF register number
69        offset: Option<i64>,
70        size: Option<u64>, // Size hint for memory read
71    },
72
73    /// Complex computed address from multi-step expression
74    /// Will be evaluated step by step in eBPF
75    ComputedLocation {
76        /// Expression that computes the final address
77        steps: Vec<PlanExprOp>,
78    },
79}
80
81/// CFA (Canonical Frame Address) computation for stack variables
82#[derive(Debug, Clone, PartialEq)]
83pub enum CfaResult {
84    /// CFA = register + offset (most common case)
85    RegisterPlusOffset {
86        register: u16, // Typically RSP or RBP
87        offset: i64,
88    },
89    /// CFA computed by DWARF expression
90    Expression { steps: Vec<PlanExprOp> },
91}
92
93/// Caller-frame recovery rules materialized as planned expression ops.
94#[derive(Debug, Clone, PartialEq)]
95pub struct CallerFrameRecovery {
96    /// Steps that compute the current frame's CFA.
97    pub cfa_steps: Vec<PlanExprOp>,
98    /// DWARF register number that holds the caller's return address.
99    pub return_address_register: u16,
100    /// Steps that recover the caller PC from the current frame.
101    pub caller_pc_steps: Vec<PlanExprOp>,
102    /// Per-register recovery steps keyed by DWARF register number.
103    pub register_recovery_steps: BTreeMap<u16, Vec<PlanExprOp>>,
104}
105
106/// Piece of a composite location
107#[derive(Debug, Clone, PartialEq)]
108pub(crate) struct PieceResult {
109    /// Location of this piece
110    pub(crate) location: RawExpressionResult,
111    /// Size in bytes
112    pub(crate) size: u64,
113    /// Bit offset within the piece (for bit fields)
114    pub(crate) bit_offset: Option<u64>,
115}
116
117/// One caller-side case for recovering a callee's DW_OP_entry_value.
118#[derive(Debug, Clone, PartialEq)]
119pub struct EntryValueCase {
120    /// Link-time caller return PC from DW_AT_call_return_pc.
121    pub caller_return_pc: u64,
122    /// Materialized planned expression ops that recover the original caller value.
123    pub value_steps: Vec<PlanExprOp>,
124}
125
126/// Stack-machine expression op selected by DWARF lowering.
127///
128/// This is the single planned expression IR shared by DWARF expression
129/// lowering, read plans, CFI recovery, and compiler/eBPF lowering.
130#[derive(Debug, Clone, PartialEq)]
131pub enum PlanExprOp {
132    /// Load register value from pt_regs
133    LoadRegister(u16), // DWARF register number
134
135    /// Push constant
136    PushConstant(i64),
137
138    /// Memory dereference via bpf_probe_read_user
139    Dereference {
140        size: MemoryAccessSize,
141    },
142
143    /// Convert a target-module TLS offset into the current thread's address.
144    ///
145    /// This models DW_OP_form_tls_address. Runtime lowering is responsible for
146    /// combining the offset with the thread pointer for the traced thread.
147    FormTlsAddress,
148
149    /// Binary arithmetic operations (pop 2, push 1)
150    Add,
151    Sub,
152    Mul,
153    Div,
154    Mod,
155
156    /// Binary bitwise operations
157    And,
158    Or,
159    Xor,
160    Shl,
161    Shr,
162    Shra, // Arithmetic shift right
163
164    /// Unary operations
165    Not,
166    Neg,
167    Abs,
168
169    /// Stack manipulation
170    Dup,
171    Drop,
172    Swap,
173    Rot,
174    Pick(u8), // Pick nth item from stack
175
176    /// Comparison operations (pop 2, push bool)
177    Eq,
178    Ne,
179    Lt,
180    Le,
181    Gt,
182    Ge,
183
184    /// Control flow (simplified for eBPF)
185    If {
186        then_branch: Vec<PlanExprOp>,
187        else_branch: Vec<PlanExprOp>,
188    },
189
190    /// Recover a DW_OP_entry_value at runtime by matching the recovered caller
191    /// return PC against caller-side DW_AT_call_return_pc cases.
192    EntryValueLookup {
193        caller_pc_steps: Vec<PlanExprOp>,
194        cases: Vec<EntryValueCase>,
195    },
196}
197
198/// Memory access size for bpf_probe_read_user
199#[derive(Debug, Clone, Copy, PartialEq)]
200pub enum MemoryAccessSize {
201    U8,  // 1 byte
202    U16, // 2 bytes
203    U32, // 4 bytes
204    U64, // 8 bytes
205}
206
207impl MemoryAccessSize {
208    /// Get size in bytes
209    pub fn bytes(&self) -> usize {
210        match self {
211            MemoryAccessSize::U8 => 1,
212            MemoryAccessSize::U16 => 2,
213            MemoryAccessSize::U32 => 4,
214            MemoryAccessSize::U64 => 8,
215        }
216    }
217
218    /// Create MemoryAccessSize from byte size
219    pub fn from_size(size: u64) -> Self {
220        match size {
221            1 => MemoryAccessSize::U8,
222            2 => MemoryAccessSize::U16,
223            4 => MemoryAccessSize::U32,
224            8 => MemoryAccessSize::U64,
225            _ if size <= 8 => MemoryAccessSize::U64, // Default to U64 for larger sizes
226            _ => MemoryAccessSize::U64,              // Fallback
227        }
228    }
229}
230
231impl DirectValueResult {
232    /// Convert compute steps to a human-readable expression
233    fn steps_to_expression(steps: &[PlanExprOp]) -> String {
234        use ghostscope_platform::register_mapping::dwarf_reg_to_name;
235
236        // Stack for expression building
237        let mut stack: Vec<String> = Vec::new();
238
239        for step in steps {
240            match step {
241                PlanExprOp::LoadRegister(r) => {
242                    let reg_name = dwarf_reg_to_name(*r).unwrap_or("r?").to_string();
243                    stack.push(reg_name);
244                }
245                PlanExprOp::PushConstant(v) => {
246                    if *v >= 0 && *v <= 0xFF {
247                        stack.push(format!("{v}"));
248                    } else {
249                        stack.push(format!("0x{v:x}"));
250                    }
251                }
252                PlanExprOp::Add => {
253                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
254                        // Special case: register + small offset
255                        if a.chars()
256                            .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
257                            && b.parse::<i64>().is_ok()
258                            && b.parse::<i64>().unwrap().abs() < 1000
259                        {
260                            stack.push(format!("{a}+{b}"));
261                        } else {
262                            stack.push(format!("({a}+{b})"));
263                        }
264                    } else {
265                        stack.push("?+?".to_string());
266                    }
267                }
268                PlanExprOp::Sub => {
269                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
270                        stack.push(format!("({a}-{b})"));
271                    } else {
272                        stack.push("?-?".to_string());
273                    }
274                }
275                PlanExprOp::Mul => {
276                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
277                        stack.push(format!("{a}*{b}"));
278                    } else {
279                        stack.push("?*?".to_string());
280                    }
281                }
282                PlanExprOp::Div => {
283                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
284                        stack.push(format!("({a}/{b})"));
285                    } else {
286                        stack.push("?/?".to_string());
287                    }
288                }
289                PlanExprOp::Mod => {
290                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
291                        stack.push(format!("({a}%{b})"));
292                    } else {
293                        stack.push("?%?".to_string());
294                    }
295                }
296                PlanExprOp::And => {
297                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
298                        stack.push(format!("({a}&{b})"));
299                    } else {
300                        stack.push("?&?".to_string());
301                    }
302                }
303                PlanExprOp::Or => {
304                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
305                        stack.push(format!("({a}|{b})"));
306                    } else {
307                        stack.push("?|?".to_string());
308                    }
309                }
310                PlanExprOp::Xor => {
311                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
312                        stack.push(format!("({a}^{b})"));
313                    } else {
314                        stack.push("?^?".to_string());
315                    }
316                }
317                PlanExprOp::Shl => {
318                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
319                        stack.push(format!("({a}<<{b})"));
320                    } else {
321                        stack.push("?<<?".to_string());
322                    }
323                }
324                PlanExprOp::Shr => {
325                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
326                        stack.push(format!("({a}>>{b})"));
327                    } else {
328                        stack.push("?>>?".to_string());
329                    }
330                }
331                PlanExprOp::Shra => {
332                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
333                        stack.push(format!("({a}>>>{b})"));
334                    } else {
335                        stack.push("?>>>?".to_string());
336                    }
337                }
338                PlanExprOp::Not => {
339                    if let Some(a) = stack.pop() {
340                        stack.push(format!("~{a}"));
341                    } else {
342                        stack.push("~?".to_string());
343                    }
344                }
345                PlanExprOp::Neg => {
346                    if let Some(a) = stack.pop() {
347                        stack.push(format!("-{a}"));
348                    } else {
349                        stack.push("-?".to_string());
350                    }
351                }
352                PlanExprOp::Abs => {
353                    if let Some(a) = stack.pop() {
354                        stack.push(format!("|{a}|"));
355                    } else {
356                        stack.push("|?|".to_string());
357                    }
358                }
359                PlanExprOp::Dereference { size } => {
360                    if let Some(a) = stack.pop() {
361                        stack.push(format!("*({a} as {size})"));
362                    } else {
363                        stack.push(format!("*(? as {size})"));
364                    }
365                }
366                PlanExprOp::FormTlsAddress => {
367                    if let Some(a) = stack.pop() {
368                        stack.push(format!("tls({a})"));
369                    } else {
370                        stack.push("tls(?)".to_string());
371                    }
372                }
373                PlanExprOp::Dup => {
374                    if let Some(top) = stack.last() {
375                        stack.push(top.clone());
376                    }
377                }
378                PlanExprOp::Drop => {
379                    stack.pop();
380                }
381                PlanExprOp::Swap => {
382                    if stack.len() >= 2 {
383                        let len = stack.len();
384                        stack.swap(len - 1, len - 2);
385                    }
386                }
387                PlanExprOp::Rot => {
388                    if stack.len() >= 3 {
389                        let len = stack.len();
390                        let third = stack.remove(len - 3);
391                        stack.push(third);
392                    }
393                }
394                PlanExprOp::Pick(n) => {
395                    if stack.len() > *n as usize {
396                        let idx = stack.len() - 1 - (*n as usize);
397                        let val = stack[idx].clone();
398                        stack.push(val);
399                    } else {
400                        stack.push("?".to_string());
401                    }
402                }
403                PlanExprOp::Eq => {
404                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
405                        stack.push(format!("({a}=={b})"));
406                    } else {
407                        stack.push("?==?".to_string());
408                    }
409                }
410                PlanExprOp::Ne => {
411                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
412                        stack.push(format!("({a}!={b})"));
413                    } else {
414                        stack.push("?!=?".to_string());
415                    }
416                }
417                PlanExprOp::Lt => {
418                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
419                        stack.push(format!("({a}<{b})"));
420                    } else {
421                        stack.push("?<?".to_string());
422                    }
423                }
424                PlanExprOp::Le => {
425                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
426                        stack.push(format!("({a}<={b})"));
427                    } else {
428                        stack.push("?<=?".to_string());
429                    }
430                }
431                PlanExprOp::Gt => {
432                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
433                        stack.push(format!("({a}>{b})"));
434                    } else {
435                        stack.push("?>?".to_string());
436                    }
437                }
438                PlanExprOp::Ge => {
439                    if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
440                        stack.push(format!("({a}>={b})"));
441                    } else {
442                        stack.push("?>=?".to_string());
443                    }
444                }
445                PlanExprOp::If {
446                    then_branch,
447                    else_branch,
448                } => {
449                    if let Some(cond) = stack.pop() {
450                        stack.push(format!("if {cond} then ... else ..."));
451                    } else {
452                        stack.push("if ? then ... else ...".to_string());
453                    }
454                    // Note: Full if-then-else evaluation would require recursive expression building
455                    _ = then_branch;
456                    _ = else_branch;
457                }
458                PlanExprOp::EntryValueLookup { cases, .. } => {
459                    stack.push(format!("entry_value[{} cases]", cases.len()));
460                }
461            }
462        }
463
464        // Return the top of stack or a placeholder
465        stack.pop().unwrap_or_else(|| "?".to_string())
466    }
467}
468
469pub(crate) fn plan_expr_steps_to_expression(steps: &[PlanExprOp]) -> String {
470    DirectValueResult::steps_to_expression(steps)
471}
472
473impl LocationResult {
474    /// Convert compute steps to a human-readable expression (reuse from DirectValueResult)
475    fn steps_to_expression(steps: &[PlanExprOp]) -> String {
476        DirectValueResult::steps_to_expression(steps)
477    }
478}
479
480impl fmt::Display for RawExpressionResult {
481    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        match self {
483            RawExpressionResult::DirectValue(dv) => write!(f, "[DirectValue] {dv}"),
484            RawExpressionResult::MemoryLocation(loc) => write!(f, "[Memory] {loc}"),
485            RawExpressionResult::Optimized => write!(f, "<optimized out>"),
486            RawExpressionResult::Composite(pieces) => {
487                write!(f, "Composite[{} pieces]", pieces.len())
488            }
489        }
490    }
491}
492
493impl fmt::Display for DirectValueResult {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        use ghostscope_platform::register_mapping::dwarf_reg_to_name;
496
497        match self {
498            DirectValueResult::Constant(c) => {
499                if *c >= 0 && *c <= 0xFF {
500                    write!(f, "{c} (0x{c:x})")
501                } else {
502                    write!(f, "0x{c:x}")
503                }
504            }
505            DirectValueResult::AbsoluteAddress(addr) => write!(f, "&@0x{addr:x}"),
506            DirectValueResult::RegisterValue(r) => {
507                if let Some(name) = dwarf_reg_to_name(*r) {
508                    write!(f, "{name}")
509                } else {
510                    write!(f, "r{r}")
511                }
512            }
513            DirectValueResult::ImplicitValue(bytes) => {
514                if bytes.len() <= 8 {
515                    write!(f, "implicit[")?;
516                    for (i, b) in bytes.iter().enumerate() {
517                        if i > 0 {
518                            write!(f, " ")?;
519                        }
520                        write!(f, "{b:02x}")?;
521                    }
522                    write!(f, "]")
523                } else {
524                    write!(f, "implicit[{} bytes]", bytes.len())
525                }
526            }
527            DirectValueResult::ComputedValue {
528                steps,
529                result_size: _,
530            } => {
531                // Convert compute steps to a readable expression
532                write!(f, "=")?;
533
534                // Simple expression builder for common patterns
535                let expr = Self::steps_to_expression(steps);
536                write!(f, "{expr}")
537            }
538        }
539    }
540}
541
542impl fmt::Display for LocationResult {
543    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544        use ghostscope_platform::register_mapping::dwarf_reg_to_name;
545
546        match self {
547            LocationResult::Address(addr) => write!(f, "@0x{addr:x}"),
548            LocationResult::RegisterAddress {
549                register,
550                offset,
551                size,
552            } => {
553                let reg_name = dwarf_reg_to_name(*register).unwrap_or("r?");
554
555                match (offset, size) {
556                    (Some(o), Some(s)) => {
557                        let offset = *o;
558                        if offset >= 0 {
559                            write!(f, "@[{reg_name}+{offset}]:{s}")
560                        } else {
561                            let neg = -offset;
562                            write!(f, "@[{reg_name}-{neg}]:{s}")
563                        }
564                    }
565                    (Some(o), None) => {
566                        let offset = *o;
567                        if offset >= 0 {
568                            write!(f, "@[{reg_name}+{offset}]")
569                        } else {
570                            let neg = -offset;
571                            write!(f, "@[{reg_name}-{neg}]")
572                        }
573                    }
574                    (None, Some(s)) => write!(f, "@[{reg_name}]:{s}"),
575                    (None, None) => write!(f, "@[{reg_name}]"),
576                }
577            }
578            LocationResult::ComputedLocation { steps } => {
579                // Convert compute steps to a readable expression for the address
580                write!(f, "@[")?;
581                let expr = Self::steps_to_expression(steps);
582                write!(f, "{expr}]")
583            }
584        }
585    }
586}
587
588impl fmt::Display for MemoryAccessSize {
589    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590        match self {
591            MemoryAccessSize::U8 => write!(f, "u8"),
592            MemoryAccessSize::U16 => write!(f, "u16"),
593            MemoryAccessSize::U32 => write!(f, "u32"),
594            MemoryAccessSize::U64 => write!(f, "u64"),
595        }
596    }
597}
598
599impl fmt::Display for PlanExprOp {
600    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601        use ghostscope_platform::register_mapping::dwarf_reg_to_name;
602
603        match self {
604            PlanExprOp::LoadRegister(r) => {
605                if let Some(name) = dwarf_reg_to_name(*r) {
606                    write!(f, "load {name}")
607                } else {
608                    write!(f, "load r{r}")
609                }
610            }
611            PlanExprOp::PushConstant(v) => write!(f, "push {v}"),
612            PlanExprOp::Dereference { size } => write!(f, "deref {size}"),
613            PlanExprOp::FormTlsAddress => write!(f, "form_tls_address"),
614            PlanExprOp::Add => write!(f, "add"),
615            PlanExprOp::Sub => write!(f, "sub"),
616            PlanExprOp::Mul => write!(f, "mul"),
617            PlanExprOp::Div => write!(f, "div"),
618            PlanExprOp::Mod => write!(f, "mod"),
619            PlanExprOp::And => write!(f, "and"),
620            PlanExprOp::Or => write!(f, "or"),
621            PlanExprOp::Xor => write!(f, "xor"),
622            PlanExprOp::Shl => write!(f, "shl"),
623            PlanExprOp::Shr => write!(f, "shr"),
624            PlanExprOp::Shra => write!(f, "shra"),
625            PlanExprOp::Not => write!(f, "not"),
626            PlanExprOp::Neg => write!(f, "neg"),
627            PlanExprOp::Abs => write!(f, "abs"),
628            PlanExprOp::Dup => write!(f, "dup"),
629            PlanExprOp::Drop => write!(f, "drop"),
630            PlanExprOp::Swap => write!(f, "swap"),
631            PlanExprOp::Rot => write!(f, "rot"),
632            PlanExprOp::Pick(n) => write!(f, "pick {n}"),
633            PlanExprOp::Eq => write!(f, "eq"),
634            PlanExprOp::Ne => write!(f, "ne"),
635            PlanExprOp::Lt => write!(f, "lt"),
636            PlanExprOp::Le => write!(f, "le"),
637            PlanExprOp::Gt => write!(f, "gt"),
638            PlanExprOp::Ge => write!(f, "ge"),
639            PlanExprOp::If {
640                then_branch,
641                else_branch,
642            } => {
643                write!(
644                    f,
645                    "if[then:{} else:{}]",
646                    then_branch.len(),
647                    else_branch.len()
648                )
649            }
650            PlanExprOp::EntryValueLookup { cases, .. } => {
651                write!(f, "entry_value_lookup[cases:{}]", cases.len())
652            }
653        }
654    }
655}