use std::collections::BTreeMap;
use anyhow::{Context, Result};
use btf_rs::Btf;
use serde::{Deserialize, Serialize};
use super::btf_offsets::{StructOrFwd, find_struct_or_fwd, member_byte_offset};
use super::reader::GuestMem;
#[cfg(test)]
pub const SCX_STATIC_STRUCT_SIZE: usize = 24;
const MAX_REASONABLE_REGION_BYTES: u64 = 1u64 << 32;
#[allow(dead_code)]
const MAX_REASONABLE_OFF: u64 = MAX_REASONABLE_REGION_BYTES;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScxStaticOffsets {
pub max_alloc_bytes: usize,
pub memory: usize,
pub off: usize,
pub struct_size: usize,
}
impl ScxStaticOffsets {
pub fn from_btf(btf: &Btf) -> Result<Self> {
let scx_static = match find_struct_or_fwd(btf, "scx_static")
.context("btf: struct scx_static not found (scheduler doesn't link sdt_alloc, or BTF only carries a forward declaration)")?
{
StructOrFwd::Full(s) => s,
StructOrFwd::Fwd => anyhow::bail!(
"btf: struct scx_static present only as BTF_KIND_FWD forward declaration; member offsets unavailable"
),
};
let max_alloc_bytes = member_byte_offset(btf, &scx_static, "max_alloc_bytes")?;
let memory = member_byte_offset(btf, &scx_static, "memory")?;
let off = member_byte_offset(btf, &scx_static, "off")?;
let struct_size = scx_static.size();
Ok(Self {
max_alloc_bytes,
memory,
off,
struct_size,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ScxStaticRange {
pub instance_name: String,
pub start_low32: u32,
pub size: u64,
pub capacity: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ScxStaticSnapshot {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ranges: Vec<ScxStaticRange>,
pub skipped: u32,
}
impl ScxStaticSnapshot {
pub fn is_empty(&self) -> bool {
self.ranges.is_empty() && self.skipped == 0
}
}
impl std::fmt::Display for ScxStaticSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.ranges.is_empty() && self.skipped == 0 {
return write!(f, "scx_static: <none>");
}
for (i, r) in self.ranges.iter().enumerate() {
if i > 0 {
f.write_str("\n")?;
}
let end = (r.start_low32 as u64).saturating_add(r.size);
write!(
f,
"scx_static {} [{:#x}..{:#x}) cap={}",
r.instance_name, r.start_low32, end, r.capacity,
)?;
}
if self.skipped > 0 {
if !self.ranges.is_empty() {
f.write_str("\n")?;
}
write!(f, "scx_static: {} instance(s) skipped", self.skipped)?;
}
Ok(())
}
}
pub type ScxStaticRangeIndex = BTreeMap<u32, u64>;
pub fn build_scx_static_range_index(snapshot: &ScxStaticSnapshot) -> ScxStaticRangeIndex {
let mut index = ScxStaticRangeIndex::new();
for range in &snapshot.ranges {
match index.entry(range.start_low32) {
std::collections::btree_map::Entry::Vacant(v) => {
v.insert(range.size);
}
std::collections::btree_map::Entry::Occupied(o) => {
tracing::warn!(
start_low32 = format_args!("{:#x}", range.start_low32),
first_size = *o.get(),
duplicate_size = range.size,
instance = %range.instance_name,
"scx_static index has duplicate start_low32 (multi-instance \
low-32 collision or torn snapshot); keeping first range",
);
}
}
}
index
}
pub fn is_arena_addr_in_scx_static_index(index: &ScxStaticRangeIndex, addr: u64) -> bool {
if index.is_empty() {
return false;
}
let key = (addr & 0xFFFF_FFFF) as u32;
let Some((&start, &size)) = index.range(..=key).next_back() else {
return false;
};
let end = (start as u64) + size;
(key as u64) < end
}
pub fn walk_scx_static<I, F>(
bss_bytes: &[u8],
offsets: &ScxStaticOffsets,
vars: I,
is_scx_static_var: F,
) -> ScxStaticSnapshot
where
I: IntoIterator<Item = (String, usize, u32)>,
F: Fn(u32) -> bool,
{
let mut snap = ScxStaticSnapshot::default();
for (name, var_offset, type_id) in vars {
if !is_scx_static_var(type_id) {
continue;
}
match read_one_scx_static(bss_bytes, var_offset, offsets) {
Some((memory_low32, off, capacity)) => {
snap.ranges.push(ScxStaticRange {
instance_name: name,
start_low32: memory_low32,
size: off,
capacity,
});
}
None => {
snap.skipped = snap.skipped.saturating_add(1);
}
}
}
snap
}
fn read_one_scx_static(
bss_bytes: &[u8],
var_offset: usize,
offsets: &ScxStaticOffsets,
) -> Option<(u32, u64, u64)> {
let slice_end = var_offset.checked_add(offsets.struct_size)?;
let slice = bss_bytes.get(var_offset..slice_end)?;
let max_alloc_bytes = read_u64_at(slice, offsets.max_alloc_bytes)?;
if max_alloc_bytes == 0 || max_alloc_bytes >= MAX_REASONABLE_REGION_BYTES {
return None;
}
let memory = read_u64_at(slice, offsets.memory)?;
if memory == 0 {
return None;
}
let off = read_u64_at(slice, offsets.off)?;
if off > max_alloc_bytes {
return None;
}
let memory_low32 = memory as u32;
Some((memory_low32, off, max_alloc_bytes))
}
fn read_u64_at(bytes: &[u8], offset: usize) -> Option<u64> {
let end = offset.checked_add(8)?;
let slice = bytes.get(offset..end)?;
let mut buf = [0u8; 8];
buf.copy_from_slice(slice);
Some(u64::from_le_bytes(buf))
}
#[allow(dead_code)]
pub fn read_scx_static_from_pa(
mem: &GuestMem,
instance_pa: u64,
offsets: &ScxStaticOffsets,
) -> Option<(u32, u64, u64)> {
let max_alloc_bytes = mem.read_u64(instance_pa, offsets.max_alloc_bytes);
if max_alloc_bytes == 0 || max_alloc_bytes >= MAX_REASONABLE_REGION_BYTES {
return None;
}
let memory = mem.read_u64(instance_pa, offsets.memory);
if memory == 0 {
return None;
}
let off = mem.read_u64(instance_pa, offsets.off);
if off > max_alloc_bytes {
return None;
}
Some((memory as u32, off, max_alloc_bytes))
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_scx_static(
offsets: &ScxStaticOffsets,
max_alloc_bytes: u64,
memory: u64,
off: u64,
) -> Vec<u8> {
let mut bytes = vec![0u8; offsets.struct_size];
bytes[offsets.max_alloc_bytes..offsets.max_alloc_bytes + 8]
.copy_from_slice(&max_alloc_bytes.to_le_bytes());
bytes[offsets.memory..offsets.memory + 8].copy_from_slice(&memory.to_le_bytes());
bytes[offsets.off..offsets.off + 8].copy_from_slice(&off.to_le_bytes());
bytes
}
fn default_offsets() -> ScxStaticOffsets {
ScxStaticOffsets {
max_alloc_bytes: 0,
memory: 8,
off: 16,
struct_size: SCX_STATIC_STRUCT_SIZE,
}
}
#[test]
fn read_u64_at_basic() {
let bytes = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xff];
assert_eq!(read_u64_at(&bytes, 0), Some(0x0807060504030201));
assert_eq!(read_u64_at(&bytes, 2), None);
assert_eq!(read_u64_at(&bytes, 100), None);
}
#[test]
fn read_u64_at_handles_offset_overflow() {
let bytes = [0u8; 16];
assert_eq!(read_u64_at(&bytes, usize::MAX), None);
}
#[test]
fn struct_size_matches_upstream_layout() {
assert_eq!(SCX_STATIC_STRUCT_SIZE, 24);
}
#[test]
fn walk_scx_static_empty_input() {
let offsets = default_offsets();
let snap = walk_scx_static(&[], &offsets, std::iter::empty(), |_| true);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 0);
}
#[test]
fn walk_scx_static_filter_rejects_all() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 100);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("not_scx_static".to_string(), 0, 42)),
|_| false,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 0);
}
#[test]
fn walk_scx_static_single_instance_happy_path() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0x1234_5678_DEAD_BEEF, 100);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 1);
assert_eq!(snap.skipped, 0);
assert_eq!(snap.ranges[0].start_low32, 0xDEAD_BEEF);
assert_eq!(snap.ranges[0].size, 100);
assert_eq!(snap.ranges[0].capacity, 4096);
assert_eq!(snap.ranges[0].instance_name, "scx_static");
}
#[test]
fn walk_scx_static_rejects_zero_memory() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0, 100);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_rejects_zero_capacity() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 0, 0xDEAD_BEEF, 0);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_rejects_oversized_capacity() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, MAX_REASONABLE_REGION_BYTES, 0xDEAD_BEEF, 0);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_rejects_off_past_capacity() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 8192);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_accepts_off_eq_capacity() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 4096);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 1);
assert_eq!(snap.ranges[0].size, 4096);
}
#[test]
fn walk_scx_static_accepts_zero_off() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 0);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 1);
assert_eq!(snap.ranges[0].size, 0);
let index = build_scx_static_range_index(&snap);
assert!(!is_arena_addr_in_scx_static_index(&index, 0xDEAD_BEEF));
}
#[test]
fn walk_scx_static_fully_consumed_excludes_endpoint() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 4096);
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 1);
assert_eq!(snap.skipped, 0);
assert_eq!(snap.ranges[0].size, 4096);
assert_eq!(snap.ranges[0].capacity, 4096);
let index = build_scx_static_range_index(&snap);
assert!(is_arena_addr_in_scx_static_index(
&index,
0xDEAD_BEEF_u64 + 4095
));
assert!(!is_arena_addr_in_scx_static_index(
&index,
0xDEAD_BEEF_u64 + 4096
));
}
#[test]
fn walk_scx_static_rejects_short_slice() {
let offsets = default_offsets();
let bytes = vec![0u8; 16];
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 0, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_rejects_overflow_offset() {
let offsets = default_offsets();
let bytes = vec![0u8; 1024];
let snap = walk_scx_static(
&bytes,
&offsets,
std::iter::once(("scx_static".to_string(), 1010, 1)),
|_| true,
);
assert_eq!(snap.ranges.len(), 0);
assert_eq!(snap.skipped, 1);
}
#[test]
fn walk_scx_static_multiple_instances() {
let offsets = default_offsets();
let mut bytes = vec![0u8; 64];
bytes[0..24].copy_from_slice(&synth_scx_static(&offsets, 4096, 0xAAAA_AAAA, 100));
bytes[32..56].copy_from_slice(&synth_scx_static(&offsets, 8192, 0xBBBB_BBBB, 200));
let vars = vec![
("scx_static_a".to_string(), 0, 1),
("scx_static_b".to_string(), 32, 1),
];
let snap = walk_scx_static(&bytes, &offsets, vars, |_| true);
assert_eq!(snap.ranges.len(), 2);
assert_eq!(snap.skipped, 0);
assert_eq!(snap.ranges[0].start_low32, 0xAAAA_AAAA);
assert_eq!(snap.ranges[0].size, 100);
assert_eq!(snap.ranges[1].start_low32, 0xBBBB_BBBB);
assert_eq!(snap.ranges[1].size, 200);
}
#[test]
fn walk_scx_static_is_deterministic() {
let offsets = default_offsets();
let bytes = synth_scx_static(&offsets, 4096, 0xDEAD_BEEF, 100);
let snap_a = walk_scx_static(
&bytes,
&offsets,
vec![("scx_static".to_string(), 0, 1)],
|_| true,
);
let snap_b = walk_scx_static(
&bytes,
&offsets,
vec![("scx_static".to_string(), 0, 1)],
|_| true,
);
assert_eq!(snap_a, snap_b);
}
#[test]
fn is_arena_addr_in_scx_static_index_empty_returns_false() {
let index = ScxStaticRangeIndex::new();
assert!(!is_arena_addr_in_scx_static_index(&index, 0));
assert!(!is_arena_addr_in_scx_static_index(&index, 0xDEAD_BEEF));
assert!(!is_arena_addr_in_scx_static_index(&index, u64::MAX));
}
#[test]
fn is_arena_addr_in_scx_static_index_inside_range_returns_true() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
}],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(is_arena_addr_in_scx_static_index(&index, 0x1000));
assert!(is_arena_addr_in_scx_static_index(&index, 0x1063));
assert!(is_arena_addr_in_scx_static_index(
&index,
0xDEAD_BEEF_0000_1000
));
}
#[test]
fn is_arena_addr_in_scx_static_index_boundary_excluded() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
}],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(!is_arena_addr_in_scx_static_index(&index, 0x1064));
}
#[test]
fn is_arena_addr_in_scx_static_index_below_range_returns_false() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
}],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(!is_arena_addr_in_scx_static_index(&index, 0x0FFF));
assert!(!is_arena_addr_in_scx_static_index(&index, 0));
}
#[test]
fn is_arena_addr_in_scx_static_index_boundary_at_start_plus_size_excluded() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x10_0000,
size: 0x4000,
capacity: 0x4000,
}],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(is_arena_addr_in_scx_static_index(&index, 0x10_3FFF));
assert!(!is_arena_addr_in_scx_static_index(&index, 0x10_4000));
}
#[test]
fn is_arena_addr_in_scx_static_index_just_below_start_returns_false() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x10_0000,
size: 0x4000,
capacity: 0x4000,
}],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(!is_arena_addr_in_scx_static_index(&index, 0x0F_FFFF));
assert!(is_arena_addr_in_scx_static_index(&index, 0x10_0000));
}
#[test]
fn is_arena_addr_in_scx_static_index_picks_correct_range() {
let snap = ScxStaticSnapshot {
ranges: vec![
ScxStaticRange {
instance_name: "scx_static_a".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
},
ScxStaticRange {
instance_name: "scx_static_b".into(),
start_low32: 0x2000,
size: 100,
capacity: 4096,
},
],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert!(is_arena_addr_in_scx_static_index(&index, 0x1050));
assert!(!is_arena_addr_in_scx_static_index(&index, 0x1500));
assert!(is_arena_addr_in_scx_static_index(&index, 0x2050));
assert!(!is_arena_addr_in_scx_static_index(&index, 0x3000));
}
#[test]
fn build_scx_static_range_index_keeps_first_on_duplicate_start() {
let snap = ScxStaticSnapshot {
ranges: vec![
ScxStaticRange {
instance_name: "scx_static_a".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
},
ScxStaticRange {
instance_name: "scx_static_b".into(),
start_low32: 0x1000, size: 200,
capacity: 4096,
},
],
skipped: 0,
};
let index = build_scx_static_range_index(&snap);
assert_eq!(index.get(&0x1000), Some(&100));
}
#[test]
fn snapshot_empty_serde_roundtrip() {
let snap = ScxStaticSnapshot::default();
let json = serde_json::to_string(&snap).unwrap();
assert!(!json.contains("\"ranges\""));
assert!(json.contains("\"skipped\":0"));
let parsed: ScxStaticSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, snap);
}
#[test]
fn snapshot_populated_serde_roundtrip() {
let snap = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
}],
skipped: 1,
};
let json = serde_json::to_string(&snap).unwrap();
let parsed: ScxStaticSnapshot = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, snap);
}
#[test]
fn snapshot_display_formats() {
let empty = ScxStaticSnapshot::default();
assert_eq!(format!("{empty}"), "scx_static: <none>");
let one = ScxStaticSnapshot {
ranges: vec![ScxStaticRange {
instance_name: "scx_static".into(),
start_low32: 0x1000,
size: 100,
capacity: 4096,
}],
skipped: 0,
};
let s = format!("{one}");
assert!(s.contains("scx_static"));
assert!(s.contains("0x1000"));
assert!(s.contains("0x1064"));
assert!(s.contains("cap=4096"));
let with_skipped = ScxStaticSnapshot {
ranges: vec![],
skipped: 3,
};
let s = format!("{with_skipped}");
assert!(s.contains("3 instance(s) skipped"));
}
#[test]
fn from_btf_against_vmlinux_returns_err() {
let path = match crate::monitor::find_test_vmlinux() {
Some(p) => p,
None => {
crate::report::test_skip("no vmlinux for BTF load");
return;
}
};
let btf = match crate::monitor::btf_offsets::load_btf_from_path(&path) {
Ok(b) => b,
Err(_) => {
crate::report::test_skip("BTF load failed");
return;
}
};
let err = ScxStaticOffsets::from_btf(&btf)
.expect_err("vmlinux BTF must NOT contain scx_static — from_btf must Err");
let msg = format!("{err:#}");
assert!(
msg.contains("scx_static"),
"error must name the missing struct so the dump pipeline can log a useful diagnostic: '{msg}'"
);
}
}