Skip to content

Commit 171d8a6

Browse files
aya: Support loading programs with ksyms
1 parent a9945b0 commit 171d8a6

File tree

16 files changed

+1552
-4
lines changed

16 files changed

+1552
-4
lines changed

aya-obj/src/btf/btf.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::{
2424
info::{FuncSecInfo, LineSecInfo},
2525
relocation::Relocation,
2626
},
27+
extern_types::ExternCollection,
2728
generated::{btf_ext_header, btf_header},
2829
util::{HashMap, bytes_of},
2930
};
@@ -264,6 +265,8 @@ pub struct Btf {
264265
strings: Vec<u8>,
265266
types: BtfTypes,
266267
_endianness: Endianness,
268+
/// Extern functions parsed from ksyms section
269+
pub(crate) externs: ExternCollection,
267270
}
268271

269272
fn add_type(header: &mut btf_header, types: &mut BtfTypes, btf_type: BtfType) -> u32 {
@@ -292,6 +295,7 @@ impl Btf {
292295
strings: vec![0],
293296
types: BtfTypes::default(),
294297
_endianness: Endianness::default(),
298+
externs: ExternCollection::new(),
295299
}
296300
}
297301

@@ -364,6 +368,7 @@ impl Btf {
364368
strings,
365369
types,
366370
_endianness: endianness,
371+
externs: ExternCollection::new(),
367372
})
368373
}
369374

@@ -501,6 +506,9 @@ impl Btf {
501506
symbol_offsets: &HashMap<String, u64>,
502507
features: &BtfFeatures,
503508
) -> Result<(), BtfError> {
509+
if !self.externs.is_empty() {
510+
self.fixup_ksyms_datasec(self.externs.datasec_id, self.externs.dummy_ksym_var_id)?;
511+
}
504512
let enum64_placeholder_id = OnceCell::new();
505513
let filler_var_id = OnceCell::new();
506514
let mut types = mem::take(&mut self.types);
@@ -812,6 +820,128 @@ impl Btf {
812820
self.types = types;
813821
Ok(())
814822
}
823+
824+
/// Fixes up BTF for `.ksyms` datasec entries containing extern kernel symbol and
825+
/// makes it acceptable by the kernel:
826+
///
827+
/// * Changes linkage of extern functions to `GLOBAL`, fixes parameter names, injects
828+
/// a dummy variable representing them in datasec.
829+
/// * Changes linkage of extern variables to `GLOBAL_ALLOCATED`, replaces their type
830+
/// with `int`.
831+
pub(crate) fn fixup_ksyms_datasec(
832+
&mut self,
833+
datasec_id: Option<u32>,
834+
dummy_var_id: Option<u32>,
835+
) -> Result<(), BtfError> {
836+
// Extract dummy variable's name offset and type ID for patching func_proto names and datasec entries.
837+
// If dummy var exists: use its name_offset (for string table patching) and btf_type (underlying int type).
838+
// If no dummy var (fallback): search for a 4-byte int type directly, with no name_offset.
839+
// Both paths provide an int_btf_id to ensure type consistency in datasec variable entries.
840+
let (dummy_var_name_offset, int_btf_id) = if let Some(dummy_id) = dummy_var_id {
841+
let dummy_type = &self.types.types[dummy_id as usize];
842+
if let BtfType::Var(v) = dummy_type {
843+
(Some(v.name_offset), v.btf_type)
844+
} else {
845+
return Err(BtfError::InvalidDatasec);
846+
}
847+
} else {
848+
let int_id = self
849+
.types
850+
.types
851+
.iter()
852+
.enumerate()
853+
.find_map(|(idx, t)| {
854+
if let BtfType::Int(int_type) = t {
855+
(int_type.size == 4).then_some(idx as u32)
856+
} else {
857+
None
858+
}
859+
})
860+
.ok_or(BtfError::InvalidDatasec)?;
861+
862+
(None, int_id)
863+
};
864+
865+
let datasec_id = datasec_id.ok_or(BtfError::InvalidDatasec)?;
866+
867+
let datasec_name = {
868+
let datasec = &self.types.types[datasec_id as usize];
869+
let BtfType::DataSec(d) = datasec else {
870+
return Err(BtfError::InvalidDatasec);
871+
};
872+
self.string_at(d.name_offset)?.into_owned()
873+
};
874+
875+
debug!("DATASEC {datasec_name}: fixing up extern ksyms");
876+
877+
let entry_type_ids: Vec<u32> = {
878+
let BtfType::DataSec(d) = &self.types.types[datasec_id as usize] else {
879+
return Err(BtfError::InvalidDatasec);
880+
};
881+
d.entries.iter().map(|e| e.btf_type).collect()
882+
};
883+
884+
let mut offset = 0u32;
885+
let size = mem::size_of::<i32>() as u32;
886+
887+
for (i, &type_id) in entry_type_ids.iter().enumerate() {
888+
match &self.types.types[type_id as usize] {
889+
BtfType::Func(f) => {
890+
let (func_name, proto_id) =
891+
{ (self.string_at(f.name_offset)?.into_owned(), f.btf_type) };
892+
893+
if let BtfType::Func(f) = &mut self.types.types[type_id as usize] {
894+
f.set_linkage(FuncLinkage::Global);
895+
}
896+
897+
if let Some(dummy_name_off) = dummy_var_name_offset {
898+
if let BtfType::FuncProto(func_proto) =
899+
&mut self.types.types[proto_id as usize]
900+
{
901+
for param in &mut func_proto.params {
902+
if param.btf_type != 0 && param.name_offset == 0 {
903+
param.name_offset = dummy_name_off;
904+
}
905+
}
906+
}
907+
}
908+
909+
if let (Some(dummy_id), BtfType::DataSec(d)) =
910+
(dummy_var_id, &mut self.types.types[datasec_id as usize])
911+
{
912+
d.entries[i].btf_type = dummy_id;
913+
}
914+
915+
debug!("DATASEC {datasec_name}: FUNC {func_name}: fixup offset {offset}");
916+
}
917+
BtfType::Var(v) => {
918+
let var_name = { self.string_at(v.name_offset)?.into_owned() };
919+
920+
if let BtfType::Var(v) = &mut self.types.types[type_id as usize] {
921+
v.linkage = VarLinkage::Global;
922+
v.btf_type = int_btf_id;
923+
}
924+
925+
debug!("DATASEC {datasec_name}: VAR {var_name}: fixup offset {offset}");
926+
}
927+
_ => unreachable!(),
928+
}
929+
930+
if let BtfType::DataSec(d) = &mut self.types.types[datasec_id as usize] {
931+
d.entries[i].offset = offset;
932+
d.entries[i].size = size;
933+
}
934+
935+
offset += size;
936+
}
937+
938+
if let BtfType::DataSec(d) = &mut self.types.types[datasec_id as usize] {
939+
d.size = offset;
940+
debug!("DATASEC {datasec_name}: fixup size to {offset}");
941+
}
942+
943+
Ok(())
944+
}
815945
}
816946

817947
impl Default for Btf {

aya-obj/src/btf/extern_types.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use alloc::vec::Vec;
2+
3+
use log::debug;
4+
5+
use crate::{
6+
KsymsError, Object,
7+
btf::{Btf, BtfError, BtfType, DataSec, DataSecEntry},
8+
extern_types::{ExternDesc, ExternType},
9+
relocation::Symbol,
10+
util::HashMap,
11+
};
12+
impl Btf {
13+
/// Creates a dummy global variable named "dummy_ksym" with a 4-byte int type for unresolved kernel symbols.
14+
pub(crate) fn create_dummy_ksym_var(&mut self) -> Result<u32, BtfError> {
15+
let int_type_id = {
16+
let mut found_id = None;
17+
for (idx, t) in self.types().enumerate() {
18+
if let BtfType::Int(int) = t {
19+
if int.size == 4 {
20+
found_id = Some((idx) as u32);
21+
break;
22+
}
23+
}
24+
}
25+
found_id
26+
};
27+
28+
let int_type_id = if let Some(id) = int_type_id {
29+
id
30+
} else {
31+
let name_offset = self.add_string("int");
32+
self.add_type(BtfType::Int(crate::btf::Int::new(
33+
name_offset,
34+
4,
35+
crate::btf::IntEncoding::Signed,
36+
0,
37+
)))
38+
};
39+
40+
debug!("Found/created int type_id: {}", int_type_id);
41+
if let Ok(BtfType::Int(int)) = self.type_by_id(int_type_id) {
42+
debug!(
43+
"Int type size: {}, encoding: {:?}",
44+
int.size,
45+
int.encoding()
46+
);
47+
}
48+
49+
let name_offset = self.add_string("dummy_ksym");
50+
let dummy_var_id = self.add_type(BtfType::Var(crate::btf::Var::new(
51+
name_offset,
52+
int_type_id,
53+
crate::btf::VarLinkage::Global,
54+
)));
55+
56+
debug!("Created dummy_var type_id: {}", dummy_var_id);
57+
if let Ok(BtfType::Var(var)) = self.type_by_id(dummy_var_id) {
58+
debug!("Dummy var points to type_id: {}", var.btf_type);
59+
}
60+
61+
Ok(dummy_var_id)
62+
}
63+
64+
/// Searches for the `.ksyms` datasec in BTF, returns it if found.
65+
fn find_ksyms_datasec(&self) -> Result<Option<(u32, DataSec)>, BtfError> {
66+
for (idx, btf_type) in self.types().enumerate() {
67+
if let BtfType::DataSec(datasec) = btf_type {
68+
let name = self.type_name(btf_type)?;
69+
if name == ".ksyms" {
70+
return Ok(Some((idx as u32, datasec.clone())));
71+
}
72+
}
73+
}
74+
75+
Ok(None)
76+
}
77+
78+
/// Checks if datasec contains any functions.
79+
pub(crate) fn datasec_has_functions(&self, datasec: &DataSec) -> bool {
80+
datasec.entries.iter().any(|entry| {
81+
self.type_by_id(entry.btf_type)
82+
.map(|t| matches!(t, BtfType::Func(_)))
83+
.unwrap_or(false)
84+
})
85+
}
86+
87+
/// Collects extern descriptors from datasec entries.
88+
pub(crate) fn collect_extern_entries(
89+
&self,
90+
datasec: &DataSec,
91+
symbol_table: &HashMap<usize, Symbol>,
92+
) -> Result<Vec<ExternDesc>, BtfError> {
93+
let mut result = Vec::new();
94+
95+
for entry in &datasec.entries {
96+
let Some(extern_desc) = self.process_datasec_entry(entry, symbol_table)? else {
97+
continue;
98+
};
99+
100+
result.push(extern_desc);
101+
}
102+
103+
Ok(result)
104+
}
105+
106+
/// Processes a single datasec entry, returns [`ExternDesc`] if it's an extern.
107+
fn process_datasec_entry(
108+
&self,
109+
entry: &DataSecEntry,
110+
symbol_table: &HashMap<usize, Symbol>,
111+
) -> Result<Option<ExternDesc>, BtfError> {
112+
let btf_type = self.type_by_id(entry.btf_type)?;
113+
114+
let (name, is_func, var_btf_type) = match btf_type {
115+
BtfType::Func(func) => {
116+
let name = self.string_at(func.name_offset)?.into_owned();
117+
(name, true, func.btf_type)
118+
}
119+
BtfType::Var(var) => {
120+
let name = self.string_at(var.name_offset)?.into_owned();
121+
(name, false, var.btf_type)
122+
}
123+
_ => return Ok(None),
124+
};
125+
126+
let symbol = find_symbol_by_name(symbol_table, &name).ok_or(BtfError::InvalidSymbolName)?;
127+
128+
// Resolve through modifiers (const, volatile, typedef, etc.)
129+
// Type ID 0 represents void in BTF
130+
let resolved_type_id = self.resolve_type(var_btf_type).unwrap_or(var_btf_type);
131+
132+
// Typeless ksyms are declared as `extern const void symbol __ksym`
133+
// They resolve to void (type_id 0) and are resolved via /proc/kallsyms
134+
let is_typeless = !is_func && resolved_type_id == 0;
135+
136+
let mut extern_desc = ExternDesc::new(
137+
name,
138+
ExternType::Ksym,
139+
entry.btf_type,
140+
symbol.is_weak,
141+
is_func,
142+
);
143+
144+
// For typeless ksyms, don't set type_id so they skip kernel BTF resolution
145+
if !is_typeless {
146+
extern_desc.type_id = Some(resolved_type_id);
147+
}
148+
149+
Ok(Some(extern_desc))
150+
}
151+
}
152+
153+
fn find_symbol_by_name<'a>(
154+
symbol_table: &'a HashMap<usize, Symbol>,
155+
name: &str,
156+
) -> Option<&'a Symbol> {
157+
symbol_table
158+
.values()
159+
.find(|sym| sym.name.as_deref() == Some(name))
160+
}
161+
162+
impl Object {
163+
/// Collects extern kernel symbols from BTF datasec entries.
164+
pub fn collect_ksyms_from_btf(&mut self) -> Result<(), KsymsError> {
165+
let btf = self.btf.as_mut().ok_or(KsymsError::NoBtf)?;
166+
let Some((datasec_id, datasec)) = btf.find_ksyms_datasec()? else {
167+
return Ok(());
168+
};
169+
170+
if btf.datasec_has_functions(&datasec) {
171+
let dummy_var_id = btf.create_dummy_ksym_var()?;
172+
btf.externs.set_dummy_var_id(dummy_var_id);
173+
}
174+
175+
let collected = btf.collect_extern_entries(&datasec, &self.symbol_table)?;
176+
177+
for extern_desc in collected {
178+
btf.externs.insert(extern_desc.name.clone(), extern_desc);
179+
}
180+
181+
if !btf.externs.is_empty() {
182+
btf.externs.datasec_id = Some(datasec_id);
183+
}
184+
185+
Ok(())
186+
}
187+
}

aya-obj/src/btf/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
#[expect(clippy::module_inception)]
44
mod btf;
5+
mod extern_types;
56
mod info;
67
mod relocation;
78
mod types;

0 commit comments

Comments
 (0)