From 512d86bba04e2992d76ba5aaebf9f9a48ce3b355 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 25 May 2022 23:53:56 +0100 Subject: [PATCH] aya: Export BTF crate Allows users to inspect program BTF for metadata. Allows additional use cases for users who wish only to deal with BTF parsing. Signed-off-by: Dave Tucker --- .github/workflows/lint.yml | 8 +- .vim/coc-settings.json | 3 +- .vscode/settings.json | 3 +- Cargo.toml | 2 +- aya/Cargo.toml | 1 + aya/src/bpf.rs | 21 ++++- aya/src/lib.rs | 5 + aya/src/obj/btf/btf.rs | 43 ++++++--- aya/src/obj/btf/info.rs | 8 +- aya/src/obj/btf/mod.rs | 8 +- aya/src/obj/btf/relocation.rs | 112 +++++++--------------- aya/src/obj/btf/types.rs | 95 ++++++++++++++----- aya/src/obj/mod.rs | 4 +- aya/src/obj/relocation.rs | 54 ++++++++++- btftool/Cargo.toml | 12 +++ btftool/src/main.rs | 173 ++++++++++++++++++++++++++++++++++ 16 files changed, 417 insertions(+), 135 deletions(-) create mode 100644 btftool/Cargo.toml create mode 100644 btftool/src/main.rs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59b3d16f..5bdec9d1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,11 +36,11 @@ jobs: - name: Run clippy run: | - cargo clippy -p aya -- --deny warnings - cargo clippy -p aya-gen -- --deny warnings - cargo clippy -p xtask -- --deny warnings + cargo clippy --all-features -p aya -- --deny warnings + cargo clippy --all-features -p aya-gen -- --deny warnings + cargo clippy --all-features -p xtask -- --deny warnings pushd bpf - cargo clippy -p aya-bpf -- --deny warnings + cargo clippy --all-features -p aya-bpf -- --deny warnings popd - name: Run miri diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json index 60fe4ee3..ce5a302d 100644 --- a/.vim/coc-settings.json +++ b/.vim/coc-settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] + "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"], + "rust-analyzer.cargo.allFeatures": true } diff --git a/.vscode/settings.json b/.vscode/settings.json index 60fe4ee3..ce5a302d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"] + "rust-analyzer.linkedProjects": ["Cargo.toml", "bpf/Cargo.toml"], + "rust-analyzer.cargo.allFeatures": true } diff --git a/Cargo.toml b/Cargo.toml index 0eed0f81..98f0d12d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["aya", "aya-gen", "xtask"] +members = ["aya", "aya-gen", "btftool", "xtask"] diff --git a/aya/Cargo.toml b/aya/Cargo.toml index e94fc53f..d1cbcc64 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -32,3 +32,4 @@ default = [] async = ["futures"] async_tokio = ["tokio", "async"] async_std = ["async-std", "async-io", "async"] +btf = [] diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index e410d15f..38fe17f9 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -530,7 +530,11 @@ impl<'a> BpfLoader<'a> { .drain() .map(|(name, map)| (name, MapLock::new(map))) .collect(); - Ok(Bpf { maps, programs }) + Ok(Bpf { + maps, + programs, + btf: obj.btf, + }) } } @@ -543,6 +547,7 @@ impl<'a> Default for BpfLoader<'a> { /// The main entry point into the library, used to work with eBPF programs and maps. #[derive(Debug)] pub struct Bpf { + btf: Option, maps: HashMap, programs: HashMap, } @@ -744,6 +749,20 @@ impl Bpf { pub fn programs_mut(&mut self) -> impl Iterator { self.programs.iter_mut().map(|(s, p)| (s.as_str(), p)) } + + /// Retrive the object BTF. + /// + /// # Examples + /// ```no_run + /// # use std::path::Path; + /// # let mut bpf = aya::Bpf::load(&[])?; + /// let btf = bpf.btf().unwrap(); + /// # Ok::<(), aya::BpfError>(()) + /// ``` + #[cfg(feature = "btf")] + pub fn btf(&mut self) -> Option<&Btf> { + self.btf.as_ref() + } } /// The error type returned by [`Bpf::load_file`] and [`Bpf::load`]. diff --git a/aya/src/lib.rs b/aya/src/lib.rs index 87de3a4e..09465dab 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -46,5 +46,10 @@ mod sys; pub mod util; pub use bpf::*; + +#[cfg(feature = "btf")] +pub use obj::btf; + pub use obj::btf::{Btf, BtfError}; + pub use object::Endianness; diff --git a/aya/src/obj/btf/btf.rs b/aya/src/obj/btf/btf.rs index f60c9d7d..da7bba4f 100644 --- a/aya/src/obj/btf/btf.rs +++ b/aya/src/obj/btf/btf.rs @@ -27,7 +27,6 @@ use super::{ }; pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; -pub(crate) const MAX_SPEC_LEN: usize = 64; /// The error type returned when `BTF` operations fail. #[derive(Error, Debug)] @@ -193,7 +192,8 @@ impl Btf { } } - pub(crate) fn types(&self) -> impl Iterator { + /// Returns an iterator over the BTF types. + pub fn types(&self) -> impl Iterator { self.types.types.iter() } @@ -231,7 +231,8 @@ impl Btf { ) } - pub(crate) fn parse(data: &[u8], endianness: Endianness) -> Result { + /// Parses a byte slice into BTF. + pub fn parse(data: &[u8], endianness: Endianness) -> Result { if data.len() < mem::size_of::() { return Err(BtfError::InvalidHeader); } @@ -282,7 +283,8 @@ impl Btf { Ok(types) } - pub(crate) fn string_at(&self, offset: u32) -> Result, BtfError> { + /// Returns the string at the provided offset. + pub fn string_at(&self, offset: u32) -> Result, BtfError> { let btf_header { hdr_len, mut str_off, @@ -308,15 +310,18 @@ impl Btf { Ok(s.to_string_lossy()) } - pub(crate) fn type_by_id(&self, type_id: u32) -> Result<&BtfType, BtfError> { + /// Returns the BTF type for a given id. + pub fn type_by_id(&self, type_id: u32) -> Result<&BtfType, BtfError> { self.types.type_by_id(type_id) } - pub(crate) fn resolve_type(&self, root_type_id: u32) -> Result { + /// Resolves the base type id by traversing the type heirarchy starting from root_type_id. + pub fn resolve_type(&self, root_type_id: u32) -> Result { self.types.resolve_type(root_type_id) } - pub(crate) fn type_name(&self, ty: &BtfType) -> Result>, BtfError> { + /// Returns the name of the BTF type. + pub fn type_name(&self, ty: &BtfType) -> Result>, BtfError> { ty.name_offset().map(|off| self.string_at(off)).transpose() } @@ -325,7 +330,8 @@ impl Btf { .and_then(|off| self.string_at(off).ok().map(String::from)) } - pub(crate) fn id_by_type_name_kind(&self, name: &str, kind: BtfKind) -> Result { + /// Returns the BTF id of the type with the provided name and of the provided kind. + pub fn id_by_type_name_kind(&self, name: &str, kind: BtfKind) -> Result { for (type_id, ty) in self.types().enumerate() { match ty.kind()? { Some(k) => { @@ -399,7 +405,8 @@ impl Btf { }) } - pub(crate) fn to_bytes(&self) -> Vec { + /// Writes the BTF to bytes. + pub fn to_bytes(&self) -> Vec { // Safety: btf_header is POD let mut buf = unsafe { bytes_of::(&self.header).to_vec() }; // Skip the first type since it's always BtfType::Unknown for type_by_id to work @@ -597,6 +604,7 @@ unsafe fn read_btf_header(data: &[u8]) -> btf_header { ptr::read_unaligned(data.as_ptr() as *const btf_header) } +/// Extended BTF Information #[derive(Debug, Clone)] pub struct BtfExt { data: Vec, @@ -611,11 +619,8 @@ pub struct BtfExt { } impl BtfExt { - pub(crate) fn parse( - data: &[u8], - endianness: Endianness, - btf: &Btf, - ) -> Result { + /// Parses a byte slice to BtfExt using the provided BTF. + pub fn parse(data: &[u8], endianness: Endianness, btf: &Btf) -> Result { // Safety: btf_ext_header is POD so read_unaligned is safe let header = unsafe { ptr::read_unaligned::(data.as_ptr() as *const btf_ext_header) @@ -757,9 +762,19 @@ impl BtfExt { self.func_info_rec_size } + /// Returns the function information + pub fn func_info(&self) -> &FuncInfo { + &self.func_info + } + pub(crate) fn line_info_rec_size(&self) -> usize { self.line_info_rec_size } + + /// Returns the line information + pub fn line_info(&self) -> &LineInfo { + &self.line_info + } } pub(crate) struct SecInfoIter<'a> { diff --git a/aya/src/obj/btf/info.rs b/aya/src/obj/btf/info.rs index 3743d5cf..2b2fbabc 100644 --- a/aya/src/obj/btf/info.rs +++ b/aya/src/obj/btf/info.rs @@ -20,7 +20,7 @@ use crate::{ * ...... */ #[derive(Debug, Clone, Default)] -pub(crate) struct FuncSecInfo { +pub struct FuncSecInfo { pub _sec_name_offset: u32, pub num_info: u32, pub func_info: Vec, @@ -79,7 +79,7 @@ impl FuncSecInfo { } #[derive(Debug, Clone)] -pub(crate) struct FuncInfo { +pub struct FuncInfo { pub data: HashMap, } @@ -99,7 +99,7 @@ impl FuncInfo { } #[derive(Debug, Clone, Default)] -pub(crate) struct LineSecInfo { +pub struct LineSecInfo { // each line info section has a header pub _sec_name_offset: u32, pub num_info: u32, @@ -169,7 +169,7 @@ impl LineSecInfo { } #[derive(Debug, Clone)] -pub(crate) struct LineInfo { +pub struct LineInfo { pub data: HashMap, } diff --git a/aya/src/obj/btf/mod.rs b/aya/src/obj/btf/mod.rs index d5324094..83042d3c 100644 --- a/aya/src/obj/btf/mod.rs +++ b/aya/src/obj/btf/mod.rs @@ -1,3 +1,4 @@ +//! BPF Type Format #[allow(clippy::module_inception)] mod btf; mod info; @@ -6,5 +7,10 @@ mod types; pub use btf::*; pub(crate) use info::*; -pub use relocation::RelocationError; +pub(crate) use relocation::*; + +#[cfg(feature = "btf")] +pub use types::*; + +#[cfg(not(feature = "btf"))] pub(crate) use types::*; diff --git a/aya/src/obj/btf/relocation.rs b/aya/src/obj/btf/relocation.rs index 06a4d0b4..bc33a634 100644 --- a/aya/src/obj/btf/relocation.rs +++ b/aya/src/obj/btf/relocation.rs @@ -2,7 +2,6 @@ use std::{ collections::HashMap, convert::{TryFrom, TryInto}, io, mem, ptr, - str::FromStr, }; use thiserror::Error; @@ -15,21 +14,19 @@ use crate::{ obj::{ btf::{ fields_are_compatible, member_bit_field_size, member_bit_offset, types_are_compatible, - BtfType, MAX_SPEC_LEN, + BtfType, }, - Btf, BtfError, Object, Program, ProgramSection, + Btf, BtfError, }, - BpfError, }; +const MAX_SPEC_LEN: usize = 64; + #[derive(Error, Debug)] -pub enum RelocationError { +pub enum BtfRelocationError { #[error(transparent)] IOError(#[from] io::Error), - #[error("program not found")] - ProgramNotFound, - #[error("invalid relocation access string {access_str}")] InvalidAccessString { access_str: String }, @@ -151,59 +148,17 @@ impl Relocation { } } -impl Object { - pub fn relocate_btf(&mut self, target_btf: &Btf) -> Result<(), BpfError> { - let (local_btf, btf_ext) = match (&self.btf, &self.btf_ext) { - (Some(btf), Some(btf_ext)) => (btf, btf_ext), - _ => return Ok(()), - }; - - let mut candidates_cache = HashMap::>::new(); - for (sec_name_off, relos) in btf_ext.relocations() { - let section_name = local_btf.string_at(*sec_name_off)?; - - let program_section = match ProgramSection::from_str(§ion_name) { - Ok(program) => program, - Err(_) => continue, - }; - let section_name = program_section.name(); - - let program = self - .programs - .get_mut(section_name) - .ok_or(BpfError::RelocationError { - function: section_name.to_owned(), - error: Box::new(RelocationError::ProgramNotFound), - })?; - match relocate_btf_program(program, relos, local_btf, target_btf, &mut candidates_cache) - { - Ok(_) => {} - Err(ErrorWrapper::BtfError(e)) => return Err(e.into()), - Err(ErrorWrapper::RelocationError(error)) => { - return Err(BpfError::RelocationError { - function: section_name.to_owned(), - error: Box::new(error), - }) - } - } - } - - Ok(()) - } -} - -fn relocate_btf_program<'target>( - program: &mut Program, +pub fn relocate_btf_program<'target>( + instructions: &mut [bpf_insn], relos: &[Relocation], local_btf: &Btf, target_btf: &'target Btf, candidates_cache: &mut HashMap>>, ) -> Result<(), ErrorWrapper> { for rel in relos { - let instructions = &mut program.function.instructions; let ins_index = rel.ins_offset as usize / std::mem::size_of::(); if ins_index >= instructions.len() { - return Err(RelocationError::InvalidInstructionIndex { + return Err(BtfRelocationError::InvalidInstructionIndex { index: ins_index, num_instructions: instructions.len(), relocation_number: rel.number, @@ -261,7 +216,7 @@ fn relocate_btf_program<'target>( }) .collect::>(); if !conflicts.is_empty() { - return Err(RelocationError::ConflictingCandidates { + return Err(BtfRelocationError::ConflictingCandidates { type_name: local_name.to_string(), candidates: conflicts, } @@ -275,7 +230,7 @@ fn relocate_btf_program<'target>( ComputedRelocation::new(rel, &local_spec, None)? }; - comp_rel.apply(program, rel, local_btf, target_btf)?; + comp_rel.apply(instructions, rel, local_btf, target_btf)?; } Ok(()) @@ -415,7 +370,7 @@ fn match_candidate<'target>( } if target_spec.parts.len() == MAX_SPEC_LEN { - return Err(RelocationError::MaximumNestingLevelReached { + return Err(BtfRelocationError::MaximumNestingLevelReached { type_name: Some(candidate.name.clone()), } .into()); @@ -467,7 +422,7 @@ fn match_member<'local, 'target>( for (index, target_member) in target_members.iter().enumerate() { if target_spec.parts.len() == MAX_SPEC_LEN { let root_ty = target_spec.btf.type_by_id(target_spec.root_type_id)?; - return Err(RelocationError::MaximumNestingLevelReached { + return Err(BtfRelocationError::MaximumNestingLevelReached { type_name: target_spec.btf.err_type_name(root_ty), } .into()); @@ -535,7 +490,7 @@ impl<'a> AccessSpec<'a> { .split(':') .map(|s| s.parse::()) .collect::, _>>() - .map_err(|_| RelocationError::InvalidAccessString { + .map_err(|_| BtfRelocationError::InvalidAccessString { access_str: spec.to_string(), })?; @@ -548,7 +503,7 @@ impl<'a> AccessSpec<'a> { | RelocationKind::TypeExists | RelocationKind::TypeSize => { if parts != [0] { - return Err(RelocationError::InvalidAccessString { + return Err(BtfRelocationError::InvalidAccessString { access_str: spec.to_string(), } .into()); @@ -565,14 +520,14 @@ impl<'a> AccessSpec<'a> { RelocationKind::EnumVariantExists | RelocationKind::EnumVariantValue => match ty { BtfType::Enum(_, members) => { if parts.len() != 1 { - return Err(RelocationError::InvalidAccessString { + return Err(BtfRelocationError::InvalidAccessString { access_str: spec.to_string(), } .into()); } let index = parts[0]; if index >= members.len() { - return Err(RelocationError::InvalidAccessIndex { + return Err(BtfRelocationError::InvalidAccessIndex { type_name: btf.err_type_name(ty), spec: spec.to_string(), index, @@ -597,7 +552,7 @@ impl<'a> AccessSpec<'a> { } } _ => { - return Err(RelocationError::InvalidRelocationKindForType { + return Err(BtfRelocationError::InvalidRelocationKindForType { relocation_number: relocation.number, relocation_kind: format!("{:?}", relocation.kind), type_kind: format!("{:?}", ty.kind()?.unwrap()), @@ -627,7 +582,7 @@ impl<'a> AccessSpec<'a> { match ty { Struct(t, members) | Union(t, members) => { if index >= members.len() { - return Err(RelocationError::InvalidAccessIndex { + return Err(BtfRelocationError::InvalidAccessIndex { type_name: btf.err_type_name(ty), spec: spec.to_string(), index, @@ -664,7 +619,7 @@ impl<'a> AccessSpec<'a> { } }; if !var_len && index >= array.nelems as usize { - return Err(RelocationError::InvalidAccessIndex { + return Err(BtfRelocationError::InvalidAccessIndex { type_name: btf.err_type_name(ty), spec: spec.to_string(), index, @@ -682,7 +637,7 @@ impl<'a> AccessSpec<'a> { bit_offset += index * size * 8; } rel_kind => { - return Err(RelocationError::InvalidRelocationKindForType { + return Err(BtfRelocationError::InvalidRelocationKindForType { relocation_number: relocation.number, relocation_kind: format!("{:?}", rel_kind), type_kind: format!("{:?}", ty.kind()), @@ -717,7 +672,7 @@ struct Accessor { } #[derive(Debug)] -struct Candidate<'a> { +pub struct Candidate<'a> { name: String, btf: &'a Btf, _ty: &'a BtfType, @@ -765,18 +720,17 @@ impl ComputedRelocation { fn apply( &self, - program: &mut Program, + instructions: &mut [bpf_insn], rel: &Relocation, local_btf: &Btf, target_btf: &Btf, ) -> Result<(), ErrorWrapper> { - let instructions = &mut program.function.instructions; let num_instructions = instructions.len(); let ins_index = rel.ins_offset as usize / std::mem::size_of::(); let mut ins = instructions .get_mut(ins_index) - .ok_or(RelocationError::InvalidInstructionIndex { + .ok_or(BtfRelocationError::InvalidInstructionIndex { index: rel.ins_offset as usize, num_instructions, relocation_number: rel.number, @@ -790,7 +744,7 @@ impl ComputedRelocation { BPF_ALU | BPF_ALU64 => { let src_reg = ins.src_reg(); if src_reg != BPF_K as u8 { - return Err(RelocationError::InvalidInstruction { + return Err(BtfRelocationError::InvalidInstruction { relocation_number: rel.number, index: ins_index, error: format!("invalid src_reg={:x} expected {:x}", src_reg, BPF_K), @@ -802,7 +756,7 @@ impl ComputedRelocation { } BPF_LDX | BPF_ST | BPF_STX => { if target_value > std::i16::MAX as u32 { - return Err(RelocationError::InvalidInstruction { + return Err(BtfRelocationError::InvalidInstruction { relocation_number: rel.number, index: ins_index, error: format!("value `{}` overflows 16 bits offset field", target_value), @@ -822,7 +776,7 @@ impl ComputedRelocation { (Int(_, local_info), Int(_, target_info)) if unsigned(*local_info) && unsigned(*target_info) => {} _ => { - return Err(RelocationError::InvalidInstruction { + return Err(BtfRelocationError::InvalidInstruction { relocation_number: rel.number, index: ins_index, error: format!( @@ -843,7 +797,7 @@ impl ComputedRelocation { 2 => BPF_H, 1 => BPF_B, size => { - return Err(RelocationError::InvalidInstruction { + return Err(BtfRelocationError::InvalidInstruction { relocation_number: rel.number, index: ins_index, error: format!("invalid target size {}", size), @@ -857,7 +811,7 @@ impl ComputedRelocation { BPF_LD => { ins.imm = target_value as i32; let mut next_ins = instructions.get_mut(ins_index + 1).ok_or( - RelocationError::InvalidInstructionIndex { + BtfRelocationError::InvalidInstructionIndex { index: ins_index + 1, num_instructions, relocation_number: rel.number, @@ -867,7 +821,7 @@ impl ComputedRelocation { next_ins.imm = 0; } class => { - return Err(RelocationError::InvalidInstruction { + return Err(BtfRelocationError::InvalidInstruction { relocation_number: rel.number, index: ins_index, error: format!("invalid instruction class {:x}", class), @@ -938,7 +892,7 @@ impl ComputedRelocation { }), rel_kind => { let ty = spec.btf.type_by_id(accessor.type_id)?; - return Err(RelocationError::InvalidRelocationKindForType { + return Err(BtfRelocationError::InvalidRelocationKindForType { relocation_number: rel.number, relocation_kind: format!("{:?}", rel_kind), type_kind: format!("{:?}", ty.kind()), @@ -955,7 +909,7 @@ impl ComputedRelocation { (ty, members[accessor.index]) } _ => { - return Err(RelocationError::InvalidRelocationKindForType { + return Err(BtfRelocationError::InvalidRelocationKindForType { relocation_number: rel.number, relocation_kind: format!("{:?}", rel.kind), type_kind: format!("{:?}", ty.kind()), @@ -1065,10 +1019,10 @@ impl ComputedRelocation { // this exists only to simplify propagating errors from relocate_btf() and to associate // RelocationError(s) with their respective program name #[derive(Error, Debug)] -enum ErrorWrapper { +pub enum ErrorWrapper { #[error(transparent)] BtfError(#[from] BtfError), #[error(transparent)] - RelocationError(#[from] RelocationError), + RelocationError(#[from] BtfRelocationError), } diff --git a/aya/src/obj/btf/types.rs b/aya/src/obj/btf/types.rs index fc61c709..97af4e56 100644 --- a/aya/src/obj/btf/types.rs +++ b/aya/src/obj/btf/types.rs @@ -18,50 +18,91 @@ use crate::{ obj::btf::{Btf, BtfError, MAX_RESOLVE_DEPTH}, }; +/// The BTF Type #[derive(Clone, Debug)] -pub(crate) enum BtfType { + +pub enum BtfType { + /// An unknown type. Unknown, + /// Forward declaration. Fwd(btf_type), + /// Constant. Const(btf_type), + /// Volatile. Volatile(btf_type), + /// Restrict. Restrict(btf_type), + /// Pointer. Ptr(btf_type), + /// Type definition. Typedef(btf_type), + /// Function. Func(btf_type), + /// Integer. Int(btf_type, u32), + /// Floating point. Float(btf_type), + /// Enumeration. Enum(btf_type, Vec), + /// Array. Array(btf_type, btf_array), + /// Struct. Struct(btf_type, Vec), + /// Union. Union(btf_type, Vec), + /// Function prototype FuncProto(btf_type, Vec), + /// Variable Var(btf_type, btf_var), + /// Data section DataSec(btf_type, Vec), + /// Declaration Tag DeclTag(btf_type, btf_decl_tag), + /// Type Tag TypeTag(btf_type), } +/// BTF Kind #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(u32)] -pub(crate) enum BtfKind { +pub enum BtfKind { + /// BTF_KIND_UNKN Unknown = BTF_KIND_UNKN, + /// BTF_KIND_INT Int = BTF_KIND_INT, + /// BTF_KIND_FLOAT Float = BTF_KIND_FLOAT, + /// BTF_KIND_PTR Ptr = BTF_KIND_PTR, + /// BTF_KIND_ARRAY Array = BTF_KIND_ARRAY, + /// BTF_KIND_STRUCT Struct = BTF_KIND_STRUCT, + /// BTF_KIND_UNION Union = BTF_KIND_UNION, + /// BTF_KIND_ENUM Enum = BTF_KIND_ENUM, + /// BTF_KIND_FWD Fwd = BTF_KIND_FWD, + /// BTF_KIND_TYPEDEF Typedef = BTF_KIND_TYPEDEF, + /// BTF_KIND_VOLATILE Volatile = BTF_KIND_VOLATILE, + /// BTF_KIND_CONST Const = BTF_KIND_CONST, + /// BTF_KIND_RESTRICT Restrict = BTF_KIND_RESTRICT, + /// BTF_KIND_FUNC Func = BTF_KIND_FUNC, + /// BTF_KIND_FUNC_PROTO FuncProto = BTF_KIND_FUNC_PROTO, + /// BTF_KIND_VAR Var = BTF_KIND_VAR, + /// BTF_KIND_DATASEC DataSec = BTF_KIND_DATASEC, + /// BTF_KIND_DECL_TAG DeclTag = BTF_KIND_DECL_TAG, + /// BTF_KIND_TYPE_TAG TypeTag = BTF_KIND_TYPE_TAG, } @@ -98,25 +139,25 @@ impl TryFrom for BtfKind { impl Display for BtfKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - BtfKind::Unknown => write!(f, "[UNKNOWN]"), - BtfKind::Int => write!(f, "[INT]"), - BtfKind::Float => write!(f, "[FLOAT]"), - BtfKind::Ptr => write!(f, "[PTR]"), - BtfKind::Array => write!(f, "[ARRAY]"), - BtfKind::Struct => write!(f, "[STRUCT]"), - BtfKind::Union => write!(f, "[UNION]"), - BtfKind::Enum => write!(f, "[ENUM]"), - BtfKind::Fwd => write!(f, "[FWD]"), - BtfKind::Typedef => write!(f, "[TYPEDEF]"), - BtfKind::Volatile => write!(f, "[VOLATILE]"), - BtfKind::Const => write!(f, "[CONST]"), - BtfKind::Restrict => write!(f, "[RESTRICT]"), - BtfKind::Func => write!(f, "[FUNC]"), - BtfKind::FuncProto => write!(f, "[FUNC_PROTO]"), - BtfKind::Var => write!(f, "[VAR]"), - BtfKind::DataSec => write!(f, "[DATASEC]"), - BtfKind::DeclTag => write!(f, "[DECL_TAG]"), - BtfKind::TypeTag => write!(f, "[TYPE_TAG]"), + BtfKind::Unknown => write!(f, "UNKNOWN"), + BtfKind::Int => write!(f, "INT"), + BtfKind::Float => write!(f, "FLOAT"), + BtfKind::Ptr => write!(f, "PTR"), + BtfKind::Array => write!(f, "ARRAY"), + BtfKind::Struct => write!(f, "STRUCT"), + BtfKind::Union => write!(f, "UNION"), + BtfKind::Enum => write!(f, "ENUM"), + BtfKind::Fwd => write!(f, "FWD"), + BtfKind::Typedef => write!(f, "TYPEDEF"), + BtfKind::Volatile => write!(f, "VOLATILE"), + BtfKind::Const => write!(f, "CONST"), + BtfKind::Restrict => write!(f, "RESTRICT"), + BtfKind::Func => write!(f, "FUNC"), + BtfKind::FuncProto => write!(f, "FUNC_PROTO"), + BtfKind::Var => write!(f, "VAR"), + BtfKind::DataSec => write!(f, "DATASEC"), + BtfKind::DeclTag => write!(f, "DECL_TAG"), + BtfKind::TypeTag => write!(f, "TYPE_TAG"), } } } @@ -307,11 +348,13 @@ impl BtfType { self.btf_type().map(|ty| ty.info) } - pub(crate) fn name_offset(&self) -> Option { + /// Returns the offset of the name of this type. + pub fn name_offset(&self) -> Option { self.btf_type().map(|ty| ty.name_off) } - pub(crate) fn kind(&self) -> Result, BtfError> { + /// Returns the BtfKind of this type. + pub fn kind(&self) -> Result, BtfError> { self.btf_type().map(type_kind).transpose() } @@ -471,11 +514,13 @@ fn type_kind(ty: &btf_type) -> Result { ((ty.info >> 24) & 0x1F).try_into() } -pub(crate) fn type_vlen(ty: &btf_type) -> usize { +/// Returns the vlen of the btf_type. +pub fn type_vlen(ty: &btf_type) -> usize { (ty.info & 0xFFFF) as usize } -pub(crate) fn member_bit_offset(info: u32, member: &btf_member) -> usize { +/// The bit offset of a struct or union member +pub fn member_bit_offset(info: u32, member: &btf_member) -> usize { let k_flag = info >> 31 == 1; let bit_offset = if k_flag { member.offset & 0xFFFFFF diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index d71e3205..5feec562 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod btf; +pub mod btf; mod relocation; use object::{ @@ -26,7 +26,7 @@ use crate::{ }; use std::slice::from_raw_parts_mut; -use self::btf::{FuncSecInfo, LineSecInfo}; +use crate::obj::btf::{FuncSecInfo, LineSecInfo}; const KERNEL_VERSION_ANY: u32 = 0xFFFF_FFFE; /// The first five __u32 of `bpf_map_def` must be defined. diff --git a/aya/src/obj/relocation.rs b/aya/src/obj/relocation.rs index af316a84..8344a1d8 100644 --- a/aya/src/obj/relocation.rs +++ b/aya/src/obj/relocation.rs @@ -1,4 +1,5 @@ -use std::{collections::HashMap, mem}; +use crate::obj::{btf::relocate_btf_program, ProgramSection}; +use std::{collections::HashMap, mem, str::FromStr}; use log::debug; use object::{SectionIndex, SymbolKind}; @@ -11,13 +12,18 @@ use crate::{ }, maps::Map, obj::{Function, Object, Program}, - BpfError, + BpfError, Btf, }; +use super::btf::{Candidate, ErrorWrapper}; + pub(crate) const INS_SIZE: usize = mem::size_of::(); #[derive(Debug, Error)] enum RelocationError { + #[error("program not found")] + ProgramNotFound, + #[error("unknown symbol, index `{index}`")] UnknownSymbol { index: usize }, @@ -117,6 +123,50 @@ impl Object { Ok(()) } + + pub fn relocate_btf(&mut self, target_btf: &Btf) -> Result<(), BpfError> { + let (local_btf, btf_ext) = match (&self.btf, &self.btf_ext) { + (Some(btf), Some(btf_ext)) => (btf, btf_ext), + _ => return Ok(()), + }; + + let mut candidates_cache = HashMap::>::new(); + for (sec_name_off, relos) in btf_ext.relocations() { + let section_name = local_btf.string_at(*sec_name_off)?; + + let program_section = match ProgramSection::from_str(§ion_name) { + Ok(program) => program, + Err(_) => continue, + }; + let section_name = program_section.name(); + + let program = self + .programs + .get_mut(section_name) + .ok_or(BpfError::RelocationError { + function: section_name.to_owned(), + error: Box::new(RelocationError::ProgramNotFound), + })?; + match relocate_btf_program( + &mut program.function.instructions, + relos, + local_btf, + target_btf, + &mut candidates_cache, + ) { + Ok(_) => {} + Err(ErrorWrapper::BtfError(e)) => return Err(e.into()), + Err(ErrorWrapper::RelocationError(error)) => { + return Err(BpfError::RelocationError { + function: section_name.to_owned(), + error: Box::new(error), + }) + } + } + } + + Ok(()) + } } fn relocate_maps<'a, I: Iterator>( diff --git a/btftool/Cargo.toml b/btftool/Cargo.toml new file mode 100644 index 00000000..7aff2f86 --- /dev/null +++ b/btftool/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "btftool" +version = "0.1.0" +authors = ["The Aya Contributors"] +edition = "2018" + +[dependencies] +clap = { version = "3", features=["derive"] } +aya = { path = "../aya", features = ["btf"] } +anyhow = "1" +object = "0.28" +thiserror = "1" \ No newline at end of file diff --git a/btftool/src/main.rs b/btftool/src/main.rs new file mode 100644 index 00000000..65ebfed3 --- /dev/null +++ b/btftool/src/main.rs @@ -0,0 +1,173 @@ +use aya::{ + btf::{member_bit_offset, type_vlen, BtfKind, BtfType}, + Btf, BtfError, Endianness, +}; +use clap::{Parser, Subcommand}; +use object::{Object, ObjectSection}; +use std::{ + fmt::{self, Write}, + fs, io, + path::Path, +}; +use thiserror::Error; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Dumps the .BTF ELF Section + Dump { file: Option }, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error(".BTF section not found")] + NoBTF, + #[error("error parsing ELF data")] + ElfError(#[from] object::read::Error), + #[error(transparent)] + IOError(io::Error), + #[error(transparent)] + FmtError(fmt::Error), + #[error(transparent)] + BtfError(BtfError), +} + +fn main() -> Result<(), anyhow::Error> { + let cli = Cli::parse(); + match &cli.command { + Commands::Dump { file } => { + dump(file.as_ref().unwrap())?; + } + } + Ok(()) +} + +fn dump>(input: P) -> Result<(), Error> { + let bin_data = fs::read(input).map_err(Error::IOError)?; + let obj_file = object::File::parse(&*bin_data).map_err(Error::ElfError)?; + if let Some(section) = obj_file.section_by_name(".BTF") { + let btf = Btf::parse(section.data()?, Endianness::default()).map_err(Error::BtfError)?; + for (i, t) in btf.types().enumerate().skip(1) { + let kind = t.kind().unwrap_or(None).unwrap_or(BtfKind::Unknown); + let name = if let Some(offset) = t.name_offset() { + if offset > 0 { + format!( + "'{}'", + btf.string_at(offset) + .unwrap_or(std::borrow::Cow::Borrowed("")) + ) + } else { + "''".to_owned() + } + } else { + "''".to_owned() + }; + let info = match t { + BtfType::Unknown => "".to_string(), + BtfType::Fwd(t) + | BtfType::Const(t) + | BtfType::Volatile(t) + | BtfType::Restrict(t) + | BtfType::Ptr(t) + | BtfType::Typedef(t) + | BtfType::Func(t) => { + format!("type_id={}", unsafe { t.__bindgen_anon_1.type_ }) + } + BtfType::Int(t, size) => { + let encoding = match (t.info & 0x0f000000) >> 24 { + 1 => "(signed)", + 2 => "(char)", + 4 => "(bool)", + _ => "(none)", + }; + let offset = (t.info & 0x00ff0000) >> 16; + let bits = t.info & 0x000000ff; + format!( + "size={} bits_offset={} nr_bits={} encoding={}", + size, offset, bits, encoding, + ) + } + BtfType::Float(_) => todo!(), + BtfType::Enum(_, _) => todo!(), + BtfType::Array(_, array) => { + format!( + "type_id={} index_type_id={} nr_elems={}", + array.type_, array.index_type, array.nelems + ) + } + BtfType::Struct(ty, members) => { + let size = unsafe { ty.__bindgen_anon_1.size }; + let vlen = type_vlen(ty); + let mut out = format!("size={} vlen={}", size, vlen,); + for m in members { + let name = btf + .string_at(m.name_off) + .unwrap_or(std::borrow::Cow::Borrowed("")); + let type_id = m.type_; + let offset = member_bit_offset(ty.info, m); + write!(out, "\n\t'{name}' type_id={type_id} bits_offset={offset}") + .map_err(Error::FmtError)?; + } + out + } + BtfType::Union(_, _) => todo!(), + BtfType::FuncProto(ty, params) => { + let ret_type_id = unsafe { ty.__bindgen_anon_1.type_ }; + let vlen = type_vlen(ty); + let mut out = format!("ret_type_id={ret_type_id} vlen={vlen}"); + for p in params { + let name = btf + .string_at(p.name_off) + .unwrap_or(std::borrow::Cow::Borrowed("")); + let type_id = p.type_; + write!(out, "\n\t'{name}' type_id={type_id}").map_err(Error::FmtError)?; + } + out + } + BtfType::Var(ty, var) => { + let type_id = unsafe { ty.__bindgen_anon_1.type_ }; + let linkage = match var.linkage { + 0 => "static".to_owned(), + 1 => "global".to_owned(), + other => format!("{other}"), + }; + format!("type_id={type_id} linkage={linkage}") + } + BtfType::DataSec(ty, secinfo) => { + let size = unsafe { ty.__bindgen_anon_1.size }; + let vlen = type_vlen(ty); + let mut out = format!("size={size} vlen={vlen}"); + for s in secinfo { + let points_to = btf.type_by_id(s.type_).unwrap(); + let name = btf + .string_at(points_to.name_offset().unwrap_or(0)) + .unwrap_or(std::borrow::Cow::Borrowed("")); + write!( + out, + "\n\ttype_id={} offset={} size={} ({} '{}')", + s.type_, + s.offset, + s.size, + points_to.kind().unwrap_or(None).unwrap_or(BtfKind::Unknown), + name + ) + .map_err(Error::FmtError)?; + } + out + } + BtfType::DeclTag(_, _) => unimplemented!("decl tag formatting not implemented"), + BtfType::TypeTag(_) => unimplemented!("type tag formatting not implemented"), + }; + println!("[{i}] {kind} {name} {info}"); + } + Ok(()) + } else { + Err(Error::NoBTF) + } +}