1use crate::core::{
4 AddressExpr, Availability, DieRef, HelperMode, InlineContextId, MemoryAccessSize,
5 PieceLocation, PlanExprOp, Provenance, Result, RuntimeCapabilities, RuntimeRequirement, TypeId,
6 UnsupportedReason, VariableId, VariableLocation, VerifierRisk,
7};
8use crate::semantics::{
9 indexable_element_layout, member_layout, strip_type_aliases, PcRange, TypeLayoutError,
10};
11use crate::TypeInfo;
12use std::path::PathBuf;
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct VisibleVariable {
17 pub name: String,
18 pub type_name: String,
19 pub dwarf_type: Option<TypeInfo>,
20 pub declaration: Option<DieRef>,
21 pub type_id: Option<TypeId>,
22 pub location: VariableLocation,
23 pub availability: Availability,
24 pub scope_depth: usize,
25 pub is_parameter: bool,
26 pub is_artificial: bool,
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct VariableQueryDiagnostic {
32 pub pc: u64,
33 pub name: Option<String>,
34 pub scope_depth: usize,
35 pub availability: Availability,
36 pub detail: String,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub struct VisibleVariablesResult {
42 pub variables: Vec<VisibleVariable>,
43 pub diagnostics: Vec<VariableQueryDiagnostic>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum VariableLoweringKind {
48 DirectValue,
49 UserMemoryRead,
50 Composite,
51 Unavailable,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct VariableLoweringPlan {
56 pub kind: VariableLoweringKind,
57 pub availability: Availability,
58 pub requirements: Vec<RuntimeRequirement>,
59 pub helper_mode: HelperMode,
60 pub required_registers: Vec<u16>,
61 pub estimated_stack_bytes: usize,
62 pub verifier_risk: VerifierRisk,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum AddressOrigin {
67 LinkTime,
68 LinkTimeBase,
69 RuntimeDerived,
70 Unknown,
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct PlannedAddress {
75 pub kind: PlannedAddressKind,
76 pub origin: AddressOrigin,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum RuntimeComputedKind {
81 Address,
82 Value,
83}
84
85#[derive(Debug, Clone, PartialEq)]
91pub struct RuntimeComputedExpr {
92 kind: RuntimeComputedKind,
93 ops: Vec<PlanExprOp>,
94}
95
96impl RuntimeComputedExpr {
97 pub(crate) fn address(ops: Vec<PlanExprOp>) -> Self {
98 Self {
99 kind: RuntimeComputedKind::Address,
100 ops,
101 }
102 }
103
104 pub(crate) fn value(ops: Vec<PlanExprOp>) -> Self {
105 Self {
106 kind: RuntimeComputedKind::Value,
107 ops,
108 }
109 }
110
111 pub fn ops(&self) -> &[PlanExprOp] {
112 &self.ops
113 }
114
115 pub fn kind(&self) -> RuntimeComputedKind {
116 self.kind
117 }
118
119 pub fn runtime_requirements(&self) -> Vec<RuntimeRequirement> {
120 requirements_for_steps(&self.ops)
121 }
122
123 pub fn required_registers(&self) -> Vec<u16> {
124 registers_for_steps(&self.ops)
125 }
126
127 pub fn estimated_stack_bytes(&self) -> usize {
128 estimate_steps_stack_bytes(&self.ops)
129 }
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub enum PlannedAddressKind {
134 Constant { address: u64 },
135 RegisterOffset { dwarf_reg: u16, offset: i64 },
136 FrameBaseRelative { offset: i64 },
137 RuntimeComputed { expr: RuntimeComputedExpr },
138}
139
140#[derive(Debug, Clone, PartialEq)]
141pub enum PlannedValue {
142 Constant {
143 value: i64,
144 size: MemoryAccessSize,
145 },
146 RegisterValue {
147 dwarf_reg: u16,
148 size: MemoryAccessSize,
149 },
150 RuntimeComputed {
151 expr: RuntimeComputedExpr,
152 result_size: MemoryAccessSize,
153 },
154 ImplicitBytes(Vec<u8>),
155 AddressValue {
156 address: PlannedAddress,
157 size: MemoryAccessSize,
158 },
159}
160
161#[derive(Debug, Clone, PartialEq)]
162pub enum VariableMaterialization {
163 DirectValue { value: PlannedValue },
164 UserMemoryRead { address: PlannedAddress },
165 Composite { pieces: Vec<PieceLocation> },
166 Unavailable { availability: Availability },
167}
168
169#[derive(Debug, Clone, PartialEq)]
170pub enum LvalueAddressPlan {
171 Address { address: PlannedAddress },
172 Unavailable { availability: Availability },
173}
174
175#[derive(Debug, Clone, PartialEq)]
176pub struct VariableMaterializationPlan {
177 pub name: String,
178 pub type_name: String,
179 pub access_path: VariableAccessPath,
180 pub module_path: Option<PathBuf>,
181 pub dwarf_type: Option<TypeInfo>,
182 pub availability: Availability,
183 pub lowering: VariableLoweringPlan,
184 pub materialization: VariableMaterialization,
185}
186
187#[derive(Debug, Clone, PartialEq)]
189pub struct VariableReadPlan {
190 pub name: String,
191 pub type_name: String,
192 pub access_path: VariableAccessPath,
193 pub module_path: Option<PathBuf>,
194 pub dwarf_type: Option<TypeInfo>,
195 pub declaration: Option<DieRef>,
196 pub type_id: Option<TypeId>,
197 pub location: VariableLocation,
198 pub availability: Availability,
199 pub scope_depth: usize,
200 pub is_parameter: bool,
201 pub is_artificial: bool,
202 pub pc_range: Option<PcRange>,
203 pub inline_context: Option<InlineContextId>,
204 pub provenance: Provenance,
205}
206
207#[derive(Debug, Clone, Default, PartialEq, Eq)]
208pub struct VariableAccessPath {
209 pub segments: Vec<VariableAccessSegment>,
210}
211
212impl VariableAccessPath {
213 pub fn new(segments: Vec<VariableAccessSegment>) -> Self {
214 Self { segments }
215 }
216
217 pub fn fields(fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
218 Self {
219 segments: fields
220 .into_iter()
221 .map(|field| VariableAccessSegment::Field(field.into()))
222 .collect(),
223 }
224 }
225
226 fn suffix(&self) -> String {
227 let mut suffix = String::new();
228 for segment in &self.segments {
229 match segment {
230 VariableAccessSegment::Field(field) => {
231 suffix.push('.');
232 suffix.push_str(field);
233 }
234 VariableAccessSegment::ArrayIndex(index) => {
235 suffix.push('[');
236 suffix.push_str(&index.to_string());
237 suffix.push(']');
238 }
239 VariableAccessSegment::Dereference => suffix.push_str(".*"),
240 }
241 }
242 suffix
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub enum VariableAccessSegment {
248 Field(String),
249 ArrayIndex(i64),
250 Dereference,
251}
252
253#[derive(Debug, thiserror::Error)]
254pub enum PlanError {
255 #[error("Variable '{name}' has no DWARF type information for access planning")]
256 MissingTypeInfo { name: String },
257
258 #[error("Unknown member '{field}' in {kind} '{type_name}' (known members: {members})")]
259 UnknownMember {
260 kind: &'static str,
261 type_name: String,
262 field: String,
263 members: String,
264 },
265
266 #[error("array access requires array or pointer type, got '{type_name}'")]
267 InvalidArrayAccess { type_name: String },
268
269 #[error("Pointer arithmetic requires a pointer or array expression, got '{type_name}'")]
270 InvalidPointerArithmetic { type_name: String },
271
272 #[error("pointer dereference requires pointer type, got '{type_name}'")]
273 InvalidPointerDereference { type_name: String },
274
275 #[error(
276 "cannot apply byte offset {offset} to value-backed aggregate location {location:?}; field/array extraction from aggregate values is not implemented"
277 )]
278 ValueBackedAggregateOffset {
279 offset: i64,
280 location: VariableLocation,
281 },
282
283 #[error("cannot dereference variable location shape {location:?}")]
284 UnsupportedDereference { location: VariableLocation },
285}
286
287impl PlanError {
288 pub fn is_value_backed_aggregate_access(&self) -> bool {
289 matches!(self, PlanError::ValueBackedAggregateOffset { .. })
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
294enum ElementIndexContext {
295 AccessPath,
296 PointerArithmetic,
297}
298
299impl VariableReadPlan {
300 pub fn from_visible_variable(variable: VisibleVariable, provenance: Provenance) -> Self {
301 Self {
302 name: variable.name,
303 type_name: variable.type_name,
304 access_path: VariableAccessPath::default(),
305 module_path: None,
306 dwarf_type: variable.dwarf_type,
307 declaration: variable.declaration,
308 type_id: variable.type_id,
309 location: variable.location,
310 availability: variable.availability,
311 scope_depth: variable.scope_depth,
312 is_parameter: variable.is_parameter,
313 is_artificial: variable.is_artificial,
314 pc_range: None,
315 inline_context: None,
316 provenance,
317 }
318 }
319
320 pub fn bpf_lowering_plan(&self, capabilities: &RuntimeCapabilities) -> VariableLoweringPlan {
321 if !self.availability.is_available() {
322 return VariableLoweringPlan {
323 kind: VariableLoweringKind::Unavailable,
324 availability: self.availability.clone(),
325 requirements: Vec::new(),
326 helper_mode: HelperMode::NoUserMemoryRead,
327 required_registers: Vec::new(),
328 estimated_stack_bytes: 0,
329 verifier_risk: VerifierRisk::Unsupported {
330 reason: "variable is unavailable".to_string(),
331 },
332 };
333 }
334
335 let kind = self.location.lowering_kind();
336 let mut requirements = self.location.runtime_requirements();
337 requirements.sort_by_key(requirement_rank);
338 requirements.dedup();
339 let mut required_registers = self.location.required_registers();
340 required_registers.sort_unstable();
341 required_registers.dedup();
342 let estimated_stack_bytes = self.location.estimated_stack_bytes();
343 let helper_mode = helper_mode_for_requirements(&requirements, capabilities);
344 let verifier_risk =
345 verifier_risk_for_requirements(&requirements, estimated_stack_bytes, capabilities);
346
347 let availability = match &verifier_risk {
348 VerifierRisk::StackBudgetExceeded { estimated, max } => {
349 Availability::Unsupported(UnsupportedReason::ExpressionShape {
350 detail: format!(
351 "estimated BPF stack use {estimated} bytes exceeds capability limit {max}"
352 ),
353 })
354 }
355 _ => requirements
356 .iter()
357 .find(|requirement| !capabilities.supports_requirement(requirement))
358 .cloned()
359 .map(Availability::Requires)
360 .unwrap_or(Availability::Available),
361 };
362
363 VariableLoweringPlan {
364 kind,
365 availability,
366 requirements,
367 helper_mode,
368 required_registers,
369 estimated_stack_bytes,
370 verifier_risk,
371 }
372 }
373
374 pub fn materialization_plan(
375 &self,
376 capabilities: &RuntimeCapabilities,
377 ) -> VariableMaterializationPlan {
378 let lowering = self.bpf_lowering_plan(capabilities);
379 let materialization = if !lowering.availability.is_available() {
380 VariableMaterialization::Unavailable {
381 availability: lowering.availability.clone(),
382 }
383 } else {
384 match lowering.kind {
385 VariableLoweringKind::DirectValue => {
386 let size = planned_value_size(self.dwarf_type.as_ref());
387 match PlannedValue::from_location(self.location.clone(), size) {
388 Some(value) => VariableMaterialization::DirectValue { value },
389 None => VariableMaterialization::Unavailable {
390 availability: Availability::Unsupported(
391 UnsupportedReason::ExpressionShape {
392 detail: format!(
393 "location {} cannot be materialized as a direct value",
394 self.location
395 ),
396 },
397 ),
398 },
399 }
400 }
401 VariableLoweringKind::UserMemoryRead => {
402 match PlannedAddress::from_location(self.location.clone()) {
403 Some(address) => VariableMaterialization::UserMemoryRead { address },
404 None => VariableMaterialization::Unavailable {
405 availability: Availability::Unsupported(
406 UnsupportedReason::AddressClass {
407 detail: format!(
408 "location {} cannot be materialized as an address",
409 self.location
410 ),
411 },
412 ),
413 },
414 }
415 }
416 VariableLoweringKind::Composite => match &self.location {
417 VariableLocation::Pieces(pieces) => VariableMaterialization::Composite {
418 pieces: pieces.clone(),
419 },
420 _ => VariableMaterialization::Unavailable {
421 availability: Availability::Unsupported(
422 UnsupportedReason::ExpressionShape {
423 detail: "composite lowering without piece locations".to_string(),
424 },
425 ),
426 },
427 },
428 VariableLoweringKind::Unavailable => VariableMaterialization::Unavailable {
429 availability: lowering.availability.clone(),
430 },
431 }
432 };
433
434 VariableMaterializationPlan {
435 name: self.name.clone(),
436 type_name: self.type_name.clone(),
437 access_path: self.access_path.clone(),
438 module_path: self.module_path.clone(),
439 dwarf_type: self.dwarf_type.clone(),
440 availability: lowering.availability.clone(),
441 lowering,
442 materialization,
443 }
444 }
445
446 pub fn lvalue_address_plan(&self) -> LvalueAddressPlan {
447 if !self.availability.is_available() {
448 LvalueAddressPlan::Unavailable {
449 availability: self.availability.clone(),
450 }
451 } else {
452 lvalue_address_materialization(&self.name, &self.location)
453 }
454 }
455
456 pub fn plan_access_path(&self, path: &VariableAccessPath) -> Result<Self> {
457 let mut plan = self.clone();
458 for segment in &path.segments {
459 plan = plan.plan_access_segment(segment)?;
460 }
461
462 plan.access_path.segments.extend(path.segments.clone());
463 plan.name.push_str(&path.suffix());
464 Ok(plan)
465 }
466
467 pub fn plan_pointer_element_index(&self, index: i64) -> Result<Self> {
472 let dwarf_type = self
473 .dwarf_type
474 .clone()
475 .ok_or_else(|| PlanError::MissingTypeInfo {
476 name: self.name.clone(),
477 })?;
478 let mut plan =
479 self.plan_element_index(&dwarf_type, index, ElementIndexContext::PointerArithmetic)?;
480 let segment = VariableAccessSegment::ArrayIndex(index);
481 plan.access_path.segments.push(segment.clone());
482 plan.name
483 .push_str(&VariableAccessPath::new(vec![segment]).suffix());
484 Ok(plan)
485 }
486
487 fn plan_access_segment(&self, segment: &VariableAccessSegment) -> Result<Self> {
488 let dwarf_type = self
489 .dwarf_type
490 .clone()
491 .ok_or_else(|| PlanError::MissingTypeInfo {
492 name: self.name.clone(),
493 })?;
494
495 match segment {
496 VariableAccessSegment::Field(field) => self.plan_field_access(&dwarf_type, field),
497 VariableAccessSegment::ArrayIndex(index) => self.plan_array_index(&dwarf_type, *index),
498 VariableAccessSegment::Dereference => self.plan_pointer_deref(&dwarf_type),
499 }
500 }
501
502 fn plan_field_access(&self, dwarf_type: &TypeInfo, field: &str) -> Result<Self> {
503 let (base_location, aggregate_type) = match strip_type_aliases(dwarf_type) {
504 TypeInfo::PointerType { target_type, .. } => (
505 dereference_location(&self.location)?,
506 strip_type_aliases(target_type).clone(),
507 ),
508 ty => (self.location.clone(), ty.clone()),
509 };
510
511 let member = member_layout(&aggregate_type, field).map_err(|err| match err {
512 TypeLayoutError::UnknownMember {
513 kind,
514 type_name,
515 field,
516 members,
517 } => PlanError::UnknownMember {
518 kind,
519 type_name,
520 field,
521 members,
522 }
523 .into(),
524 TypeLayoutError::InvalidMemberBase { type_name } => {
525 anyhow::anyhow!("member '{}' not found on type '{}'", field, type_name)
526 }
527 })?;
528
529 let mut plan = self.clone();
530 plan.location = add_location_offset(base_location, member.offset as i64)?;
531 plan.type_name = member.member_type.type_name();
532 plan.dwarf_type = Some(member.member_type);
533 plan.type_id = None;
534 Ok(plan)
535 }
536
537 fn plan_array_index(&self, dwarf_type: &TypeInfo, index: i64) -> Result<Self> {
538 self.plan_element_index(dwarf_type, index, ElementIndexContext::AccessPath)
539 }
540
541 fn plan_element_index(
542 &self,
543 dwarf_type: &TypeInfo,
544 index: i64,
545 context: ElementIndexContext,
546 ) -> Result<Self> {
547 let base_location = match strip_type_aliases(dwarf_type) {
548 TypeInfo::ArrayType { .. } => self.location.clone(),
549 TypeInfo::PointerType { .. } => dereference_location(&self.location)?,
550 ty => {
551 let type_name = ty.type_name();
552 return Err(match context {
553 ElementIndexContext::AccessPath => PlanError::InvalidArrayAccess { type_name },
554 ElementIndexContext::PointerArithmetic => {
555 PlanError::InvalidPointerArithmetic { type_name }
556 }
557 }
558 .into());
559 }
560 };
561 let layout = indexable_element_layout(dwarf_type)
562 .expect("array and pointer types must have element layout");
563
564 let byte_offset = index.saturating_mul(layout.stride as i64);
565 let mut plan = self.clone();
566 plan.location = add_location_offset(base_location, byte_offset)?;
567 plan.type_name = layout.element_type.type_name();
568 plan.dwarf_type = Some(layout.element_type);
569 plan.type_id = None;
570 Ok(plan)
571 }
572
573 fn plan_pointer_deref(&self, dwarf_type: &TypeInfo) -> Result<Self> {
574 let target_type = match strip_type_aliases(dwarf_type) {
575 TypeInfo::PointerType { target_type, .. } => target_type.as_ref().clone(),
576 ty => {
577 return Err(PlanError::InvalidPointerDereference {
578 type_name: ty.type_name(),
579 }
580 .into());
581 }
582 };
583
584 let mut plan = self.clone();
585 plan.location = dereference_location(&self.location)?;
586 plan.type_name = target_type.type_name();
587 plan.dwarf_type = Some(target_type);
588 plan.type_id = None;
589 Ok(plan)
590 }
591}
592
593impl PlannedValue {
594 pub fn from_location(location: VariableLocation, size: MemoryAccessSize) -> Option<Self> {
595 match location {
596 VariableLocation::RegisterValue { dwarf_reg } => {
597 Some(Self::RegisterValue { dwarf_reg, size })
598 }
599 VariableLocation::ComputedValue(steps) => {
600 if let [PlanExprOp::PushConstant(value)] = steps.as_slice() {
601 Some(Self::Constant {
602 value: *value,
603 size,
604 })
605 } else {
606 Some(Self::RuntimeComputed {
607 expr: RuntimeComputedExpr::value(steps),
608 result_size: size,
609 })
610 }
611 }
612 VariableLocation::ImplicitValue(bytes) => Some(Self::ImplicitBytes(bytes)),
613 VariableLocation::AbsoluteAddressValue(expr) => {
614 PlannedAddress::from_location(VariableLocation::AbsoluteAddressValue(expr))
615 .map(|address| Self::AddressValue { address, size })
616 }
617 VariableLocation::Address(_)
618 | VariableLocation::RegisterAddress { .. }
619 | VariableLocation::FrameBaseRelative { .. }
620 | VariableLocation::ComputedAddress(_)
621 | VariableLocation::Pieces(_)
622 | VariableLocation::OptimizedOut
623 | VariableLocation::Unknown => None,
624 }
625 }
626}
627
628impl PlannedAddress {
629 pub fn from_location(location: VariableLocation) -> Option<Self> {
630 let (kind, origin) = match location {
631 VariableLocation::Address(expr) | VariableLocation::AbsoluteAddressValue(expr) => {
632 let origin = address_origin_for_steps(&expr.steps);
633 (PlannedAddressKind::from_steps(expr.steps), origin)
634 }
635 VariableLocation::RegisterAddress { dwarf_reg, offset } => (
636 PlannedAddressKind::RegisterOffset { dwarf_reg, offset },
637 AddressOrigin::RuntimeDerived,
638 ),
639 VariableLocation::FrameBaseRelative { offset } => (
640 PlannedAddressKind::FrameBaseRelative { offset },
641 AddressOrigin::RuntimeDerived,
642 ),
643 VariableLocation::ComputedAddress(steps) => {
644 let origin = address_origin_for_steps(&steps);
645 (PlannedAddressKind::from_steps(steps), origin)
646 }
647 VariableLocation::RegisterValue { .. }
648 | VariableLocation::ComputedValue(_)
649 | VariableLocation::ImplicitValue(_)
650 | VariableLocation::Pieces(_)
651 | VariableLocation::OptimizedOut
652 | VariableLocation::Unknown => return None,
653 };
654
655 Some(Self { kind, origin })
656 }
657
658 pub fn constant_link_time_address(&self) -> Option<u64> {
659 match (&self.origin, &self.kind) {
660 (AddressOrigin::LinkTime, PlannedAddressKind::Constant { address }) => Some(*address),
661 (AddressOrigin::LinkTime, PlannedAddressKind::RuntimeComputed { expr }) => {
662 fold_constant_steps(expr.ops())
663 }
664 _ => None,
665 }
666 }
667
668 pub fn link_time_base_and_runtime_tail(&self) -> Option<(u64, &[PlanExprOp])> {
669 if self.origin != AddressOrigin::LinkTimeBase {
670 return None;
671 }
672
673 match &self.kind {
674 PlannedAddressKind::RuntimeComputed { expr } => {
675 link_time_base_and_runtime_tail(expr.ops())
676 }
677 _ => None,
678 }
679 }
680}
681
682impl PlannedAddressKind {
683 fn from_steps(steps: Vec<PlanExprOp>) -> Self {
684 match fold_constant_steps(&steps) {
685 Some(address) => Self::Constant { address },
686 None => Self::RuntimeComputed {
687 expr: RuntimeComputedExpr::address(steps),
688 },
689 }
690 }
691}
692
693impl RuntimeCapabilities {
694 pub fn supports_requirement(&self, requirement: &RuntimeRequirement) -> bool {
695 match requirement {
696 RuntimeRequirement::CallerFrame | RuntimeRequirement::DwarfCfiRecovery => {
697 self.bounded_loops
698 }
699 RuntimeRequirement::SleepableUprobe => self.sleepable_uprobe,
700 RuntimeRequirement::UserMemoryRead => {
701 self.regular_uprobe || self.sleepable_uprobe || self.copy_from_user_task
702 }
703 }
704 }
705}
706
707fn planned_value_size(dwarf_type: Option<&TypeInfo>) -> MemoryAccessSize {
708 dwarf_type
709 .map(|ty| MemoryAccessSize::from_size(ty.size()))
710 .unwrap_or(MemoryAccessSize::U64)
711}
712
713fn lvalue_address_materialization(name: &str, location: &VariableLocation) -> LvalueAddressPlan {
714 match location {
715 VariableLocation::Address(_)
716 | VariableLocation::RegisterAddress { .. }
717 | VariableLocation::FrameBaseRelative { .. }
718 | VariableLocation::ComputedAddress(_) => {
719 match PlannedAddress::from_location(location.clone()) {
720 Some(address) => LvalueAddressPlan::Address { address },
721 None => LvalueAddressPlan::Unavailable {
722 availability: Availability::Unsupported(UnsupportedReason::AddressClass {
723 detail: format!(
724 "DWARF variable '{name}' has an address-backed location that could not be planned"
725 ),
726 }),
727 },
728 }
729 }
730 VariableLocation::OptimizedOut => LvalueAddressPlan::Unavailable {
731 availability: Availability::OptimizedOut,
732 },
733 VariableLocation::Pieces(_) => LvalueAddressPlan::Unavailable {
734 availability: Availability::Unsupported(UnsupportedReason::ExpressionShape {
735 detail: "split variable pieces cannot be materialized as one lvalue address"
736 .to_string(),
737 }),
738 },
739 VariableLocation::AbsoluteAddressValue(_)
740 | VariableLocation::RegisterValue { .. }
741 | VariableLocation::ComputedValue(_)
742 | VariableLocation::ImplicitValue(_) => LvalueAddressPlan::Unavailable {
743 availability: Availability::Unsupported(UnsupportedReason::AddressClass {
744 detail: "cannot take address of value-backed DWARF expression".to_string(),
745 }),
746 },
747 VariableLocation::Unknown => LvalueAddressPlan::Unavailable {
748 availability: Availability::Unsupported(UnsupportedReason::AddressClass {
749 detail: "unknown DWARF variable location".to_string(),
750 }),
751 },
752 }
753}
754
755fn address_origin_for_steps(steps: &[PlanExprOp]) -> AddressOrigin {
756 if fold_constant_steps(steps).is_some() {
757 return AddressOrigin::LinkTime;
758 }
759
760 if link_time_base_and_runtime_tail(steps).is_some() {
761 return AddressOrigin::LinkTimeBase;
762 }
763
764 if steps_reference_runtime_state(steps) {
765 AddressOrigin::RuntimeDerived
766 } else {
767 AddressOrigin::Unknown
768 }
769}
770
771fn fold_constant_steps(steps: &[PlanExprOp]) -> Option<u64> {
772 let mut const_stack: Vec<i64> = Vec::new();
773 for step in steps {
774 match step {
775 PlanExprOp::PushConstant(value) => const_stack.push(*value),
776 PlanExprOp::Add => {
777 let rhs = const_stack.pop()?;
778 let lhs = const_stack.pop()?;
779 const_stack.push(lhs.saturating_add(rhs));
780 }
781 _ => return None,
782 }
783 }
784
785 if const_stack.len() == 1 && const_stack[0] >= 0 {
786 Some(const_stack[0] as u64)
787 } else {
788 None
789 }
790}
791
792fn link_time_base_and_runtime_tail(steps: &[PlanExprOp]) -> Option<(u64, &[PlanExprOp])> {
793 let Some(PlanExprOp::PushConstant(base)) = steps.first() else {
794 return None;
795 };
796
797 if *base < 0 {
798 return None;
799 }
800
801 for step in steps.iter().skip(1) {
802 match step {
803 PlanExprOp::LoadRegister(_) => {
804 break;
805 }
806 PlanExprOp::FormTlsAddress => return None,
807 PlanExprOp::Dereference { .. } => {
808 return Some((*base as u64, &steps[1..]));
809 }
810 _ => {}
811 }
812 }
813
814 None
815}
816
817fn steps_reference_runtime_state(steps: &[PlanExprOp]) -> bool {
818 steps.iter().any(|step| match step {
819 PlanExprOp::LoadRegister(_)
820 | PlanExprOp::Dereference { .. }
821 | PlanExprOp::FormTlsAddress
822 | PlanExprOp::EntryValueLookup { .. } => true,
823 PlanExprOp::If {
824 then_branch,
825 else_branch,
826 } => {
827 steps_reference_runtime_state(then_branch) || steps_reference_runtime_state(else_branch)
828 }
829 _ => false,
830 })
831}
832
833trait VariableLocationLoweringExt {
834 fn lowering_kind(&self) -> VariableLoweringKind;
835 fn runtime_requirements(&self) -> Vec<RuntimeRequirement>;
836 fn required_registers(&self) -> Vec<u16>;
837 fn estimated_stack_bytes(&self) -> usize;
838}
839
840impl VariableLocationLoweringExt for VariableLocation {
841 fn lowering_kind(&self) -> VariableLoweringKind {
842 match self {
843 VariableLocation::Address(_)
844 | VariableLocation::RegisterAddress { .. }
845 | VariableLocation::ComputedAddress(_)
846 | VariableLocation::FrameBaseRelative { .. } => VariableLoweringKind::UserMemoryRead,
847 VariableLocation::AbsoluteAddressValue(_)
848 | VariableLocation::RegisterValue { .. }
849 | VariableLocation::ComputedValue(_)
850 | VariableLocation::ImplicitValue(_) => VariableLoweringKind::DirectValue,
851 VariableLocation::Pieces(_) => VariableLoweringKind::Composite,
852 VariableLocation::OptimizedOut | VariableLocation::Unknown => {
853 VariableLoweringKind::Unavailable
854 }
855 }
856 }
857
858 fn runtime_requirements(&self) -> Vec<RuntimeRequirement> {
859 match self {
860 VariableLocation::Address(_)
861 | VariableLocation::RegisterAddress { .. }
862 | VariableLocation::ComputedAddress(_) => {
863 let mut requirements = vec![RuntimeRequirement::UserMemoryRead];
864 if let VariableLocation::ComputedAddress(steps) = self {
865 requirements.extend(requirements_for_steps(steps));
866 }
867 requirements
868 }
869 VariableLocation::FrameBaseRelative { .. } => vec![
870 RuntimeRequirement::DwarfCfiRecovery,
871 RuntimeRequirement::UserMemoryRead,
872 ],
873 VariableLocation::AbsoluteAddressValue(expr) => requirements_for_steps(&expr.steps),
874 VariableLocation::ComputedValue(steps) => requirements_for_steps(steps),
875 VariableLocation::Pieces(pieces) => pieces
876 .iter()
877 .flat_map(|piece| piece.location.runtime_requirements())
878 .collect(),
879 VariableLocation::RegisterValue { .. }
880 | VariableLocation::ImplicitValue(_)
881 | VariableLocation::OptimizedOut
882 | VariableLocation::Unknown => Vec::new(),
883 }
884 }
885
886 fn required_registers(&self) -> Vec<u16> {
887 match self {
888 VariableLocation::RegisterValue { dwarf_reg } => vec![*dwarf_reg],
889 VariableLocation::RegisterAddress { dwarf_reg, .. } => vec![*dwarf_reg],
890 VariableLocation::AbsoluteAddressValue(expr) => registers_for_steps(&expr.steps),
891 VariableLocation::ComputedValue(steps) | VariableLocation::ComputedAddress(steps) => {
892 registers_for_steps(steps)
893 }
894 VariableLocation::Pieces(pieces) => pieces
895 .iter()
896 .flat_map(|piece| piece.location.required_registers())
897 .collect(),
898 VariableLocation::Address(_)
899 | VariableLocation::FrameBaseRelative { .. }
900 | VariableLocation::ImplicitValue(_)
901 | VariableLocation::OptimizedOut
902 | VariableLocation::Unknown => Vec::new(),
903 }
904 }
905
906 fn estimated_stack_bytes(&self) -> usize {
907 match self {
908 VariableLocation::AbsoluteAddressValue(expr) => {
909 estimate_steps_stack_bytes(&expr.steps).max(8)
910 }
911 VariableLocation::ComputedValue(steps) | VariableLocation::ComputedAddress(steps) => {
912 estimate_steps_stack_bytes(steps)
913 }
914 VariableLocation::Pieces(pieces) => pieces
915 .iter()
916 .map(|piece| piece.location.estimated_stack_bytes())
917 .max()
918 .unwrap_or(0),
919 VariableLocation::Address(_)
920 | VariableLocation::RegisterValue { .. }
921 | VariableLocation::RegisterAddress { .. }
922 | VariableLocation::FrameBaseRelative { .. } => 8,
923 VariableLocation::ImplicitValue(bytes) => bytes.len(),
924 VariableLocation::OptimizedOut | VariableLocation::Unknown => 0,
925 }
926 }
927}
928
929fn requirements_for_steps(steps: &[PlanExprOp]) -> Vec<RuntimeRequirement> {
930 let mut requirements = Vec::new();
931 for step in steps {
932 match step {
933 PlanExprOp::Dereference { .. } => requirements.push(RuntimeRequirement::UserMemoryRead),
934 PlanExprOp::EntryValueLookup {
935 caller_pc_steps,
936 cases,
937 } => {
938 requirements.push(RuntimeRequirement::CallerFrame);
939 requirements.extend(requirements_for_steps(caller_pc_steps));
940 for case in cases {
941 requirements.extend(requirements_for_steps(&case.value_steps));
942 }
943 }
944 PlanExprOp::If {
945 then_branch,
946 else_branch,
947 } => {
948 requirements.extend(requirements_for_steps(then_branch));
949 requirements.extend(requirements_for_steps(else_branch));
950 }
951 _ => {}
952 }
953 }
954 requirements
955}
956
957fn registers_for_steps(steps: &[PlanExprOp]) -> Vec<u16> {
958 let mut registers = Vec::new();
959 collect_registers_for_steps(steps, &mut registers);
960 registers
961}
962
963fn collect_registers_for_steps(steps: &[PlanExprOp], registers: &mut Vec<u16>) {
964 for step in steps {
965 match step {
966 PlanExprOp::LoadRegister(register) => registers.push(*register),
967 PlanExprOp::EntryValueLookup {
968 caller_pc_steps,
969 cases,
970 } => {
971 collect_registers_for_steps(caller_pc_steps, registers);
972 for case in cases {
973 collect_registers_for_steps(&case.value_steps, registers);
974 }
975 }
976 PlanExprOp::If {
977 then_branch,
978 else_branch,
979 } => {
980 collect_registers_for_steps(then_branch, registers);
981 collect_registers_for_steps(else_branch, registers);
982 }
983 _ => {}
984 }
985 }
986}
987
988fn estimate_steps_stack_bytes(steps: &[PlanExprOp]) -> usize {
989 let nested = steps
990 .iter()
991 .map(|step| match step {
992 PlanExprOp::EntryValueLookup {
993 caller_pc_steps,
994 cases,
995 } => cases
996 .iter()
997 .map(|case| estimate_steps_stack_bytes(&case.value_steps))
998 .chain(std::iter::once(estimate_steps_stack_bytes(caller_pc_steps)))
999 .max()
1000 .unwrap_or(0),
1001 PlanExprOp::If {
1002 then_branch,
1003 else_branch,
1004 } => {
1005 estimate_steps_stack_bytes(then_branch).max(estimate_steps_stack_bytes(else_branch))
1006 }
1007 _ => 0,
1008 })
1009 .max()
1010 .unwrap_or(0);
1011 steps.len().saturating_mul(8).max(nested)
1012}
1013
1014fn helper_mode_for_requirements(
1015 requirements: &[RuntimeRequirement],
1016 capabilities: &RuntimeCapabilities,
1017) -> HelperMode {
1018 if !requirements.contains(&RuntimeRequirement::UserMemoryRead) {
1019 HelperMode::NoUserMemoryRead
1020 } else if capabilities.sleepable_uprobe && capabilities.copy_from_user_task {
1021 HelperMode::CopyFromUserTask
1022 } else {
1023 HelperMode::ProbeReadUser
1024 }
1025}
1026
1027fn verifier_risk_for_requirements(
1028 requirements: &[RuntimeRequirement],
1029 estimated_stack_bytes: usize,
1030 capabilities: &RuntimeCapabilities,
1031) -> VerifierRisk {
1032 if estimated_stack_bytes > capabilities.max_bpf_stack_bytes {
1033 return VerifierRisk::StackBudgetExceeded {
1034 estimated: estimated_stack_bytes,
1035 max: capabilities.max_bpf_stack_bytes,
1036 };
1037 }
1038
1039 if requirements.iter().any(|requirement| {
1040 matches!(
1041 requirement,
1042 RuntimeRequirement::CallerFrame | RuntimeRequirement::DwarfCfiRecovery
1043 )
1044 }) {
1045 VerifierRisk::RequiresBoundedLoops
1046 } else {
1047 VerifierRisk::Low
1048 }
1049}
1050
1051fn requirement_rank(requirement: &RuntimeRequirement) -> u8 {
1052 match requirement {
1053 RuntimeRequirement::CallerFrame => 0,
1054 RuntimeRequirement::SleepableUprobe => 1,
1055 RuntimeRequirement::UserMemoryRead => 2,
1056 RuntimeRequirement::DwarfCfiRecovery => 3,
1057 }
1058}
1059
1060pub fn add_location_offset(location: VariableLocation, offset: i64) -> Result<VariableLocation> {
1062 match location {
1063 VariableLocation::Address(expr) => {
1064 Ok(VariableLocation::Address(offset_address_expr(expr, offset)))
1065 }
1066 VariableLocation::RegisterAddress {
1067 dwarf_reg,
1068 offset: base,
1069 } => Ok(VariableLocation::RegisterAddress {
1070 dwarf_reg,
1071 offset: base.saturating_add(offset),
1072 }),
1073 VariableLocation::FrameBaseRelative { offset: base } => {
1074 Ok(VariableLocation::FrameBaseRelative {
1075 offset: base.saturating_add(offset),
1076 })
1077 }
1078 VariableLocation::ComputedAddress(mut steps) => {
1079 push_add_offset(&mut steps, offset);
1080 Ok(VariableLocation::ComputedAddress(steps))
1081 }
1082 VariableLocation::OptimizedOut => Ok(VariableLocation::OptimizedOut),
1083 VariableLocation::Unknown => Ok(VariableLocation::Unknown),
1084 VariableLocation::AbsoluteAddressValue(_)
1085 | VariableLocation::RegisterValue { .. }
1086 | VariableLocation::ComputedValue(_)
1087 | VariableLocation::ImplicitValue(_)
1088 | VariableLocation::Pieces(_) => {
1089 Err(PlanError::ValueBackedAggregateOffset { offset, location }.into())
1090 }
1091 }
1092}
1093
1094fn offset_address_expr(mut expr: AddressExpr, offset: i64) -> AddressExpr {
1095 if let [PlanExprOp::PushConstant(base)] = expr.steps.as_mut_slice() {
1096 *base = base.saturating_add(offset);
1097 return expr;
1098 }
1099 push_add_offset(&mut expr.steps, offset);
1100 expr
1101}
1102
1103fn push_add_offset(steps: &mut Vec<PlanExprOp>, offset: i64) {
1104 if offset != 0 {
1105 steps.push(PlanExprOp::PushConstant(offset));
1106 steps.push(PlanExprOp::Add);
1107 }
1108}
1109
1110pub fn dereference_location(location: &VariableLocation) -> Result<VariableLocation> {
1112 match location {
1113 VariableLocation::AbsoluteAddressValue(expr) => Ok(VariableLocation::Address(expr.clone())),
1114 VariableLocation::RegisterValue { dwarf_reg } => {
1115 Ok(VariableLocation::ComputedAddress(vec![
1116 PlanExprOp::LoadRegister(*dwarf_reg),
1117 ]))
1118 }
1119 VariableLocation::ComputedValue(steps) => {
1120 Ok(VariableLocation::ComputedAddress(steps.clone()))
1121 }
1122 VariableLocation::ImplicitValue(bytes) => {
1123 let mut address = 0u64;
1124 for (index, byte) in bytes.iter().take(8).enumerate() {
1125 address |= (*byte as u64) << (index * 8);
1126 }
1127 Ok(VariableLocation::Address(AddressExpr::constant(address)))
1128 }
1129 VariableLocation::Address(expr) => {
1130 let mut steps = expr.steps.clone();
1131 steps.push(PlanExprOp::Dereference {
1132 size: MemoryAccessSize::U64,
1133 });
1134 Ok(VariableLocation::ComputedAddress(steps))
1135 }
1136 VariableLocation::RegisterAddress { dwarf_reg, offset } => {
1137 let mut steps = vec![PlanExprOp::LoadRegister(*dwarf_reg)];
1138 push_add_offset(&mut steps, *offset);
1139 steps.push(PlanExprOp::Dereference {
1140 size: MemoryAccessSize::U64,
1141 });
1142 Ok(VariableLocation::ComputedAddress(steps))
1143 }
1144 VariableLocation::ComputedAddress(steps) => {
1145 let mut steps = steps.clone();
1146 steps.push(PlanExprOp::Dereference {
1147 size: MemoryAccessSize::U64,
1148 });
1149 Ok(VariableLocation::ComputedAddress(steps))
1150 }
1151 VariableLocation::OptimizedOut => Ok(VariableLocation::OptimizedOut),
1152 VariableLocation::Unknown => Ok(VariableLocation::Unknown),
1153 VariableLocation::FrameBaseRelative { .. } | VariableLocation::Pieces(_) => {
1154 Err(PlanError::UnsupportedDereference {
1155 location: location.clone(),
1156 }
1157 .into())
1158 }
1159 }
1160}
1161
1162#[derive(Debug, Clone, PartialEq)]
1163pub struct VariablePlan {
1164 pub variable_id: VariableId,
1165 pub name: String,
1166 pub ty: TypeId,
1167 pub declaration: DieRef,
1168 pub pc_range: Option<PcRange>,
1169 pub inline_context: Option<InlineContextId>,
1170 pub location: VariableLocation,
1171 pub availability: Availability,
1172 pub provenance: Provenance,
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178 use crate::core::{AddressExpr, EntryValueCase, MemoryAccessSize, TargetArch};
1179 use crate::StructMember;
1180
1181 fn capabilities(regular_uprobe: bool) -> RuntimeCapabilities {
1182 RuntimeCapabilities {
1183 regular_uprobe,
1184 sleepable_uprobe: false,
1185 uprobe_multi: false,
1186 copy_from_user_task: false,
1187 max_bpf_stack_bytes: 512,
1188 bounded_loops: true,
1189 arch: TargetArch::X86_64,
1190 }
1191 }
1192
1193 fn read_plan(location: VariableLocation) -> VariableReadPlan {
1194 VariableReadPlan {
1195 name: "value".to_string(),
1196 type_name: "int".to_string(),
1197 access_path: VariableAccessPath::default(),
1198 module_path: None,
1199 dwarf_type: None,
1200 declaration: None,
1201 type_id: None,
1202 location,
1203 availability: Availability::Available,
1204 scope_depth: 0,
1205 is_parameter: false,
1206 is_artificial: false,
1207 pc_range: None,
1208 inline_context: None,
1209 provenance: Provenance::DirectDie,
1210 }
1211 }
1212
1213 fn typed_read_plan(location: VariableLocation, dwarf_type: TypeInfo) -> VariableReadPlan {
1214 VariableReadPlan {
1215 type_name: dwarf_type.type_name(),
1216 dwarf_type: Some(dwarf_type),
1217 ..read_plan(location)
1218 }
1219 }
1220
1221 #[test]
1222 fn lvalue_address_plan_accepts_address_locations_without_type_info() {
1223 let plan = read_plan(VariableLocation::RegisterAddress {
1224 dwarf_reg: 6,
1225 offset: -16,
1226 });
1227
1228 let lvalue = plan.lvalue_address_plan();
1229
1230 assert_eq!(
1231 lvalue,
1232 LvalueAddressPlan::Address {
1233 address: PlannedAddress {
1234 kind: PlannedAddressKind::RegisterOffset {
1235 dwarf_reg: 6,
1236 offset: -16
1237 },
1238 origin: AddressOrigin::RuntimeDerived,
1239 }
1240 }
1241 );
1242 }
1243
1244 #[test]
1245 fn lvalue_address_plan_rejects_value_backed_locations() {
1246 let plan = read_plan(VariableLocation::RegisterValue { dwarf_reg: 0 });
1247
1248 let lvalue = plan.lvalue_address_plan();
1249
1250 assert!(matches!(
1251 lvalue,
1252 LvalueAddressPlan::Unavailable {
1253 availability: Availability::Unsupported(UnsupportedReason::AddressClass { .. })
1254 }
1255 ));
1256 match lvalue {
1257 LvalueAddressPlan::Unavailable {
1258 availability: Availability::Unsupported(UnsupportedReason::AddressClass { detail }),
1259 } => {
1260 assert!(detail.contains("value-backed"));
1261 }
1262 other => panic!("unexpected lvalue availability: {other:?}"),
1263 }
1264 }
1265
1266 #[test]
1267 fn lvalue_address_plan_rejects_absolute_address_values() {
1268 let plan = read_plan(VariableLocation::AbsoluteAddressValue(
1269 AddressExpr::constant(0x2000),
1270 ));
1271
1272 let lvalue = plan.lvalue_address_plan();
1273
1274 match lvalue {
1275 LvalueAddressPlan::Unavailable {
1276 availability: Availability::Unsupported(UnsupportedReason::AddressClass { detail }),
1277 } => {
1278 assert!(detail.contains("value-backed"));
1279 }
1280 other => panic!("unexpected lvalue availability: {other:?}"),
1281 }
1282 }
1283
1284 #[test]
1285 fn lvalue_address_plan_rejects_piece_locations() {
1286 let plan = read_plan(VariableLocation::Pieces(vec![PieceLocation {
1287 bit_offset: 0,
1288 bit_size: 32,
1289 location: Box::new(VariableLocation::RegisterValue { dwarf_reg: 0 }),
1290 }]));
1291
1292 let lvalue = plan.lvalue_address_plan();
1293
1294 match lvalue {
1295 LvalueAddressPlan::Unavailable {
1296 availability:
1297 Availability::Unsupported(UnsupportedReason::ExpressionShape { detail }),
1298 } => {
1299 assert!(detail.contains("split variable pieces"));
1300 }
1301 other => panic!("unexpected lvalue availability: {other:?}"),
1302 }
1303 }
1304
1305 #[test]
1306 fn lvalue_address_plan_preserves_optimized_out_availability() {
1307 let plan = VariableReadPlan {
1308 availability: Availability::OptimizedOut,
1309 ..read_plan(VariableLocation::OptimizedOut)
1310 };
1311
1312 let lvalue = plan.lvalue_address_plan();
1313
1314 assert_eq!(
1315 lvalue,
1316 LvalueAddressPlan::Unavailable {
1317 availability: Availability::OptimizedOut
1318 }
1319 );
1320 }
1321
1322 #[test]
1323 fn register_value_lowers_without_runtime_requirements() {
1324 let plan = read_plan(VariableLocation::RegisterValue { dwarf_reg: 0 });
1325 let lowering = plan.bpf_lowering_plan(&capabilities(false));
1326
1327 assert_eq!(lowering.kind, VariableLoweringKind::DirectValue);
1328 assert_eq!(lowering.availability, Availability::Available);
1329 assert!(lowering.requirements.is_empty());
1330 }
1331
1332 #[test]
1333 fn memory_location_requires_user_memory_read() {
1334 let plan = read_plan(VariableLocation::Address(AddressExpr::constant(0x1000)));
1335 let lowering = plan.bpf_lowering_plan(&capabilities(false));
1336
1337 assert_eq!(lowering.kind, VariableLoweringKind::UserMemoryRead);
1338 assert_eq!(
1339 lowering.availability,
1340 Availability::Requires(RuntimeRequirement::UserMemoryRead)
1341 );
1342 assert_eq!(
1343 lowering.requirements,
1344 vec![RuntimeRequirement::UserMemoryRead]
1345 );
1346 }
1347
1348 #[test]
1349 fn memory_location_is_available_with_regular_uprobe() {
1350 let plan = read_plan(VariableLocation::Address(AddressExpr::constant(0x1000)));
1351 let lowering = plan.bpf_lowering_plan(&capabilities(true));
1352
1353 assert_eq!(lowering.kind, VariableLoweringKind::UserMemoryRead);
1354 assert_eq!(lowering.availability, Availability::Available);
1355 assert_eq!(lowering.helper_mode, HelperMode::ProbeReadUser);
1356 assert_eq!(lowering.verifier_risk, VerifierRisk::Low);
1357 assert!(lowering.required_registers.is_empty());
1358 }
1359
1360 #[test]
1361 fn materialization_plan_preserves_link_time_address_origin() {
1362 let plan = read_plan(VariableLocation::Address(AddressExpr::constant(0x1000)));
1363 let materialized = plan.materialization_plan(&capabilities(true));
1364
1365 match materialized.materialization {
1366 VariableMaterialization::UserMemoryRead { address } => {
1367 assert_eq!(address.origin, AddressOrigin::LinkTime);
1368 assert_eq!(address.constant_link_time_address(), Some(0x1000));
1369 assert_eq!(
1370 address.kind,
1371 PlannedAddressKind::Constant { address: 0x1000 }
1372 );
1373 }
1374 other => panic!("unexpected materialization: {other:?}"),
1375 }
1376 }
1377
1378 #[test]
1379 fn materialization_plan_preserves_module_path_origin() {
1380 let mut plan = read_plan(VariableLocation::Address(AddressExpr::constant(0x1000)));
1381 plan.module_path = Some(PathBuf::from("/tmp/libstate.so"));
1382
1383 let materialized = plan.materialization_plan(&capabilities(true));
1384
1385 assert_eq!(
1386 materialized.module_path,
1387 Some(PathBuf::from("/tmp/libstate.so"))
1388 );
1389 }
1390
1391 #[test]
1392 fn materialization_plan_converts_register_address_to_address_kind() {
1393 let plan = read_plan(VariableLocation::RegisterAddress {
1394 dwarf_reg: 6,
1395 offset: -16,
1396 });
1397 let materialized = plan.materialization_plan(&capabilities(true));
1398
1399 match materialized.materialization {
1400 VariableMaterialization::UserMemoryRead { address } => {
1401 assert_eq!(address.origin, AddressOrigin::RuntimeDerived);
1402 assert_eq!(
1403 address.kind,
1404 PlannedAddressKind::RegisterOffset {
1405 dwarf_reg: 6,
1406 offset: -16
1407 }
1408 );
1409 }
1410 other => panic!("unexpected materialization: {other:?}"),
1411 }
1412 }
1413
1414 #[test]
1415 fn materialization_plan_marks_static_base_before_deref() {
1416 let plan = read_plan(VariableLocation::ComputedAddress(vec![
1417 PlanExprOp::PushConstant(0x3000),
1418 PlanExprOp::Dereference {
1419 size: MemoryAccessSize::U64,
1420 },
1421 PlanExprOp::PushConstant(16),
1422 PlanExprOp::Add,
1423 ]));
1424 let materialized = plan.materialization_plan(&capabilities(true));
1425
1426 match materialized.materialization {
1427 VariableMaterialization::UserMemoryRead { address } => {
1428 assert_eq!(address.origin, AddressOrigin::LinkTimeBase);
1429 match &address.kind {
1430 PlannedAddressKind::RuntimeComputed { expr } => {
1431 assert_eq!(expr.kind(), RuntimeComputedKind::Address);
1432 }
1433 other => panic!("unexpected address kind: {other:?}"),
1434 }
1435 let (base, tail) = address
1436 .link_time_base_and_runtime_tail()
1437 .expect("link-time base");
1438 assert_eq!(base, 0x3000);
1439 assert_eq!(tail.len(), 3);
1440 }
1441 other => panic!("unexpected materialization: {other:?}"),
1442 }
1443 }
1444
1445 #[test]
1446 fn materialization_plan_preserves_arithmetic_before_first_deref() {
1447 let plan = read_plan(VariableLocation::ComputedAddress(vec![
1448 PlanExprOp::PushConstant(0x3000),
1449 PlanExprOp::PushConstant(8),
1450 PlanExprOp::Add,
1451 PlanExprOp::Dereference {
1452 size: MemoryAccessSize::U64,
1453 },
1454 ]));
1455 let materialized = plan.materialization_plan(&capabilities(true));
1456
1457 match materialized.materialization {
1458 VariableMaterialization::UserMemoryRead { address } => {
1459 assert_eq!(address.origin, AddressOrigin::LinkTimeBase);
1460 let (base, tail) = address
1461 .link_time_base_and_runtime_tail()
1462 .expect("link-time base");
1463 assert_eq!(base, 0x3000);
1464 assert_eq!(
1465 tail,
1466 &[
1467 PlanExprOp::PushConstant(8),
1468 PlanExprOp::Add,
1469 PlanExprOp::Dereference {
1470 size: MemoryAccessSize::U64,
1471 },
1472 ]
1473 );
1474 }
1475 other => panic!("unexpected materialization: {other:?}"),
1476 }
1477 }
1478
1479 #[test]
1480 fn materialization_plan_keeps_absolute_address_value_direct() {
1481 let plan = read_plan(VariableLocation::AbsoluteAddressValue(
1482 AddressExpr::constant(0x2000),
1483 ));
1484 let materialized = plan.materialization_plan(&capabilities(false));
1485
1486 match materialized.materialization {
1487 VariableMaterialization::DirectValue {
1488 value:
1489 PlannedValue::AddressValue {
1490 address:
1491 PlannedAddress {
1492 origin: AddressOrigin::LinkTime,
1493 kind: PlannedAddressKind::Constant { address: 0x2000 },
1494 ..
1495 },
1496 size: MemoryAccessSize::U64,
1497 },
1498 } => {}
1499 VariableMaterialization::DirectValue { value } => {
1500 panic!("unexpected direct value: {value:?}");
1501 }
1502 other => panic!("unexpected materialization: {other:?}"),
1503 }
1504 }
1505
1506 #[test]
1507 fn materialization_plan_converts_constant_direct_value() {
1508 let plan = read_plan(VariableLocation::ComputedValue(vec![
1509 PlanExprOp::PushConstant(42),
1510 ]));
1511 let materialized = plan.materialization_plan(&capabilities(false));
1512
1513 match materialized.materialization {
1514 VariableMaterialization::DirectValue {
1515 value:
1516 PlannedValue::Constant {
1517 value: 42,
1518 size: MemoryAccessSize::U64,
1519 },
1520 } => {}
1521 other => panic!("unexpected materialization: {other:?}"),
1522 }
1523 }
1524
1525 #[test]
1526 fn materialization_plan_records_direct_value_size_from_type() {
1527 let byte_type = TypeInfo::BaseType {
1528 name: "uint8_t".to_string(),
1529 size: 1,
1530 encoding: gimli::constants::DW_ATE_unsigned.0 as u16,
1531 };
1532 let plan = typed_read_plan(
1533 VariableLocation::ComputedValue(vec![
1534 PlanExprOp::LoadRegister(0),
1535 PlanExprOp::PushConstant(1),
1536 PlanExprOp::Add,
1537 ]),
1538 byte_type,
1539 );
1540 let materialized = plan.materialization_plan(&capabilities(false));
1541
1542 match materialized.materialization {
1543 VariableMaterialization::DirectValue {
1544 value:
1545 PlannedValue::RuntimeComputed {
1546 ref expr,
1547 result_size: MemoryAccessSize::U8,
1548 ..
1549 },
1550 } => {
1551 assert_eq!(expr.kind(), RuntimeComputedKind::Value);
1552 }
1553 other => panic!("unexpected materialization: {other:?}"),
1554 }
1555 }
1556
1557 #[test]
1558 fn materialization_plan_converts_register_direct_value() {
1559 let plan = read_plan(VariableLocation::RegisterValue { dwarf_reg: 6 });
1560 let materialized = plan.materialization_plan(&capabilities(false));
1561
1562 match materialized.materialization {
1563 VariableMaterialization::DirectValue {
1564 value:
1565 PlannedValue::RegisterValue {
1566 dwarf_reg: 6,
1567 size: MemoryAccessSize::U64,
1568 },
1569 } => {}
1570 other => panic!("unexpected materialization: {other:?}"),
1571 }
1572 }
1573
1574 #[test]
1575 fn materialization_plan_surfaces_piece_locations_without_first_piece_fallback() {
1576 let plan = read_plan(VariableLocation::Pieces(vec![PieceLocation {
1577 bit_offset: 0,
1578 bit_size: 32,
1579 location: Box::new(VariableLocation::RegisterValue { dwarf_reg: 0 }),
1580 }]));
1581 let materialized = plan.materialization_plan(&capabilities(true));
1582
1583 match materialized.materialization {
1584 VariableMaterialization::Composite { pieces } => {
1585 assert_eq!(pieces.len(), 1);
1586 }
1587 other => panic!("unexpected materialization: {other:?}"),
1588 }
1589 }
1590
1591 #[test]
1592 fn absolute_address_value_lowers_without_user_memory_read() {
1593 let plan = read_plan(VariableLocation::AbsoluteAddressValue(
1594 AddressExpr::constant(0x1000),
1595 ));
1596 let lowering = plan.bpf_lowering_plan(&capabilities(false));
1597
1598 assert_eq!(lowering.kind, VariableLoweringKind::DirectValue);
1599 assert_eq!(lowering.availability, Availability::Available);
1600 assert!(lowering.requirements.is_empty());
1601 }
1602
1603 #[test]
1604 fn memory_location_prefers_copy_from_user_task_when_available() {
1605 let mut capabilities = capabilities(false);
1606 capabilities.sleepable_uprobe = true;
1607 capabilities.copy_from_user_task = true;
1608 let plan = read_plan(VariableLocation::Address(AddressExpr::constant(0x1000)));
1609 let lowering = plan.bpf_lowering_plan(&capabilities);
1610
1611 assert_eq!(lowering.availability, Availability::Available);
1612 assert_eq!(lowering.helper_mode, HelperMode::CopyFromUserTask);
1613 }
1614
1615 #[test]
1616 fn register_address_records_required_register() {
1617 let plan = read_plan(VariableLocation::RegisterAddress {
1618 dwarf_reg: 6,
1619 offset: -16,
1620 });
1621 let lowering = plan.bpf_lowering_plan(&capabilities(true));
1622
1623 assert_eq!(lowering.required_registers, vec![6]);
1624 assert_eq!(lowering.estimated_stack_bytes, 8);
1625 }
1626
1627 #[test]
1628 fn entry_value_steps_surface_caller_frame_and_memory_requirements() {
1629 let plan = read_plan(VariableLocation::ComputedValue(vec![
1630 PlanExprOp::EntryValueLookup {
1631 caller_pc_steps: vec![
1632 PlanExprOp::LoadRegister(7),
1633 PlanExprOp::Dereference {
1634 size: MemoryAccessSize::U64,
1635 },
1636 ],
1637 cases: vec![EntryValueCase {
1638 caller_return_pc: 0x10,
1639 value_steps: vec![PlanExprOp::LoadRegister(5)],
1640 }],
1641 },
1642 ]));
1643 let lowering = plan.bpf_lowering_plan(&capabilities(true));
1644
1645 assert_eq!(lowering.availability, Availability::Available);
1646 assert_eq!(
1647 lowering.requirements,
1648 vec![
1649 RuntimeRequirement::CallerFrame,
1650 RuntimeRequirement::UserMemoryRead
1651 ]
1652 );
1653 assert_eq!(lowering.required_registers, vec![5, 7]);
1654 assert_eq!(lowering.verifier_risk, VerifierRisk::RequiresBoundedLoops);
1655 }
1656
1657 #[test]
1658 fn stack_budget_excess_reports_unsupported_availability() {
1659 let mut capabilities = capabilities(true);
1660 capabilities.max_bpf_stack_bytes = 16;
1661 let plan = read_plan(VariableLocation::ComputedValue(vec![
1662 PlanExprOp::PushConstant(1);
1663 8
1664 ]));
1665 let lowering = plan.bpf_lowering_plan(&capabilities);
1666
1667 assert!(matches!(
1668 lowering.availability,
1669 Availability::Unsupported(UnsupportedReason::ExpressionShape { .. })
1670 ));
1671 assert_eq!(
1672 lowering.verifier_risk,
1673 VerifierRisk::StackBudgetExceeded {
1674 estimated: 64,
1675 max: 16,
1676 }
1677 );
1678 }
1679
1680 #[test]
1681 fn field_access_adds_member_offset_and_type() {
1682 let int_type = TypeInfo::BaseType {
1683 name: "int".to_string(),
1684 size: 4,
1685 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1686 };
1687 let plan = typed_read_plan(
1688 VariableLocation::RegisterAddress {
1689 dwarf_reg: 6,
1690 offset: -32,
1691 },
1692 TypeInfo::StructType {
1693 name: "Request".to_string(),
1694 size: 16,
1695 members: vec![StructMember {
1696 name: "fd".to_string(),
1697 member_type: int_type.clone(),
1698 offset: 12,
1699 bit_offset: None,
1700 bit_size: None,
1701 }],
1702 },
1703 );
1704
1705 let access = VariableAccessPath::fields(["fd"]);
1706 let planned = plan.plan_access_path(&access).expect("field access");
1707
1708 assert_eq!(planned.name, "value.fd");
1709 assert_eq!(planned.access_path, access);
1710 assert_eq!(planned.dwarf_type, Some(int_type));
1711 assert_eq!(
1712 planned.location,
1713 VariableLocation::RegisterAddress {
1714 dwarf_reg: 6,
1715 offset: -20,
1716 }
1717 );
1718 assert_eq!(
1719 planned
1720 .materialization_plan(&capabilities(true))
1721 .access_path
1722 .segments,
1723 vec![VariableAccessSegment::Field("fd".to_string())]
1724 );
1725 }
1726
1727 #[test]
1728 fn field_access_unknown_member_reports_known_members() {
1729 let int_type = TypeInfo::BaseType {
1730 name: "int".to_string(),
1731 size: 4,
1732 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1733 };
1734 let plan = typed_read_plan(
1735 VariableLocation::Address(AddressExpr::constant(0x1000)),
1736 TypeInfo::StructType {
1737 name: "Request".to_string(),
1738 size: 8,
1739 members: vec![
1740 StructMember {
1741 name: "fd".to_string(),
1742 member_type: int_type.clone(),
1743 offset: 0,
1744 bit_offset: None,
1745 bit_size: None,
1746 },
1747 StructMember {
1748 name: "flags".to_string(),
1749 member_type: int_type,
1750 offset: 4,
1751 bit_offset: None,
1752 bit_size: None,
1753 },
1754 ],
1755 },
1756 );
1757
1758 let err = plan
1759 .plan_access_path(&VariableAccessPath::fields(["missing"]))
1760 .expect_err("unknown member should fail");
1761
1762 assert_eq!(
1763 err.to_string(),
1764 "Unknown member 'missing' in struct 'Request' (known members: fd, flags)"
1765 );
1766 }
1767
1768 #[test]
1769 fn field_access_folds_constant_address_offsets() {
1770 let int_type = TypeInfo::BaseType {
1771 name: "int".to_string(),
1772 size: 4,
1773 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1774 };
1775 let plan = typed_read_plan(
1776 VariableLocation::Address(AddressExpr::constant(0x1000)),
1777 TypeInfo::StructType {
1778 name: "Request".to_string(),
1779 size: 16,
1780 members: vec![StructMember {
1781 name: "fd".to_string(),
1782 member_type: int_type,
1783 offset: 12,
1784 bit_offset: None,
1785 bit_size: None,
1786 }],
1787 },
1788 );
1789
1790 let planned = plan
1791 .plan_access_path(&VariableAccessPath::fields(["fd"]))
1792 .expect("field access");
1793
1794 assert_eq!(
1795 planned.location,
1796 VariableLocation::Address(AddressExpr::constant(0x100c))
1797 );
1798 }
1799
1800 #[test]
1801 fn field_access_rejects_value_backed_aggregates() {
1802 let int_type = TypeInfo::BaseType {
1803 name: "int".to_string(),
1804 size: 4,
1805 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1806 };
1807 let struct_type = TypeInfo::StructType {
1808 name: "Pair".to_string(),
1809 size: 8,
1810 members: vec![StructMember {
1811 name: "b".to_string(),
1812 member_type: int_type,
1813 offset: 4,
1814 bit_offset: None,
1815 bit_size: None,
1816 }],
1817 };
1818 let access = VariableAccessPath::fields(["b"]);
1819
1820 for location in [
1821 VariableLocation::AbsoluteAddressValue(AddressExpr::constant(0x1000)),
1822 VariableLocation::RegisterValue { dwarf_reg: 0 },
1823 VariableLocation::ComputedValue(vec![PlanExprOp::LoadRegister(0)]),
1824 ] {
1825 let plan = typed_read_plan(location, struct_type.clone());
1826 let err = plan
1827 .plan_access_path(&access)
1828 .expect_err("value-backed aggregate field access should fail");
1829 assert!(
1830 err.downcast_ref::<PlanError>()
1831 .is_some_and(PlanError::is_value_backed_aggregate_access),
1832 "unexpected error: {err}"
1833 );
1834 }
1835 }
1836
1837 #[test]
1838 fn array_index_rejects_value_backed_aggregates() {
1839 let int_type = TypeInfo::BaseType {
1840 name: "int".to_string(),
1841 size: 4,
1842 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1843 };
1844 let array_type = TypeInfo::ArrayType {
1845 element_type: Box::new(int_type),
1846 element_count: Some(2),
1847 total_size: Some(8),
1848 };
1849 let access = VariableAccessPath::new(vec![VariableAccessSegment::ArrayIndex(1)]);
1850
1851 for location in [
1852 VariableLocation::AbsoluteAddressValue(AddressExpr::constant(0x1000)),
1853 VariableLocation::RegisterValue { dwarf_reg: 0 },
1854 VariableLocation::ComputedValue(vec![PlanExprOp::LoadRegister(0)]),
1855 ] {
1856 let plan = typed_read_plan(location, array_type.clone());
1857 let err = plan
1858 .plan_access_path(&access)
1859 .expect_err("value-backed aggregate array access should fail");
1860 assert!(
1861 err.downcast_ref::<PlanError>()
1862 .is_some_and(PlanError::is_value_backed_aggregate_access),
1863 "unexpected error: {err}"
1864 );
1865 }
1866 }
1867
1868 #[test]
1869 fn pointer_field_access_dereferences_then_offsets() {
1870 let int_type = TypeInfo::BaseType {
1871 name: "int".to_string(),
1872 size: 4,
1873 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1874 };
1875 let struct_type = TypeInfo::StructType {
1876 name: "Node".to_string(),
1877 size: 16,
1878 members: vec![StructMember {
1879 name: "value".to_string(),
1880 member_type: int_type,
1881 offset: 8,
1882 bit_offset: None,
1883 bit_size: None,
1884 }],
1885 };
1886 let plan = typed_read_plan(
1887 VariableLocation::RegisterValue { dwarf_reg: 5 },
1888 TypeInfo::PointerType {
1889 target_type: Box::new(struct_type),
1890 size: 8,
1891 },
1892 );
1893
1894 let access = VariableAccessPath::fields(["value"]);
1895 let planned = plan.plan_access_path(&access).expect("pointer field");
1896
1897 assert_eq!(
1898 planned.location,
1899 VariableLocation::ComputedAddress(vec![
1900 PlanExprOp::LoadRegister(5),
1901 PlanExprOp::PushConstant(8),
1902 PlanExprOp::Add,
1903 ])
1904 );
1905 }
1906
1907 #[test]
1908 fn pointer_field_access_from_absolute_address_value_rebases_memory_location() {
1909 let int_type = TypeInfo::BaseType {
1910 name: "int".to_string(),
1911 size: 4,
1912 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1913 };
1914 let struct_type = TypeInfo::StructType {
1915 name: "Node".to_string(),
1916 size: 16,
1917 members: vec![StructMember {
1918 name: "value".to_string(),
1919 member_type: int_type,
1920 offset: 8,
1921 bit_offset: None,
1922 bit_size: None,
1923 }],
1924 };
1925 let plan = typed_read_plan(
1926 VariableLocation::AbsoluteAddressValue(AddressExpr::constant(0x1000)),
1927 TypeInfo::PointerType {
1928 target_type: Box::new(struct_type),
1929 size: 8,
1930 },
1931 );
1932
1933 let planned = plan
1934 .plan_access_path(&VariableAccessPath::fields(["value"]))
1935 .expect("pointer field");
1936
1937 assert_eq!(
1938 planned.location,
1939 VariableLocation::Address(AddressExpr::constant(0x1008))
1940 );
1941 }
1942
1943 #[test]
1944 fn pointer_field_access_from_computed_value_uses_value_as_address() {
1945 let int_type = TypeInfo::BaseType {
1946 name: "int".to_string(),
1947 size: 4,
1948 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1949 };
1950 let struct_type = TypeInfo::StructType {
1951 name: "Node".to_string(),
1952 size: 16,
1953 members: vec![StructMember {
1954 name: "value".to_string(),
1955 member_type: int_type,
1956 offset: 8,
1957 bit_offset: None,
1958 bit_size: None,
1959 }],
1960 };
1961 let plan = typed_read_plan(
1962 VariableLocation::ComputedValue(vec![PlanExprOp::PushConstant(0x2000)]),
1963 TypeInfo::PointerType {
1964 target_type: Box::new(struct_type),
1965 size: 8,
1966 },
1967 );
1968
1969 let planned = plan
1970 .plan_access_path(&VariableAccessPath::fields(["value"]))
1971 .expect("pointer field");
1972
1973 assert_eq!(
1974 planned.location,
1975 VariableLocation::ComputedAddress(vec![
1976 PlanExprOp::PushConstant(0x2000),
1977 PlanExprOp::PushConstant(8),
1978 PlanExprOp::Add,
1979 ])
1980 );
1981 }
1982
1983 #[test]
1984 fn pointer_element_index_is_planned_in_dwarf_semantics() {
1985 let int_type = TypeInfo::BaseType {
1986 name: "int".to_string(),
1987 size: 4,
1988 encoding: gimli::constants::DW_ATE_signed.0 as u16,
1989 };
1990 let plan = typed_read_plan(
1991 VariableLocation::RegisterValue { dwarf_reg: 5 },
1992 TypeInfo::PointerType {
1993 target_type: Box::new(int_type),
1994 size: 8,
1995 },
1996 );
1997
1998 let planned = plan
1999 .plan_pointer_element_index(3)
2000 .expect("pointer element index");
2001
2002 assert_eq!(planned.name, "value[3]");
2003 assert_eq!(
2004 planned.location,
2005 VariableLocation::ComputedAddress(vec![
2006 PlanExprOp::LoadRegister(5),
2007 PlanExprOp::PushConstant(12),
2008 PlanExprOp::Add,
2009 ])
2010 );
2011 }
2012
2013 #[test]
2014 fn pointer_element_index_rejects_aggregate_arithmetic_with_pointer_error() {
2015 let int_type = TypeInfo::BaseType {
2016 name: "int".to_string(),
2017 size: 4,
2018 encoding: gimli::constants::DW_ATE_signed.0 as u16,
2019 };
2020 let plan = typed_read_plan(
2021 VariableLocation::Address(AddressExpr::constant(0x1000)),
2022 TypeInfo::StructType {
2023 name: "GlobalState".to_string(),
2024 size: 16,
2025 members: vec![StructMember {
2026 name: "counter".to_string(),
2027 member_type: int_type,
2028 offset: 0,
2029 bit_offset: None,
2030 bit_size: None,
2031 }],
2032 },
2033 );
2034
2035 let err = plan
2036 .plan_pointer_element_index(1)
2037 .expect_err("struct arithmetic must be rejected");
2038 let plan_error = err
2039 .downcast_ref::<PlanError>()
2040 .expect("structured plan error");
2041 assert!(matches!(
2042 plan_error,
2043 PlanError::InvalidPointerArithmetic { type_name }
2044 if type_name == "struct GlobalState"
2045 ));
2046 }
2047
2048 #[test]
2049 fn array_index_access_uses_element_stride() {
2050 let int_type = TypeInfo::BaseType {
2051 name: "int".to_string(),
2052 size: 4,
2053 encoding: gimli::constants::DW_ATE_signed.0 as u16,
2054 };
2055 let plan = typed_read_plan(
2056 VariableLocation::Address(AddressExpr::constant(0x1000)),
2057 TypeInfo::ArrayType {
2058 element_type: Box::new(int_type),
2059 element_count: Some(8),
2060 total_size: Some(32),
2061 },
2062 );
2063
2064 let access = VariableAccessPath::new(vec![VariableAccessSegment::ArrayIndex(3)]);
2065 let planned = plan.plan_access_path(&access).expect("array index");
2066
2067 assert_eq!(planned.name, "value[3]");
2068 assert_eq!(
2069 planned.location,
2070 VariableLocation::Address(AddressExpr::constant(0x100c))
2071 );
2072 }
2073}