//! Parse and introspect btf information, from files or loaded objects. //! //! To find a specific type you can use one of 3 methods //! //! - [Btf::type_by_name] //! - [Btf::type_by_id] //! - [Btf::type_by_kind] //! //! All of these are generic over `K`, which is any type that can be created from a [`BtfType`], //! for all of these methods, not finding any type by the passed parameter or finding a type of //! another [`BtfKind`] will result in a [`None`] being returned (or filtered out in the case of //! [`Btf::type_by_kind`]). If you want to get a type independently of the kind, just make sure `K` //! binds to [`BtfType`]. 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; /// The various btf types. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(u32)] pub enum BtfKind { /// [Void](types::Void) Void = 0, /// [Int](types::Int) Int, /// [Ptr](types::Ptr) Ptr, /// [Array](types::Array) Array, /// [Struct](types::Struct) Struct, /// [Union](types::Union) Union, /// [Enum](types::Enum) Enum, /// [Fwd](types::Fwd) Fwd, /// [Typedef](types::Typedef) Typedef, /// [Volatile](types::Volatile) Volatile, /// [Const](types::Const) Const, /// [Restrict](types::Restrict) Restrict, /// [Func](types::Func) Func, /// [FuncProto](types::FuncProto) FuncProto, /// [Var](types::Var) Var, /// [DataSec](types::DataSec) DataSec, /// [Float](types::Float) Float, /// [DeclTag](types::DeclTag) DeclTag, /// [TypeTag](types::TypeTag) TypeTag, /// [Enum64](types::Enum64) Enum64, } impl TryFrom for BtfKind { type Error = u32; fn try_from(value: u32) -> Result { 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), }) } } /// The id of a btf type. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TypeId(u32); impl From for TypeId { fn from(s: u32) -> Self { Self(s) } } impl From 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), } /// The btf information of a bpf object. /// /// The lifetime bound protects against this object outliving its source. This can happen when it /// was derived from an [`Object`](super::Object), which owns the data this structs points too. When /// instead the [`Btf::from_path`] method is used, the lifetime will be `'static` since it doesn't /// borrow from anything. pub struct Btf<'source> { ptr: NonNull, drop_policy: DropPolicy, _marker: PhantomData<&'source ()>, } impl Btf<'static> { /// Load the btf information from specified path. pub fn from_path>(path: P) -> Result { fn inner(path: &Path) -> Result> { 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()) } /// Load the vmlinux btf information from few well-known locations. pub fn from_vmlinux() -> Result { 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, }) } /// Load the btf information of an bpf object from a program id. pub fn from_prog_id(id: u32) -> Result { let fd = parse_ret_i32(unsafe { libbpf_sys::bpf_prog_get_fd_by_id(id) })?; let fd = unsafe { // SAFETY: parse_ret_i32 will check that this fd is above -1 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::(), &mut (size_of::() 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> { /// Create a new `Btf` instance from the given [`libbpf_sys::bpf_object`]. pub fn from_bpf_object(obj: &'btf libbpf_sys::bpf_object) -> Result> { Self::from_bpf_object_raw(obj) } fn from_bpf_object_raw(obj: *const libbpf_sys::bpf_object) -> Result> { let ptr = unsafe { // SAFETY: the obj pointer is valid since it's behind a reference. libbpf_sys::bpf_object__btf(obj) }; // Contrary to general `libbpf` contract, `bpf_object__btf` may // return `NULL` without setting `errno`. 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)) } /// From raw bytes coming from an object file. pub fn from_raw(name: &'btf str, object_file: &'btf [u8]) -> Result> { 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::() 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")?; // SAFETY: The pointer has been validated. 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 => { // SAFETY: The obj pointer is valid because we checked // its validity. unsafe { // We free it here, otherwise it will be a memory // leak as this codepath (Ok(None) | Err(e)) does // not reference it anymore and as such it can be // dropped. libbpf_sys::bpf_object__close(bpf_obj) }; x } } } /// Gets a string at a given offset. /// /// Returns [`None`] when the offset is out of bounds or if the name is empty. fn name_at(&self, offset: u32) -> Option<&OsStr> { let name = unsafe { // SAFETY: // Assuming that btf is a valid pointer, this is always okay to call. libbpf_sys::btf__name_by_offset(self.ptr.as_ptr(), offset) }; NonNull::new(name as *mut _) .map(|p| unsafe { // SAFETY: a non-null pointer coming from libbpf is always valid OsStr::from_bytes(CStr::from_ptr(p.as_ptr()).to_bytes()) }) .filter(|s| !s.is_empty()) // treat empty strings as none } /// Whether this btf instance has no types. pub fn is_empty(&self) -> bool { self.len() == 0 } /// The number of [BtfType]s in this object. pub fn len(&self) -> usize { unsafe { // SAFETY: the btf pointer is valid. libbpf_sys::btf__type_cnt(self.ptr.as_ptr()) as usize } } /// The btf pointer size. pub fn ptr_size(&self) -> Result { 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") }) } /// Find a btf type by name /// /// # Panics /// If `name` has null bytes. pub fn type_by_name<'s, K>(&'s self, name: &str) -> Option where K: TryFrom>, { let c_string = CString::new(name) .map_err(|_| Error::with_invalid_data(format!("{name:?} contains null bytes"))) .unwrap(); let ty = unsafe { // SAFETY: the btf pointer is valid and the c_string pointer was created from safe code // therefore it's also valid. 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 _)) } } /// Find a type by it's [TypeId]. pub fn type_by_id<'s, K>(&'s self, type_id: TypeId) -> Option where K: TryFrom>, { let btf_type = unsafe { // SAFETY: the btf pointer is valid. 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 { // SAFETY: if it is non-null then it points to a valid type. btf_type.as_ref() }; let name = self.name_at(ty.name_off); BtfType { type_id, name, source: self, ty, } .try_into() .ok() } /// Find all types of a specific type kind. pub fn type_by_kind<'s, K>(&'s self) -> impl Iterator + 's where K: TryFrom>, { (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; /// Retrieve the underlying [`libbpf_sys::btf`] object. fn as_libbpf_object(&self) -> NonNull { 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)) // SANITY: A type with this ID should always exist // given that BTF IDs are fully populated up // to `len`. Conversion to `BtfType` is // always infallible. .map(|id| self.0.type_by_id::>(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 { // SAFETY: the btf pointer is valid. libbpf_sys::btf__free(self.ptr.as_ptr()) } } DropPolicy::ObjPtr(obj) => { unsafe { // SAFETY: the bpf obj pointer is valid. // closing the obj automatically frees the associated btf object. libbpf_sys::bpf_object__close(obj) } } } } } /// An undiscriminated btf type /// /// The [`btf_type_match`](crate::btf_type_match) can be used to match on the variants of this type /// as if it was a rust enum. /// /// You can also use the [`TryFrom`] trait to convert to any of the possible [`types`]. #[derive(Clone, Copy)] pub struct BtfType<'btf> { type_id: TypeId, name: Option<&'btf OsStr>, source: &'btf Btf<'btf>, /// the __bindgen_anon_1 field is a union defined as /// ```no_run /// union btf_type__bindgen_ty_1 { /// size_: u32, /// type_: u32, /// } /// ``` 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> { /// This type's type id. #[inline] pub fn type_id(&self) -> TypeId { self.type_id } /// This type's name. #[inline] pub fn name(&'_ self) -> Option<&'btf OsStr> { self.name } /// This type's kind. #[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 } /// Whether this represent's a modifier. #[inline] pub fn is_mod(&self) -> bool { matches!( self.kind(), BtfKind::Volatile | BtfKind::Const | BtfKind::Restrict | BtfKind::TypeTag ) } /// Whether this represents any kind of enum. #[inline] pub fn is_any_enum(&self) -> bool { matches!(self.kind(), BtfKind::Enum | BtfKind::Enum64) } /// Whether this btf type is core compatible to `other`. #[inline] pub fn is_core_compat(&self, other: &Self) -> bool { self.kind() == other.kind() || (self.is_any_enum() && other.is_any_enum()) } /// Whether this type represents a composite type (struct/union). #[inline] pub fn is_composite(&self) -> bool { matches!(self.kind(), BtfKind::Struct | BtfKind::Union) } /// The size of the described type. /// /// # Safety /// /// This function can only be called when the [`Self::kind`] returns one of: /// - [`BtfKind::Int`], /// - [`BtfKind::Float`], /// - [`BtfKind::Enum`], /// - [`BtfKind::Struct`], /// - [`BtfKind::Union`], /// - [`BtfKind::DataSec`], /// - [`BtfKind::Enum64`], #[inline] unsafe fn size_unchecked(&self) -> u32 { unsafe { self.ty.__bindgen_anon_1.size } } /// The [`TypeId`] of the referenced type. /// /// # Safety /// This function can only be called when the [`Self::kind`] returns one of: /// - [`BtfKind::Ptr`], /// - [`BtfKind::Typedef`], /// - [`BtfKind::Volatile`], /// - [`BtfKind::Const`], /// - [`BtfKind::Restrict`], /// - [`BtfKind::Func`], /// - [`BtfKind::FuncProto`], /// - [`BtfKind::Var`], /// - [`BtfKind::DeclTag`], /// - [`BtfKind::TypeTag`], #[inline] unsafe fn referenced_type_id_unchecked(&self) -> TypeId { unsafe { self.ty.__bindgen_anon_1.type_ }.into() } /// If this type implements [`ReferencesType`], returns the type it references. pub fn next_type(&self) -> Option { 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 { // SAFETY: we checked the kind 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, } } /// Given a type, follows the refering type ids until it finds a type that isn't a modifier or /// a [`BtfKind::Typedef`]. /// /// See [is_mod](Self::is_mod). 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; } } } /// Returns the alignment of this type, if this type points to some modifier or typedef, those /// will be skipped until the underlying type (with an alignment) is found. /// /// See [skip_mods_and_typedefs](Self::skip_mods_and_typedefs). pub fn alignment(&self) -> Result { 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 + 7) / 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::(m.ty) .unwrap() .alignment()?, ); } Ok(align) } BtfKind::Enum | BtfKind::Enum64 | BtfKind::Float => { Ok(Ord::min(skipped.source.ptr_size()?, unsafe { // SAFETY: We checked the type. // Unwrap: Enums in C have always size >= 1 NonZeroUsize::new_unchecked(skipped.size_unchecked() as usize) })) } BtfKind::Var => { let var = types::Var::try_from(skipped).unwrap(); var.source .type_by_id::(var.referenced_type_id()) .unwrap() .alignment() } BtfKind::DataSec => unsafe { // SAFETY: We checked the type. 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(), ))), } } } /// Some btf types have a size field, describing their size. /// /// # Safety /// /// It's only safe to implement this for types where the underlying btf_type has a .size set. /// /// See the [docs](https://www.kernel.org/doc/html/latest/bpf/btf.html) for a reference of which /// [`BtfKind`] can implement this trait. pub unsafe trait HasSize<'btf>: Deref> + sealed::Sealed { /// The size of the described type. #[inline] fn size(&self) -> usize { unsafe { self.size_unchecked() as usize } } } /// Some btf types refer to other types by their type id. /// /// # Safety /// /// It's only safe to implement this for types where the underlying btf_type has a .type set. /// /// See the [docs](https://www.kernel.org/doc/html/latest/bpf/btf.html) for a reference of which /// [`BtfKind`] can implement this trait. pub unsafe trait ReferencesType<'btf>: Deref> + sealed::Sealed { /// The referenced type's id. #[inline] fn referenced_type_id(&self) -> TypeId { unsafe { self.referenced_type_id_unchecked() } } /// The referenced type. #[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, ] { // check if discriminants match after a roundtrip conversion assert_eq!( discriminant(&t), discriminant(&BtfKind::try_from(t as u32).unwrap()) ); } } }