From 2b247ef3d7412db272edd9c3ee15217471195050 Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Thu, 23 Feb 2023 13:02:56 +0100 Subject: [PATCH 1/5] Added helper module aiming at providing higher level APIs to get information about BTF objects Signed-off-by: Quentin JEROME --- aya-obj/src/btf/helpers.rs | 352 +++++++++++++++++++++++++++++++++++++ aya-obj/src/btf/mod.rs | 2 + 2 files changed, 354 insertions(+) create mode 100644 aya-obj/src/btf/helpers.rs diff --git a/aya-obj/src/btf/helpers.rs b/aya-obj/src/btf/helpers.rs new file mode 100644 index 00000000..87c492ad --- /dev/null +++ b/aya-obj/src/btf/helpers.rs @@ -0,0 +1,352 @@ +use super::*; +use alloc::{ + borrow::Cow, + string::{String, ToString}, + vec, + vec::Vec, +}; + +/// Helper structure used to describe a BTF structure +#[derive(Debug)] +pub struct StructHelper { + /// structure's name + pub name: String, + /// structure's BTF type + pub btf_type: BtfType, + /// members of the structure + pub members: Vec, +} + +impl Default for StructHelper { + fn default() -> Self { + StructHelper { + name: "".into(), + btf_type: BtfType::Unknown, + members: vec![], + } + } +} + +impl StructHelper { + pub(crate) fn with_type(name: String, bt: BtfType) -> Self { + StructHelper { + name, + btf_type: bt, + ..Default::default() + } + } + + // resolve structure members recursively + fn resolve_members_rec( + &mut self, + btf: &Btf, + t: &BtfType, + member_path: &MemberPath, + base_offset: u32, + ) -> Result<(), BtfError> { + if let Some(members) = t.members() { + let mut member_offset = base_offset; + for m in members { + let member_name = btf.string_at(m.name_offset)?; + // we get the member type + let member_type = btf.type_by_id(m.btf_type)?; + let member_size = member_type.size().unwrap_or_default(); + let p = MemberPath::from_other_with_name(member_path, member_name.to_string()); + + match member_type { + BtfType::Struct(_) => { + // we push the structure name + self.members.push(MemberHelper::new( + p.clone(), + member_offset, + member_type.clone(), + None, + )); + // we process all members of the structure + self.resolve_members_rec(btf, member_type, &p, member_offset)?; + } + + BtfType::Union(_) => { + // unions don't have names so path is unchanged + self.resolve_members_rec(btf, member_type, member_path, member_offset)?; + } + + BtfType::Array(array) => { + self.members.push(MemberHelper::new( + p, + member_offset, + member_type.clone(), + Some(btf.type_by_id(array.array.element_type)?.clone()), + )); + } + _ => { + self.members.push(MemberHelper::new( + p, + member_offset, + member_type.clone(), + None, + )); + } + } + + // members of unions all have the same offset + if !matches!(t.kind(), BtfKind::Union) { + member_offset += member_size; + } + } + } + Ok(()) + } + + /// Creates a new structure descriptor for structure named `struct_name` from a [`Btf`] + /// # Arguments + /// * `struct_name` - must be a valid name of a structure defined inside the [`Btf`] object. + pub fn from_btf>(btf: &Btf, struct_name: T) -> Result { + let struct_name = struct_name.as_ref(); + let i_struct = btf.id_by_type_name_kind(struct_name, BtfKind::Struct)?; + let st = btf.type_by_id(i_struct)?; + + let mut struct_desc: Self = Self::with_type(struct_name.into(), st.clone()); + //let mp = MemberPath::with_struct_name(struct_name.into()); + let mp = MemberPath::new(); + + struct_desc.resolve_members_rec(btf, st, &mp, 0)?; + Ok(struct_desc) + } + + /// Get the member located at path relative to the top level structure + /// # Arguments + /// * `p` - must be a string containing dots (.) as path separators + pub fn get_member>(&self, path: P) -> Option<&MemberHelper> { + let path = path.as_ref(); + self.members.iter().find(|m| m.is_path(path)) + } + + /// Returns the size of the [`StructHelper`]. This might not be the real size of + /// the structure as it does not take alignement into account. In order to have + /// the size of aligned structure use [`size_of_aligned`] method. + /// + /// [`size_of_aligned`]: #size_of_aligned + pub fn size_of(&self) -> usize { + // a simple way to get the size of the structure is to + // get the last member offset and add the member's size to it + if let Some(last) = self.members.last() { + return last.offset as usize + last.size_of(); + } + // if there is no member the structure size must be 0 + 0 + } + + /// Return aligned size of structure according to architecture pointer size + pub fn size_of_aligned(&self) -> usize { + let un_size = self.size_of(); + let align = core::mem::size_of::<*const usize>(); + let modulo = un_size % align; + if un_size == 0 { + return 0; + } + + if modulo == 0 { + // we are already aligned + return un_size; + } + + // compute aligned size_of + (un_size - modulo) + align + } +} + +/// Path of a member inside a structure +#[derive(Clone)] +pub struct MemberPath(Vec); + +impl MemberPath { + pub(crate) fn new() -> Self { + MemberPath(vec![]) + } + + pub(crate) fn with_struct_name(struct_name: String) -> Self { + MemberPath(vec![struct_name]) + } + + pub(crate) fn from_other_with_name(o: &Self, name: String) -> Self { + let mut new = o.clone(); + new.push_name(name); + new + } + + pub(crate) fn push_name(&mut self, name: String) { + self.0.push(name) + } + + pub(crate) fn eq_str>(&self, s: T) -> bool { + s.as_ref() == self.to_string().as_str() + } +} + +impl core::fmt::Display for MemberPath { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0.join(".")) + } +} + +impl core::fmt::Debug for MemberPath { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0.join(".")) + } +} + +/// Structure holding information about BTF structure offset +#[derive(Debug)] +pub struct MemberHelper { + /// full path of the structure member, starting with top level structure name (ex: task_struct.thread_info) + pub path: MemberPath, + /// relative offset (to the top level structure) of that member + pub offset: u32, + /// [BtfType] associated to the member + pub btf_type: BtfType, + /// if btf_type is a [BtfType::Array] this is the [BtfType] of the element + pub element_type: Option, +} + +impl MemberHelper { + pub(crate) fn new( + path: MemberPath, + offset: u32, + btf_type: BtfType, + element_type: Option, + ) -> Self { + MemberHelper { + path, + offset, + btf_type, + element_type, + } + } + + /// Checks if member path matches string + /// # Arguments + /// * `p` - must be a string containing dots (.) as path separators + pub fn is_path>(&self, p: T) -> bool { + self.path.eq_str(p) + } + + /// Returns member's name + pub fn member_name(&self) -> Cow { + Cow::from(self.path.0.last().unwrap()) + } + + /// Returns the size of the BTF member + pub fn size_of(&self) -> usize { + // different size computation for arrays + if let BtfType::Array(a) = &self.btf_type { + let etype = self + .element_type + .as_ref() + .expect("element type should not be missing"); + // we multiply the size of the element with the length of the array + return (etype.size().unwrap_or_default() * a.array.len) as usize; + } + + self.btf_type.size().unwrap_or_default() as usize + } + + /// Returns the size of aligned member + pub fn size_of_aligned(&self) -> usize { + let un_size = self.size_of(); + let align = core::mem::size_of::<*const usize>(); + let modulo = un_size % align; + if un_size == 0 { + return 0; + } + + if modulo == 0 { + // we are already aligned + return un_size; + } + + // compute aligned size_of + (un_size - modulo) + align + } +} + +mod test { + use std::println; + + use alloc::task; + + use super::*; + + #[test] + fn test_offsets() { + let btf = Btf::from_sys_fs().unwrap(); + let ptr_size = core::mem::size_of::<*const u32>() as u32; + + let hlist_head = StructHelper::from_btf(&btf, "hlist_head").unwrap(); + // hlist_h ead is only one pointer + assert_eq!(hlist_head.get_member("first").unwrap().offset, 0); + + // hlist_node is a structure of two pointers + let hlist_node = StructHelper::from_btf(&btf, "hlist_node").unwrap(); + assert_eq!(hlist_node.get_member("next").unwrap().offset, 0); + assert_eq!(hlist_node.get_member("pprev").unwrap().offset, ptr_size); + } + + #[test] + fn test_get_member() { + let btf = Btf::from_sys_fs().unwrap(); + + let mount = StructHelper::from_btf(&btf, "mount").unwrap(); + + assert!(mount.get_member("mnt").is_some()); + assert!(mount.get_member("mnt.mnt_root").is_some()); + assert!(mount.get_member("mnt.mnt_sb").is_some()); + assert!(mount.get_member("mnt_parent").is_some()); + + // testing unexisting members + assert!(mount.get_member("").is_none()); + assert!(mount.get_member("mnt.unkwnown_member").is_none()); + assert!(mount.get_member("mnt.mnt_root.unk").is_none()); + + let task_struct = StructHelper::from_btf(&btf, "task_struct").unwrap(); + for m in &task_struct.members { + assert!(task_struct.get_member(m.path.to_string()).is_some()); + } + } + + #[test] + fn test_validate_size_of() { + // the structures used in this test are relatively small and + // are supposed not to change a lot accross kernels + + let btf = Btf::from_sys_fs().unwrap(); + let ptr_size = core::mem::size_of::<*const u32>(); + + // hlist_h ead is only one pointer + assert_eq!( + StructHelper::from_btf(&btf, "hlist_head") + .unwrap() + .size_of_aligned(), + ptr_size + ); + + // hlist_node is two pointers + assert_eq!( + StructHelper::from_btf(&btf, "hlist_node") + .unwrap() + .size_of_aligned(), + ptr_size * 2 + ); + + // this is a struct with a int and an array of 4 int + let trs = StructHelper::from_btf(&btf, "task_rss_stat").unwrap(); + // unaligned size is 20 + assert_eq!(trs.size_of(), 20); + // aligned size is 24 + if ptr_size == 8 { + assert_eq!(trs.size_of_aligned(), 24); + } else if ptr_size == 4 { + assert_eq!(trs.size_of_aligned(), 20); + } + } +} diff --git a/aya-obj/src/btf/mod.rs b/aya-obj/src/btf/mod.rs index a5606e5e..d08f81fb 100644 --- a/aya-obj/src/btf/mod.rs +++ b/aya-obj/src/btf/mod.rs @@ -6,6 +6,8 @@ mod info; mod relocation; mod types; +pub mod helpers; + pub use btf::*; pub use info::*; pub use relocation::BtfRelocationError; From 770ab66e9c343fcc194d5a4e93f8c98446cab70e Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Thu, 23 Feb 2023 13:38:16 +0100 Subject: [PATCH 2/5] Fixed missing documentation and unused_import in mod test Signed-off-by: Quentin JEROME --- aya-obj/src/btf/helpers.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/aya-obj/src/btf/helpers.rs b/aya-obj/src/btf/helpers.rs index 87c492ad..aa7a5a72 100644 --- a/aya-obj/src/btf/helpers.rs +++ b/aya-obj/src/btf/helpers.rs @@ -1,3 +1,6 @@ +//! Helper module containing higher level APIs to query +//! BTF objects. + use super::*; use alloc::{ borrow::Cow, @@ -165,10 +168,6 @@ impl MemberPath { MemberPath(vec![]) } - pub(crate) fn with_struct_name(struct_name: String) -> Self { - MemberPath(vec![struct_name]) - } - pub(crate) fn from_other_with_name(o: &Self, name: String) -> Self { let mut new = o.clone(); new.push_name(name); @@ -270,10 +269,8 @@ impl MemberHelper { } } +#[cfg(test)] mod test { - use std::println; - - use alloc::task; use super::*; From 1db17d5ad3b804ef2048123ea0c79ab22d00f1b3 Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Fri, 24 Feb 2023 19:38:50 +0100 Subject: [PATCH 3/5] Fixed bugs in btf helpers: * typedef were not handled properly and were generating wrong offsets * anonymous structures were not handled properly and were generating garbage members Signed-off-by: Quentin JEROME --- aya-obj/src/btf/helpers.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/aya-obj/src/btf/helpers.rs b/aya-obj/src/btf/helpers.rs index aa7a5a72..c98de246 100644 --- a/aya-obj/src/btf/helpers.rs +++ b/aya-obj/src/btf/helpers.rs @@ -53,8 +53,13 @@ impl StructHelper { let member_name = btf.string_at(m.name_offset)?; // we get the member type let member_type = btf.type_by_id(m.btf_type)?; - let member_size = member_type.size().unwrap_or_default(); - let p = MemberPath::from_other_with_name(member_path, member_name.to_string()); + let mut member_size = member_type.size().unwrap_or_default(); + + let mut p = member_path.clone(); + // if not anonymous + if !member_name.is_empty() { + p = MemberPath::from_other_with_name(member_path, member_name.to_string()); + } match member_type { BtfType::Struct(_) => { @@ -82,6 +87,17 @@ impl StructHelper { Some(btf.type_by_id(array.array.element_type)?.clone()), )); } + + BtfType::Typedef(td) => { + member_size = btf.type_by_id(td.btf_type)?.size().unwrap_or_default(); + self.members.push(MemberHelper::new( + p, + member_offset, + member_type.clone(), + None, + )); + } + _ => { self.members.push(MemberHelper::new( p, @@ -287,6 +303,9 @@ mod test { let hlist_node = StructHelper::from_btf(&btf, "hlist_node").unwrap(); assert_eq!(hlist_node.get_member("next").unwrap().offset, 0); assert_eq!(hlist_node.get_member("pprev").unwrap().offset, ptr_size); + + let dentry = StructHelper::from_btf(&btf, "dentry").unwrap(); + assert_eq!(dentry.get_member("d_name").unwrap().offset, 32); } #[test] From b648e46fe61a1624550eef4548c2b2c050270a2b Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Fri, 24 Feb 2023 20:43:48 +0100 Subject: [PATCH 4/5] Fixed bug: - When typedefs are nested - Offset issue when dealing with arrays Signed-off-by: Quentin JEROME --- aya-obj/src/btf/helpers.rs | 39 +++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/aya-obj/src/btf/helpers.rs b/aya-obj/src/btf/helpers.rs index c98de246..17df2f5b 100644 --- a/aya-obj/src/btf/helpers.rs +++ b/aya-obj/src/btf/helpers.rs @@ -39,6 +39,19 @@ impl StructHelper { } } + fn resolve_indirect_type(btf: &Btf, t: &BtfType) -> Result { + match t { + BtfType::Typedef(td) => { + let t = btf.type_by_id(td.btf_type)?; + if matches!(t, BtfType::Typedef(_)) { + return Self::resolve_indirect_type(btf, t); + } + Ok(t.clone()) + } + _ => Ok(t.clone()), + } + } + // resolve structure members recursively fn resolve_members_rec( &mut self, @@ -51,8 +64,10 @@ impl StructHelper { let mut member_offset = base_offset; for m in members { let member_name = btf.string_at(m.name_offset)?; + // we get the member type - let member_type = btf.type_by_id(m.btf_type)?; + let member_type = &Self::resolve_indirect_type(btf, btf.type_by_id(m.btf_type)?)?; + let mut member_size = member_type.size().unwrap_or_default(); let mut p = member_path.clone(); @@ -80,21 +95,17 @@ impl StructHelper { } BtfType::Array(array) => { - self.members.push(MemberHelper::new( - p, - member_offset, - member_type.clone(), - Some(btf.type_by_id(array.array.element_type)?.clone()), - )); - } + let element_type = Self::resolve_indirect_type( + btf, + btf.type_by_id(array.array.element_type)?, + )?; + member_size = array.array.len * element_type.size().unwrap_or_default(); - BtfType::Typedef(td) => { - member_size = btf.type_by_id(td.btf_type)?.size().unwrap_or_default(); self.members.push(MemberHelper::new( p, member_offset, member_type.clone(), - None, + Some(element_type.clone()), )); } @@ -110,6 +121,9 @@ impl StructHelper { // members of unions all have the same offset if !matches!(t.kind(), BtfKind::Union) { + // a zero member_size is an issue as two members + // will have the same offsets + debug_assert_ne!(member_size, 0); member_offset += member_size; } } @@ -304,8 +318,11 @@ mod test { assert_eq!(hlist_node.get_member("next").unwrap().offset, 0); assert_eq!(hlist_node.get_member("pprev").unwrap().offset, ptr_size); + // might be too tight to kernel version, consider removing if problematic let dentry = StructHelper::from_btf(&btf, "dentry").unwrap(); assert_eq!(dentry.get_member("d_name").unwrap().offset, 32); + assert_eq!(dentry.get_member("d_lockref").unwrap().offset, 88); + assert_eq!(dentry.get_member("d_alias").unwrap().offset, 176); } #[test] From 68aa8a8d0b3f219551a004ea9a637768e6a38755 Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Fri, 24 Feb 2023 21:23:10 +0100 Subject: [PATCH 5/5] Fixed bug: - duplicated members under some conditions (it was not causing any functional issue) Signed-off-by: Quentin JEROME --- aya-obj/src/btf/helpers.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/aya-obj/src/btf/helpers.rs b/aya-obj/src/btf/helpers.rs index 17df2f5b..fd289a2f 100644 --- a/aya-obj/src/btf/helpers.rs +++ b/aya-obj/src/btf/helpers.rs @@ -78,13 +78,20 @@ impl StructHelper { match member_type { BtfType::Struct(_) => { - // we push the structure name - self.members.push(MemberHelper::new( - p.clone(), - member_offset, - member_type.clone(), - None, - )); + // in case of structure containing an unamed union and again + // a struct the algorithm creates duplicates members so + // we need to check if we aleady have the member before insertion. + // this is not optimal, it would be better in a hashmap + if !self.contains_member(p.to_string()) { + // we push the structure name + self.members.push(MemberHelper::new( + p.clone(), + member_offset, + member_type.clone(), + None, + )); + } + // we process all members of the structure self.resolve_members_rec(btf, member_type, &p, member_offset)?; } @@ -121,9 +128,6 @@ impl StructHelper { // members of unions all have the same offset if !matches!(t.kind(), BtfKind::Union) { - // a zero member_size is an issue as two members - // will have the same offsets - debug_assert_ne!(member_size, 0); member_offset += member_size; } } @@ -155,6 +159,11 @@ impl StructHelper { self.members.iter().find(|m| m.is_path(path)) } + #[inline] + pub(crate) fn contains_member>(&self, path: P) -> bool { + self.get_member(path).is_some() + } + /// Returns the size of the [`StructHelper`]. This might not be the real size of /// the structure as it does not take alignement into account. In order to have /// the size of aligned structure use [`size_of_aligned`] method.