Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions aya-obj/src/btf/btf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
info::{FuncSecInfo, LineSecInfo},
relocation::Relocation,
},
extern_types::ExternCollection,
generated::{btf_ext_header, btf_header},
util::{HashMap, bytes_of},
};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -292,6 +295,7 @@ impl Btf {
strings: vec![0],
types: BtfTypes::default(),
_endianness: Endianness::default(),
externs: ExternCollection::new(),
}
}

Expand Down Expand Up @@ -364,6 +368,7 @@ impl Btf {
strings,
types,
_endianness: endianness,
externs: ExternCollection::new(),
})
}

Expand Down Expand Up @@ -500,6 +505,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);
Expand Down Expand Up @@ -805,6 +813,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 = 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}");
}
_ => continue,
}

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 {
Expand Down
186 changes: 186 additions & 0 deletions aya-obj/src/btf/extern_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
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) -> u32 {
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);
}

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)
.is_ok_and(|t| matches!(t, BtfType::Func(_)))
})
}

/// 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, var_btf_type) = match btf_type {
BtfType::Func(func) => {
let name = self.string_at(func.name_offset)?.into_owned();
(name, true, func.btf_type)
}
BtfType::Var(var) => {
let name = self.string_at(var.name_offset)?.into_owned();
(name, false, var.btf_type)
}
_ => return Ok(None),
};

let symbol = find_symbol_by_name(symbol_table, &name).ok_or(BtfError::InvalidSymbolName)?;

// Resolve through modifiers (const, volatile, typedef, etc.)
// Type ID 0 represents void in BTF
let resolved_type_id = self.resolve_type(var_btf_type).unwrap_or(var_btf_type);

// Typeless ksyms are declared as `extern const void symbol __ksym`
// They resolve to void (type_id 0) and are resolved via /proc/kallsyms
let is_typeless = !is_func && resolved_type_id == 0;

let mut extern_desc = ExternDesc::new(
name,
ExternType::Ksym,
entry.btf_type,
symbol.is_weak,
is_func,
);

// For typeless ksyms, don't set type_id so they skip kernel BTF resolution
if !is_typeless {
extern_desc.type_id = Some(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(())
}
}
1 change: 1 addition & 0 deletions aya-obj/src/btf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#[expect(clippy::module_inception, reason = "TODO")]
mod btf;
mod extern_types;
mod info;
mod relocation;
mod types;
Expand Down
Loading