pull/1372/merge
altugbozkurt07 1 day ago committed by GitHub
commit bb4255ca7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -24,6 +24,7 @@ use crate::{
info::{FuncSecInfo, LineSecInfo},
relocation::Relocation,
},
extern_types::ExternCollection,
generated::{btf_ext_header, btf_header},
util::{HashMap, bytes_of},
};
@ -264,6 +265,8 @@ pub struct Btf {
strings: Vec<u8>,
types: BtfTypes,
_endianness: Endianness,
/// Extern functions parsed from ksyms section
pub(crate) externs: ExternCollection,
}
fn add_type(header: &mut btf_header, types: &mut BtfTypes, btf_type: BtfType) -> u32 {
@ -292,6 +295,7 @@ impl Btf {
strings: vec![0],
types: BtfTypes::default(),
_endianness: Endianness::default(),
externs: ExternCollection::new(),
}
}
@ -364,6 +368,7 @@ impl Btf {
strings,
types,
_endianness: endianness,
externs: ExternCollection::new(),
})
}
@ -501,6 +506,9 @@ impl Btf {
symbol_offsets: &HashMap<String, u64>,
features: &BtfFeatures,
) -> Result<(), BtfError> {
if !self.externs.is_empty() {
self.fixup_ksyms_datasec(self.externs.datasec_id, self.externs.dummy_ksym_var_id)?;
}
let enum64_placeholder_id = OnceCell::new();
let filler_var_id = OnceCell::new();
let mut types = mem::take(&mut self.types);
@ -812,6 +820,128 @@ impl Btf {
self.types = types;
Ok(())
}
/// Fixes up BTF for `.ksyms` datasec entries containing extern kernel symbol and
/// makes it acceptable by the kernel:
///
/// * Changes linkage of extern functions to `GLOBAL`, fixes parameter names, injects
/// a dummy variable representing them in datasec.
/// * Changes linkage of extern variables to `GLOBAL_ALLOCATED`, replaces their type
/// with `int`.
pub(crate) fn fixup_ksyms_datasec(
&mut self,
datasec_id: Option<u32>,
dummy_var_id: Option<u32>,
) -> Result<(), BtfError> {
// Extract dummy variable's name offset and type ID for patching func_proto names and datasec entries.
// If dummy var exists: use its name_offset (for string table patching) and btf_type (underlying int type).
// If no dummy var (fallback): search for a 4-byte int type directly, with no name_offset.
// Both paths provide an int_btf_id to ensure type consistency in datasec variable entries.
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 {
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)
};
let datasec_id = datasec_id.ok_or(BtfError::InvalidDatasec)?;
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");
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;
for (i, &type_id) in entry_type_ids.iter().enumerate() {
match &self.types.types[type_id as usize] {
BtfType::Func(f) => {
let (func_name, proto_id) =
{ (self.string_at(f.name_offset)?.into_owned(), f.btf_type) };
if let BtfType::Func(f) = &mut self.types.types[type_id as usize] {
f.set_linkage(FuncLinkage::Global);
}
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;
}
}
}
}
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}");
}
BtfType::Var(v) => {
let var_name = { self.string_at(v.name_offset)?.into_owned() };
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!(),
}
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;
}
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,178 @@
use alloc::vec::Vec;
use log::debug;
use crate::{
KsymsError, Object,
btf::{Btf, BtfError, BtfType, DataSec, DataSecEntry},
extern_types::{ExternDesc, ExternType},
relocation::Symbol,
util::HashMap,
};
impl Btf {
/// Creates a dummy global variable named "dummy_ksym" with a 4-byte int type for unresolved kernel symbols.
pub(crate) fn create_dummy_ksym_var(&mut self) -> Result<u32, BtfError> {
let int_type_id = {
let mut found_id = None;
for (idx, t) in self.types().enumerate() {
if let BtfType::Int(int) = t {
if int.size == 4 {
found_id = Some((idx) as u32);
break;
}
}
}
found_id
};
let int_type_id = if let Some(id) = int_type_id {
id
} else {
let name_offset = self.add_string("int");
self.add_type(BtfType::Int(crate::btf::Int::new(
name_offset,
4,
crate::btf::IntEncoding::Signed,
0,
)))
};
debug!("Found/created int type_id: {}", int_type_id);
if let Ok(BtfType::Int(int)) = self.type_by_id(int_type_id) {
debug!(
"Int type size: {}, encoding: {:?}",
int.size,
int.encoding()
);
}
let name_offset = self.add_string("dummy_ksym");
let dummy_var_id = self.add_type(BtfType::Var(crate::btf::Var::new(
name_offset,
int_type_id,
crate::btf::VarLinkage::Global,
)));
debug!("Created dummy_var type_id: {}", dummy_var_id);
if let Ok(BtfType::Var(var)) = self.type_by_id(dummy_var_id) {
debug!("Dummy var points to type_id: {}", var.btf_type);
}
Ok(dummy_var_id)
}
/// Searches for the `.ksyms` datasec in BTF, returns it if found.
fn find_ksyms_datasec(&self) -> Result<Option<(u32, DataSec)>, BtfError> {
for (idx, btf_type) in self.types().enumerate() {
if let BtfType::DataSec(datasec) = btf_type {
let name = self.type_name(btf_type)?;
if name == ".ksyms" {
return Ok(Some((idx as u32, datasec.clone())));
}
}
}
Ok(None)
}
/// Checks if datasec contains any functions.
pub(crate) fn datasec_has_functions(&self, datasec: &DataSec) -> bool {
datasec.entries.iter().any(|entry| {
self.type_by_id(entry.btf_type)
.map(|t| matches!(t, BtfType::Func(_)))
.unwrap_or(false)
})
}
/// Collects extern descriptors from datasec entries.
pub(crate) fn collect_extern_entries(
&self,
datasec: &DataSec,
symbol_table: &HashMap<usize, Symbol>,
) -> Result<Vec<ExternDesc>, BtfError> {
let mut result = Vec::new();
for entry in &datasec.entries {
let Some(extern_desc) = self.process_datasec_entry(entry, symbol_table)? else {
continue;
};
result.push(extern_desc);
}
Ok(result)
}
/// Processes a single datasec entry, returns [`ExternDesc`] if it's an extern.
fn process_datasec_entry(
&self,
entry: &DataSecEntry,
symbol_table: &HashMap<usize, Symbol>,
) -> Result<Option<ExternDesc>, BtfError> {
let btf_type = self.type_by_id(entry.btf_type)?;
let (name, is_func, resolved_type_id) = match btf_type {
BtfType::Func(func) => {
let name = self.string_at(func.name_offset)?.into_owned();
let resolved_type = self.resolve_type(func.btf_type).ok();
(name, true, resolved_type)
}
BtfType::Var(var) => {
let name = self.string_at(var.name_offset)?.into_owned();
let resolved_type = self.resolve_type(var.btf_type).ok();
(name, false, resolved_type)
}
_ => return Ok(None),
};
let symbol = find_symbol_by_name(symbol_table, &name).ok_or(BtfError::InvalidSymbolName)?;
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))
}
}
fn find_symbol_by_name<'a>(
symbol_table: &'a HashMap<usize, Symbol>,
name: &str,
) -> Option<&'a Symbol> {
symbol_table
.values()
.find(|sym| sym.name.as_deref() == Some(name))
}
impl Object {
/// Collects extern kernel symbols from BTF datasec entries.
pub fn collect_ksyms_from_btf(&mut self) -> Result<(), KsymsError> {
let btf = self.btf.as_mut().ok_or(KsymsError::NoBtf)?;
let Some((datasec_id, datasec)) = btf.find_ksyms_datasec()? else {
return Ok(());
};
if btf.datasec_has_functions(&datasec) {
let dummy_var_id = btf.create_dummy_ksym_var()?;
btf.externs.set_dummy_var_id(dummy_var_id);
}
let collected = btf.collect_extern_entries(&datasec, &self.symbol_table)?;
for extern_desc in collected {
btf.externs.insert(extern_desc.name.clone(), extern_desc);
}
if !btf.externs.is_empty() {
btf.externs.datasec_id = Some(datasec_id);
}
Ok(())
}
}

@ -2,6 +2,7 @@
#[expect(clippy::module_inception)]
mod btf;
mod extern_types;
mod info;
mod relocation;
mod types;

@ -0,0 +1,480 @@
//! Extern type resolution, relocation
use alloc::{
string::{String, ToString as _},
vec::Vec,
};
use crate::{
Object,
btf::{Btf, BtfError, BtfKind, BtfType},
util::HashMap,
};
impl Object {
/// Resolves typed externs through kernel `BTF`.
pub fn resolve_typed_externs(
&mut self,
kernel_btf: &mut Btf,
) -> core::result::Result<(), KsymsError> {
let mut resolutions = Vec::new();
{
let obj_btf = self.btf.as_ref().ok_or(KsymsError::NoBtf)?;
for (name, extern_desc) in obj_btf.externs.iter() {
if extern_desc.type_id.is_none() {
continue;
}
let btf_type = obj_btf.type_by_id(extern_desc.btf_id)?;
let kernel_btf_id = match btf_type {
BtfType::Func(_) => {
self.resolve_extern_function_internal(name, extern_desc, kernel_btf)?
}
BtfType::Var(_) => {
self.resolve_extern_variable_internal(name, extern_desc, kernel_btf)?
}
_ => {
return Err(KsymsError::InvalidExternType { name: name.clone() });
}
};
if let Some(btf_id) = kernel_btf_id {
resolutions.push((name.clone(), btf_id));
}
}
}
let obj_mut = self.btf.as_mut().ok_or(KsymsError::NoBtf)?;
for (name, kernel_btf_id) in resolutions {
if let Some(ext) = obj_mut.externs.get_mut(&name) {
ext.kernel_btf_id = Some(kernel_btf_id);
ext.is_resolved = true;
}
}
Ok(())
}
/// Resolves typless extern vars found in `.ksyms` section through `kallsyms`.
#[cfg(feature = "std")]
pub fn resolve_typeless_externs(&mut self) -> core::result::Result<(), KsymsError> {
use std::{fs::File, io::BufRead as _};
let unresolved: Vec<String> = self.get_unresolved_ksym_vars();
if unresolved.is_empty() {
return Ok(());
}
let file = File::open("/proc/kallsyms")?;
let reader = std::io::BufReader::new(file);
let lines: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
self.resolve_kallsyms_from_lines(&lines, &unresolved)?;
Ok(())
}
fn resolve_kallsyms_from_lines(
&mut self,
lines: &[String],
unresolved: &[String],
) -> core::result::Result<(), KsymsError> {
let Some(obj_btf) = self.btf.as_mut() else {
return Err(KsymsError::NoBtf);
};
let mut resolved_count = 0;
for line in lines {
// 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];
if !unresolved.iter().any(|s| s.as_str() == sym_name) {
continue;
}
let addr = u64::from_str_radix(addr_str, 16).map_err(|_| {
KsymsError::KallsymsParseError(alloc::format!("invalid address: {}", addr_str))
})?;
if let Some(ext) = obj_btf.externs.get_mut(sym_name) {
if ext.is_resolved {
if let Some(existing_addr) = ext.ksym_addr {
if existing_addr != addr {
return Err(KsymsError::AmbiguousResolution {
name: sym_name.to_string(),
first_addr: existing_addr,
second_addr: addr,
});
}
}
}
ext.ksym_addr = Some(addr);
ext.is_resolved = true;
resolved_count += 1;
}
if resolved_count == unresolved.len() {
break;
}
}
// Check for unresolved non-weak symbols
for (name, ext) in obj_btf.externs.externs.iter() {
if ext.extern_type == ExternType::Ksym && !ext.is_resolved && !ext.is_weak {
return Err(KsymsError::UnresolvedExtern { name: name.clone() });
}
}
Ok(())
}
/// Gets unresolved ksyms variables from [`ExternCollection`].
fn get_unresolved_ksym_vars(&self) -> Vec<String> {
let Some(obj_btf) = self.btf.as_ref() else {
return Vec::new();
};
obj_btf
.externs
.externs
.iter()
.filter(|(_, ext)| {
ext.extern_type == ExternType::Ksym && !ext.is_func && !ext.is_resolved
})
.map(|(name, _)| name.clone())
.collect()
}
/// Resolves a single exterm function. Returns BTF ID if found, otherwise
/// returns `None`.
fn resolve_extern_function_internal(
&self,
name: &str,
extern_desc: &ExternDesc,
kernel_btf: &Btf,
) -> core::result::Result<Option<u32>, KsymsError> {
let lookup_name = extern_desc.essent_name.as_deref().unwrap_or(name);
let kernel_func_id = match kernel_btf.id_by_type_name_kind(lookup_name, BtfKind::Func) {
Ok(id) => id,
Err(_) => {
if extern_desc.is_weak {
return Ok(None);
}
return Err(KsymsError::FunctionNotFound {
name: lookup_name.to_string(),
});
}
};
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(KsymsError::BtfError(BtfError::UnexpectedBtfType {
type_id: kernel_func_id,
}));
}
};
let local_proto_id =
extern_desc
.type_id
.ok_or(KsymsError::BtfError(BtfError::UnknownBtfType {
type_id: 0,
}))?;
let obj_btf = self.btf.as_ref().ok_or(KsymsError::NoBtf)?;
let compatible =
crate::btf::types_are_compatible(obj_btf, local_proto_id, kernel_btf, kernel_proto_id)?;
if !compatible {
if extern_desc.is_weak {
return Ok(None);
}
return Err(KsymsError::IncompatibleFunctionSignature {
name: lookup_name.to_string(),
});
}
Ok(Some(kernel_func_id))
}
/// Resolves a single exterm variable. Returns BTF ID if found, otherwise
/// returns `None`.
fn resolve_extern_variable_internal(
&self,
name: &str,
extern_desc: &ExternDesc,
kernel_btf: &Btf,
) -> core::result::Result<Option<u32>, KsymsError> {
let kernel_var_id = match kernel_btf.id_by_type_name_kind(name, BtfKind::Var) {
Ok(id) => id,
Err(_) => {
if extern_desc.is_weak {
return Ok(None);
}
return Err(KsymsError::VariableNotFound {
name: name.to_string(),
});
}
};
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(KsymsError::BtfError(BtfError::UnexpectedBtfType {
type_id: kernel_var_id,
}));
}
};
let local_type_id =
extern_desc
.type_id
.ok_or(KsymsError::BtfError(BtfError::UnknownBtfType {
type_id: 0,
}))?;
let obj_btf = self.btf.as_ref().ok_or(KsymsError::NoBtf)?;
let compatible =
crate::btf::types_are_compatible(obj_btf, local_type_id, kernel_btf, kernel_type_id)?;
if !compatible {
return Err(KsymsError::IncompatibleVariableType {
name: name.to_string(),
});
}
Ok(Some(kernel_var_id))
}
}
/// Errors that can occur during `ksyms`` operation fails
#[derive(Debug, thiserror::Error)]
pub enum KsymsError {
/// A non-weak extern variable was not found in kernel BTF
#[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,
/// Resolved Extern's kallsyms address does not match agains the loaded kallsyms
#[error("extern (ksym) '{name}': resolution is ambiguous: {first_addr:#x} or {second_addr:#x}")]
AmbiguousResolution {
/// The name of the symbol with ambiguous resolution
name: String,
/// The first address found
first_addr: u64,
/// The second (conflicting) address found
second_addr: u64,
},
/// Could not read kallsyms entries
#[cfg(feature = "std")]
#[error("failed to read /proc/kallsyms: {0}")]
KallsymsReadError(#[from] std::io::Error),
/// Failed to parse kallsyms data
#[error("failed to parse kallsyms: {0}")]
KallsymsParseError(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,
},
/// Function not found when trying to patch its instructions
#[error("function '{name}' not found")]
FunctionNotFound {
/// The section index
name: String,
},
}
/// 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>,
/// For names with flavors: stripped essential name
pub(crate) essent_name: Option<String>,
}
/// Given 'some_struct_name___with_flavor' return the length of a name prefix
/// before last triple underscore. Struct name part after last triple
/// underscore is ignored by BPF CO-RE relocation during relocation matching.
fn essential_name_len(name: &str) -> usize {
let n = name.len();
if n < 5 {
return n;
}
for i in (0..=n - 5).rev() {
if is_flavor_sep(name, i) {
return i + 1;
}
}
n
}
fn is_flavor_sep(s: &str, pos: usize) -> bool {
let bytes = s.as_bytes();
if pos + 4 >= bytes.len() {
return false;
}
bytes[pos] != b'_'
&& bytes[pos + 1] == b'_'
&& bytes[pos + 2] == b'_'
&& bytes[pos + 3] == b'_'
&& bytes[pos + 4] != b'_'
}
impl ExternDesc {
pub(crate) fn new(
name: String,
extern_type: ExternType,
btf_id: u32,
is_weak: bool,
is_func: bool,
) -> Self {
let essent_len = essential_name_len(&name);
let essent_name = if essent_len != name.len() {
Some(name[..essent_len].to_string())
} else {
None
};
Self {
name: name.clone(),
extern_type,
btf_id,
is_weak,
is_func,
is_resolved: false,
kernel_btf_id: None,
ksym_addr: None,
type_id: None,
essent_name,
}
}
}
/// 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>,
/// Index ID of `.ksyms`datasec entry in BTF types.
pub(crate) datasec_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,7 @@ extern crate alloc;
extern crate std;
pub mod btf;
pub mod extern_types;
#[expect(
clippy::all,
clippy::cast_lossless,
@ -97,6 +98,7 @@ pub mod programs;
pub mod relocation;
mod util;
pub use extern_types::KsymsError;
pub use maps::Map;
pub use obj::*;

@ -18,6 +18,7 @@ use object::{
};
use crate::{
KsymsError,
btf::{
Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSecEntry, FuncSecInfo, LineSecInfo,
},
@ -473,6 +474,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() {
@ -496,6 +498,8 @@ impl Object {
if let Some(s) = obj.section_by_name(".BTF.ext") {
bpf_obj.parse_section(Section::try_from(&s)?)?;
}
bpf_obj.collect_ksyms_from_btf()?;
}
for s in obj.sections() {
@ -504,7 +508,6 @@ impl Object {
continue;
}
}
bpf_obj.parse_section(Section::try_from(&s)?)?;
}
@ -936,6 +939,9 @@ pub enum ParseError {
#[error("error parsing ELF data")]
ElfError(object::read::Error),
#[error("error collecting Externs: {0}")]
KsymsError(#[from] KsymsError),
/// Error parsing BTF object
#[error("BTF error")]
BtfError(#[from] BtfError),
@ -998,6 +1004,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 +1107,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 +1533,7 @@ mod tests {
size,
is_definition: false,
kind: SymbolKind::Text,
is_weak: false,
},
);
obj.symbols_by_section
@ -2666,6 +2691,7 @@ mod tests {
size: 3,
is_definition: true,
kind: SymbolKind::Data,
is_weak: false,
},
);

@ -8,6 +8,7 @@ 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 +24,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 +88,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")]
MissingKallsymsAddr {
/// Name of the extern symbol
name: String,
},
}
#[derive(Debug, Copy, Clone)]
@ -105,6 +128,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 +177,35 @@ impl Object {
Ok(())
}
/// Relocates extern ksym references after BTF resolution
pub fn relocate_externs(&mut self) -> Result<(), EbpfRelocationError> {
if let Some(obj_btf) = self.btf.as_mut() {
for (name, extern_desc) in &obj_btf.externs.externs {
debug!(
"[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(),
&obj_btf.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 +244,100 @@ impl Object {
}
}
fn relocate_externs<'a, I: Iterator<Item = &'a Relocation>>(
fun: &mut Function,
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 and resolved
if extern_desc.type_id.is_some() && extern_desc.is_resolved {
// Typed ksyms - use BTF ID
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 or unresolved typed ksyms
let addr = extern_desc
.ksym_addr
.ok_or(RelocationError::MissingKallsymsAddr {
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 +526,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 +674,7 @@ mod test {
size,
is_definition: false,
kind: SymbolKind::Data,
is_weak: false,
}
}

@ -8,7 +8,7 @@ use std::{
};
use aya_obj::{
EbpfSectionKind, Features, Object, ParseError, ProgramSection,
EbpfSectionKind, Features, KsymsError, Object, ParseError, ProgramSection,
btf::{Btf, BtfError, BtfFeatures, BtfRelocationError},
generated::{
BPF_F_SLEEPABLE, BPF_F_XDP_HAS_FRAGS,
@ -511,6 +511,12 @@ impl<'a> EbpfLoader<'a> {
if let Some(btf) = &btf {
obj.relocate_btf(btf)?;
}
if let Some(kernel_btf) = self.btf.as_mut() {
obj.resolve_typed_externs(kernel_btf.to_mut())?;
obj.resolve_typeless_externs()?;
}
let mut maps = HashMap::new();
for (name, mut obj) in obj.maps.drain() {
if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) =
@ -576,6 +582,9 @@ 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);
@ -1173,6 +1182,10 @@ pub enum EbpfError {
#[error("error relocating section")]
BtfRelocationError(#[from] BtfRelocationError),
/// Error patching extern kernel symbol instructions
#[error("kernel symbol instruction patching error: {0}")]
KsymsError(#[from] KsymsError),
/// No BTF parsed for object
#[error("no BTF parsed for object")]
NoBTF,

@ -64,6 +64,10 @@ path = "src/raw_tracepoint.rs"
name = "redirect"
path = "src/redirect.rs"
[[bin]]
name = "ksyms"
path = "src/ksyms.rs"
[[bin]]
name = "relocations"
path = "src/relocations.rs"

@ -0,0 +1,80 @@
#![no_std]
#![no_main]
#![expect(unused_crate_dependencies, reason = "used in other bins")]
use aya_ebpf::{
macros::{map, tracepoint},
maps::Array,
programs::TracePointContext,
};
#[cfg(not(test))]
extern crate ebpf_panic;
#[repr(C)]
struct TestData {
counter: u64,
read_value: u64,
}
#[map]
static DATA_MAP: Array<TestData> = Array::with_max_entries(1, 0);
// Opaque type for dynptr
#[repr(C)]
struct bpf_dynptr {
_opaque: [u64; 2],
}
unsafe extern "C" {
// Kfunc with multiple arguments: (data, size, flags, dynptr)
fn bpf_dynptr_from_mem(data: *mut u8, size: u32, flags: u64, ptr: *mut bpf_dynptr) -> i32;
// Kfunc with multiple arguments: (dynptr, offset, dst, len)
fn bpf_dynptr_read(ptr: *const bpf_dynptr, offset: u32, dst: *mut u8, len: u32) -> i32;
}
#[tracepoint]
fn sys_enter(ctx: TracePointContext) -> u32 {
match try_sys_enter(&ctx) {
Ok(_) => 0,
Err(_) => 1,
}
}
fn try_sys_enter(_ctx: &TracePointContext) -> Result<(), ()> {
unsafe {
if let Some(data) = DATA_MAP.get_ptr_mut(0) {
// Test data
let mut test_val: u64 = 0x1234567890ABCDEF;
let mut dynptr: bpf_dynptr = core::mem::zeroed();
// Test kfunc with 4 arguments
let ret = bpf_dynptr_from_mem(
&mut test_val as *mut u64 as *mut u8,
8, // size
0, // flags
&mut dynptr,
);
if ret == 0 {
let mut read_buf: u64 = 0;
// Test another kfunc with 4 arguments
let ret = bpf_dynptr_read(
&dynptr,
0, // offset
&mut read_buf as *mut u64 as *mut u8,
8, // len
);
if ret == 0 {
(*data).read_value = read_buf;
}
}
(*data).counter = (*data).counter.wrapping_add(1);
}
}
Ok(())
}

@ -0,0 +1,32 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
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),
("ksyms.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 => "ksyms.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",
@ -58,6 +59,7 @@ bpf_file!(
TWO_PROGS => "two_progs",
XDP_SEC => "xdp_sec",
UPROBE_COOKIE => "uprobe_cookie",
KSYMS_RS => "ksyms",
);
#[cfg(test)]

@ -5,6 +5,7 @@ mod elf;
mod feature_probe;
mod info;
mod iter;
mod ksyms;
mod linear_data_structures;
mod load;
mod log;

@ -0,0 +1,22 @@
use aya::{
Btf, Ebpf,
programs::{BtfTracePoint, TracePoint, Xdp},
};
use test_log::test;
#[test]
fn test_ksym_btf_tracepoint() {
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();
}
#[test]
fn test_ksym_tracepoint() {
let mut ebpf = Ebpf::load(crate::KSYMS_RS).unwrap();
let prog: &mut TracePoint = ebpf.program_mut("sys_enter").unwrap().try_into().unwrap();
prog.load().unwrap();
prog.attach("sched", "sched_switch").unwrap();
}
Loading…
Cancel
Save