pub mod types;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io;
use std::marker::PhantomData;
use std::mem::size_of;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::os::raw::c_ulong;
use std::os::raw::c_void;
use std::os::unix::prelude::AsRawFd;
use std::os::unix::prelude::FromRawFd;
use std::os::unix::prelude::OsStrExt;
use std::os::unix::prelude::OwnedFd;
use std::path::Path;
use std::ptr;
use std::ptr::NonNull;
use crate::util::parse_ret_i32;
use crate::util::validate_bpf_ret;
use crate::AsRawLibbpf;
use crate::Error;
use crate::ErrorExt as _;
use crate::Result;
use self::types::Composite;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum BtfKind {
Void = 0,
Int,
Ptr,
Array,
Struct,
Union,
Enum,
Fwd,
Typedef,
Volatile,
Const,
Restrict,
Func,
FuncProto,
Var,
DataSec,
Float,
DeclTag,
TypeTag,
Enum64,
}
impl TryFrom<u32> for BtfKind {
type Error = u32;
fn try_from(value: u32) -> Result<Self, Self::Error> {
use BtfKind::*;
Ok(match value {
x if x == Void as u32 => Void,
x if x == Int as u32 => Int,
x if x == Ptr as u32 => Ptr,
x if x == Array as u32 => Array,
x if x == Struct as u32 => Struct,
x if x == Union as u32 => Union,
x if x == Enum as u32 => Enum,
x if x == Fwd as u32 => Fwd,
x if x == Typedef as u32 => Typedef,
x if x == Volatile as u32 => Volatile,
x if x == Const as u32 => Const,
x if x == Restrict as u32 => Restrict,
x if x == Func as u32 => Func,
x if x == FuncProto as u32 => FuncProto,
x if x == Var as u32 => Var,
x if x == DataSec as u32 => DataSec,
x if x == Float as u32 => Float,
x if x == DeclTag as u32 => DeclTag,
x if x == TypeTag as u32 => TypeTag,
x if x == Enum64 as u32 => Enum64,
v => return Err(v),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypeId(u32);
impl From<u32> for TypeId {
fn from(s: u32) -> Self {
Self(s)
}
}
impl From<TypeId> for u32 {
fn from(t: TypeId) -> Self {
t.0
}
}
impl Display for TypeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug)]
enum DropPolicy {
Nothing,
SelfPtrOnly,
ObjPtr(*mut libbpf_sys::bpf_object),
}
pub struct Btf<'source> {
ptr: NonNull<libbpf_sys::btf>,
drop_policy: DropPolicy,
_marker: PhantomData<&'source ()>,
}
impl Btf<'static> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
fn inner(path: &Path) -> Result<Btf<'static>> {
let path = CString::new(path.as_os_str().as_bytes()).map_err(|_| {
Error::with_invalid_data(format!("invalid path {path:?}, has null bytes"))
})?;
let ptr = unsafe { libbpf_sys::btf__parse(path.as_ptr(), ptr::null_mut()) };
let ptr = validate_bpf_ret(ptr).context("failed to parse BTF information")?;
Ok(Btf {
ptr,
drop_policy: DropPolicy::SelfPtrOnly,
_marker: PhantomData,
})
}
inner(path.as_ref())
}
pub fn from_vmlinux() -> Result<Self> {
let ptr = unsafe { libbpf_sys::btf__load_vmlinux_btf() };
let ptr = validate_bpf_ret(ptr).context("failed to load BTF from vmlinux")?;
Ok(Btf {
ptr,
drop_policy: DropPolicy::SelfPtrOnly,
_marker: PhantomData,
})
}
pub fn from_prog_id(id: u32) -> Result<Self> {
let fd = parse_ret_i32(unsafe { libbpf_sys::bpf_prog_get_fd_by_id(id) })?;
let fd = unsafe {
OwnedFd::from_raw_fd(fd)
};
let mut info = libbpf_sys::bpf_prog_info::default();
parse_ret_i32(unsafe {
libbpf_sys::bpf_obj_get_info_by_fd(
fd.as_raw_fd(),
(&mut info as *mut libbpf_sys::bpf_prog_info).cast::<c_void>(),
&mut (size_of::<libbpf_sys::bpf_prog_info>() as u32),
)
})?;
let ptr = unsafe { libbpf_sys::btf__load_from_kernel_by_id(info.btf_id) };
let ptr = validate_bpf_ret(ptr).context("failed to load BTF from kernel")?;
Ok(Self {
ptr,
drop_policy: DropPolicy::SelfPtrOnly,
_marker: PhantomData,
})
}
}
impl<'btf> Btf<'btf> {
pub fn from_bpf_object(obj: &'btf libbpf_sys::bpf_object) -> Result<Option<Self>> {
Self::from_bpf_object_raw(obj)
}
fn from_bpf_object_raw(obj: *const libbpf_sys::bpf_object) -> Result<Option<Self>> {
let ptr = unsafe {
libbpf_sys::bpf_object__btf(obj)
};
if ptr.is_null() {
return Ok(None)
}
let ptr = validate_bpf_ret(ptr).context("failed to create BTF from BPF object")?;
let slf = Self {
ptr,
drop_policy: DropPolicy::Nothing,
_marker: PhantomData,
};
Ok(Some(slf))
}
pub fn from_raw(name: &'btf str, object_file: &'btf [u8]) -> Result<Option<Self>> {
let cname = CString::new(name)
.map_err(|_| Error::with_invalid_data(format!("invalid path {name:?}, has null bytes")))
.unwrap();
let obj_opts = libbpf_sys::bpf_object_open_opts {
sz: size_of::<libbpf_sys::bpf_object_open_opts>() as libbpf_sys::size_t,
object_name: cname.as_ptr(),
..Default::default()
};
let ptr = unsafe {
libbpf_sys::bpf_object__open_mem(
object_file.as_ptr() as *const c_void,
object_file.len() as c_ulong,
&obj_opts,
)
};
let mut bpf_obj = validate_bpf_ret(ptr).context("failed to open BPF object from memory")?;
let bpf_obj = unsafe { bpf_obj.as_mut() };
match Self::from_bpf_object_raw(bpf_obj) {
Ok(Some(this)) => Ok(Some(Self {
drop_policy: DropPolicy::ObjPtr(bpf_obj),
..this
})),
x => {
unsafe {
libbpf_sys::bpf_object__close(bpf_obj)
};
x
}
}
}
fn name_at(&self, offset: u32) -> Option<&'btf OsStr> {
let name = unsafe {
libbpf_sys::btf__name_by_offset(self.ptr.as_ptr(), offset)
};
NonNull::new(name as *mut _)
.map(|p| unsafe {
OsStr::from_bytes(CStr::from_ptr(p.as_ptr()).to_bytes())
})
.filter(|s| !s.is_empty()) }
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
unsafe {
libbpf_sys::btf__type_cnt(self.ptr.as_ptr()) as usize
}
}
pub fn ptr_size(&self) -> Result<NonZeroUsize> {
let sz = unsafe { libbpf_sys::btf__pointer_size(self.ptr.as_ptr()) as usize };
NonZeroUsize::new(sz).ok_or_else(|| {
Error::with_io_error(io::ErrorKind::Other, "could not determine pointer size")
})
}
pub fn type_by_name<'s, K>(&'s self, name: &str) -> Option<K>
where
K: TryFrom<BtfType<'s>>,
{
let c_string = CString::new(name)
.map_err(|_| Error::with_invalid_data(format!("{name:?} contains null bytes")))
.unwrap();
let ty = unsafe {
libbpf_sys::btf__find_by_name(self.ptr.as_ptr(), c_string.as_ptr())
};
if ty < 0 {
None
} else {
self.type_by_id(TypeId(ty as _))
}
}
pub fn type_by_id<'s, K>(&'s self, type_id: TypeId) -> Option<K>
where
K: TryFrom<BtfType<'s>>,
{
let btf_type = unsafe {
libbpf_sys::btf__type_by_id(self.ptr.as_ptr(), type_id.0)
};
let btf_type = NonNull::new(btf_type as *mut libbpf_sys::btf_type)?;
let ty = unsafe {
btf_type.as_ref()
};
let name = self.name_at(ty.name_off);
BtfType {
type_id,
name,
source: self,
ty,
}
.try_into()
.ok()
}
pub fn type_by_kind<'s, K>(&'s self) -> impl Iterator<Item = K> + 's
where
K: TryFrom<BtfType<'s>>,
{
(1..self.len() as u32)
.map(TypeId::from)
.filter_map(|id| self.type_by_id(id))
.filter_map(|t| K::try_from(t).ok())
}
}
impl AsRawLibbpf for Btf<'_> {
type LibbpfType = libbpf_sys::btf;
fn as_libbpf_object(&self) -> NonNull<Self::LibbpfType> {
self.ptr
}
}
impl Debug for Btf<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
struct BtfDumper<'btf>(&'btf Btf<'btf>);
impl Debug for BtfDumper<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_list()
.entries(
(1..self.0.len())
.map(|i| TypeId::from(i as u32))
.map(|id| self.0.type_by_id::<BtfType<'_>>(id).unwrap()),
)
.finish()
}
}
f.debug_tuple("Btf<'_>").field(&BtfDumper(self)).finish()
}
}
impl Drop for Btf<'_> {
fn drop(&mut self) {
match self.drop_policy {
DropPolicy::Nothing => {}
DropPolicy::SelfPtrOnly => {
unsafe {
libbpf_sys::btf__free(self.ptr.as_ptr())
}
}
DropPolicy::ObjPtr(obj) => {
unsafe {
libbpf_sys::bpf_object__close(obj)
}
}
}
}
}
#[derive(Clone, Copy)]
pub struct BtfType<'btf> {
type_id: TypeId,
name: Option<&'btf OsStr>,
source: &'btf Btf<'btf>,
ty: &'btf libbpf_sys::btf_type,
}
impl Debug for BtfType<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BtfType")
.field("type_id", &self.type_id)
.field("name", &self.name())
.field("source", &self.source.as_libbpf_object())
.field("ty", &(self.ty as *const _))
.finish()
}
}
impl<'btf> BtfType<'btf> {
#[inline]
pub fn type_id(&self) -> TypeId {
self.type_id
}
#[inline]
pub fn name(&'_ self) -> Option<&'btf OsStr> {
self.name
}
#[inline]
pub fn kind(&self) -> BtfKind {
((self.ty.info >> 24) & 0x1f).try_into().unwrap()
}
#[inline]
fn vlen(&self) -> u32 {
self.ty.info & 0xffff
}
#[inline]
fn kind_flag(&self) -> bool {
(self.ty.info >> 31) == 1
}
#[inline]
pub fn is_mod(&self) -> bool {
matches!(
self.kind(),
BtfKind::Volatile | BtfKind::Const | BtfKind::Restrict | BtfKind::TypeTag
)
}
#[inline]
pub fn is_any_enum(&self) -> bool {
matches!(self.kind(), BtfKind::Enum | BtfKind::Enum64)
}
#[inline]
pub fn is_core_compat(&self, other: &Self) -> bool {
self.kind() == other.kind() || (self.is_any_enum() && other.is_any_enum())
}
#[inline]
pub fn is_composite(&self) -> bool {
matches!(self.kind(), BtfKind::Struct | BtfKind::Union)
}
#[inline]
unsafe fn size_unchecked(&self) -> u32 {
unsafe { self.ty.__bindgen_anon_1.size }
}
#[inline]
unsafe fn referenced_type_id_unchecked(&self) -> TypeId {
unsafe { self.ty.__bindgen_anon_1.type_ }.into()
}
pub fn next_type(&self) -> Option<Self> {
match self.kind() {
BtfKind::Ptr
| BtfKind::Typedef
| BtfKind::Volatile
| BtfKind::Const
| BtfKind::Restrict
| BtfKind::Func
| BtfKind::FuncProto
| BtfKind::Var
| BtfKind::DeclTag
| BtfKind::TypeTag => {
let tid = unsafe {
self.referenced_type_id_unchecked()
};
self.source.type_by_id(tid)
}
BtfKind::Void
| BtfKind::Int
| BtfKind::Array
| BtfKind::Struct
| BtfKind::Union
| BtfKind::Enum
| BtfKind::Fwd
| BtfKind::DataSec
| BtfKind::Float
| BtfKind::Enum64 => None,
}
}
pub fn skip_mods_and_typedefs(&self) -> Self {
let mut ty = *self;
loop {
if ty.is_mod() || ty.kind() == BtfKind::Typedef {
ty = ty.next_type().unwrap();
} else {
return ty;
}
}
}
pub fn alignment(&self) -> Result<NonZeroUsize> {
let skipped = self.skip_mods_and_typedefs();
match skipped.kind() {
BtfKind::Int => {
let ptr_size = skipped.source.ptr_size()?;
let int = types::Int::try_from(skipped).unwrap();
Ok(Ord::min(
ptr_size,
NonZeroUsize::new(int.bits.div_ceil(8).into()).unwrap(),
))
}
BtfKind::Ptr => skipped.source.ptr_size(),
BtfKind::Array => types::Array::try_from(skipped)
.unwrap()
.contained_type()
.alignment(),
BtfKind::Struct | BtfKind::Union => {
let c = Composite::try_from(skipped).unwrap();
let mut align = NonZeroUsize::new(1usize).unwrap();
for m in c.iter() {
align = Ord::max(
align,
skipped
.source
.type_by_id::<Self>(m.ty)
.unwrap()
.alignment()?,
);
}
Ok(align)
}
BtfKind::Enum | BtfKind::Enum64 | BtfKind::Float => {
Ok(Ord::min(skipped.source.ptr_size()?, unsafe {
NonZeroUsize::new_unchecked(skipped.size_unchecked() as usize)
}))
}
BtfKind::Var => {
let var = types::Var::try_from(skipped).unwrap();
var.source
.type_by_id::<Self>(var.referenced_type_id())
.unwrap()
.alignment()
}
BtfKind::DataSec => unsafe {
NonZeroUsize::new(skipped.size_unchecked() as usize)
}
.ok_or_else(|| Error::with_invalid_data("DataSec with size of 0")),
BtfKind::Void
| BtfKind::Volatile
| BtfKind::Const
| BtfKind::Restrict
| BtfKind::Typedef
| BtfKind::FuncProto
| BtfKind::Fwd
| BtfKind::Func
| BtfKind::DeclTag
| BtfKind::TypeTag => Err(Error::with_invalid_data(format!(
"Cannot get alignment of type with kind {:?}. TypeId is {}",
skipped.kind(),
skipped.type_id(),
))),
}
}
}
pub unsafe trait HasSize<'btf>: Deref<Target = BtfType<'btf>> + sealed::Sealed {
#[inline]
fn size(&self) -> usize {
unsafe { self.size_unchecked() as usize }
}
}
pub unsafe trait ReferencesType<'btf>:
Deref<Target = BtfType<'btf>> + sealed::Sealed
{
#[inline]
fn referenced_type_id(&self) -> TypeId {
unsafe { self.referenced_type_id_unchecked() }
}
#[inline]
fn referenced_type(&self) -> BtfType<'btf> {
self.source.type_by_id(self.referenced_type_id()).unwrap()
}
}
mod sealed {
pub trait Sealed {}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::discriminant;
#[test]
fn from_vmlinux() {
assert!(Btf::from_vmlinux().is_ok());
}
#[test]
fn btf_kind() {
use BtfKind::*;
for t in [
Void, Int, Ptr, Array, Struct, Union, Enum, Fwd, Typedef, Volatile, Const, Restrict,
Func, FuncProto, Var, DataSec, Float, DeclTag, TypeTag, Enum64,
] {
assert_eq!(
discriminant(&t),
discriminant(&BtfKind::try_from(t as u32).unwrap())
);
}
}
}