From de90b7df1f782533be0d020b60c53af584688e56 Mon Sep 17 00:00:00 2001 From: altug bozkurt Date: Wed, 22 Oct 2025 15:40:26 +0300 Subject: [PATCH] aya: Support loading programs with ksyms --- aya-obj/src/btf/btf.rs | 141 +++++++ aya-obj/src/extern_types.rs | 497 +++++++++++++++++++++++ aya-obj/src/lib.rs | 3 + aya-obj/src/obj.rs | 223 +++++++++- aya-obj/src/relocation.rs | 161 +++++++- aya/src/bpf.rs | 30 ++ test/integration-test/bpf/ksysm.bpf.c | 34 ++ test/integration-test/build.rs | 1 + test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + test/integration-test/src/tests/ksyms.rs | 14 + 11 files changed, 1102 insertions(+), 4 deletions(-) create mode 100644 aya-obj/src/extern_types.rs create mode 100644 test/integration-test/bpf/ksysm.bpf.c create mode 100644 test/integration-test/src/tests/ksyms.rs diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index ff28b164..4ea19445 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -812,6 +812,147 @@ impl Btf { self.types = types; Ok(()) } + + /// Fixup BTF for .ksyms datasec entries containing extern kernel symbols + /// This modifies extern functions and variables to make them acceptable to the kernel: + /// - Functions: Change linkage to GLOBAL, fix param names, replace with dummy var in datasec + /// - Variables: Change linkage to GLOBAL_ALLOCATED, replace type with int + pub(crate) fn fixup_ksyms_datasec( + &mut self, + datasec_id: u32, + dummy_var_id: Option, + ) -> Result<(), BtfError> { + // Get dummy var info and int type ID upfront + let (dummy_var_name_offset, int_btf_id) = if let Some(dummy_id) = dummy_var_id { + let dummy_type = &self.types.types[dummy_id as usize]; + if let BtfType::Var(v) = dummy_type { + (Some(v.name_offset), v.btf_type) + } else { + return Err(BtfError::InvalidDatasec); + } + } else { + // Fallback: find int type if no dummy var + let int_id = self + .types + .types + .iter() + .enumerate() + .find_map(|(idx, t)| { + if let BtfType::Int(int_type) = t { + (int_type.size == 4).then_some(idx as u32) + } else { + None + } + }) + .ok_or(BtfError::InvalidDatasec)?; + + (None, int_id) + }; + + // Get datasec name before mutable operations + let datasec_name = { + let datasec = &self.types.types[datasec_id as usize]; + let BtfType::DataSec(d) = datasec else { + return Err(BtfError::InvalidDatasec); + }; + self.string_at(d.name_offset)?.into_owned() + }; + + debug!("DATASEC {datasec_name}: fixing up extern ksyms"); + + // Collect entry type IDs to avoid borrowing issues during mutation + let entry_type_ids: Vec = { + let BtfType::DataSec(d) = &self.types.types[datasec_id as usize] else { + return Err(BtfError::InvalidDatasec); + }; + d.entries.iter().map(|e| e.btf_type).collect() + }; + + let mut offset = 0u32; + let size = mem::size_of::() as u32; + + // Process each entry type + for (i, &type_id) in entry_type_ids.iter().enumerate() { + let type_kind = match &self.types.types[type_id as usize] { + BtfType::Func(_) => "func", + BtfType::Var(_) => "var", + _ => return Err(BtfError::InvalidDatasec), + }; + + match type_kind { + "func" => { + // Get func info + let (func_name, proto_id) = { + let BtfType::Func(f) = &self.types.types[type_id as usize] else { + return Err(BtfError::InvalidDatasec); + }; + (self.string_at(f.name_offset)?.into_owned(), f.btf_type) + }; + + // Fixup the FUNC type + if let BtfType::Func(f) = &mut self.types.types[type_id as usize] { + f.set_linkage(FuncLinkage::Global); + } + + // Fix function prototype param names + if let Some(dummy_name_off) = dummy_var_name_offset { + if let BtfType::FuncProto(func_proto) = + &mut self.types.types[proto_id as usize] + { + for param in &mut func_proto.params { + if param.btf_type != 0 && param.name_offset == 0 { + param.name_offset = dummy_name_off; + } + } + } + } + + // Replace with dummy var in datasec entry + if let (Some(dummy_id), BtfType::DataSec(d)) = + (dummy_var_id, &mut self.types.types[datasec_id as usize]) + { + d.entries[i].btf_type = dummy_id; + } + + debug!("DATASEC {datasec_name}: FUNC {func_name}: fixup offset {offset}"); + } + "var" => { + // Get var name + let var_name = { + let BtfType::Var(v) = &self.types.types[type_id as usize] else { + return Err(BtfError::InvalidDatasec); + }; + self.string_at(v.name_offset)?.into_owned() + }; + + // Fixup the VAR type + if let BtfType::Var(v) = &mut self.types.types[type_id as usize] { + v.linkage = VarLinkage::Global; + v.btf_type = int_btf_id; + } + + debug!("DATASEC {datasec_name}: VAR {var_name}: fixup offset {offset}"); + } + _ => unreachable!(), + } + + // Update offset and size in datasec entry + if let BtfType::DataSec(d) = &mut self.types.types[datasec_id as usize] { + d.entries[i].offset = offset; + d.entries[i].size = size; + } + + offset += size; + } + + // Update section size + if let BtfType::DataSec(d) = &mut self.types.types[datasec_id as usize] { + d.size = offset; + debug!("DATASEC {datasec_name}: fixup size to {offset}"); + } + + Ok(()) + } } impl Default for Btf { diff --git a/aya-obj/src/extern_types.rs b/aya-obj/src/extern_types.rs new file mode 100644 index 00000000..9e219ea7 --- /dev/null +++ b/aya-obj/src/extern_types.rs @@ -0,0 +1,497 @@ +use std::{ + collections::HashMap, + format, + string::{String, ToString as _}, + vec::Vec, +}; + +use crate::{ + Object, + btf::{Btf, BtfError, BtfKind, BtfType}, +}; + +impl Object { + /// Resolve all extern kernel symbols (functions and variables) against kernel BTF + /// + /// This is the main entry point for resolving extern symbols declared in .ksyms section. + /// It dispatches to separate handlers for functions and variables. + /// + /// # Arguments + /// + /// * `kernel_btf` - Kernel BTF loaded from `/sys/kernel/btf/vmlinux` + /// + /// # Returns + /// + /// Returns `Ok(())` if all non-weak extern symbols were successfully resolved. + /// + /// # Example + /// + /// ```no_run + /// use aya_obj::{Object, btf::Btf}; + /// + /// let mut obj = Object::parse(&data)?; + /// let kernel_btf = Btf::from_sys_fs()?; + /// + /// // Resolve all extern kernel symbols (functions and variables) + /// obj.resolve_extern_ksyms(&kernel_btf)?; + /// ``` + pub fn resolve_extern_ksyms( + &mut self, + kernel_btf: &Btf, + ) -> std::result::Result<(), KsymResolveError> { + // Check if we have any externs to resolve + if self.externs.externs.is_empty() { + return Ok(()); + } + + let obj_btf = self.btf.as_ref().ok_or(KsymResolveError::NoBtf)?; + + // Dispatch based on extern type (like libbpf does at line 8232-8236) + let mut resolutions = Vec::new(); + + for (name, extern_desc) in self.externs.iter() { + // Skip if extern has no type_id (typeless ksyms - not supported yet) + if extern_desc.type_id.is_none() { + continue; + } + + // Dispatch to appropriate resolver + let btf_type = obj_btf.type_by_id(extern_desc.btf_id)?; + let kernel_btf_id = match btf_type { + BtfType::Func(_) => { + if extern_desc.is_weak { + return Err(KsymResolveError::WeakExternFunctionUnsupported { + name: name.to_string(), + }); + } + // Resolve function + self.resolve_extern_function_internal(name, extern_desc, obj_btf, kernel_btf)? + } + BtfType::Var(_) => { + // Resolve variable + self.resolve_extern_variable_internal(name, extern_desc, obj_btf, kernel_btf)? + } + _ => { + return Err(KsymResolveError::InvalidExternType { name: name.clone() }); + } + }; + + // Collect resolution if not None (None means weak extern not found) + if let Some(btf_id) = kernel_btf_id { + resolutions.push((name.clone(), btf_id)); + } + } + + // Apply all resolutions + for (name, kernel_btf_id) in resolutions { + if let Some(ext) = self.externs.get_mut(&name) { + ext.kernel_btf_id = Some(kernel_btf_id); + ext.is_resolved = true; + } + } + + self.resolve_kallsyms()?; + + Ok(()) + } + + fn resolve_kallsyms(&mut self) -> std::result::Result<(), KsymResolveError> { + use std::{ + fs::File, + io::{BufRead as _, BufReader}, + }; + + // Find all unresolved variable externs + let unresolved: Vec = self + .externs + .externs + .iter() + .filter(|(_, ext)| { + ext.extern_type == ExternType::Ksym && !ext.is_func && !ext.is_resolved + }) + .map(|(name, _)| name.clone()) + .collect(); + + if unresolved.is_empty() { + return Ok(()); + } + + // Read kallsyms + let file = + File::open("/proc/kallsyms").map_err(|e| KsymResolveError::KallsymsReadError { + error: e.to_string(), + })?; + + let reader = BufReader::new(file); + let mut resolved_count = 0; + + for line in reader.lines() { + let line = line.map_err(|e| KsymResolveError::KallsymsReadError { + error: e.to_string(), + })?; + + // Parse: [] + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 3 { + continue; + } + + let addr_str = parts[0]; + let sym_name = parts[2]; + + // Check if this symbol is one we need + if !unresolved.contains(&sym_name.to_string()) { + continue; + } + + // Parse address + let addr = u64::from_str_radix(addr_str, 16).map_err(|_| { + KsymResolveError::KallsymsReadError { + error: format!("invalid address: {}", addr_str), + } + })?; + + // Update extern descriptor + if let Some(ext) = self.externs.get_mut(sym_name) { + ext.ksym_addr = Some(addr); + ext.is_resolved = true; + resolved_count += 1; + } + + // Early exit if we've resolved everything + if resolved_count == unresolved.len() { + break; + } + } + + // Check for unresolved non-weak symbols + for (name, ext) in self.externs.externs.iter() { + if !ext.is_resolved && !ext.is_weak { + return Err(KsymResolveError::VariableNotFound { name: name.clone() }); + } + } + + Ok(()) + } + + fn resolve_extern_function_internal( + &self, + name: &str, + extern_desc: &ExternDesc, + obj_btf: &Btf, + kernel_btf: &Btf, + ) -> std::result::Result, KsymResolveError> { + // Look up function in kernel BTF + let kernel_func_id = match kernel_btf.id_by_type_name_kind(name, BtfKind::Func) { + Ok(id) => id, + Err(_) => { + return Err(KsymResolveError::FunctionNotFound { + name: name.to_string(), + }); + } + }; + + // Get kernel function prototype + let kernel_func_type = kernel_btf.type_by_id(kernel_func_id)?; + let kernel_proto_id = match kernel_func_type { + BtfType::Func(func) => func.btf_type, + _ => { + return Err(KsymResolveError::BtfError(BtfError::UnexpectedBtfType { + type_id: kernel_func_id, + })); + } + }; + + // Get local function prototype + let local_proto_id = + extern_desc + .type_id + .ok_or(KsymResolveError::BtfError(BtfError::UnknownBtfType { + type_id: 0, + }))?; + + // Check compatibility + let compatible = + crate::btf::types_are_compatible(obj_btf, local_proto_id, kernel_btf, kernel_proto_id)?; + + if !compatible { + return Err(KsymResolveError::IncompatibleFunctionSignature { + name: name.to_string(), + }); + } + + Ok(Some(kernel_func_id)) + } + + /// Internal: Resolve a single extern variable + /// Returns Some(btf_id) on success, None for weak externs not found + fn resolve_extern_variable_internal( + &self, + name: &str, + extern_desc: &ExternDesc, + obj_btf: &Btf, + kernel_btf: &Btf, + ) -> std::result::Result, KsymResolveError> { + // Look up variable in kernel BTF + let kernel_var_id = match kernel_btf.id_by_type_name_kind(name, BtfKind::Var) { + Ok(id) => id, + Err(_) => { + return Err(KsymResolveError::VariableNotFound { + name: name.to_string(), + }); + } + }; + + // Get the variable's type (VAR points to actual type) + let kernel_var_type = kernel_btf.type_by_id(kernel_var_id)?; + let kernel_type_id = match kernel_var_type { + BtfType::Var(var) => var.btf_type, + _ => { + return Err(KsymResolveError::BtfError(BtfError::UnexpectedBtfType { + type_id: kernel_var_id, + })); + } + }; + + // Get local variable's type + let local_type_id = + extern_desc + .type_id + .ok_or(KsymResolveError::BtfError(BtfError::UnknownBtfType { + type_id: 0, + }))?; + + // Check type compatibility + let compatible = + crate::btf::types_are_compatible(obj_btf, local_type_id, kernel_btf, kernel_type_id)?; + + if !compatible { + return Err(KsymResolveError::IncompatibleVariableType { + name: name.to_string(), + }); + } + + Ok(Some(kernel_var_id)) + } +} +/// Errors that can occur during ksym resolution +#[derive(Debug, thiserror::Error)] +pub enum KsymResolveError { + /// A non-weak extern function was not found in kernel BTF + #[error("kernel function '{name}' not found in kernel BTF")] + FunctionNotFound { + /// The name of the function that was not found + name: String, + }, + + /// A non-weak extern variable was not found in kernel BTF or kallsyms + #[error("kernel variable '{name}' not found in kernel BTF or kallsyms")] + VariableNotFound { + /// The name of the variable that was not found + name: String, + }, + + /// The extern function's signature is incompatible with the kernel function + #[error("kernel function '{name}' has incompatible signature")] + IncompatibleFunctionSignature { + /// The name of the function with incompatible signature + name: String, + }, + + /// The extern variable's type is incompatible with the kernel variable + #[error("kernel variable '{name}' has incompatible type")] + IncompatibleVariableType { + /// The name of the variable with incompatible type + name: String, + }, + + /// The extern symbol has an invalid BTF type (neither Func nor Var) + #[error("extern '{name}' has invalid BTF type (neither Func nor Var)")] + InvalidExternType { + /// The name of the extern with invalid type + name: String, + }, + + /// An error occurred while working with BTF data + #[error("BTF error: {0}")] + BtfError(#[from] BtfError), + + /// The object file has no BTF information + #[error("object has no BTF information")] + NoBtf, + + /// The object file has no BTF information + #[error("Weak extern functions are not supported")] + WeakExternFunctionUnsupported { + /// The name of the extern with weak type + name: String, + }, + + /// Could not read kallsyms entries + #[error("failed to read /proc/kallsyms: {error}")] + KallsymsReadError { + /// Could not read kallsyms entries + error: String, + }, +} + +/// Errors that can occur during extern symbol instruction patching +#[derive(Debug, thiserror::Error)] +pub enum KsymPatchError { + /// An extern symbol was not found during instruction patching + #[error("extern symbol '{name}' not found in extern collection")] + ExternNotFound { + /// The name of the missing extern + name: String, + }, + + /// An unresolved non-weak extern was encountered during patching + #[error( + "extern symbol '{name}' is not resolved (non-weak externs must be resolved before patching)" + )] + UnresolvedExtern { + /// The name of the unresolved extern + name: String, + }, + + /// Invalid instruction offset encountered + #[error("invalid instruction offset {offset} in function '{function_name}'")] + InvalidInstructionOffset { + /// The invalid offset + offset: usize, + /// The function name + function_name: String, + }, + + /// An extern function call instruction was expected but not found + #[error("expected call instruction at offset {offset} in function '{function_name}'")] + ExpectedCallInstruction { + /// The instruction offset + offset: usize, + /// The function name + function_name: String, + }, + + /// An ld_imm64 instruction was expected but not found + #[error("expected ld_imm64 instruction at offset {offset} in function '{function_name}'")] + ExpectedLdImm64Instruction { + /// The instruction offset + offset: usize, + /// The function name + function_name: String, + }, + + /// Program not found when trying to patch its instructions + #[error("program '{name}' not found")] + ProgramNotFound { + /// The program name + name: String, + }, + + /// Function not found when trying to patch its instructions + #[error("function not found for program at section {section_index}, address {address:#x}")] + FunctionNotFound { + /// The section index + section_index: usize, + /// The address + address: u64, + }, +} + +/// Type of extern symbol +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExternType { + /// Kernel configuration variable (.kconfig section) + Kconfig, + /// Kernel symbol - variable or function (.ksyms section) + Ksym, +} + +/// Descriptor for an extern symbol +#[derive(Debug, Clone)] +pub(crate) struct ExternDesc { + /// Symbol name + pub(crate) name: String, + + /// Type of extern (Kconfig or Ksym) + pub(crate) extern_type: ExternType, + + /// BTF type ID in local (program) BTF + pub(crate) btf_id: u32, + + /// Whether this is a weak symbol + pub(crate) is_weak: bool, + + /// Whether this is a function (vs variable) + pub(crate) is_func: bool, + + /// Whether extern has been resolved + pub(crate) is_resolved: bool, + + /// For ksym: kernel BTF ID (after resolution) + pub(crate) kernel_btf_id: Option, + + /// For ksym variables: resolved kernel address + pub(crate) ksym_addr: Option, + + /// For ksym: resolved type ID (after skipping modifiers/typedefs) + pub(crate) type_id: Option, +} + +impl ExternDesc { + pub(crate) fn new( + name: String, + extern_type: ExternType, + btf_id: u32, + is_weak: bool, + is_func: bool, + ) -> Self { + Self { + name, + extern_type, + btf_id, + is_weak, + is_func, + is_resolved: false, + kernel_btf_id: None, + ksym_addr: None, + type_id: None, + } + } +} + +/// Collection of extern symbols +#[derive(Debug, Default, Clone)] +pub struct ExternCollection { + /// Map of extern descriptors by name + pub(crate) externs: HashMap, + + /// BTF ID of dummy ksym variable (if created) + pub(crate) dummy_ksym_var_id: Option, +} + +impl ExternCollection { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn insert(&mut self, name: String, desc: ExternDesc) { + self.externs.insert(name, desc); + } + + pub(crate) fn get_mut(&mut self, name: &str) -> Option<&mut ExternDesc> { + self.externs.get_mut(name) + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.externs.iter() + } + + pub(crate) fn set_dummy_var_id(&mut self, id: u32) { + self.dummy_ksym_var_id = Some(id); + } + + pub(crate) fn is_empty(&self) -> bool { + self.externs.is_empty() + } +} diff --git a/aya-obj/src/lib.rs b/aya-obj/src/lib.rs index 6a6f4479..93628b02 100644 --- a/aya-obj/src/lib.rs +++ b/aya-obj/src/lib.rs @@ -76,6 +76,8 @@ extern crate alloc; extern crate std; pub mod btf; +/// ksym implementation +pub mod extern_types; #[expect( clippy::all, clippy::cast_lossless, @@ -97,6 +99,7 @@ pub mod programs; pub mod relocation; mod util; +pub use extern_types::{KsymPatchError, KsymResolveError}; pub use maps::Map; pub use obj::*; diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index b451595a..50345425 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -10,7 +10,7 @@ use alloc::{ }; use core::{ffi::CStr, mem, ptr, slice::from_raw_parts_mut, str::FromStr}; -use log::debug; +use log::{debug, info}; use object::{ Endianness, ObjectSymbol as _, ObjectSymbolTable as _, RelocationTarget, SectionIndex, SectionKind, SymbolKind, @@ -19,8 +19,10 @@ use object::{ use crate::{ btf::{ - Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSecEntry, FuncSecInfo, LineSecInfo, + Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSec, DataSecEntry, FuncSecInfo, + LineSecInfo, }, + extern_types::{ExternCollection, ExternDesc, ExternType}, generated::{ BPF_CALL, BPF_F_RDONLY_PROG, BPF_JMP, BPF_K, bpf_func_id::*, bpf_insn, bpf_map_info, bpf_map_type::BPF_MAP_TYPE_ARRAY, @@ -141,6 +143,9 @@ pub struct Object { pub(crate) symbol_table: HashMap, pub(crate) symbols_by_section: HashMap>, pub(crate) section_infos: HashMap, + /// Extern functions parsed from ksyms section + pub(crate) externs: ExternCollection, + // symbol_offset_by_name caches symbols that could be referenced from a // BTF VAR type so the offsets can be fixed up pub(crate) symbol_offset_by_name: HashMap, @@ -473,6 +478,7 @@ impl Object { size: symbol.size(), is_definition: symbol.is_definition(), kind: symbol.kind(), + is_weak: symbol.is_weak(), }; bpf_obj.symbol_table.insert(symbol.index().0, sym); if let Some(section_idx) = symbol.section().index() { @@ -493,6 +499,8 @@ impl Object { // when parsing program sections if let Some(s) = obj.section_by_name(".BTF") { bpf_obj.parse_section(Section::try_from(&s)?)?; + + bpf_obj.collect_ksyms_from_btf()?; if let Some(s) = obj.section_by_name(".BTF.ext") { bpf_obj.parse_section(Section::try_from(&s)?)?; } @@ -526,6 +534,7 @@ impl Object { symbols_by_section: HashMap::new(), section_infos: HashMap::new(), symbol_offset_by_name: HashMap::new(), + externs: ExternCollection::new(), } } @@ -831,6 +840,196 @@ impl Object { Ok(()) } + fn create_dummy_ksym_var(&mut self) -> Result { + let btf = self.btf.as_mut().ok_or(ParseError::NoBTF)?; + + let int_type_id = { + let mut found_id = None; + for (idx, t) in btf.types().enumerate() { + if let BtfType::Int(int) = t { + if int.size == 4 { + found_id = Some((idx) as u32); + break; + } + } + } + found_id + }; + + // Create int type if not found + let int_type_id = if let Some(id) = int_type_id { + id + } else { + let name_offset = btf.add_string("int"); + btf.add_type(BtfType::Int(crate::btf::Int::new( + name_offset, + 4, + crate::btf::IntEncoding::Signed, + 0, + ))) + }; + + info!("Found/created int type_id: {}", int_type_id); + if let Ok(BtfType::Int(int)) = btf.type_by_id(int_type_id) { + info!( + "Int type size: {}, encoding: {:?}", + int.size, + int.encoding() + ); + } + + // Create dummy_ksym variable + let name_offset = btf.add_string("dummy_ksym"); + let dummy_var_id = btf.add_type(BtfType::Var(crate::btf::Var::new( + name_offset, + int_type_id, + crate::btf::VarLinkage::Global, + ))); + + // After creating dummy_var + info!("Created dummy_var type_id: {}", dummy_var_id); + if let Ok(BtfType::Var(var)) = btf.type_by_id(dummy_var_id) { + info!("Dummy var points to type_id: {}", var.btf_type); + } + + Ok(dummy_var_id) + } + + /// Collect extern kernel symbols from BTF DATASEC entries + fn collect_ksyms_from_btf(&mut self) -> Result<(), ParseError> { + // Find .ksyms DATASEC + let Some((datasec_id, datasec)) = self.find_ksyms_datasec()? else { + return Ok(()); // No .ksyms, nothing to do + }; + + // Create dummy var if needed (before we borrow btf immutably again) + if Self::datasec_has_functions(self.btf.as_ref().unwrap(), &datasec) { + let dummy_var_id = self.create_dummy_ksym_var()?; + self.externs.set_dummy_var_id(dummy_var_id); + } + + // Collect all extern info + let collected = self.collect_extern_entries(&datasec)?; + + // Store in ExternCollection + for extern_desc in collected { + self.externs.insert(extern_desc.name.clone(), extern_desc); + } + + // Apply BTF fixups + if !self.externs.is_empty() { + self.fixup_ksyms_btf(datasec_id)?; + } + + Ok(()) + } + + /// Find .ksyms DATASEC in BTF, returns (id, datasec) if found + fn find_ksyms_datasec(&self) -> Result, ParseError> { + let Some(btf) = self.btf.as_ref() else { + return Ok(None); + }; + + for (idx, btf_type) in btf.types().enumerate() { + if let BtfType::DataSec(datasec) = btf_type { + let name = btf.type_name(btf_type).map_err(|_| ParseError::NoBTF)?; + if name == ".ksyms" { + return Ok(Some((idx as u32, datasec.clone()))); + } + } + } + + Ok(None) + } + + /// Check if datasec contains any functions + fn datasec_has_functions(btf: &Btf, datasec: &DataSec) -> bool { + datasec.entries.iter().any(|entry| { + btf.type_by_id(entry.btf_type) + .map(|t| matches!(t, BtfType::Func(_))) + .unwrap_or(false) + }) + } + + /// Collect extern descriptors from datasec entries + fn collect_extern_entries(&self, datasec: &DataSec) -> Result, ParseError> { + let btf = self.btf.as_ref().ok_or(ParseError::NoBTF)?; + let mut result = Vec::new(); + + for entry in &datasec.entries { + let Some(extern_desc) = self.process_datasec_entry(btf, entry)? else { + continue; // Skip non-extern entries + }; + + result.push(extern_desc); + } + + Ok(result) + } + + /// Process a single datasec entry, returning ExternDesc if it's an extern + fn process_datasec_entry( + &self, + btf: &Btf, + entry: &DataSecEntry, + ) -> Result, ParseError> { + let btf_type = btf.type_by_id(entry.btf_type)?; + + // Extract extern info based on type + let (name, is_func, resolved_type_id) = match btf_type { + BtfType::Func(func) => { + let name = btf.string_at(func.name_offset)?.into_owned(); + let resolved_type = btf.resolve_type(func.btf_type)?; + (name, true, Some(resolved_type)) + } + BtfType::Var(var) => { + let name = btf.string_at(var.name_offset)?.into_owned(); + let resolved_type = btf.resolve_type(var.btf_type).ok(); + (name, false, resolved_type) + } + _ => return Ok(None), // Not func or var + }; + + // Look up symbol + let symbol = self + .find_symbol_by_name(&name) + .ok_or(ParseError::SymbolNotFound { name: name.clone() })?; + + // Validate weak functions + if is_func && symbol.is_weak { + return Err(ParseError::WeakFuncNotSupported { name }); + } + + // Create descriptor + let mut extern_desc = ExternDesc::new( + name, + ExternType::Ksym, + entry.btf_type, + symbol.is_weak, + is_func, + ); + + extern_desc.type_id = resolved_type_id; + + Ok(Some(extern_desc)) + } + + /// Apply BTF fixups for .ksyms DATASEC + fn fixup_ksyms_btf(&mut self, datasec_id: u32) -> Result<(), ParseError> { + let btf = self.btf.as_mut().ok_or(ParseError::NoBTF)?; + let dummy_var_id = self.externs.dummy_ksym_var_id; + + btf.fixup_ksyms_datasec(datasec_id, dummy_var_id) + .map_err(ParseError::BtfError) + } + + /// Helper to find symbol by name + fn find_symbol_by_name(&self, name: &str) -> Option<&Symbol> { + self.symbol_table + .values() + .find(|sym| sym.name.as_deref() == Some(name)) + } + fn parse_section(&mut self, section: Section<'_>) -> Result<(), ParseError> { self.section_infos .insert(section.name.to_owned(), (section.index, section.size)); @@ -998,6 +1197,23 @@ pub enum ParseError { /// No BTF parsed for object #[error("no BTF parsed for object")] NoBTF, + + /// Extern weak function not supported + #[error("extern weak function '{name}' is not supported in this implementation")] + WeakFuncNotSupported { name: String }, + + /// Extern function in kconfig section not supported + #[error( + "extern function '{name}' cannot be used in {section} section (only variables allowed)" + )] + FuncInKconfig { name: String, section: String }, + + #[error("duplicate extern symbol '{name}' found in extern section")] + DuplicateExtern { name: String }, + + /// Invalid kconfig variable size + #[error("extern kconfig variable '{name}' has invalid or zero size")] + InvalidKconfigSize { name: String }, } /// Invalid bindings to the bpf type from the parsed/received value. @@ -1084,6 +1300,7 @@ impl<'a> TryFrom<&'a ObjSection<'_, '_>> for Section<'a> { error, }; let name = section.name().map_err(map_err)?; + //println!("Section name altug got: {}", name); let kind = match EbpfSectionKind::from_name(name) { EbpfSectionKind::Undefined => { if section.kind() == SectionKind::Text && section.size() > 0 { @@ -1509,6 +1726,7 @@ mod tests { size, is_definition: false, kind: SymbolKind::Text, + is_weak: false, }, ); obj.symbols_by_section @@ -2666,6 +2884,7 @@ mod tests { size: 3, is_definition: true, kind: SymbolKind::Data, + is_weak: false, }, ); diff --git a/aya-obj/src/relocation.rs b/aya-obj/src/relocation.rs index 850eaaa3..a74bbb06 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -2,12 +2,14 @@ use alloc::{borrow::ToOwned as _, collections::BTreeMap, string::String}; use core::mem; +use std::eprintln; use log::debug; use object::{SectionIndex, SymbolKind}; use crate::{ EbpfSectionKind, + extern_types::ExternDesc, generated::{ BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE, bpf_insn, @@ -23,6 +25,8 @@ type RawFd = std::os::fd::RawFd; type RawFd = core::ffi::c_int; pub(crate) const INS_SIZE: usize = mem::size_of::(); +pub(crate) const BPF_PSEUDO_KFUNC_CALL: u32 = 2; +pub(crate) const BPF_PSEUDO_BTF_ID: u32 = 3; /// The error type returned by [`Object::relocate_maps`] and [`Object::relocate_calls`] #[derive(thiserror::Error, Debug)] @@ -85,6 +89,26 @@ pub enum RelocationError { /// The relocation number relocation_number: usize, }, + /// Extern not found + #[error("extern `{name}` not found")] + ExternNotFound { + /// Name of the extern symbol + name: String, + }, + + /// Unresolved extern + #[error("extern `{name}` was not resolved against kernel BTF")] + UnresolvedExtern { + /// Name of the extern symbol + name: String, + }, + + /// Missing kernel BTF ID + #[error("extern `{name}` is missing kernel BTF ID")] + MissingKernelBtfId { + /// Name of the extern symbol + name: String, + }, } #[derive(Debug, Copy, Clone)] @@ -105,6 +129,17 @@ pub(crate) struct Symbol { pub(crate) size: u64, pub(crate) is_definition: bool, pub(crate) kind: SymbolKind, + pub(crate) is_weak: bool, +} + +impl Symbol { + /// Returns true if this symbol is an extern (undefined) symbol + pub(crate) fn is_extern(&self) -> bool { + self.section_index.is_none() + && self.name.is_some() + && !self.is_definition + && self.kind == SymbolKind::Unknown + } } impl Object { @@ -143,6 +178,32 @@ impl Object { Ok(()) } + /// Relocates extern ksym references after BTF resolution + pub fn relocate_externs(&mut self) -> Result<(), EbpfRelocationError> { + for (name, extern_desc) in &self.externs.externs { + eprintln!( + "[DEBUG] Extern '{}': resolved={}, btf_id={:?}", + name, extern_desc.is_resolved, extern_desc.kernel_btf_id + ); + } + + for function in self.functions.values_mut() { + if let Some(relocations) = self.relocations.get(&function.section_index) { + relocate_externs( + function, + relocations.values(), + &self.externs.externs, + &self.symbol_table, + ) + .map_err(|error| EbpfRelocationError { + function: function.name.clone(), + error, + })?; + } + } + Ok(()) + } + /// Relocates function calls pub fn relocate_calls( &mut self, @@ -181,6 +242,100 @@ impl Object { } } +fn relocate_externs<'a, I: Iterator>( + fun: &mut Function, // ← Different first parameter (not &mut self) + relocations: I, + externs: &HashMap, + symbol_table: &HashMap, +) -> Result<(), RelocationError> { + let section_offset = fun.section_offset; + let instructions = &mut fun.instructions; + let function_size = instructions.len() * INS_SIZE; + + for (rel_n, rel) in relocations.enumerate() { + let rel_offset = rel.offset as usize; + if rel_offset < section_offset || rel_offset >= section_offset + function_size { + continue; + } + + let ins_offset = rel_offset - section_offset; + if ins_offset % INS_SIZE != 0 { + return Err(RelocationError::InvalidRelocationOffset { + offset: rel.offset, + relocation_number: rel_n, + }); + } + let ins_index = ins_offset / INS_SIZE; + + let sym = symbol_table + .get(&rel.symbol_index) + .ok_or(RelocationError::UnknownSymbol { + index: rel.symbol_index, + })?; + + // Only process extern symbols + if !sym.is_extern() { + continue; + } + + let extern_name = sym.name.as_ref().unwrap(); + let extern_desc = externs + .get(extern_name) + .ok_or(RelocationError::ExternNotFound { + name: extern_name.clone(), + })?; + + let ins = &mut instructions[ins_index]; + let is_call = insn_is_call(ins); + + if is_call { + ins.set_src_reg(BPF_PSEUDO_KFUNC_CALL as u8); + if extern_desc.is_resolved { + let kernel_btf_id = extern_desc.kernel_btf_id.unwrap(); + ins.imm = kernel_btf_id as i32; + ins.off = 0; // btf_fd_idx, typically 0 for vmlinux + } else { + // Unresolved weak kfunc call + poison_kfunc_call(ins, rel.symbol_index); + } + } else { + // Variable references - check if typed or typeless + if extern_desc.type_id.is_some() { + // Typed ksyms - use BTF ID (existing behavior) + let kernel_btf_id = extern_desc.kernel_btf_id.unwrap(); + ins.set_src_reg(BPF_PSEUDO_BTF_ID as u8); + ins.imm = kernel_btf_id as i32; + // Note: For typed ksyms, insn[1].imm would be kernel_btf_obj_fd + // but that's typically 0 for vmlinux symbols + instructions[ins_index + 1].imm = 0; + } else { + // Typeless ksyms - use kernel address split across two instructions + let addr = extern_desc + .ksym_addr + .ok_or(RelocationError::MissingKernelBtfId { + name: extern_name.clone(), + })?; + + // Split 64-bit address across two instructions + ins.imm = (addr & 0xFFFFFFFF) as i32; + instructions[ins_index + 1].imm = (addr >> 32) as i32; + } + } + } + + Ok(()) +} + +const POISON_CALL_KFUNC_BASE: i32 = 2002000000; + +fn poison_kfunc_call(ins: &mut bpf_insn, ext_idx: usize) { + ins.code = (BPF_JMP | BPF_CALL) as u8; + ins.set_dst_reg(0); + ins.set_src_reg(0); + ins.off = 0; + ins.imm = POISON_CALL_KFUNC_BASE + ext_idx as i32; +} + fn relocate_maps<'a, I: Iterator>( fun: &mut Function, relocations: I, @@ -369,8 +524,9 @@ impl<'a> FunctionLinker<'a> { .map(|sym| (rel, sym)) }) .filter(|(_rel, sym)| { - // only consider text relocations, data relocations are - // relocated in relocate_maps() + if sym.is_extern() { + return false; + } sym.kind == SymbolKind::Text || sym .section_index @@ -516,6 +672,7 @@ mod test { size, is_definition: false, kind: SymbolKind::Data, + is_weak: false, } } diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 6280d070..b21ed48f 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -94,6 +94,22 @@ pub fn features() -> &'static Features { &FEATURES } +pub(crate) static KERNEL_BTF: LazyLock> = LazyLock::new(|| match Btf::from_sys_fs() { + Ok(btf) => { + debug!("Loaded kernel BTF"); + Some(btf) + } + Err(e) => { + debug!("Failed to load kernel BTF: {}", e); + None + } +}); + +/// Returns a reference to the kernel BTF if available. +pub fn kernel_btf() -> Option<&'static Btf> { + KERNEL_BTF.as_ref() +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying @@ -506,6 +522,11 @@ impl<'a> EbpfLoader<'a> { if let Some(btf) = &btf { obj.relocate_btf(btf)?; } + + if let Some(kernel_btf) = kernel_btf() { + obj.resolve_extern_ksyms(kernel_btf)?; + } + let mut maps = HashMap::new(); for (name, mut obj) in obj.maps.drain() { if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) = @@ -571,6 +592,7 @@ impl<'a> EbpfLoader<'a> { .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())), &text_sections, )?; + obj.relocate_externs()?; obj.relocate_calls(&text_sections)?; obj.sanitize_functions(&FEATURES); @@ -1168,6 +1190,14 @@ pub enum EbpfError { #[error("error relocating section")] BtfRelocationError(#[from] BtfRelocationError), + /// Error resolving extern kernel symbols (functions and variables) + #[error("kernel symbol resolution error: {0}")] + KsymResolve(#[from] aya_obj::KsymResolveError), + + /// Error patching extern kernel symbol instructions + #[error("kernel symbol instruction patching error: {0}")] + KsymPatch(#[from] aya_obj::KsymPatchError), + /// No BTF parsed for object #[error("no BTF parsed for object")] NoBTF, diff --git a/test/integration-test/bpf/ksysm.bpf.c b/test/integration-test/bpf/ksysm.bpf.c new file mode 100644 index 00000000..43233c93 --- /dev/null +++ b/test/integration-test/bpf/ksysm.bpf.c @@ -0,0 +1,34 @@ +// clang-format off +#include +#include +#include +#include +// clang-format on + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u64); +} counter_map SEC(".maps"); + +extern void bpf_rcu_read_lock(void) __attribute__((section(".ksyms"))); +extern void bpf_rcu_read_unlock(void) __attribute__((section(".ksyms"))); + +SEC("tp_btf/sys_enter") +int BPF_PROG(sys_enter, struct pt_regs *regs, long id) { + __u32 key = 0; + __u64 *count; + + bpf_rcu_read_lock(); + + count = bpf_map_lookup_elem(&counter_map, &key); + if (count) { + __sync_fetch_and_add(count, 1); + } + + bpf_rcu_read_unlock(); + return 0; +} \ No newline at end of file diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index 47c3b9da..a6c67ffc 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -84,6 +84,7 @@ fn main() -> Result<()> { ("struct_flavors_reloc.bpf.c", true), ("text_64_64_reloc.c", false), ("variables_reloc.bpf.c", false), + ("ksysm.bpf.c", true), ]; const C_BPF_HEADERS: &[&str] = &["reloc.h", "struct_with_scalars.h"]; diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 64f37d18..416c69ea 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -11,6 +11,7 @@ bpf_file!( ITER_TASK => "iter.bpf.o", MAIN => "main.bpf.o", MULTIMAP_BTF => "multimap-btf.bpf.o", + KSYMS => "ksysm.bpf.o", ENUM_SIGNED_32_RELOC_BPF => "enum_signed_32_reloc.bpf.o", ENUM_SIGNED_32_RELOC_BTF => "enum_signed_32_reloc.bpf.target.o", diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index b7d4d492..a6ed5fda 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -20,3 +20,4 @@ mod strncmp; mod tcx; mod uprobe_cookie; mod xdp; +mod ksyms; diff --git a/test/integration-test/src/tests/ksyms.rs b/test/integration-test/src/tests/ksyms.rs new file mode 100644 index 00000000..feaba483 --- /dev/null +++ b/test/integration-test/src/tests/ksyms.rs @@ -0,0 +1,14 @@ +use aya::{Btf, Ebpf, programs::BtfTracePoint}; +use test_log::test; + +#[test] +fn test_ksym() { + let env = env!("OUT_DIR"); + println!("out dir {}", env); + let mut ebpf = Ebpf::load(crate::KSYMS).unwrap(); + + let prog: &mut BtfTracePoint = ebpf.program_mut("sys_enter").unwrap().try_into().unwrap(); + let btf = Btf::from_sys_fs().unwrap(); + prog.load("sys_enter", &btf).unwrap(); + prog.attach().unwrap(); +}