aya: Support loading programs with ksyms

reviewable/pr1372/r1
altug bozkurt 2 weeks ago
parent 03e8487177
commit de90b7df1f

@ -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<u32>,
) -> 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<u32> = {
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::<i32>() 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 {

@ -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<String> = 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: <addr> <type> <name> [<module>]
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<Option<u32>, 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<Option<u32>, 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<u32>,
/// For ksym variables: resolved kernel address
pub(crate) ksym_addr: Option<u64>,
/// For ksym: resolved type ID (after skipping modifiers/typedefs)
pub(crate) type_id: Option<u32>,
}
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<String, ExternDesc>,
/// BTF ID of dummy ksym variable (if created)
pub(crate) dummy_ksym_var_id: Option<u32>,
}
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<Item = (&String, &ExternDesc)> {
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()
}
}

@ -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::*;

@ -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<usize, Symbol>,
pub(crate) symbols_by_section: HashMap<SectionIndex, Vec<usize>>,
pub(crate) section_infos: HashMap<String, (SectionIndex, u64)>,
/// 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<String, u64>,
@ -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<u32, ParseError> {
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<Option<(u32, DataSec)>, 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<Vec<ExternDesc>, 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<Option<ExternDesc>, 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,
},
);

@ -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::<bpf_insn>();
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<Item = &'a Relocation>>(
fun: &mut Function, // ← Different first parameter (not &mut self)
relocations: I,
externs: &HashMap<String, ExternDesc>,
symbol_table: &HashMap<usize, Symbol>,
) -> 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<Item = &'a Relocation>>(
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,
}
}

@ -94,6 +94,22 @@ pub fn features() -> &'static Features {
&FEATURES
}
pub(crate) static KERNEL_BTF: LazyLock<Option<Btf>> = 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,

@ -0,0 +1,34 @@
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>
// 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;
}

@ -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"];

@ -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",

@ -20,3 +20,4 @@ mod strncmp;
mod tcx;
mod uprobe_cookie;
mod xdp;
mod ksyms;

@ -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();
}
Loading…
Cancel
Save