blob: 088a05e968aa7c67bdc394f0bdce3ddabf2db58b [file] [log] [blame]
//! 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<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),
})
}
}
/// The id of a btf type.
#[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),
}
/// 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<libbpf_sys::btf>,
drop_policy: DropPolicy,
_marker: PhantomData<&'source ()>,
}
impl Btf<'static> {
/// Load the btf information from specified path.
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())
}
/// Load the vmlinux btf information from few well-known locations.
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,
})
}
/// Load the btf information of an bpf object from a program id.
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 {
// 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::<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> {
/// Create a new `Btf` instance from the given [`libbpf_sys::bpf_object`].
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 {
// 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<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")?;
// 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<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")
})
}
/// Find a btf type by name
///
/// # Panics
/// If `name` has null bytes.
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 {
// 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<K>
where
K: TryFrom<BtfType<'s>>,
{
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<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;
/// Retrieve the underlying [`libbpf_sys::btf`] object.
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))
// 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::<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 {
// 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<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 {
// 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<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 + 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::<Self>(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::<Self>(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<Target = BtfType<'btf>> + 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<Target = BtfType<'btf>> + 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())
);
}
}
}