use super::*;
use crate::monitor::cast_analysis::{BpfInsn, CastMap, InitialReg, analyze_casts};
fn cast_ldx_dw_mem_code() -> u8 {
use libbpf_rs::libbpf_sys as bs;
(bs::BPF_LDX | bs::BPF_DW | bs::BPF_MEM) as u8
}
fn cast_exit_code() -> u8 {
use libbpf_rs::libbpf_sys as bs;
(bs::BPF_JMP | bs::BPF_EXIT) as u8
}
fn cast_ldx_dw(dst: u8, src: u8, off: i16) -> BpfInsn {
BpfInsn::new(cast_ldx_dw_mem_code(), dst, src, off, 0)
}
fn cast_exit() -> BpfInsn {
BpfInsn::new(cast_exit_code(), 0, 0, 0, 0)
}
fn cast_addr_space_cast(dst: u8, src: u8, imm: i32) -> BpfInsn {
use libbpf_rs::libbpf_sys as bs;
let code = (bs::BPF_ALU64 | bs::BPF_MOV | bs::BPF_X) as u8;
BpfInsn::new(code, dst, src, 1, imm)
}
fn cast_btf_t_at_offset_8_q_at_offset_0() -> (Vec<u8>, u32, u32) {
let (strings, n_int, n_t, n_q, n_f, n_x) = cast_strings_for_t_q();
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 16,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 8,
}],
},
CastSynType::Struct {
name_off: n_q,
size: 8,
members: vec![CastSynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
];
(cast_build_btf(&types, &strings), 2, 3)
}
#[test]
fn cast_pipeline_analyzer_output_drives_renderer_intercept() {
let (blob, t_id, q_id) = cast_btf_t_at_offset_8_q_at_offset_0();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let insns = vec![
cast_ldx_dw(2, 1, 8),
cast_addr_space_cast(2, 2, 1),
cast_ldx_dw(3, 2, 0),
cast_exit(),
];
let cast_map = analyze_casts(
&insns,
&btf,
&[InitialReg {
reg: 1,
struct_type_id: t_id,
}],
&[],
&[],
&[],
);
assert_eq!(
cast_map.get(&(t_id, 8)),
Some(&CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena,
}),
"analyzer must emit (T, 8) → (Q, Arena); got: {cast_map:?}"
);
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let mut outer_bytes = vec![0u8; 16];
outer_bytes[8..16].copy_from_slice(&TARGET_ADDR.to_le_bytes());
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct {
type_name,
ref members,
} = v
else {
panic!("expected outer Struct render, got {v:?}");
};
assert_eq!(type_name.as_deref(), Some("T"));
assert_eq!(members.len(), 1, "T has a single u64 member at offset 8");
assert_eq!(members[0].name, "f");
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!(
"(T, 8) cast hit must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR, "Ptr value must be the loaded u64");
assert!(
deref_skipped_reason.is_none(),
"successful chase must carry no skip reason; got {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("chase succeeded → deref must be Some");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be the rendered Q struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("Q"),
"deref payload must carry Q's name"
);
assert_eq!(inner_members.len(), 1);
assert_eq!(inner_members[0].name, "x");
let RenderedValue::Uint { bits, value } = inner_members[0].value else {
panic!("Q.x must render as Uint, got {:?}", inner_members[0].value);
};
assert_eq!(bits, 64);
assert_eq!(value, 0x42);
}
#[test]
fn cast_pipeline_modifier_chain_renderer_peels_to_analyzer_struct_id() {
let (strings, n_int, n_t, n_q, n_f, n_x) = cast_strings_for_t_q();
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let mut strings = strings;
let n_typedef = push(&mut strings, "T_alias");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 16,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 8,
}],
},
CastSynType::Struct {
name_off: n_q,
size: 8,
members: vec![CastSynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Const { type_id: 2 },
CastSynType::Typedef {
name_off: n_typedef,
type_id: 4,
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let q_id: u32 = 3;
let typedef_id: u32 = 5;
let insns = vec![
cast_ldx_dw(2, 1, 8),
cast_addr_space_cast(2, 2, 1),
cast_ldx_dw(3, 2, 0),
cast_exit(),
];
let cast_map = analyze_casts(
&insns,
&btf,
&[InitialReg {
reg: 1,
struct_type_id: typedef_id,
}],
&[],
&[],
&[],
);
assert_eq!(
cast_map.get(&(t_id, 8)),
Some(&CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena
}),
"analyzer must peel typedef→const→struct and key on T_id={t_id}; got: {cast_map:?}"
);
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let mut outer_bytes = vec![0u8; 16];
outer_bytes[8..16].copy_from_slice(&TARGET_ADDR.to_le_bytes());
let inner_bytes = 0x99u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
..Default::default()
};
let v = render_value_with_mem(&btf, typedef_id, &outer_bytes, &reader);
let RenderedValue::Struct {
type_name,
ref members,
} = v
else {
panic!("expected Struct render after peel, got {v:?}");
};
assert_eq!(
type_name.as_deref(),
Some("T"),
"renderer must collapse typedef/const wrappers to underlying T name"
);
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!(
"modifier-chain peel must reach the cast intercept; got {:?}. \
A failure here means the renderer's peel diverges from the \
analyzer's — the integration is broken.",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(deref_skipped_reason.is_none());
let inner = deref.as_deref().expect("chase deref Some");
let RenderedValue::Struct {
type_name: ref inner_name,
..
} = *inner
else {
panic!("deref payload must be Q struct, got {inner:?}");
};
assert_eq!(inner_name.as_deref(), Some("Q"));
}
#[test]
fn cast_pipeline_multi_field_only_flagged_offsets_render_as_ptr() {
let (strings, n_int, n_t, n_q, _n_f, n_x) = cast_strings_for_t_q();
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let mut strings = strings;
let n_f0 = push(&mut strings, "f0");
let n_f1 = push(&mut strings, "f1");
let n_f2 = push(&mut strings, "f2");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 24,
members: vec![
CastSynMember {
name_off: n_f0,
type_id: 1,
byte_offset: 0,
},
CastSynMember {
name_off: n_f1,
type_id: 1,
byte_offset: 8,
},
CastSynMember {
name_off: n_f2,
type_id: 1,
byte_offset: 16,
},
],
},
CastSynType::Struct {
name_off: n_q,
size: 8,
members: vec![CastSynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let q_id: u32 = 3;
let mut cast_map: CastMap = std::collections::BTreeMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena,
},
);
cast_map.insert(
(t_id, 8),
CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena,
},
);
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const ADDR_F0: u64 = 0x10_0000_1000;
const ADDR_F1: u64 = 0x10_0000_2000;
const COUNTER_F2: u64 = 0x10_0000_3000;
let mut outer_bytes = vec![0u8; 24];
outer_bytes[0..8].copy_from_slice(&ADDR_F0.to_le_bytes());
outer_bytes[8..16].copy_from_slice(&ADDR_F1.to_le_bytes());
outer_bytes[16..24].copy_from_slice(&COUNTER_F2.to_le_bytes());
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(ADDR_F0, 0xAAu64.to_le_bytes().to_vec());
arena_bytes.insert(ADDR_F1, 0xBBu64.to_le_bytes().to_vec());
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected outer Struct render, got {v:?}");
};
assert_eq!(members.len(), 3, "T has three u64 members");
assert_eq!(members[0].name, "f0");
assert_eq!(members[1].name, "f1");
assert_eq!(members[2].name, "f2");
let RenderedValue::Ptr {
value: f0_value,
ref deref,
..
} = members[0].value
else {
panic!(
"f0 (offset 0) must render as Ptr (cast map hit); got {:?}",
members[0].value
);
};
assert_eq!(f0_value, ADDR_F0);
assert!(deref.is_some(), "f0 chase must succeed (deref Some)");
let RenderedValue::Ptr {
value: f1_value,
ref deref,
..
} = members[1].value
else {
panic!(
"f1 (offset 8) must render as Ptr (cast map hit); got {:?}",
members[1].value
);
};
assert_eq!(f1_value, ADDR_F1);
assert!(deref.is_some(), "f1 chase must succeed (deref Some)");
let RenderedValue::Uint {
bits: f2_bits,
value: f2_value,
} = members[2].value
else {
panic!(
"f2 (offset 16) must render as Uint (no cast map entry); \
got {:?}. A failure here means a hit on one offset is \
contaminating unrelated offsets in the same struct.",
members[2].value
);
};
assert_eq!(f2_bits, 64);
assert_eq!(f2_value, COUNTER_F2);
}
#[test]
fn cast_pipeline_empty_cast_map_renders_uint() {
let (blob, t_id, _q_id) = cast_btf_t_at_offset_8_q_at_offset_0();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let cast_map: CastMap = std::collections::BTreeMap::new();
let reader = CastStubReader {
cast_map: Some(cast_map),
..Default::default()
};
let mut outer_bytes = vec![0u8; 16];
outer_bytes[8..16].copy_from_slice(&0xCAFE_F00Du64.to_le_bytes());
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Uint { bits, value } = members[0].value else {
panic!(
"empty cast map must leave u64 as Uint; got {:?}. A \
failure here means an empty BTreeMap is being treated \
as 'wildcard hit' instead of 'no hits' — a regression \
that would promote every u64 to a phantom pointer.",
members[0].value
);
};
assert_eq!(bits, 64);
assert_eq!(value, 0xCAFE_F00D);
}
#[test]
fn cast_pipeline_wrong_struct_id_does_not_intercept() {
let (strings, n_int, n_t, n_q, n_f, n_x) = cast_strings_for_t_q();
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let mut strings = strings;
let n_u = push(&mut strings, "U");
let n_g = push(&mut strings, "g");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 8,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Struct {
name_off: n_q,
size: 8,
members: vec![CastSynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Struct {
name_off: n_u,
size: 8,
members: vec![CastSynMember {
name_off: n_g,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let q_id: u32 = 3;
let u_id: u32 = 4;
let mut cast_map: CastMap = std::collections::BTreeMap::new();
cast_map.insert(
(u_id, 0),
CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena,
},
);
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const VAL: u64 = 0x10_0000_1000;
let outer_bytes = VAL.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(VAL, 0x77u64.to_le_bytes().to_vec());
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Uint { bits, value } = members[0].value else {
panic!(
"(T, 0) must miss the (U, 0) cast entry → render as \
Uint; got {:?}. A failure here means cast_lookup is \
ignoring parent_type_id, which would promote every \
u64 at the entry's offset across every struct in the \
scheduler.",
members[0].value
);
};
assert_eq!(bits, 64);
assert_eq!(value, VAL);
}
#[test]
fn cast_chase_arena_hint_with_non_arena_value_falls_through_to_kernel_arm() {
let (blob, t_id, q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const OUT_OF_WINDOW: u64 = 0x0F_FFFF_FFFF;
let outer_bytes = OUT_OF_WINDOW.to_le_bytes().to_vec();
let mut cast_map: crate::monitor::cast_analysis::CastMap = std::collections::BTreeMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!("must surface as Ptr; got {:?}", members[0].value);
};
assert_eq!(value, OUT_OF_WINDOW);
assert!(deref.is_none());
let reason = deref_skipped_reason.as_deref().expect("skip reason");
assert!(
reason.contains("read_kva failed"),
"skip reason must mention 'read_kva failed' (kernel arm); got: {reason}"
);
assert!(
reason.contains("cast analysis may have flagged"),
"Arena→kernel runtime dispatch must annotate suffix; got: {reason}"
);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→kernel"),
"runtime kernel dispatch must produce cast→kernel annotation"
);
}