diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index c0e5ea33..1761bd02 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -14,8 +14,9 @@ use object::{Endianness, SectionIndex}; use crate::{ Object, btf::{ - Array, BtfEnum, BtfKind, BtfMember, BtfType, Const, Enum, FuncInfo, FuncLinkage, Int, - IntEncoding, LineInfo, Struct, Typedef, Union, VarLinkage, + Array, BtfEnum, BtfKind, BtfMember, BtfRebaseInfo, BtfRebaseInfoField, BtfType, Const, + Enum, FuncInfo, FuncLinkage, Int, IntEncoding, LineInfo, Struct, Typedef, Union, + VarLinkage, info::{FuncSecInfo, LineSecInfo}, relocation::Relocation, }, @@ -268,6 +269,50 @@ impl Btf { } } + /// Merges a base BTF and multiple split BTFs into a single BTF. + pub fn merge_split_btfs(mut base_btf: Btf, split_btfs: &[Btf]) -> Btf { + let strings_rebase_from = base_btf.strings.len() as u32; + let types_rebase_from = base_btf.types.types.len() as u32; + for split_btf in split_btfs { + let Btf { + header: split_btf_header, + strings: split_btf_strings, + types: split_btf_types, + _endianness, + } = split_btf; + + let rebase_info = BtfRebaseInfo { + strings: BtfRebaseInfoField { + rebase_from: strings_rebase_from, + new_offset: base_btf.strings.len() as u32, + }, + types: BtfRebaseInfoField { + rebase_from: types_rebase_from, + new_offset: base_btf.types.types.len() as u32, + }, + }; + + // Append the strings from the split BTF. We concatenate it with the + // existing strings, and will rebase the types to take the new + // offsets into account. + base_btf.strings.extend(split_btf_strings); + + // Skip over the Unknown type at offset 0, as this only exists in + // the "base" BTF, and not the "split" BTFs. Append the rest of the + // types from the split BTF. + for ty in split_btf_types.types.iter().skip(1) { + base_btf.types.types.push(ty.rebase(&rebase_info)); + } + + // Update the header. + base_btf.header.str_len = base_btf.strings.len() as u32; + base_btf.header.type_len += split_btf_header.type_len; + base_btf.header.str_off += split_btf_header.type_len; + } + + base_btf + } + pub(crate) fn is_empty(&self) -> bool { // the first one is awlays BtfType::Unknown self.types.types.len() < 2 @@ -296,10 +341,34 @@ impl Btf { type_id as u32 } - /// Loads BTF metadata from `/sys/kernel/btf/vmlinux`. + /// Loads BTF metadata from `/sys/kernel/btf/`. #[cfg(feature = "std")] pub fn from_sys_fs() -> Result { - Btf::parse_file("/sys/kernel/btf/vmlinux", Endianness::default()) + let base_btf = Btf::parse_file("/sys/kernel/btf/vmlinux", Endianness::default())?; + let mut split_btfs = vec![]; + let sys_kernel_btf_path = "/sys/kernel/btf"; + let dir_iter = + std::fs::read_dir(sys_kernel_btf_path).map_err(|error| BtfError::FileError { + path: sys_kernel_btf_path.into(), + error, + })?; + for entry in dir_iter { + let entry = entry.map_err(|error| BtfError::FileError { + path: sys_kernel_btf_path.into(), + error, + })?; + let file_type = entry.file_type().map_err(|error| BtfError::FileError { + path: entry.path(), + error, + })?; + + if !file_type.is_file() { + continue; + } + split_btfs.push(Btf::parse_file(entry.path(), Endianness::default())?); + } + + Ok(Self::merge_split_btfs(base_btf, &split_btfs)) } /// Loads BTF metadata from the given `path`. diff --git a/aya-obj/src/btf/types.rs b/aya-obj/src/btf/types.rs index 0f96e84a..897560bc 100644 --- a/aya-obj/src/btf/types.rs +++ b/aya-obj/src/btf/types.rs @@ -7,6 +7,45 @@ use object::Endianness; use crate::btf::{Btf, BtfError, MAX_RESOLVE_DEPTH}; +#[derive(Debug)] +pub(crate) struct BtfRebaseInfoField { + /// Index of the first offset to allow rebasing from. + /// + /// Offsets below this value are considered to be part of the "base BTF", + /// and as such should not be relocated. + pub rebase_from: u32, + /// The new starting offset. + pub new_offset: u32, +} + +impl BtfRebaseInfoField { + fn rebase(&self, offset: u32) -> u32 { + let &Self { + rebase_from, + new_offset, + } = self; + match offset.checked_sub(rebase_from) { + None => offset, + Some(offset) => new_offset + offset, + } + } +} + +pub(crate) struct BtfRebaseInfo { + pub strings: BtfRebaseInfoField, + pub types: BtfRebaseInfoField, +} + +impl BtfRebaseInfo { + fn rebase_str(&self, str_offset: u32) -> u32 { + self.strings.rebase(str_offset) + } + + fn rebase_type(&self, type_offset: u32) -> u32 { + self.types.rebase(type_offset) + } +} + #[derive(Clone, Debug)] pub enum BtfType { Unknown, @@ -51,6 +90,19 @@ impl Fwd { pub(crate) fn type_info_size(&self) -> usize { mem::size_of::() } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + _unused, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + _unused, + } + } } #[repr(C)] @@ -82,6 +134,19 @@ impl Const { btf_type, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(C)] @@ -104,6 +169,19 @@ impl Volatile { pub(crate) fn type_info_size(&self) -> usize { mem::size_of::() } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[derive(Clone, Debug)] @@ -125,6 +203,19 @@ impl Restrict { pub(crate) fn type_info_size(&self) -> usize { mem::size_of::() } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + _info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + _info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(C)] @@ -156,6 +247,19 @@ impl Ptr { btf_type, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(C)] @@ -187,6 +291,19 @@ impl Typedef { btf_type, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(C)] @@ -217,6 +334,19 @@ impl Float { size, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + } + } } #[repr(C)] @@ -276,6 +406,19 @@ impl Func { pub(crate) fn set_linkage(&mut self, linkage: FuncLinkage) { self.info = (self.info & 0xFFFF0000) | (linkage as u32) & 0xFFFF; } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(C)] @@ -307,6 +450,19 @@ impl TypeTag { btf_type, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + } + } } #[repr(u32)] @@ -391,6 +547,21 @@ impl Int { pub(crate) fn bits(&self) -> u32 { self.data & 0x000000ff } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + data, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + data, + } + } } #[repr(C)] @@ -404,6 +575,14 @@ impl BtfEnum { pub fn new(name_offset: u32, value: u32) -> Self { Self { name_offset, value } } + + fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { name_offset, value } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + value, + } + } } #[repr(C)] @@ -470,6 +649,21 @@ impl Enum { self.info &= !(1 << 31); } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + ref variants, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + variants: variants.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(C)] @@ -488,6 +682,19 @@ impl BtfEnum64 { value_high: (value >> 32) as u32, } } + + fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + value_low, + value_high, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + value_low, + value_high, + } + } } #[repr(C)] @@ -562,6 +769,21 @@ impl Enum64 { variants, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + ref variants, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + variants: variants.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(C)] @@ -572,6 +794,21 @@ pub(crate) struct BtfMember { pub(crate) offset: u32, } +impl BtfMember { + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + btf_type, + offset, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + btf_type: rebase_info.rebase_type(btf_type), + offset, + } + } +} + #[repr(C)] #[derive(Clone, Debug)] pub struct Struct { @@ -649,6 +886,21 @@ impl Struct { size as usize } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + ref members, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + members: members.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(C)] @@ -728,6 +980,21 @@ impl Union { size as usize } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + ref members, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + members: members.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(C)] @@ -738,6 +1005,21 @@ pub(crate) struct BtfArray { pub(crate) len: u32, } +impl BtfArray { + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + element_type, + index_type, + len, + } = self; + Self { + element_type: rebase_info.rebase_type(element_type), + index_type: rebase_info.rebase_type(index_type), + len, + } + } +} + #[repr(C)] #[derive(Clone, Debug)] pub struct Array { @@ -786,6 +1068,21 @@ impl Array { }, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + _unused, + ref array, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + _unused, + array: array.rebase(rebase_info), + } + } } #[repr(C)] @@ -795,6 +1092,19 @@ pub struct BtfParam { pub btf_type: u32, } +impl BtfParam { + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + btf_type, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + btf_type: rebase_info.rebase_type(btf_type), + } + } +} + #[repr(C)] #[derive(Clone, Debug)] pub struct FuncProto { @@ -847,6 +1157,21 @@ impl FuncProto { params, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + return_type, + ref params, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + return_type: rebase_info.rebase_type(return_type), + params: params.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(u32)] @@ -912,6 +1237,21 @@ impl Var { linkage, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + ref linkage, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + linkage: linkage.clone(), + } + } } #[repr(C)] @@ -922,6 +1262,21 @@ pub struct DataSecEntry { pub size: u32, } +impl DataSecEntry { + fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + btf_type, + offset, + size, + } = self; + Self { + btf_type: rebase_info.rebase_type(btf_type), + offset, + size, + } + } +} + #[repr(C)] #[derive(Clone, Debug)] pub struct DataSec { @@ -981,6 +1336,21 @@ impl DataSec { entries, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + size, + ref entries, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + size, + entries: entries.iter().map(|v| v.rebase(rebase_info)).collect(), + } + } } #[repr(C)] @@ -1026,6 +1396,21 @@ impl DeclTag { component_index, } } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + let &Self { + name_offset, + info, + btf_type, + component_index, + } = self; + Self { + name_offset: rebase_info.rebase_str(name_offset), + info, + btf_type: rebase_info.rebase_type(btf_type), + component_index, + } + } } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -1425,6 +1810,31 @@ impl BtfType { (BtfKind::Enum, BtfKind::Enum64) | (BtfKind::Enum64, BtfKind::Enum) ) } + + pub(crate) fn rebase(&self, rebase_info: &BtfRebaseInfo) -> Self { + match self { + BtfType::Unknown => BtfType::Unknown, + BtfType::Fwd(t) => BtfType::Fwd(t.rebase(rebase_info)), + BtfType::Const(t) => BtfType::Const(t.rebase(rebase_info)), + BtfType::Volatile(t) => BtfType::Volatile(t.rebase(rebase_info)), + BtfType::Restrict(t) => BtfType::Restrict(t.rebase(rebase_info)), + BtfType::Ptr(t) => BtfType::Ptr(t.rebase(rebase_info)), + BtfType::Typedef(t) => BtfType::Typedef(t.rebase(rebase_info)), + BtfType::Func(t) => BtfType::Func(t.rebase(rebase_info)), + BtfType::Int(t) => BtfType::Int(t.rebase(rebase_info)), + BtfType::Float(t) => BtfType::Float(t.rebase(rebase_info)), + BtfType::Enum(t) => BtfType::Enum(t.rebase(rebase_info)), + BtfType::Enum64(t) => BtfType::Enum64(t.rebase(rebase_info)), + BtfType::Array(t) => BtfType::Array(t.rebase(rebase_info)), + BtfType::Struct(t) => BtfType::Struct(t.rebase(rebase_info)), + BtfType::Union(t) => BtfType::Union(t.rebase(rebase_info)), + BtfType::FuncProto(t) => BtfType::FuncProto(t.rebase(rebase_info)), + BtfType::Var(t) => BtfType::Var(t.rebase(rebase_info)), + BtfType::DataSec(t) => BtfType::DataSec(t.rebase(rebase_info)), + BtfType::DeclTag(t) => BtfType::DeclTag(t.rebase(rebase_info)), + BtfType::TypeTag(t) => BtfType::TypeTag(t.rebase(rebase_info)), + } + } } fn type_kind(info: u32) -> Result { diff --git a/test/integration-test/bpf/split.bpf.c b/test/integration-test/bpf/split.bpf.c new file mode 100644 index 00000000..5697bb3b --- /dev/null +++ b/test/integration-test/bpf/split.bpf.c @@ -0,0 +1,29 @@ +// clang-format off +#include +#include +#include +// clang-format on + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 1); +} output_map SEC(".maps"); + +long set_output(__u64 value) { + __u32 key = 0; + return bpf_map_update_elem(&output_map, &key, &value, BPF_ANY); +} + +// Try to access ip_tables structures. In most distros, ip_tables is compiled +// and loaded as a separate module, making it a pretty good target. +SEC("uprobe") int check_can_access_module(void *ctx) { + int is_successful = + bpf_core_type_exists(struct ipt_entry) && + bpf_core_field_offset(struct ipt_entry, target_offset) != 0; + set_output(is_successful); + return is_successful; +} diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index 47bfce48..694fd34a 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -67,6 +67,7 @@ fn main() -> Result<()> { ("main.bpf.c", false), ("multimap-btf.bpf.c", false), ("reloc.bpf.c", true), + ("split.bpf.c", false), ("text_64_64_reloc.c", false), ("variables_reloc.bpf.c", false), ]; diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 90277bb4..6b857b4c 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -8,6 +8,7 @@ pub const MULTIMAP_BTF: &[u8] = pub const RELOC_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.o")); pub const RELOC_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o")); +pub const SPLIT_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/split.bpf.o")); pub const TEXT_64_64_RELOC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o")); pub const VARIABLES_RELOC: &[u8] = diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index 9ca83669..4af52c83 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -1,5 +1,6 @@ mod bpf_probe_read; mod btf_relocations; +mod btf_split; mod elf; mod info; mod iter; diff --git a/test/integration-test/src/tests/btf_split.rs b/test/integration-test/src/tests/btf_split.rs new file mode 100644 index 00000000..ec777f92 --- /dev/null +++ b/test/integration-test/src/tests/btf_split.rs @@ -0,0 +1,36 @@ +//! Test to make sure loading split BTF (kernel module BTF) works properly. + +use aya::{EbpfLoader, maps::Array, programs::UProbe}; + +#[test] +fn rebase_tests() { + // First, check that we have ip_tables in the split btf. + if !matches!(std::fs::exists("/sys/kernel/btf/ip_tables"), Ok(true)) { + eprintln!( + "skipping test on kernel, as ip_tables is not loaded as an external kernel module." + ); + return; + } + let mut bpf = EbpfLoader::new().load(crate::SPLIT_BPF).unwrap(); + let program: &mut UProbe = bpf + .program_mut("check_can_access_module") + .unwrap() + .try_into() + .unwrap(); + program.load().unwrap(); + program + .attach("trigger_btf_split_program", "/proc/self/exe", None, None) + .unwrap(); + + trigger_btf_split_program(); + + let output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap(); + let key = 0; + assert_eq!(output_map.get(&key, 0).unwrap(), 1) +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub extern "C" fn trigger_btf_split_program() { + core::hint::black_box(trigger_btf_split_program); +}