use std::{
collections::HashMap,
ffi::{c_void, CStr},
os::{
raw::c_char,
unix::prelude::{OsStrExt, PermissionsExt},
},
path::PathBuf,
ptr::NonNull,
};
use crate::{
btf_container::BtfContainer,
elf_container::ElfContainer,
helper::btf::create_elf_with_btf_section,
meta::{ComposedObject, EunomiaObjectMeta, RunnerConfig},
skeleton::{BTF_PATH_ENV_NAME, VMLINUX_BTF_PATH},
};
use anyhow::{anyhow, bail, Result};
use bpf_compatible_rs::get_current_system_btf_file;
use libbpf_rs::{
libbpf_sys::{
self, bpf_map__name, bpf_map__value_size, bpf_object__btf, bpf_object__next_map,
btf__get_raw_data,
},
ObjectBuilder, OpenObject,
};
use super::preload::PreLoadBpfSkeleton;
pub struct BpfSkeletonBuilder<'a> {
btf_archive_path: Option<&'a str>,
object_meta: &'a EunomiaObjectMeta,
bpf_object: &'a [u8],
runner_config: Option<RunnerConfig>,
}
impl<'a> BpfSkeletonBuilder<'a> {
pub fn from_object_meta_and_object_buffer(
meta: &'a EunomiaObjectMeta,
bpf_object: &'a [u8],
btf_archive_path: Option<&'a str>,
) -> Self {
Self {
btf_archive_path,
object_meta: meta,
bpf_object,
runner_config: None,
}
}
pub fn from_json_package(
package: &'a ComposedObject,
btf_archive_path: Option<&'a str>,
) -> Self {
Self::from_object_meta_and_object_buffer(
&package.meta,
&package.bpf_object,
btf_archive_path,
)
}
pub fn set_runner_config(self, cfg: RunnerConfig) -> Self {
Self {
runner_config: Some(cfg),
..self
}
}
pub fn build(self) -> Result<PreLoadBpfSkeleton> {
let mut open_bpts = ObjectBuilder::default()
.opts(self.object_meta.bpf_skel.obj_name.as_bytes().as_ptr() as *const c_char);
let path_holder = if let Some(base_path) = self.btf_archive_path.as_ref() {
let path = get_current_system_btf_file(PathBuf::from(base_path).as_path())?;
if !path.exists() {
bail!("BTF file not found for current system: {}", path.display());
}
Some(path)
} else {
None
};
let env_btf_file_path = std::env::var_os(BTF_PATH_ENV_NAME);
let vmlinux_btf_exists = if PathBuf::from(VMLINUX_BTF_PATH).exists() {
match std::fs::metadata(VMLINUX_BTF_PATH) {
Ok(meta) => {
meta.permissions().mode() & 0o0400 != 0
}
Err(e) => {
log::info!("Failed to get metadata of {}: {}", VMLINUX_BTF_PATH, e);
false
}
}
} else {
false
};
if path_holder.is_some() && !vmlinux_btf_exists {
open_bpts.btf_custom_path = path_holder
.as_ref()
.unwrap()
.as_os_str()
.as_bytes()
.as_ptr() as *const i8;
} else if let Some(env_btf) = env_btf_file_path.as_ref() {
open_bpts.btf_custom_path = env_btf.as_bytes().as_ptr() as *const i8;
} else if !vmlinux_btf_exists {
bail!("All ways tried to find vmlinux BTF, but not found. Please provide the vmlinux btf using env `BTF_FILE_PATH`. (Tried parameter `btf_archive_path`, {}, and {})",BTF_PATH_ENV_NAME,VMLINUX_BTF_PATH);
};
let open_result = unsafe {
libbpf_sys::bpf_object__open_mem(
self.bpf_object.as_ptr() as *const c_void,
self.bpf_object.len() as libbpf_sys::size_t,
&open_bpts,
)
};
if open_result.is_null() {
bail!(
"Failed to open bpf object: bpf_object__open_mem returned NULL with errno={}",
errno::errno()
);
}
let btf = {
let btf = unsafe { bpf_object__btf(open_result) };
if btf.is_null() {
bail!("Failed to get btf* from the bpf_object: {}", errno::errno());
}
let mut dumped_size: u32 = 0;
let raw_data = unsafe { btf__get_raw_data(btf, &mut dumped_size as *mut u32) };
if raw_data.is_null() {
bail!(
"Failed to get the raw btf data from btf *: {}",
errno::errno()
);
}
let data =
unsafe { std::slice::from_raw_parts(raw_data as *const u8, dumped_size as usize) };
BtfContainer::new_from_binary(&create_elf_with_btf_section(data, true)?)?
};
let map_value_sizes = {
let mut sizes = HashMap::default();
let mut curr_map = std::ptr::null();
loop {
curr_map = unsafe { bpf_object__next_map(open_result, curr_map) };
if curr_map.is_null() {
break;
}
let map_name = unsafe { CStr::from_ptr(bpf_map__name(curr_map)) }
.to_str()
.map_err(|e| anyhow!("Map name contains invalid character: {}", e))?;
let value_size = unsafe { bpf_map__value_size(curr_map) };
sizes.insert(map_name.into(), value_size);
}
sizes
};
let open_object = unsafe { OpenObject::from_ptr(NonNull::new_unchecked(open_result)) }?;
Ok(PreLoadBpfSkeleton {
bpf_object: open_object,
config_data: self.runner_config.unwrap_or_default(),
btf,
meta: self.object_meta.clone(),
map_value_sizes,
raw_elf: ElfContainer::new_from_binary(self.bpf_object)?,
})
}
}
#[cfg(test)]
#[cfg(not(feature = "no-load-bpf-tests"))]
mod tests {
use libbpf_rs::libbpf_sys::{bpf_map__fd, bpf_map__initial_value, size_t};
use crate::{
meta::ComposedObject, skeleton::builder::BpfSkeletonBuilder, tests::get_assets_dir,
};
#[test]
fn test_bpf_skeleton_builder_1() {
let package = serde_json::from_str::<ComposedObject>(
&std::fs::read_to_string(get_assets_dir().join("runqlat.json")).unwrap(),
)
.unwrap();
let preload = BpfSkeletonBuilder::from_json_package(&package, None)
.build()
.unwrap();
let bpf_object = preload.bpf_object;
for map in package.meta.bpf_skel.maps.iter() {
let map_from_bpf = bpf_object.map(map.name.as_str()).unwrap();
println!("{:?}", map_from_bpf);
}
for prog in package.meta.bpf_skel.progs.iter() {
let prog_from_bpf = bpf_object.prog(prog.name.as_str()).unwrap();
println!("{:?}", prog_from_bpf);
}
{
let s = bpf_object.load().unwrap();
for map in s.maps_iter() {
let mptr = map.as_libbpf_bpf_map_ptr().unwrap();
let mut s: size_t = 0;
let ptr = unsafe { bpf_map__initial_value(mptr.as_ptr(), &mut s as *mut _) };
println!(
"{} key size {} value size {} max ent {} mmapedptr {:?} mmaped size {} fd {}",
map.name(),
map.key_size(),
map.value_size(),
map.info().unwrap().info.max_entries,
ptr,
s,
unsafe { bpf_map__fd(mptr.as_ptr()) }
);
}
}
}
}