Skip to main content

ghostscope_dwarf/core/
evaluation.rs

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