use super::btf_offsets::BpfProgOffsets;
use super::idr::{translate_any_kva, xa_load};
use super::reader::GuestMem;
use super::symbols::text_kva_to_pa;
const BPF_PROG_TYPE_STRUCT_OPS: u32 = 27;
const BPF_OBJ_NAME_LEN: usize = 16;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProgVerifierStats {
pub name: String,
pub verified_insns: u32,
}
pub(crate) fn find_struct_ops_progs(
mem: &GuestMem,
cr3_pa: u64,
page_offset: u64,
prog_idr_kva: u64,
offsets: &BpfProgOffsets,
l5: bool,
) -> Vec<ProgVerifierStats> {
let idr_pa = text_kva_to_pa(prog_idr_kva);
let xa_head = mem.read_u64(idr_pa, offsets.idr_xa_head);
if xa_head == 0 {
return Vec::new();
}
let idr_next = mem.read_u32(idr_pa, offsets.idr_next);
let mut progs = Vec::new();
for id in 0..idr_next {
let Some(entry) = xa_load(
mem,
page_offset,
xa_head,
id as u64,
offsets.xa_node_slots,
offsets.xa_node_shift,
) else {
continue;
};
if entry == 0 {
continue;
}
let Some(prog_pa) = translate_any_kva(mem, cr3_pa, page_offset, entry, l5) else {
continue;
};
let prog_type = mem.read_u32(prog_pa, offsets.prog_type);
if prog_type != BPF_PROG_TYPE_STRUCT_OPS {
continue;
}
let aux_kva = mem.read_u64(prog_pa, offsets.prog_aux);
if aux_kva == 0 {
continue;
}
let Some(aux_pa) = translate_any_kva(mem, cr3_pa, page_offset, aux_kva, l5) else {
continue;
};
let verified_insns = mem.read_u32(aux_pa, offsets.aux_verified_insns);
let mut name_buf = [0u8; BPF_OBJ_NAME_LEN];
mem.read_bytes(aux_pa + offsets.aux_name as u64, &mut name_buf);
let name_len = name_buf
.iter()
.position(|&b| b == 0)
.unwrap_or(BPF_OBJ_NAME_LEN);
let name = String::from_utf8_lossy(&name_buf[..name_len]).to_string();
progs.push(ProgVerifierStats {
name,
verified_insns,
});
}
progs
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct ProgRuntimeStats {
pub name: String,
pub cnt: u64,
pub nsecs: u64,
}
#[derive(Debug, Clone)]
pub struct CachedProgInfo {
pub name: String,
pub stats_percpu_kva: u64,
}
pub(crate) fn discover_struct_ops_stats(
mem: &GuestMem,
cr3_pa: u64,
page_offset: u64,
prog_idr_kva: u64,
offsets: &BpfProgOffsets,
l5: bool,
) -> Vec<CachedProgInfo> {
let idr_pa = text_kva_to_pa(prog_idr_kva);
let xa_head = mem.read_u64(idr_pa, offsets.idr_xa_head);
if xa_head == 0 {
return Vec::new();
}
let idr_next = mem.read_u32(idr_pa, offsets.idr_next);
let mut cached = Vec::new();
for id in 0..idr_next {
let Some(entry) = xa_load(
mem,
page_offset,
xa_head,
id as u64,
offsets.xa_node_slots,
offsets.xa_node_shift,
) else {
continue;
};
if entry == 0 {
continue;
}
let Some(prog_pa) = translate_any_kva(mem, cr3_pa, page_offset, entry, l5) else {
continue;
};
let prog_type = mem.read_u32(prog_pa, offsets.prog_type);
if prog_type != BPF_PROG_TYPE_STRUCT_OPS {
continue;
}
let aux_kva = mem.read_u64(prog_pa, offsets.prog_aux);
if aux_kva == 0 {
continue;
}
let Some(aux_pa) = translate_any_kva(mem, cr3_pa, page_offset, aux_kva, l5) else {
continue;
};
let mut name_buf = [0u8; BPF_OBJ_NAME_LEN];
mem.read_bytes(aux_pa + offsets.aux_name as u64, &mut name_buf);
let name_len = name_buf
.iter()
.position(|&b| b == 0)
.unwrap_or(BPF_OBJ_NAME_LEN);
let name = String::from_utf8_lossy(&name_buf[..name_len]).to_string();
let stats_percpu_kva = mem.read_u64(prog_pa, offsets.prog_stats);
if stats_percpu_kva == 0 {
continue;
}
cached.push(CachedProgInfo {
name,
stats_percpu_kva,
});
}
cached
}
pub(crate) fn read_prog_runtime_stats(
mem: &GuestMem,
cached: &[CachedProgInfo],
per_cpu_offsets: &[u64],
page_offset: u64,
offsets: &BpfProgOffsets,
) -> Vec<ProgRuntimeStats> {
cached
.iter()
.map(|prog| {
let mut cnt: u64 = 0;
let mut nsecs: u64 = 0;
for &cpu_off in per_cpu_offsets {
let stats_kva = prog.stats_percpu_kva.wrapping_add(cpu_off);
let stats_pa = super::symbols::kva_to_pa(stats_kva, page_offset);
if stats_pa < mem.size() {
cnt += mem.read_u64(stats_pa, offsets.stats_cnt);
nsecs += mem.read_u64(stats_pa, offsets.stats_nsecs);
}
}
ProgRuntimeStats {
name: prog.name.clone(),
cnt,
nsecs,
}
})
.collect()
}
pub struct BpfProgAccessor<'a> {
kernel: &'a super::guest::GuestKernel<'a>,
prog_idr_kva: u64,
offsets: &'a BpfProgOffsets,
}
impl<'a> BpfProgAccessor<'a> {
pub fn from_guest_kernel(
kernel: &'a super::guest::GuestKernel<'a>,
offsets: &'a BpfProgOffsets,
) -> anyhow::Result<Self> {
let prog_idr_kva = kernel
.symbol_kva("prog_idr")
.ok_or_else(|| anyhow::anyhow!("prog_idr symbol not found in vmlinux"))?;
Ok(Self {
kernel,
prog_idr_kva,
offsets,
})
}
pub fn struct_ops_progs(&self) -> Vec<ProgVerifierStats> {
find_struct_ops_progs(
self.kernel.mem(),
self.kernel.cr3_pa(),
self.kernel.page_offset(),
self.prog_idr_kva,
self.offsets,
self.kernel.l5(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prog_verifier_stats_serde_roundtrip() {
let info = ProgVerifierStats {
name: "dispatch".to_string(),
verified_insns: 42000,
};
let json = serde_json::to_string(&info).unwrap();
let loaded: ProgVerifierStats = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.name, "dispatch");
assert_eq!(loaded.verified_insns, 42000);
}
#[test]
fn prog_verifier_stats_vec_serde_roundtrip() {
let stats = vec![
ProgVerifierStats {
name: "dispatch".to_string(),
verified_insns: 100000,
},
ProgVerifierStats {
name: "enqueue".to_string(),
verified_insns: 50000,
},
];
let json = serde_json::to_vec(&stats).unwrap();
let loaded: Vec<ProgVerifierStats> = serde_json::from_slice(&json).unwrap();
assert_eq!(loaded.len(), 2);
assert_eq!(loaded[0].name, "dispatch");
assert_eq!(loaded[0].verified_insns, 100000);
assert_eq!(loaded[1].name, "enqueue");
assert_eq!(loaded[1].verified_insns, 50000);
}
#[test]
fn prog_verifier_stats_empty_name() {
let info = ProgVerifierStats {
name: String::new(),
verified_insns: 0,
};
let json = serde_json::to_string(&info).unwrap();
let loaded: ProgVerifierStats = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.name, "");
assert_eq!(loaded.verified_insns, 0);
}
#[test]
fn prog_verifier_stats_max_values() {
let info = ProgVerifierStats {
name: "x".repeat(16),
verified_insns: u32::MAX,
};
let json = serde_json::to_string(&info).unwrap();
let loaded: ProgVerifierStats = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.verified_insns, u32::MAX);
assert_eq!(loaded.name.len(), 16);
}
}