Merge branch 'aya-rs:main' into main

pull/530/head
qjerome 1 year ago committed by GitHub
commit 4801058f22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,9 +14,6 @@ panic = "abort"
[profile.release] [profile.release]
panic = "abort" panic = "abort"
[profile.dev.package.integration-ebpf]
opt-level = 2
overflow-checks = false
[profile.release.package.integration-ebpf] [profile.release.package.integration-ebpf]
debug = 2 debug = 2
codegen-units = 1

@ -15,7 +15,7 @@ userspace = [ "aya" ]
[dependencies] [dependencies]
aya = { path = "../aya", version = "0.11.0", optional=true } aya = { path = "../aya", version = "0.11.0", optional=true }
num_enum = { version = "0.5", default-features = false } num_enum = { version = "0.6", default-features = false }
[lib] [lib]
path = "src/lib.rs" path = "src/lib.rs"

@ -1045,9 +1045,7 @@ mod tests {
let name_offset = btf.add_string("&mut int".to_string()); let name_offset = btf.add_string("&mut int".to_string());
let ptr_type_id = btf.add_type(BtfType::Ptr(Ptr::new(name_offset, int_type_id))); let ptr_type_id = btf.add_type(BtfType::Ptr(Ptr::new(name_offset, int_type_id)));
let features = BtfFeatures { let features = Default::default();
..Default::default()
};
btf.fixup_and_sanitize(&HashMap::new(), &HashMap::new(), &features) btf.fixup_and_sanitize(&HashMap::new(), &HashMap::new(), &features)
.unwrap(); .unwrap();

@ -18,7 +18,9 @@ mod linux_bindings_riscv64;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
mod linux_bindings_x86_64; mod linux_bindings_x86_64;
pub use btf_internal_bindings::*; // don't re-export __u8 __u16 etc which are already exported by the
// linux_bindings_* module
pub use btf_internal_bindings::{bpf_core_relo, bpf_core_relo_kind, btf_ext_header};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
pub use linux_bindings_x86_64::*; pub use linux_bindings_x86_64::*;

@ -37,8 +37,9 @@
//! let bytes = std::fs::read("program.o").unwrap(); //! let bytes = std::fs::read("program.o").unwrap();
//! let mut object = Object::parse(&bytes).unwrap(); //! let mut object = Object::parse(&bytes).unwrap();
//! // Relocate the programs //! // Relocate the programs
//! object.relocate_calls().unwrap(); //! let text_sections = std::collections::HashSet::new();
//! object.relocate_maps(std::iter::empty()).unwrap(); //! object.relocate_calls(&text_sections).unwrap();
//! object.relocate_maps(std::iter::empty(), &text_sections).unwrap();
//! //!
//! // Run with rbpf //! // Run with rbpf
//! let instructions = &object.programs["prog_name"].function.instructions; //! let instructions = &object.programs["prog_name"].function.instructions;

@ -2,7 +2,10 @@
use core::mem; use core::mem;
use crate::thiserror::{self, Error}; use crate::{
thiserror::{self, Error},
BpfSectionKind,
};
use alloc::vec::Vec; use alloc::vec::Vec;
/// Invalid map type encontered /// Invalid map type encontered
@ -139,33 +142,6 @@ pub struct bpf_map_def {
/// The first five __u32 of `bpf_map_def` must be defined. /// The first five __u32 of `bpf_map_def` must be defined.
pub(crate) const MINIMUM_MAP_SIZE: usize = mem::size_of::<u32>() * 5; pub(crate) const MINIMUM_MAP_SIZE: usize = mem::size_of::<u32>() * 5;
/// Kinds of maps
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MapKind {
/// A map holding `.bss` section data
Bss,
/// A map holding `.data` section data
Data,
/// A map holding `.rodata` section data
Rodata,
/// Other maps
Other,
}
impl From<&str> for MapKind {
fn from(s: &str) -> Self {
if s == ".bss" {
MapKind::Bss
} else if s.starts_with(".data") {
MapKind::Data
} else if s.starts_with(".rodata") {
MapKind::Rodata
} else {
MapKind::Other
}
}
}
/// Map data defined in `maps` or `.maps` sections /// Map data defined in `maps` or `.maps` sections
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Map { pub enum Map {
@ -248,14 +224,6 @@ impl Map {
} }
} }
/// Returns the map kind
pub fn kind(&self) -> MapKind {
match self {
Map::Legacy(m) => m.kind,
Map::Btf(m) => m.kind,
}
}
/// Returns the section index /// Returns the section index
pub fn section_index(&self) -> usize { pub fn section_index(&self) -> usize {
match self { match self {
@ -264,11 +232,22 @@ impl Map {
} }
} }
/// Returns the symbol index /// Returns the section kind.
pub fn symbol_index(&self) -> usize { pub fn section_kind(&self) -> BpfSectionKind {
match self {
Map::Legacy(m) => m.section_kind,
Map::Btf(_) => BpfSectionKind::BtfMaps,
}
}
/// Returns the symbol index.
///
/// This is `None` for data maps (.bss, .data and .rodata) since those don't
/// need symbols in order to be relocated.
pub fn symbol_index(&self) -> Option<usize> {
match self { match self {
Map::Legacy(m) => m.symbol_index, Map::Legacy(m) => m.symbol_index,
Map::Btf(m) => m.symbol_index, Map::Btf(m) => Some(m.symbol_index),
} }
} }
} }
@ -283,12 +262,16 @@ pub struct LegacyMap {
pub def: bpf_map_def, pub def: bpf_map_def,
/// The section index /// The section index
pub section_index: usize, pub section_index: usize,
/// The symbol index /// The section kind
pub symbol_index: usize, pub section_kind: BpfSectionKind,
/// The symbol index.
///
/// This is None for data maps (.bss .data and .rodata). We don't need
/// symbols to relocate those since they don't contain multiple maps, but
/// are just a flat array of bytes.
pub symbol_index: Option<usize>,
/// The map data /// The map data
pub data: Vec<u8>, pub data: Vec<u8>,
/// The map kind
pub kind: MapKind,
} }
/// A BTF-defined map, most likely from a `.maps` section. /// A BTF-defined map, most likely from a `.maps` section.
@ -298,6 +281,5 @@ pub struct BtfMap {
pub def: BtfMapDef, pub def: BtfMapDef,
pub(crate) section_index: usize, pub(crate) section_index: usize,
pub(crate) symbol_index: usize, pub(crate) symbol_index: usize,
pub(crate) kind: MapKind,
pub(crate) data: Vec<u8>, pub(crate) data: Vec<u8>,
} }

@ -15,7 +15,7 @@ use object::{
}; };
use crate::{ use crate::{
maps::{BtfMap, LegacyMap, Map, MapKind, MINIMUM_MAP_SIZE}, maps::{BtfMap, LegacyMap, Map, MINIMUM_MAP_SIZE},
relocation::*, relocation::*,
thiserror::{self, Error}, thiserror::{self, Error},
util::HashMap, util::HashMap,
@ -52,14 +52,13 @@ pub struct Object {
/// in [ProgramSection]s as keys. /// in [ProgramSection]s as keys.
pub programs: HashMap<String, Program>, pub programs: HashMap<String, Program>,
/// Functions /// Functions
pub functions: HashMap<u64, Function>, pub functions: HashMap<(usize, u64), Function>,
pub(crate) relocations: HashMap<SectionIndex, HashMap<u64, Relocation>>, pub(crate) relocations: HashMap<SectionIndex, HashMap<u64, Relocation>>,
pub(crate) symbols_by_index: HashMap<usize, Symbol>, pub(crate) symbol_table: HashMap<usize, Symbol>,
pub(crate) section_sizes: HashMap<String, u64>, pub(crate) section_sizes: HashMap<String, u64>,
// symbol_offset_by_name caches symbols that could be referenced from a // symbol_offset_by_name caches symbols that could be referenced from a
// BTF VAR type so the offsets can be fixed up // BTF VAR type so the offsets can be fixed up
pub(crate) symbol_offset_by_name: HashMap<String, u64>, pub(crate) symbol_offset_by_name: HashMap<String, u64>,
pub(crate) text_section_index: Option<usize>,
} }
/// An eBPF program /// An eBPF program
@ -522,7 +521,7 @@ impl Object {
is_definition: symbol.is_definition(), is_definition: symbol.is_definition(),
kind: symbol.kind(), kind: symbol.kind(),
}; };
bpf_obj.symbols_by_index.insert(symbol.index().0, sym); bpf_obj.symbol_table.insert(symbol.index().0, sym);
if symbol.is_global() || symbol.kind() == SymbolKind::Data { if symbol.is_global() || symbol.kind() == SymbolKind::Data {
bpf_obj.symbol_offset_by_name.insert(name, symbol.address()); bpf_obj.symbol_offset_by_name.insert(name, symbol.address());
@ -564,17 +563,16 @@ impl Object {
programs: HashMap::new(), programs: HashMap::new(),
functions: HashMap::new(), functions: HashMap::new(),
relocations: HashMap::new(), relocations: HashMap::new(),
symbols_by_index: HashMap::new(), symbol_table: HashMap::new(),
section_sizes: HashMap::new(), section_sizes: HashMap::new(),
symbol_offset_by_name: HashMap::new(), symbol_offset_by_name: HashMap::new(),
text_section_index: None,
} }
} }
/// Patches map data /// Patches map data
pub fn patch_map_data(&mut self, globals: HashMap<&str, &[u8]>) -> Result<(), ParseError> { pub fn patch_map_data(&mut self, globals: HashMap<&str, &[u8]>) -> Result<(), ParseError> {
let symbols: HashMap<String, &Symbol> = self let symbols: HashMap<String, &Symbol> = self
.symbols_by_index .symbol_table
.iter() .iter()
.filter(|(_, s)| s.name.is_some()) .filter(|(_, s)| s.name.is_some())
.map(|(_, s)| (s.name.as_ref().unwrap().clone(), s)) .map(|(_, s)| (s.name.as_ref().unwrap().clone(), s))
@ -668,12 +666,10 @@ impl Object {
}) })
} }
fn parse_text_section(&mut self, mut section: Section) -> Result<(), ParseError> { fn parse_text_section(&mut self, section: Section) -> Result<(), ParseError> {
self.text_section_index = Some(section.index.0);
let mut symbols_by_address = HashMap::new(); let mut symbols_by_address = HashMap::new();
for sym in self.symbols_by_index.values() { for sym in self.symbol_table.values() {
if sym.is_definition if sym.is_definition
&& sym.kind == SymbolKind::Text && sym.kind == SymbolKind::Text
&& sym.section_index == Some(section.index.0) && sym.section_index == Some(section.index.0)
@ -729,7 +725,7 @@ impl Object {
}; };
self.functions.insert( self.functions.insert(
sym.address, (section.index.0, sym.address),
Function { Function {
address, address,
name: sym.name.clone().unwrap(), name: sym.name.clone().unwrap(),
@ -753,7 +749,7 @@ impl Object {
section.index, section.index,
section section
.relocations .relocations
.drain(..) .into_iter()
.map(|rel| (rel.offset, rel)) .map(|rel| (rel.offset, rel))
.collect(), .collect(),
); );
@ -762,37 +758,6 @@ impl Object {
Ok(()) Ok(())
} }
fn parse_map_section(
&mut self,
section: &Section,
symbols: Vec<Symbol>,
) -> Result<(), ParseError> {
if symbols.is_empty() {
return Err(ParseError::NoSymbolsInMapSection {});
}
for (i, sym) in symbols.iter().enumerate() {
let start = sym.address as usize;
let end = start + sym.size as usize;
let data = &section.data[start..end];
let name = sym
.name
.as_ref()
.ok_or(ParseError::MapSymbolNameNotFound { i })?;
let def = parse_map_def(name, data)?;
self.maps.insert(
name.to_string(),
Map::Legacy(LegacyMap {
section_index: section.index.0,
symbol_index: sym.index,
def,
data: Vec::new(),
kind: MapKind::Other,
}),
);
}
Ok(())
}
fn parse_btf_maps( fn parse_btf_maps(
&mut self, &mut self,
section: &Section, section: &Section,
@ -825,7 +790,6 @@ impl Object {
def, def,
section_index: section.index.0, section_index: section.index.0,
symbol_index, symbol_index,
kind: MapKind::Other,
data: Vec::new(), data: Vec::new(),
}), }),
); );
@ -836,7 +800,7 @@ impl Object {
Ok(()) Ok(())
} }
fn parse_section(&mut self, mut section: Section) -> Result<(), ParseError> { fn parse_section(&mut self, section: Section) -> Result<(), ParseError> {
let mut parts = section.name.rsplitn(2, '/').collect::<Vec<_>>(); let mut parts = section.name.rsplitn(2, '/').collect::<Vec<_>>();
parts.reverse(); parts.reverse();
@ -851,16 +815,16 @@ impl Object {
self.section_sizes self.section_sizes
.insert(section.name.to_owned(), section.size); .insert(section.name.to_owned(), section.size);
match section.kind { match section.kind {
BpfSectionKind::Data => { BpfSectionKind::Data | BpfSectionKind::Rodata | BpfSectionKind::Bss => {
self.maps self.maps
.insert(section.name.to_string(), parse_map(&section, section.name)?); .insert(section.name.to_string(), parse_data_map_section(&section)?);
} }
BpfSectionKind::Text => self.parse_text_section(section)?, BpfSectionKind::Text => self.parse_text_section(section)?,
BpfSectionKind::Btf => self.parse_btf(&section)?, BpfSectionKind::Btf => self.parse_btf(&section)?,
BpfSectionKind::BtfExt => self.parse_btf_ext(&section)?, BpfSectionKind::BtfExt => self.parse_btf_ext(&section)?,
BpfSectionKind::BtfMaps => { BpfSectionKind::BtfMaps => {
let symbols: HashMap<String, Symbol> = self let symbols: HashMap<String, Symbol> = self
.symbols_by_index .symbol_table
.values() .values()
.filter(|s| { .filter(|s| {
if let Some(idx) = s.section_index { if let Some(idx) = s.section_index {
@ -875,19 +839,24 @@ impl Object {
self.parse_btf_maps(&section, symbols)? self.parse_btf_maps(&section, symbols)?
} }
BpfSectionKind::Maps => { BpfSectionKind::Maps => {
let symbols: Vec<Symbol> = self // take out self.maps so we can borrow the iterator below
.symbols_by_index // without cloning or collecting
.values() let mut maps = mem::take(&mut self.maps);
.filter(|s| {
if let Some(idx) = s.section_index { // extract the symbols for the .maps section, we'll need them
idx == section.index.0 // during parsing
} else { let symbols = self.symbol_table.values().filter(|s| {
false s.section_index
} .map(|idx| idx == section.index.0)
}) .unwrap_or(false)
.cloned() });
.collect();
self.parse_map_section(&section, symbols)? let res = parse_maps_section(&mut maps, &section, symbols);
// put the maps back
self.maps = maps;
res?
} }
BpfSectionKind::Program => { BpfSectionKind::Program => {
let program = self.parse_program(&section)?; let program = self.parse_program(&section)?;
@ -898,7 +867,7 @@ impl Object {
section.index, section.index,
section section
.relocations .relocations
.drain(..) .into_iter()
.map(|rel| (rel.offset, rel)) .map(|rel| (rel.offset, rel))
.collect(), .collect(),
); );
@ -911,6 +880,45 @@ impl Object {
} }
} }
// Parses multiple map definition contained in a single `maps` section (which is
// different from `.maps` which is used for BTF). We can tell where each map is
// based on the symbol table.
fn parse_maps_section<'a, I: Iterator<Item = &'a Symbol>>(
maps: &mut HashMap<String, Map>,
section: &Section,
symbols: I,
) -> Result<(), ParseError> {
let mut have_symbols = false;
// each symbol in the section is a separate map
for (i, sym) in symbols.enumerate() {
let start = sym.address as usize;
let end = start + sym.size as usize;
let data = &section.data[start..end];
let name = sym
.name
.as_ref()
.ok_or(ParseError::MapSymbolNameNotFound { i })?;
let def = parse_map_def(name, data)?;
maps.insert(
name.to_string(),
Map::Legacy(LegacyMap {
section_index: section.index.0,
section_kind: section.kind,
symbol_index: Some(sym.index),
def,
data: Vec::new(),
}),
);
have_symbols = true;
}
if !have_symbols {
return Err(ParseError::NoSymbolsForMapsSection);
}
Ok(())
}
/// Errors caught during parsing the object file /// Errors caught during parsing the object file
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[allow(missing_docs)] #[allow(missing_docs)]
@ -974,25 +982,40 @@ pub enum ParseError {
#[error("the map number {i} in the `maps` section doesn't have a symbol name")] #[error("the map number {i} in the `maps` section doesn't have a symbol name")]
MapSymbolNameNotFound { i: usize }, MapSymbolNameNotFound { i: usize },
#[error("no symbols found for the maps included in the maps section")] #[error("no symbols for `maps` section, can't parse maps")]
NoSymbolsInMapSection {}, NoSymbolsForMapsSection,
/// No BTF parsed for object /// No BTF parsed for object
#[error("no BTF parsed for object")] #[error("no BTF parsed for object")]
NoBTF, NoBTF,
} }
#[derive(Debug)] /// The kind of an ELF section.
enum BpfSectionKind { #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BpfSectionKind {
/// Undefined
Undefined, Undefined,
/// `maps`
Maps, Maps,
/// `.maps`
BtfMaps, BtfMaps,
/// A program section
Program, Program,
/// `.data`
Data, Data,
/// `.rodata`
Rodata,
/// `.bss`
Bss,
/// `.text`
Text, Text,
/// `.BTF`
Btf, Btf,
/// `.BTF.ext`
BtfExt, BtfExt,
/// `license`
License, License,
/// `version`
Version, Version,
} }
@ -1070,6 +1093,7 @@ impl<'data, 'file, 'a> TryFrom<&'a ObjSection<'data, 'file>> for Section<'a> {
_ => return Err(ParseError::UnsupportedRelocationTarget), _ => return Err(ParseError::UnsupportedRelocationTarget),
}, },
offset, offset,
size: r.size(),
}) })
}) })
.collect::<Result<Vec<_>, _>>()?, .collect::<Result<Vec<_>, _>>()?,
@ -1159,10 +1183,11 @@ impl From<KernelVersion> for u32 {
} }
} }
fn parse_map(section: &Section, name: &str) -> Result<Map, ParseError> { // Parsed '.bss' '.data' and '.rodata' sections. These sections are arrays of
let kind = MapKind::from(name); // bytes and are relocated based on their section index.
let (def, data) = match kind { fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> {
MapKind::Bss | MapKind::Data | MapKind::Rodata => { let (def, data) = match section.kind {
BpfSectionKind::Bss | BpfSectionKind::Data | BpfSectionKind::Rodata => {
let def = bpf_map_def { let def = bpf_map_def {
map_type: BPF_MAP_TYPE_ARRAY as u32, map_type: BPF_MAP_TYPE_ARRAY as u32,
key_size: mem::size_of::<u32>() as u32, key_size: mem::size_of::<u32>() as u32,
@ -1170,7 +1195,7 @@ fn parse_map(section: &Section, name: &str) -> Result<Map, ParseError> {
// .bss will always have data.len() == 0 // .bss will always have data.len() == 0
value_size: section.size as u32, value_size: section.size as u32,
max_entries: 1, max_entries: 1,
map_flags: if kind == MapKind::Rodata { map_flags: if section.kind == BpfSectionKind::Rodata {
BPF_F_RDONLY_PROG BPF_F_RDONLY_PROG
} else { } else {
0 0
@ -1179,14 +1204,15 @@ fn parse_map(section: &Section, name: &str) -> Result<Map, ParseError> {
}; };
(def, section.data.to_vec()) (def, section.data.to_vec())
} }
MapKind::Other => (parse_map_def(name, section.data)?, Vec::new()), _ => unreachable!(),
}; };
Ok(Map::Legacy(LegacyMap { Ok(Map::Legacy(LegacyMap {
section_index: section.index.0, section_index: section.index.0,
symbol_index: 0, section_kind: section.kind,
// Data maps don't require symbols to be relocated
symbol_index: None,
def, def,
data, data,
kind,
})) }))
} }
@ -1306,8 +1332,6 @@ pub fn parse_map_info(info: bpf_map_info, pinned: PinningType) -> Map {
section_index: 0, section_index: 0,
symbol_index: 0, symbol_index: 0,
data: Vec::new(), data: Vec::new(),
// We should never be loading the .bss or .data or .rodata FDs
kind: MapKind::Other,
}) })
} else { } else {
Map::Legacy(LegacyMap { Map::Legacy(LegacyMap {
@ -1321,10 +1345,9 @@ pub fn parse_map_info(info: bpf_map_info, pinned: PinningType) -> Map {
id: info.id, id: info.id,
}, },
section_index: 0, section_index: 0,
symbol_index: 0, symbol_index: None,
section_kind: BpfSectionKind::Undefined,
data: Vec::new(), data: Vec::new(),
// We should never be loading the .bss or .data or .rodata FDs
kind: MapKind::Other,
}) })
} }
} }
@ -1373,8 +1396,8 @@ mod tests {
} }
fn fake_sym(obj: &mut Object, section_index: usize, address: u64, name: &str, size: u64) { fn fake_sym(obj: &mut Object, section_index: usize, address: u64, name: &str, size: u64) {
let idx = obj.symbols_by_index.len(); let idx = obj.symbol_table.len();
obj.symbols_by_index.insert( obj.symbol_table.insert(
idx + 1, idx + 1,
Symbol { Symbol {
index: idx + 1, index: idx + 1,
@ -1510,65 +1533,21 @@ mod tests {
assert_eq!(parse_map_def("foo", &buf).unwrap(), def); assert_eq!(parse_map_def("foo", &buf).unwrap(), def);
} }
#[test]
fn test_parse_map_error() {
assert!(matches!(
parse_map(&fake_section(BpfSectionKind::Maps, "maps/foo", &[]), "foo",),
Err(ParseError::InvalidMapDefinition { .. })
));
}
#[test]
fn test_parse_map() {
assert!(matches!(
parse_map(
&fake_section(
BpfSectionKind::Maps,
"maps/foo",
bytes_of(&bpf_map_def {
map_type: 1,
key_size: 2,
value_size: 3,
max_entries: 4,
map_flags: 5,
id: 0,
pinning: PinningType::None,
})
),
"foo"
),
Ok(Map::Legacy(LegacyMap{
section_index: 0,
def: bpf_map_def {
map_type: 1,
key_size: 2,
value_size: 3,
max_entries: 4,
map_flags: 5,
id: 0,
pinning: PinningType::None,
},
data,
..
})) if data.is_empty()
))
}
#[test] #[test]
fn test_parse_map_data() { fn test_parse_map_data() {
let map_data = b"map data"; let map_data = b"map data";
assert!(matches!( assert!(matches!(
parse_map( parse_data_map_section(
&fake_section( &fake_section(
BpfSectionKind::Data, BpfSectionKind::Data,
".bss", ".bss",
map_data, map_data,
), ),
".bss"
), ),
Ok(Map::Legacy(LegacyMap { Ok(Map::Legacy(LegacyMap {
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Data,
symbol_index: None,
def: bpf_map_def { def: bpf_map_def {
map_type: _map_type, map_type: _map_type,
key_size: 4, key_size: 4,
@ -1579,8 +1558,7 @@ mod tests {
pinning: PinningType::None, pinning: PinningType::None,
}, },
data, data,
kind })) if data == map_data && value_size == map_data.len() as u32
})) if data == map_data && value_size == map_data.len() as u32 && kind == MapKind::Bss
)) ))
} }
@ -2227,12 +2205,12 @@ mod tests {
pinning: PinningType::None, pinning: PinningType::None,
}, },
section_index: 1, section_index: 1,
symbol_index: 1, section_kind: BpfSectionKind::Rodata,
symbol_index: Some(1),
data: vec![0, 0, 0], data: vec![0, 0, 0],
kind: MapKind::Rodata,
}), }),
); );
obj.symbols_by_index.insert( obj.symbol_table.insert(
1, 1,
Symbol { Symbol {
index: 1, index: 1,

@ -1,6 +1,7 @@
//! Program relocation handling. //! Program relocation handling.
use core::mem; use core::mem;
use std::collections::HashSet;
use alloc::{borrow::ToOwned, string::String}; use alloc::{borrow::ToOwned, string::String};
use log::debug; use log::debug;
@ -15,6 +16,7 @@ use crate::{
obj::{Function, Object, Program}, obj::{Function, Object, Program},
thiserror::{self, Error}, thiserror::{self, Error},
util::HashMap, util::HashMap,
BpfSectionKind,
}; };
pub(crate) const INS_SIZE: usize = mem::size_of::<bpf_insn>(); pub(crate) const INS_SIZE: usize = mem::size_of::<bpf_insn>();
@ -84,6 +86,7 @@ pub enum RelocationError {
pub(crate) struct Relocation { pub(crate) struct Relocation {
// byte offset of the instruction to be relocated // byte offset of the instruction to be relocated
pub(crate) offset: u64, pub(crate) offset: u64,
pub(crate) size: u8,
// index of the symbol to relocate to // index of the symbol to relocate to
pub(crate) symbol_index: usize, pub(crate) symbol_index: usize,
} }
@ -104,12 +107,15 @@ impl Object {
pub fn relocate_maps<'a, I: Iterator<Item = (&'a str, Option<i32>, &'a Map)>>( pub fn relocate_maps<'a, I: Iterator<Item = (&'a str, Option<i32>, &'a Map)>>(
&mut self, &mut self,
maps: I, maps: I,
text_sections: &HashSet<usize>,
) -> Result<(), BpfRelocationError> { ) -> Result<(), BpfRelocationError> {
let mut maps_by_section = HashMap::new(); let mut maps_by_section = HashMap::new();
let mut maps_by_symbol = HashMap::new(); let mut maps_by_symbol = HashMap::new();
for (name, fd, map) in maps { for (name, fd, map) in maps {
maps_by_section.insert(map.section_index(), (name, fd, map)); maps_by_section.insert(map.section_index(), (name, fd, map));
maps_by_symbol.insert(map.symbol_index(), (name, fd, map)); if let Some(index) = map.symbol_index() {
maps_by_symbol.insert(index, (name, fd, map));
}
} }
let functions = self let functions = self
@ -125,8 +131,8 @@ impl Object {
relocations.values(), relocations.values(),
&maps_by_section, &maps_by_section,
&maps_by_symbol, &maps_by_symbol,
&self.symbols_by_index, &self.symbol_table,
self.text_section_index, text_sections,
) )
.map_err(|error| BpfRelocationError { .map_err(|error| BpfRelocationError {
function: function.name.clone(), function: function.name.clone(),
@ -139,13 +145,16 @@ impl Object {
} }
/// Relocates function calls /// Relocates function calls
pub fn relocate_calls(&mut self) -> Result<(), BpfRelocationError> { pub fn relocate_calls(
&mut self,
text_sections: &HashSet<usize>,
) -> Result<(), BpfRelocationError> {
for (name, program) in self.programs.iter_mut() { for (name, program) in self.programs.iter_mut() {
let linker = FunctionLinker::new( let linker = FunctionLinker::new(
self.text_section_index,
&self.functions, &self.functions,
&self.relocations, &self.relocations,
&self.symbols_by_index, &self.symbol_table,
text_sections,
); );
linker.link(program).map_err(|error| BpfRelocationError { linker.link(program).map_err(|error| BpfRelocationError {
function: name.to_owned(), function: name.to_owned(),
@ -163,7 +172,7 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
maps_by_section: &HashMap<usize, (&str, Option<i32>, &Map)>, maps_by_section: &HashMap<usize, (&str, Option<i32>, &Map)>,
maps_by_symbol: &HashMap<usize, (&str, Option<i32>, &Map)>, maps_by_symbol: &HashMap<usize, (&str, Option<i32>, &Map)>,
symbol_table: &HashMap<usize, Symbol>, symbol_table: &HashMap<usize, Symbol>,
text_section_index: Option<usize>, text_sections: &HashSet<usize>,
) -> Result<(), RelocationError> { ) -> Result<(), RelocationError> {
let section_offset = fun.section_offset; let section_offset = fun.section_offset;
let instructions = &mut fun.instructions; let instructions = &mut fun.instructions;
@ -193,34 +202,54 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
index: rel.symbol_index, index: rel.symbol_index,
})?; })?;
let section_index = match sym.section_index { let Some(section_index) = sym.section_index else {
Some(index) => index,
// this is not a map relocation // this is not a map relocation
None => continue, continue;
}; };
// calls and relocation to .text symbols are handled in a separate step // calls and relocation to .text symbols are handled in a separate step
if insn_is_call(&instructions[ins_index]) || sym.section_index == text_section_index { if insn_is_call(&instructions[ins_index]) || text_sections.contains(&section_index) {
continue; continue;
} }
let (name, fd, map) = if maps_by_symbol.contains_key(&rel.symbol_index) { let (name, fd, map) = if let Some(m) = maps_by_symbol.get(&rel.symbol_index) {
maps_by_symbol let map = &m.2;
.get(&rel.symbol_index) debug!(
.ok_or(RelocationError::SectionNotFound { "relocating map by symbol index {:?}, kind {:?} at insn {ins_index} in section {}",
symbol_index: rel.symbol_index, map.symbol_index(),
symbol_name: sym.name.clone(), map.section_kind(),
section_index, fun.section_index.0
})? );
debug_assert_eq!(map.symbol_index().unwrap(), rel.symbol_index);
m
} else { } else {
maps_by_section let Some(m) = maps_by_section.get(&section_index) else {
.get(&section_index) debug!(
.ok_or(RelocationError::SectionNotFound { "failed relocating map by section index {}",
section_index
);
return Err(RelocationError::SectionNotFound {
symbol_index: rel.symbol_index, symbol_index: rel.symbol_index,
symbol_name: sym.name.clone(), symbol_name: sym.name.clone(),
section_index, section_index,
})? });
}; };
let map = &m.2;
debug!(
"relocating map by section index {}, kind {:?} at insn {ins_index} in section {}",
map.section_index(),
map.section_kind(),
fun.section_index.0,
);
debug_assert_eq!(map.symbol_index(), None);
debug_assert!(matches!(
map.section_kind(),
BpfSectionKind::Bss | BpfSectionKind::Data | BpfSectionKind::Rodata
),);
m
};
debug_assert_eq!(map.section_index(), section_index);
let map_fd = fd.ok_or_else(|| RelocationError::MapNotCreated { let map_fd = fd.ok_or_else(|| RelocationError::MapNotCreated {
name: (*name).into(), name: (*name).into(),
@ -240,26 +269,26 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
} }
struct FunctionLinker<'a> { struct FunctionLinker<'a> {
text_section_index: Option<usize>, functions: &'a HashMap<(usize, u64), Function>,
functions: &'a HashMap<u64, Function>,
linked_functions: HashMap<u64, usize>, linked_functions: HashMap<u64, usize>,
relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>, relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>,
symbol_table: &'a HashMap<usize, Symbol>, symbol_table: &'a HashMap<usize, Symbol>,
text_sections: &'a HashSet<usize>,
} }
impl<'a> FunctionLinker<'a> { impl<'a> FunctionLinker<'a> {
fn new( fn new(
text_section_index: Option<usize>, functions: &'a HashMap<(usize, u64), Function>,
functions: &'a HashMap<u64, Function>,
relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>, relocations: &'a HashMap<SectionIndex, HashMap<u64, Relocation>>,
symbol_table: &'a HashMap<usize, Symbol>, symbol_table: &'a HashMap<usize, Symbol>,
text_sections: &'a HashSet<usize>,
) -> FunctionLinker<'a> { ) -> FunctionLinker<'a> {
FunctionLinker { FunctionLinker {
text_section_index,
functions, functions,
linked_functions: HashMap::new(), linked_functions: HashMap::new(),
relocations, relocations,
symbol_table, symbol_table,
text_sections,
} }
} }
@ -289,6 +318,10 @@ impl<'a> FunctionLinker<'a> {
// at `start_ins`. We'll use `start_ins` to do pc-relative calls. // at `start_ins`. We'll use `start_ins` to do pc-relative calls.
let start_ins = program.instructions.len(); let start_ins = program.instructions.len();
program.instructions.extend(&fun.instructions); program.instructions.extend(&fun.instructions);
debug!(
"linked function `{}` at instruction {}",
fun.name, start_ins
);
// link func and line info into the main program // link func and line info into the main program
// the offset needs to be adjusted // the offset needs to be adjusted
@ -305,101 +338,110 @@ impl<'a> FunctionLinker<'a> {
fn relocate(&mut self, program: &mut Function, fun: &Function) -> Result<(), RelocationError> { fn relocate(&mut self, program: &mut Function, fun: &Function) -> Result<(), RelocationError> {
let relocations = self.relocations.get(&fun.section_index); let relocations = self.relocations.get(&fun.section_index);
debug!("relocating program {} function {}", program.name, fun.name);
let n_instructions = fun.instructions.len(); let n_instructions = fun.instructions.len();
let start_ins = program.instructions.len() - n_instructions; let start_ins = program.instructions.len() - n_instructions;
debug!(
"relocating program `{}` function `{}` size {}",
program.name, fun.name, n_instructions
);
// process all the instructions. We can't only loop over relocations since we need to // process all the instructions. We can't only loop over relocations since we need to
// patch pc-relative calls too. // patch pc-relative calls too.
for ins_index in start_ins..start_ins + n_instructions { for ins_index in start_ins..start_ins + n_instructions {
let ins = program.instructions[ins_index]; let ins = program.instructions[ins_index];
let is_call = insn_is_call(&ins); let is_call = insn_is_call(&ins);
// only resolve relocations for calls or for instructions that let rel = relocations
// reference symbols in the .text section (eg let callback = .and_then(|relocations| {
// &some_fun) relocations
let rel = if let Some(relocations) = relocations { .get(&((fun.section_offset + (ins_index - start_ins) * INS_SIZE) as u64))
self.text_relocation_info(
relocations,
(fun.section_offset + (ins_index - start_ins) * INS_SIZE) as u64,
)?
// if not a call and not a .text reference, ignore the
// relocation (see relocate_maps())
.and_then(|(_, sym)| {
if is_call {
return Some(sym.address);
}
match sym.kind {
SymbolKind::Text => Some(sym.address),
SymbolKind::Section if sym.section_index == self.text_section_index => {
Some(sym.address + ins.imm as u64)
}
_ => None,
}
}) })
} else { .and_then(|rel| {
None // get the symbol for the relocation
}; self.symbol_table
.get(&rel.symbol_index)
.map(|sym| (rel, sym))
})
.filter(|(_rel, sym)| {
// only consider text relocations, data relocations are
// relocated in relocate_maps()
sym.kind == SymbolKind::Text
|| sym
.section_index
.map(|section_index| self.text_sections.contains(&section_index))
.unwrap_or(false)
});
// some_fun() or let x = &some_fun trigger linking, everything else // not a call and not a text relocation, we don't need to do anything
// can be ignored here
if !is_call && rel.is_none() { if !is_call && rel.is_none() {
continue; continue;
} }
let callee_address = if let Some(address) = rel { let (callee_section_index, callee_address) = if let Some((rel, sym)) = rel {
// We have a relocation entry for the instruction at `ins_index`, the address of let address = match sym.kind {
// the callee is the address of the relocation's target symbol. SymbolKind::Text => sym.address,
address // R_BPF_64_32 this is a call
SymbolKind::Section if rel.size == 32 => {
sym.address + (ins.imm + 1) as u64 * INS_SIZE as u64
}
// R_BPF_64_64 this is a ld_imm64 text relocation
SymbolKind::Section if rel.size == 64 => sym.address + ins.imm as u64,
_ => todo!(), // FIXME: return an error here,
};
(sym.section_index.unwrap(), address)
} else { } else {
// The caller and the callee are in the same ELF section and this is a pc-relative // The caller and the callee are in the same ELF section and this is a pc-relative
// call. Resolve the pc-relative imm to an absolute address. // call. Resolve the pc-relative imm to an absolute address.
let ins_size = INS_SIZE as i64; let ins_size = INS_SIZE as i64;
(
fun.section_index.0,
(fun.section_offset as i64 (fun.section_offset as i64
+ ((ins_index - start_ins) as i64) * ins_size + ((ins_index - start_ins) as i64) * ins_size
+ (ins.imm + 1) as i64 * ins_size) as u64 + (ins.imm + 1) as i64 * ins_size) as u64,
)
}; };
debug!( debug!(
"relocating {} to callee address {} ({})", "relocating {} to callee address {:#x} in section {} ({}) at instruction {ins_index}",
if is_call { "call" } else { "reference" }, if is_call { "call" } else { "reference" },
callee_address, callee_address,
callee_section_index,
if rel.is_some() { if rel.is_some() {
"relocation" "relocation"
} else { } else {
"relative" "pc-relative"
}, },
); );
// lookup and link the callee if it hasn't been linked already. `callee_ins_index` will // lookup and link the callee if it hasn't been linked already. `callee_ins_index` will
// contain the instruction index of the callee inside the program. // contain the instruction index of the callee inside the program.
let callee = let callee = self
self.functions .functions
.get(&callee_address) .get(&(callee_section_index, callee_address))
.ok_or(RelocationError::UnknownFunction { .ok_or(RelocationError::UnknownFunction {
address: callee_address, address: callee_address,
caller_name: fun.name.clone(), caller_name: fun.name.clone(),
})?; })?;
debug!("callee is {}", callee.name); debug!("callee is `{}`", callee.name);
let callee_ins_index = self.link_function(program, callee)?; let callee_ins_index = self.link_function(program, callee)? as i32;
let mut ins = &mut program.instructions[ins_index]; let mut ins = &mut program.instructions[ins_index];
ins.imm = if callee_ins_index < ins_index { let ins_index = ins_index as i32;
-((ins_index - callee_ins_index + 1) as i32) ins.imm = callee_ins_index - ins_index - 1;
} else { debug!(
(callee_ins_index - ins_index - 1) as i32 "callee `{}` is at ins {callee_ins_index}, {} from current instruction {ins_index}",
}; callee.name, ins.imm
);
if !is_call { if !is_call {
ins.set_src_reg(BPF_PSEUDO_FUNC as u8); ins.set_src_reg(BPF_PSEUDO_FUNC as u8);
} }
} }
debug!( debug!(
"finished relocating program {} function {}", "finished relocating program `{}` function `{}`",
program.name, fun.name program.name, fun.name
); );
@ -438,25 +480,6 @@ impl<'a> FunctionLinker<'a> {
} }
Ok(()) Ok(())
} }
fn text_relocation_info(
&self,
relocations: &HashMap<u64, Relocation>,
offset: u64,
) -> Result<Option<(Relocation, Symbol)>, RelocationError> {
if let Some(rel) = relocations.get(&offset) {
let sym =
self.symbol_table
.get(&rel.symbol_index)
.ok_or(RelocationError::UnknownSymbol {
index: rel.symbol_index,
})?;
Ok(Some((*rel, sym.clone())))
} else {
Ok(None)
}
}
} }
fn insn_is_call(ins: &bpf_insn) -> bool { fn insn_is_call(ins: &bpf_insn) -> bool {
@ -476,7 +499,10 @@ fn insn_is_call(ins: &bpf_insn) -> bool {
mod test { mod test {
use alloc::{string::ToString, vec, vec::Vec}; use alloc::{string::ToString, vec, vec::Vec};
use crate::maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, MapKind}; use crate::{
maps::{BtfMap, LegacyMap, Map},
BpfSectionKind,
};
use super::*; use super::*;
@ -498,25 +524,20 @@ mod test {
fn fake_legacy_map(symbol_index: usize) -> Map { fn fake_legacy_map(symbol_index: usize) -> Map {
Map::Legacy(LegacyMap { Map::Legacy(LegacyMap {
def: bpf_map_def { def: Default::default(),
..Default::default()
},
section_index: 0, section_index: 0,
symbol_index, section_kind: BpfSectionKind::Undefined,
symbol_index: Some(symbol_index),
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}) })
} }
fn fake_btf_map(symbol_index: usize) -> Map { fn fake_btf_map(symbol_index: usize) -> Map {
Map::Btf(BtfMap { Map::Btf(BtfMap {
def: BtfMapDef { def: Default::default(),
..Default::default()
},
section_index: 0, section_index: 0,
symbol_index, symbol_index,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}) })
} }
@ -549,6 +570,7 @@ mod test {
let relocations = vec![Relocation { let relocations = vec![Relocation {
offset: 0x0, offset: 0x0,
symbol_index: 1, symbol_index: 1,
size: 64,
}]; }];
let maps_by_section = HashMap::new(); let maps_by_section = HashMap::new();
@ -561,7 +583,7 @@ mod test {
&maps_by_section, &maps_by_section,
&maps_by_symbol, &maps_by_symbol,
&symbol_table, &symbol_table,
None, &HashSet::new(),
) )
.unwrap(); .unwrap();
@ -596,10 +618,12 @@ mod test {
Relocation { Relocation {
offset: 0x0, offset: 0x0,
symbol_index: 1, symbol_index: 1,
size: 64,
}, },
Relocation { Relocation {
offset: mem::size_of::<bpf_insn>() as u64, offset: mem::size_of::<bpf_insn>() as u64,
symbol_index: 2, symbol_index: 2,
size: 64,
}, },
]; ];
let maps_by_section = HashMap::new(); let maps_by_section = HashMap::new();
@ -617,7 +641,7 @@ mod test {
&maps_by_section, &maps_by_section,
&maps_by_symbol, &maps_by_symbol,
&symbol_table, &symbol_table,
None, &HashSet::new(),
) )
.unwrap(); .unwrap();
@ -646,6 +670,7 @@ mod test {
let relocations = vec![Relocation { let relocations = vec![Relocation {
offset: 0x0, offset: 0x0,
symbol_index: 1, symbol_index: 1,
size: 64,
}]; }];
let maps_by_section = HashMap::new(); let maps_by_section = HashMap::new();
@ -658,7 +683,7 @@ mod test {
&maps_by_section, &maps_by_section,
&maps_by_symbol, &maps_by_symbol,
&symbol_table, &symbol_table,
None, &HashSet::new(),
) )
.unwrap(); .unwrap();
@ -693,10 +718,12 @@ mod test {
Relocation { Relocation {
offset: 0x0, offset: 0x0,
symbol_index: 1, symbol_index: 1,
size: 64,
}, },
Relocation { Relocation {
offset: mem::size_of::<bpf_insn>() as u64, offset: mem::size_of::<bpf_insn>() as u64,
symbol_index: 2, symbol_index: 2,
size: 64,
}, },
]; ];
let maps_by_section = HashMap::new(); let maps_by_section = HashMap::new();
@ -714,7 +741,7 @@ mod test {
&maps_by_section, &maps_by_section,
&maps_by_symbol, &maps_by_symbol,
&symbol_table, &symbol_table,
None, &HashSet::new(),
) )
.unwrap(); .unwrap();

@ -11,6 +11,7 @@ use aya_obj::{
btf::{BtfFeatures, BtfRelocationError}, btf::{BtfFeatures, BtfRelocationError},
generated::BPF_F_XDP_HAS_FRAGS, generated::BPF_F_XDP_HAS_FRAGS,
relocation::BpfRelocationError, relocation::BpfRelocationError,
BpfSectionKind,
}; };
use log::debug; use log::debug;
use thiserror::Error; use thiserror::Error;
@ -23,7 +24,6 @@ use crate::{
maps::{Map, MapData, MapError}, maps::{Map, MapData, MapError},
obj::{ obj::{
btf::{Btf, BtfError}, btf::{Btf, BtfError},
maps::MapKind,
Object, ParseError, ProgramSection, Object, ParseError, ProgramSection,
}, },
programs::{ programs::{
@ -38,7 +38,7 @@ use crate::{
is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported, is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_prog_name_supported, retry_with_verifier_logs, is_prog_name_supported, retry_with_verifier_logs,
}, },
util::{bytes_of, possible_cpus, VerifierLog, POSSIBLE_CPUS}, util::{bytes_of, bytes_of_slice, possible_cpus, VerifierLog, POSSIBLE_CPUS},
}; };
pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
@ -210,15 +210,17 @@ impl<'a> BpfLoader<'a> {
self self
} }
/// Sets the value of a global variable /// Sets the value of a global variable.
///
/// From Rust eBPF, a global variable can be defined as follows:
/// ///
/// From Rust eBPF, a global variable would be constructed as follows:
/// ```no_run /// ```no_run
/// #[no_mangle] /// #[no_mangle]
/// static VERSION: i32 = 0; /// static VERSION: i32 = 0;
/// ``` /// ```
/// Then it would be accessed with `core::ptr::read_volatile` inside ///
/// functions: /// Then it can be accessed using `core::ptr::read_volatile`:
///
/// ```no_run /// ```no_run
/// # #[no_mangle] /// # #[no_mangle]
/// # static VERSION: i32 = 0; /// # static VERSION: i32 = 0;
@ -226,10 +228,12 @@ impl<'a> BpfLoader<'a> {
/// let version = core::ptr::read_volatile(&VERSION); /// let version = core::ptr::read_volatile(&VERSION);
/// # } /// # }
/// ``` /// ```
/// If using a struct, ensure that it is `#[repr(C)]` to ensure the size will
/// match that of the corresponding ELF symbol.
/// ///
/// From C eBPF, you would annotate a variable as `volatile const` /// The type of a global variable must be `Pod` (plain old data), for instance `u8`, `u32` and
/// all other primitive types. You may use custom types as well, but you must ensure that those
/// types are `#[repr(C)]` and only contain other `Pod` types.
///
/// From C eBPF, you would annotate a global variable as `volatile const`.
/// ///
/// # Example /// # Example
/// ///
@ -238,14 +242,17 @@ impl<'a> BpfLoader<'a> {
/// ///
/// let bpf = BpfLoader::new() /// let bpf = BpfLoader::new()
/// .set_global("VERSION", &2) /// .set_global("VERSION", &2)
/// .set_global("PIDS", &[1234u16, 5678])
/// .load_file("file.o")?; /// .load_file("file.o")?;
/// # Ok::<(), aya::BpfError>(()) /// # Ok::<(), aya::BpfError>(())
/// ``` /// ```
/// ///
pub fn set_global<V: Pod>(&mut self, name: &'a str, value: &'a V) -> &mut BpfLoader<'a> { pub fn set_global<T: Into<GlobalData<'a>>>(
// Safety: value is POD &mut self,
let data = unsafe { bytes_of(value) }; name: &'a str,
self.globals.insert(name, data); value: T,
) -> &mut BpfLoader<'a> {
self.globals.insert(name, value.into().bytes);
self self
} }
@ -408,14 +415,14 @@ impl<'a> BpfLoader<'a> {
} }
PinningType::None => map.create(&name)?, PinningType::None => map.create(&name)?,
}; };
if !map.obj.data().is_empty() && map.obj.kind() != MapKind::Bss { if !map.obj.data().is_empty() && map.obj.section_kind() != BpfSectionKind::Bss {
bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data_mut().as_mut_ptr(), 0) bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data_mut().as_mut_ptr(), 0)
.map_err(|(_, io_error)| MapError::SyscallError { .map_err(|(_, io_error)| MapError::SyscallError {
call: "bpf_map_update_elem".to_owned(), call: "bpf_map_update_elem".to_owned(),
io_error, io_error,
})?; })?;
} }
if map.obj.kind() == MapKind::Rodata { if map.obj.section_kind() == BpfSectionKind::Rodata {
bpf_map_freeze(fd).map_err(|(_, io_error)| MapError::SyscallError { bpf_map_freeze(fd).map_err(|(_, io_error)| MapError::SyscallError {
call: "bpf_map_freeze".to_owned(), call: "bpf_map_freeze".to_owned(),
io_error, io_error,
@ -424,11 +431,18 @@ impl<'a> BpfLoader<'a> {
maps.insert(name, map); maps.insert(name, map);
} }
let text_sections = obj
.functions
.keys()
.map(|(section_index, _)| *section_index)
.collect();
obj.relocate_maps( obj.relocate_maps(
maps.iter() maps.iter()
.map(|(s, data)| (s.as_str(), data.fd, &data.obj)), .map(|(s, data)| (s.as_str(), data.fd, &data.obj)),
&text_sections,
)?; )?;
obj.relocate_calls()?; obj.relocate_calls(&text_sections)?;
let programs = obj let programs = obj
.programs .programs
@ -897,3 +911,28 @@ fn load_btf(raw_btf: Vec<u8>) -> Result<RawFd, BtfError> {
} }
} }
} }
/// Global data that can be exported to eBPF programs before they are loaded.
///
/// Valid global data includes `Pod` types and slices of `Pod` types. See also
/// [BpfLoader::set_global].
pub struct GlobalData<'a> {
bytes: &'a [u8],
}
impl<'a, T: Pod> From<&'a [T]> for GlobalData<'a> {
fn from(s: &'a [T]) -> Self {
GlobalData {
bytes: bytes_of_slice(s),
}
}
}
impl<'a, T: Pod> From<&'a T> for GlobalData<'a> {
fn from(v: &'a T) -> Self {
GlobalData {
// Safety: v is Pod
bytes: unsafe { bytes_of(v) },
}
}
}

@ -84,10 +84,7 @@ mod tests {
bpf_map_type::{BPF_MAP_TYPE_BLOOM_FILTER, BPF_MAP_TYPE_PERF_EVENT_ARRAY}, bpf_map_type::{BPF_MAP_TYPE_BLOOM_FILTER, BPF_MAP_TYPE_PERF_EVENT_ARRAY},
}, },
maps::{Map, MapData}, maps::{Map, MapData},
obj::{ obj::{self, maps::LegacyMap, BpfSectionKind},
self,
maps::{LegacyMap, MapKind},
},
sys::{override_syscall, SysResult, Syscall}, sys::{override_syscall, SysResult, Syscall},
}; };
use libc::{EFAULT, ENOENT}; use libc::{EFAULT, ENOENT};
@ -103,9 +100,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: None,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}) })
} }
@ -142,9 +139,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: None,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}), }),
fd: None, fd: None,
pinned: false, pinned: false,

@ -117,10 +117,7 @@ mod tests {
bpf_map_type::{BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH}, bpf_map_type::{BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH},
}, },
maps::{Map, MapData}, maps::{Map, MapData},
obj::{ obj::{self, maps::LegacyMap, BpfSectionKind},
self,
maps::{LegacyMap, MapKind},
},
sys::{override_syscall, SysResult, Syscall}, sys::{override_syscall, SysResult, Syscall},
}; };
@ -136,9 +133,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
section_kind: BpfSectionKind::Maps,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other, symbol_index: None,
symbol_index: 0,
}) })
} }
@ -267,9 +264,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: None,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}), }),
fd: Some(42), fd: Some(42),
pinned: false, pinned: false,

@ -247,10 +247,7 @@ mod tests {
bpf_map_type::{BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_PERF_EVENT_ARRAY}, bpf_map_type::{BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_PERF_EVENT_ARRAY},
}, },
maps::{Map, MapData}, maps::{Map, MapData},
obj::{ obj::{self, maps::LegacyMap, BpfSectionKind},
self,
maps::{LegacyMap, MapKind},
},
sys::{override_syscall, SysResult, Syscall}, sys::{override_syscall, SysResult, Syscall},
}; };
use libc::{EFAULT, ENOENT}; use libc::{EFAULT, ENOENT};
@ -266,9 +263,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: None,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}) })
} }
@ -322,9 +319,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: None,
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}), }),
fd: None, fd: None,
btf_fd: None, btf_fd: None,

@ -845,7 +845,7 @@ mod tests {
bpf_map_def, bpf_map_def,
generated::{bpf_cmd, bpf_map_type::BPF_MAP_TYPE_HASH}, generated::{bpf_cmd, bpf_map_type::BPF_MAP_TYPE_HASH},
maps::MapData, maps::MapData,
obj::maps::{LegacyMap, MapKind}, obj::{maps::LegacyMap, BpfSectionKind},
sys::{override_syscall, Syscall}, sys::{override_syscall, Syscall},
}; };
@ -861,9 +861,9 @@ mod tests {
..Default::default() ..Default::default()
}, },
section_index: 0, section_index: 0,
symbol_index: 0, section_kind: BpfSectionKind::Maps,
symbol_index: Some(0),
data: Vec::new(), data: Vec::new(),
kind: MapKind::Other,
}) })
} }

@ -33,7 +33,13 @@ pub(crate) fn find_tracefs_path() -> Result<&'static Path, ProgramError> {
]; ];
for mount in known_mounts { for mount in known_mounts {
if mount.exists() { // Check that the mount point exists and is not empty
// Documented here: (https://www.kernel.org/doc/Documentation/trace/ftrace.txt)
// In some cases, tracefs will only mount at /sys/kernel/debug/tracing
// but, the kernel will still create the directory /sys/kernel/tracing.
// The user may be expected to manually mount the directory in order for it to
// exist in /sys/kernel/tracing according to the documentation.
if mount.exists() && mount.read_dir().ok()?.next().is_some() {
return Some(mount); return Some(mount);
} }
} }

@ -8,7 +8,10 @@ use std::{
str::FromStr, str::FromStr,
}; };
use crate::generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}; use crate::{
generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK},
Pod,
};
use libc::{if_nametoindex, sysconf, _SC_PAGESIZE}; use libc::{if_nametoindex, sysconf, _SC_PAGESIZE};
@ -150,11 +153,20 @@ pub(crate) fn page_size() -> usize {
} }
// bytes_of converts a <T> to a byte slice // bytes_of converts a <T> to a byte slice
pub(crate) unsafe fn bytes_of<T>(val: &T) -> &[u8] { pub(crate) unsafe fn bytes_of<T: Pod>(val: &T) -> &[u8] {
let size = mem::size_of::<T>(); let size = mem::size_of::<T>();
slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size)
} }
pub(crate) fn bytes_of_slice<T: Pod>(val: &[T]) -> &[u8] {
let size = val.len().wrapping_mul(mem::size_of::<T>());
// Safety:
// Any alignment is allowed.
// The size is determined in this function.
// The Pod trait ensures the type is valid to cast to bytes.
unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) }
}
const MIN_LOG_BUF_SIZE: usize = 1024 * 10; const MIN_LOG_BUF_SIZE: usize = 1024 * 10;
const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize; const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;

@ -22,3 +22,7 @@ path = "src/pass.rs"
[[bin]] [[bin]]
name = "test" name = "test"
path = "src/test.rs" path = "src/test.rs"
[[bin]]
name = "relocations"
path = "src/relocations.rs"

@ -0,0 +1,28 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 2);
} RESULTS SEC(".maps");
static __u64
inc_cb(void *map, __u32 *key, void *val,
void *data)
{
__u64 *value = val;
*value += 1;
return 0;
}
SEC("uprobe/test_text_64_64_reloc")
int test_text_64_64_reloc(struct pt_regs *ctx)
{
bpf_for_each_map_elem(&RESULTS, inc_cb, NULL, 0);
return 0;
}

@ -0,0 +1,45 @@
#![no_std]
#![no_main]
use core::hint;
use aya_bpf::{
macros::{map, uprobe},
maps::Array,
programs::ProbeContext,
};
#[map]
static mut RESULTS: Array<u64> = Array::with_max_entries(3, 0);
#[uprobe]
pub fn test_64_32_call_relocs(_ctx: ProbeContext) {
// this will link set_result and do a forward call
set_result(0, hint::black_box(1));
// set_result is already linked, this will just do the forward call
set_result(1, hint::black_box(2));
// this will link set_result_backward after set_result. Then will do a
// backward call to set_result.
set_result_backward(2, hint::black_box(3));
}
#[inline(never)]
fn set_result(index: u32, value: u64) {
unsafe {
if let Some(v) = RESULTS.get_ptr_mut(index) {
*v = value;
}
}
}
#[inline(never)]
fn set_result_backward(index: u32, value: u64) {
set_result(index, value);
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}

@ -10,7 +10,7 @@ pub fn integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let expanded = quote! { let expanded = quote! {
#item #item
inventory::submit!(IntegrationTest { inventory::submit!(crate::IntegrationTest {
name: concat!(module_path!(), "::", #name_str), name: concat!(module_path!(), "::", #name_str),
test_fn: #name, test_fn: #name,
}); });

@ -0,0 +1,313 @@
use anyhow::{Context, Result};
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
use tempfile::TempDir;
use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness};
use super::integration_test;
// In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
// special meaning, they just have "nice" bit patterns that can be helpful while debugging.
#[integration_test]
fn relocate_field() {
let test = RelocationTest {
local_definition: r#"
struct foo {
__u8 a;
__u8 b;
__u8 c;
__u8 d;
};
"#,
target_btf: r#"
struct foo {
__u8 a;
__u8 c;
__u8 b;
__u8 d;
} s1;
"#,
relocation_code: r#"
__u8 memory[] = {1, 2, 3, 4};
struct foo *ptr = (struct foo *) &memory;
value = __builtin_preserve_access_index(ptr->c);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 2);
assert_eq!(test.run_no_btf().unwrap(), 3);
}
#[integration_test]
fn relocate_enum() {
let test = RelocationTest {
local_definition: r#"
enum foo { D = 0xAAAAAAAA };
"#,
target_btf: r#"
enum foo { D = 0xBBBBBBBB } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 0xBBBBBBBB);
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA);
}
#[integration_test]
fn relocate_enum_signed() {
let test = RelocationTest {
local_definition: r#"
enum foo { D = -0x7AAAAAAA };
"#,
target_btf: r#"
enum foo { D = -0x7BBBBBBB } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap() as i64, -0x7BBBBBBBi64);
assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64);
}
#[integration_test]
fn relocate_enum64() {
let test = RelocationTest {
local_definition: r#"
enum foo { D = 0xAAAAAAAABBBBBBBB };
"#,
target_btf: r#"
enum foo { D = 0xCCCCCCCCDDDDDDDD } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 0xCCCCCCCCDDDDDDDD);
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB);
}
#[integration_test]
fn relocate_enum64_signed() {
let test = RelocationTest {
local_definition: r#"
enum foo { D = -0xAAAAAAABBBBBBBB };
"#,
target_btf: r#"
enum foo { D = -0xCCCCCCCDDDDDDDD } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap() as i64, -0xCCCCCCCDDDDDDDDi64);
assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
}
#[integration_test]
fn relocate_pointer() {
let test = RelocationTest {
local_definition: r#"
struct foo {};
struct bar { struct foo *f; };
"#,
target_btf: r#"
struct foo {};
struct bar { struct foo *f; };
"#,
relocation_code: r#"
__u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0};
struct bar* ptr = (struct bar *) &memory;
value = (__u64) __builtin_preserve_access_index(ptr->f);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 42);
assert_eq!(test.run_no_btf().unwrap(), 42);
}
/// Utility code for running relocation tests:
/// - Generates the eBPF program using probided local definition and relocation code
/// - Generates the BTF from the target btf code
struct RelocationTest {
/// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
local_definition: &'static str,
/// Target data structure definition. What the vmlinux would actually contain.
target_btf: &'static str,
/// Code executed by the eBPF program to test the relocation.
/// The format should be:
// __u8 memory[] = { ... };
// __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
//
// The generated code will be executed by attaching a tracepoint to sched_switch
// and emitting `__u32 value` an a map. See the code template below for more details.
relocation_code: &'static str,
}
impl RelocationTest {
/// Build a RelocationTestRunner
fn build(&self) -> Result<RelocationTestRunner> {
Ok(RelocationTestRunner {
ebpf: self.build_ebpf()?,
btf: self.build_btf()?,
})
}
/// - Generate the source eBPF filling a template
/// - Compile it with clang
fn build_ebpf(&self) -> Result<Vec<u8>> {
let local_definition = self.local_definition;
let relocation_code = self.relocation_code;
let (_tmp_dir, compiled_file) = compile(&format!(
r#"
#include <linux/bpf.h>
static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
{local_definition}
struct {{
int (*type)[BPF_MAP_TYPE_ARRAY];
__u32 *key;
__u64 *value;
int (*max_entries)[1];
}} output_map
__attribute__((section(".maps"), used));
__attribute__((section("tracepoint/bpf_prog"), used))
int bpf_prog(void *ctx) {{
__u32 key = 0;
__u64 value = 0;
{relocation_code}
bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
return 0;
}}
char _license[] __attribute__((section("license"), used)) = "GPL";
"#
))
.context("Failed to compile eBPF program")?;
let bytecode =
std::fs::read(compiled_file).context("Error reading compiled eBPF program")?;
Ok(bytecode)
}
/// - Generate the target BTF source with a mock main()
/// - Compile it with clang
/// - Extract the BTF with llvm-objcopy
fn build_btf(&self) -> Result<Btf> {
let target_btf = self.target_btf;
let relocation_code = self.relocation_code;
// BTF files can be generated and inspected with these commands:
// $ clang -c -g -O2 -target bpf target.c
// $ pahole --btf_encode_detached=target.btf -V target.o
// $ bpftool btf dump file ./target.btf format c
let (tmp_dir, compiled_file) = compile(&format!(
r#"
#include <linux/bpf.h>
{target_btf}
int main() {{
__u64 value = 0;
// This is needed to make sure to emit BTF for the defined types,
// it could be dead code eliminated if we don't.
{relocation_code};
return value;
}}
"#
))
.context("Failed to compile BTF")?;
Command::new("llvm-objcopy")
.current_dir(tmp_dir.path())
.args(["--dump-section", ".BTF=target.btf"])
.arg(compiled_file)
.status()
.context("Failed to run llvm-objcopy")?
.success()
.then_some(())
.context("Failed to extract BTF")?;
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
.context("Error parsing generated BTF")?;
Ok(btf)
}
}
/// Compile an eBPF program and return the path of the compiled object.
/// Also returns a TempDir handler, dropping it will clear the created dicretory.
fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
let source = tmp_dir.path().join("source.c");
std::fs::write(&source, source_code).context("Writing bpf program failed")?;
Command::new("clang")
.current_dir(&tmp_dir)
.args(["-c", "-g", "-O2", "-target", "bpf"])
.arg(&source)
.status()
.context("Failed to run clang")?
.success()
.then_some(())
.context("Failed to compile eBPF source")?;
Ok((tmp_dir, source.with_extension("o")))
}
struct RelocationTestRunner {
ebpf: Vec<u8>,
btf: Btf,
}
impl RelocationTestRunner {
/// Run test and return the output value
fn run(&self) -> Result<u64> {
self.run_internal(true).context("Error running with BTF")
}
/// Run without loading btf
fn run_no_btf(&self) -> Result<u64> {
self.run_internal(false)
.context("Error running without BTF")
}
fn run_internal(&self, with_relocations: bool) -> Result<u64> {
let mut loader = BpfLoader::new();
if with_relocations {
loader.btf(Some(&self.btf));
} else {
loader.btf(None);
}
let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
let program: &mut TracePoint = bpf
.program_mut("bpf_prog")
.context("bpf_prog not found")?
.try_into()
.context("program not a tracepoint")?;
program.load().context("Loading tracepoint failed")?;
// Attach to sched_switch and wait some time to make sure it executed at least once
program
.attach("sched", "sched_switch")
.context("attach failed")?;
sleep(Duration::from_millis(1000));
// To inspect the loaded eBPF bytecode, increse the timeout and run:
// $ sudo bpftool prog dump xlated name bpf_prog
let output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap();
let key = 0;
output_map.get(&key, 0).context("Getting key 0 failed")
}
}

@ -1,11 +1,11 @@
use super::{integration_test, IntegrationTest}; use super::integration_test;
use aya::include_bytes_aligned; use aya::include_bytes_aligned;
use object::{Object, ObjectSymbol}; use object::{Object, ObjectSymbol};
#[integration_test] #[integration_test]
fn test_maps() { fn test_maps() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/map_test"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/map_test");
let obj_file = object::File::parse(bytes).unwrap(); let obj_file = object::File::parse(bytes).unwrap();
if obj_file.section_by_name("maps").is_none() { if obj_file.section_by_name("maps").is_none() {
panic!("No 'maps' ELF section"); panic!("No 'maps' ELF section");

@ -13,14 +13,14 @@ use log::warn;
use crate::tests::kernel_version; use crate::tests::kernel_version;
use super::{integration_test, IntegrationTest}; use super::integration_test;
const MAX_RETRIES: u32 = 100; const MAX_RETRIES: u32 = 100;
const RETRY_DURATION_MS: u64 = 10; const RETRY_DURATION_MS: u64 = 10;
#[integration_test] #[integration_test]
fn long_name() { fn long_name() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/name_test"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/name_test");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let name_prog: &mut Xdp = bpf let name_prog: &mut Xdp = bpf
.program_mut("ihaveaverylongname") .program_mut("ihaveaverylongname")
@ -38,7 +38,7 @@ fn long_name() {
#[integration_test] #[integration_test]
fn multiple_btf_maps() { fn multiple_btf_maps() {
let bytes = let bytes =
include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap-btf.bpf.o"); include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let map_1: Array<_, u64> = bpf.take_map("map_1").unwrap().try_into().unwrap(); let map_1: Array<_, u64> = bpf.take_map("map_1").unwrap().try_into().unwrap();
@ -85,7 +85,7 @@ macro_rules! assert_loaded {
#[integration_test] #[integration_test]
fn unload_xdp() { fn unload_xdp() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/test"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf let prog: &mut Xdp = bpf
.program_mut("test_unload_xdp") .program_mut("test_unload_xdp")
@ -115,7 +115,7 @@ fn unload_xdp() {
#[integration_test] #[integration_test]
fn unload_kprobe() { fn unload_kprobe() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/test"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut KProbe = bpf let prog: &mut KProbe = bpf
.program_mut("test_unload_kpr") .program_mut("test_unload_kpr")
@ -150,7 +150,7 @@ fn pin_link() {
return; return;
} }
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/test"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf let prog: &mut Xdp = bpf
.program_mut("test_unload_xdp") .program_mut("test_unload_xdp")
@ -185,7 +185,7 @@ fn pin_lifecycle() {
return; return;
} }
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass");
// 1. Load Program and Pin // 1. Load Program and Pin
{ {

@ -4,6 +4,7 @@ use libc::{uname, utsname};
use regex::Regex; use regex::Regex;
use std::{ffi::CStr, mem}; use std::{ffi::CStr, mem};
pub mod btf_relocations;
pub mod elf; pub mod elf;
pub mod load; pub mod load;
pub mod rbpf; pub mod rbpf;

@ -4,11 +4,11 @@ use std::collections::HashMap;
use aya::include_bytes_aligned; use aya::include_bytes_aligned;
use aya_obj::{generated::bpf_insn, Object, ProgramSection}; use aya_obj::{generated::bpf_insn, Object, ProgramSection};
use super::{integration_test, IntegrationTest}; use super::integration_test;
#[integration_test] #[integration_test]
fn run_with_rbpf() { fn run_with_rbpf() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass");
let object = Object::parse(bytes).unwrap(); let object = Object::parse(bytes).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);
@ -36,7 +36,7 @@ static mut MULTIMAP_MAPS: [*mut Vec<u64>; 2] = [null_mut(), null_mut()];
#[integration_test] #[integration_test]
fn use_map_with_rbpf() { fn use_map_with_rbpf() {
let bytes = let bytes =
include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/multimap-btf.bpf.o"); include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o");
let mut object = Object::parse(bytes).unwrap(); let mut object = Object::parse(bytes).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);
@ -69,14 +69,20 @@ fn use_map_with_rbpf() {
} }
} }
let text_sections = object
.functions
.iter()
.map(|((section_index, _), _)| *section_index)
.collect();
object object
.relocate_maps( .relocate_maps(
maps.iter() maps.iter()
.map(|(s, (fd, map))| (s.as_ref() as &str, Some(*fd), map)), .map(|(s, (fd, map))| (s.as_ref() as &str, Some(*fd), map)),
&text_sections,
) )
.expect("Relocation failed"); .expect("Relocation failed");
// Actually there is no local function call involved. // Actually there is no local function call involved.
object.relocate_calls().unwrap(); object.relocate_calls(&text_sections).unwrap();
// Executes the program // Executes the program
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);

@ -1,313 +1,70 @@
use anyhow::{Context, Result}; use std::{process::exit, time::Duration};
use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
use tempfile::TempDir;
use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness}; use aya::{
include_bytes_aligned,
use super::{integration_test, IntegrationTest}; programs::{ProgramError, UProbe},
Bpf,
// In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no
// special meaning, they just have "nice" bit patterns that can be helpful while debugging.
#[integration_test]
fn relocate_field() {
let test = RelocationTest {
local_definition: r#"
struct foo {
__u8 a;
__u8 b;
__u8 c;
__u8 d;
}; };
"#, use integration_test_macros::integration_test;
target_btf: r#"
struct foo {
__u8 a;
__u8 c;
__u8 b;
__u8 d;
} s1;
"#,
relocation_code: r#"
__u8 memory[] = {1, 2, 3, 4};
struct foo *ptr = (struct foo *) &memory;
value = __builtin_preserve_access_index(ptr->c);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 2);
assert_eq!(test.run_no_btf().unwrap(), 3);
}
#[integration_test] #[integration_test]
fn relocate_enum() { fn relocations() {
let test = RelocationTest { let bpf = load_and_attach(
local_definition: r#" "test_64_32_call_relocs",
enum foo { D = 0xAAAAAAAA }; include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/relocations"),
"#, );
target_btf: r#"
enum foo { D = 0xBBBBBBBB } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 0xBBBBBBBB);
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA);
}
#[integration_test] trigger_relocations_program();
fn relocate_enum_signed() { std::thread::sleep(Duration::from_millis(100));
let test = RelocationTest {
local_definition: r#"
enum foo { D = -0x7AAAAAAA };
"#,
target_btf: r#"
enum foo { D = -0x7BBBBBBB } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap() as i64, -0x7BBBBBBBi64);
assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64);
}
#[integration_test] let m = aya::maps::Array::<_, u64>::try_from(bpf.map("RESULTS").unwrap()).unwrap();
fn relocate_enum64() { assert_eq!(m.get(&0, 0).unwrap(), 1);
let test = RelocationTest { assert_eq!(m.get(&1, 0).unwrap(), 2);
local_definition: r#" assert_eq!(m.get(&2, 0).unwrap(), 3);
enum foo { D = 0xAAAAAAAABBBBBBBB };
"#,
target_btf: r#"
enum foo { D = 0xCCCCCCCCDDDDDDDD } e1;
"#,
relocation_code: r#"
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE);
"#,
}
.build()
.unwrap();
assert_eq!(test.run().unwrap(), 0xCCCCCCCCDDDDDDDD);
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB);
} }
#[integration_test] #[integration_test]
fn relocate_enum64_signed() { fn text_64_64_reloc() {
let test = RelocationTest { let mut bpf = load_and_attach(
local_definition: r#" "test_text_64_64_reloc",
enum foo { D = -0xAAAAAAABBBBBBBB }; include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/text_64_64_reloc.o"),
"#, );
target_btf: r#"
enum foo { D = -0xCCCCCCCDDDDDDDD } e1; let mut m = aya::maps::Array::<_, u64>::try_from(bpf.map_mut("RESULTS").unwrap()).unwrap();
"#, m.set(0, 1, 0).unwrap();
relocation_code: r#" m.set(1, 2, 0).unwrap();
#define BPF_ENUMVAL_VALUE 1
value = __builtin_preserve_enum_value(*(typeof(enum foo) *)D, BPF_ENUMVAL_VALUE); trigger_relocations_program();
"#, std::thread::sleep(Duration::from_millis(100));
}
.build() assert_eq!(m.get(&0, 0).unwrap(), 2);
.unwrap(); assert_eq!(m.get(&1, 0).unwrap(), 3);
assert_eq!(test.run().unwrap() as i64, -0xCCCCCCCDDDDDDDDi64); }
assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
} fn load_and_attach(name: &str, bytes: &[u8]) -> Bpf {
let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut UProbe = bpf.program_mut(name).unwrap().try_into().unwrap();
if let Err(ProgramError::LoadError {
io_error,
verifier_log,
}) = prog.load()
{
println!("Failed to load program `{name}`: {io_error}. Verifier log:\n{verifier_log:#}");
exit(1);
};
#[integration_test] prog.attach(
fn relocate_pointer() { Some("trigger_relocations_program"),
let test = RelocationTest { 0,
local_definition: r#" "/proc/self/exe",
struct foo {}; None,
struct bar { struct foo *f; }; )
"#,
target_btf: r#"
struct foo {};
struct bar { struct foo *f; };
"#,
relocation_code: r#"
__u8 memory[] = {42, 0, 0, 0, 0, 0, 0, 0};
struct bar* ptr = (struct bar *) &memory;
value = (__u64) __builtin_preserve_access_index(ptr->f);
"#,
}
.build()
.unwrap(); .unwrap();
assert_eq!(test.run().unwrap(), 42);
assert_eq!(test.run_no_btf().unwrap(), 42);
}
/// Utility code for running relocation tests: bpf
/// - Generates the eBPF program using probided local definition and relocation code
/// - Generates the BTF from the target btf code
struct RelocationTest {
/// Data structure definition, local to the eBPF program and embedded in the eBPF bytecode
local_definition: &'static str,
/// Target data structure definition. What the vmlinux would actually contain.
target_btf: &'static str,
/// Code executed by the eBPF program to test the relocation.
/// The format should be:
// __u8 memory[] = { ... };
// __u32 value = BPF_CORE_READ((struct foo *)&memory, ...);
//
// The generated code will be executed by attaching a tracepoint to sched_switch
// and emitting `__u32 value` an a map. See the code template below for more details.
relocation_code: &'static str,
} }
impl RelocationTest { #[no_mangle]
/// Build a RelocationTestRunner #[inline(never)]
fn build(&self) -> Result<RelocationTestRunner> { pub extern "C" fn trigger_relocations_program() {}
Ok(RelocationTestRunner {
ebpf: self.build_ebpf()?,
btf: self.build_btf()?,
})
}
/// - Generate the source eBPF filling a template
/// - Compile it with clang
fn build_ebpf(&self) -> Result<Vec<u8>> {
let local_definition = self.local_definition;
let relocation_code = self.relocation_code;
let (_tmp_dir, compiled_file) = compile(&format!(
r#"
#include <linux/bpf.h>
static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
{local_definition}
struct {{
int (*type)[BPF_MAP_TYPE_ARRAY];
__u32 *key;
__u64 *value;
int (*max_entries)[1];
}} output_map
__attribute__((section(".maps"), used));
__attribute__((section("tracepoint/bpf_prog"), used))
int bpf_prog(void *ctx) {{
__u32 key = 0;
__u64 value = 0;
{relocation_code}
bpf_map_update_elem(&output_map, &key, &value, BPF_ANY);
return 0;
}}
char _license[] __attribute__((section("license"), used)) = "GPL";
"#
))
.context("Failed to compile eBPF program")?;
let bytecode =
std::fs::read(compiled_file).context("Error reading compiled eBPF program")?;
Ok(bytecode)
}
/// - Generate the target BTF source with a mock main()
/// - Compile it with clang
/// - Extract the BTF with llvm-objcopy
fn build_btf(&self) -> Result<Btf> {
let target_btf = self.target_btf;
let relocation_code = self.relocation_code;
// BTF files can be generated and inspected with these commands:
// $ clang -c -g -O2 -target bpf target.c
// $ pahole --btf_encode_detached=target.btf -V target.o
// $ bpftool btf dump file ./target.btf format c
let (tmp_dir, compiled_file) = compile(&format!(
r#"
#include <linux/bpf.h>
{target_btf}
int main() {{
__u64 value = 0;
// This is needed to make sure to emit BTF for the defined types,
// it could be dead code eliminated if we don't.
{relocation_code};
return value;
}}
"#
))
.context("Failed to compile BTF")?;
Command::new("llvm-objcopy")
.current_dir(tmp_dir.path())
.args(["--dump-section", ".BTF=target.btf"])
.arg(compiled_file)
.status()
.context("Failed to run llvm-objcopy")?
.success()
.then_some(())
.context("Failed to extract BTF")?;
let btf = Btf::parse_file(tmp_dir.path().join("target.btf"), Endianness::default())
.context("Error parsing generated BTF")?;
Ok(btf)
}
}
/// Compile an eBPF program and return the path of the compiled object.
/// Also returns a TempDir handler, dropping it will clear the created dicretory.
fn compile(source_code: &str) -> Result<(TempDir, PathBuf)> {
let tmp_dir = tempfile::tempdir().context("Error making temp dir")?;
let source = tmp_dir.path().join("source.c");
std::fs::write(&source, source_code).context("Writing bpf program failed")?;
Command::new("clang")
.current_dir(&tmp_dir)
.args(["-c", "-g", "-O2", "-target", "bpf"])
.arg(&source)
.status()
.context("Failed to run clang")?
.success()
.then_some(())
.context("Failed to compile eBPF source")?;
Ok((tmp_dir, source.with_extension("o")))
}
struct RelocationTestRunner {
ebpf: Vec<u8>,
btf: Btf,
}
impl RelocationTestRunner {
/// Run test and return the output value
fn run(&self) -> Result<u64> {
self.run_internal(true).context("Error running with BTF")
}
/// Run without loading btf
fn run_no_btf(&self) -> Result<u64> {
self.run_internal(false)
.context("Error running without BTF")
}
fn run_internal(&self, with_relocations: bool) -> Result<u64> {
let mut loader = BpfLoader::new();
if with_relocations {
loader.btf(Some(&self.btf));
} else {
loader.btf(None);
}
let mut bpf = loader.load(&self.ebpf).context("Loading eBPF failed")?;
let program: &mut TracePoint = bpf
.program_mut("bpf_prog")
.context("bpf_prog not found")?
.try_into()
.context("program not a tracepoint")?;
program.load().context("Loading tracepoint failed")?;
// Attach to sched_switch and wait some time to make sure it executed at least once
program
.attach("sched", "sched_switch")
.context("attach failed")?;
sleep(Duration::from_millis(1000));
// To inspect the loaded eBPF bytecode, increse the timeout and run:
// $ sudo bpftool prog dump xlated name bpf_prog
let output_map: Array<_, u64> = bpf.take_map("output_map").unwrap().try_into().unwrap();
let key = 0;
output_map.get(&key, 0).context("Getting key 0 failed")
}
}

@ -5,11 +5,11 @@ use aya::{
}; };
use log::info; use log::info;
use super::{integration_test, kernel_version, IntegrationTest}; use super::{integration_test, kernel_version};
#[integration_test] #[integration_test]
fn xdp() { fn xdp() {
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass"); let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass");
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
dispatcher.load().unwrap(); dispatcher.load().unwrap();
@ -28,13 +28,14 @@ fn extension() {
} }
// TODO: Check kernel version == 5.9 or later // TODO: Check kernel version == 5.9 or later
let main_bytes = let main_bytes =
include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/main.bpf.o"); include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/main.bpf.o");
let mut bpf = Bpf::load(main_bytes).unwrap(); let mut bpf = Bpf::load(main_bytes).unwrap();
let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
pass.load().unwrap(); pass.load().unwrap();
pass.attach("lo", XdpFlags::default()).unwrap(); pass.attach("lo", XdpFlags::default()).unwrap();
let ext_bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/ext.bpf.o"); let ext_bytes =
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/ext.bpf.o");
let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap(); let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap();
let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap();
drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap();

@ -40,7 +40,7 @@ fi
# Test Image # Test Image
if [ -z "${AYA_TEST_IMAGE}" ]; then if [ -z "${AYA_TEST_IMAGE}" ]; then
AYA_TEST_IMAGE="fedora37" AYA_TEST_IMAGE="fedora38"
fi fi
case "${AYA_TEST_IMAGE}" in case "${AYA_TEST_IMAGE}" in
@ -59,6 +59,14 @@ download_images() {
curl -o "${AYA_IMGDIR}/fedora37.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}" curl -o "${AYA_IMGDIR}/fedora37.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}"
fi fi
;; ;;
fedora38)
if [ ! -f "${AYA_IMGDIR}/fedora38.${AYA_GUEST_ARCH}.qcow2" ]; then
IMAGE="Fedora-Cloud-Base-38_Beta-1.3.${AYA_GUEST_ARCH}.qcow2"
IMAGE_URL="https://fr2.rpmfind.net/linux/fedora/linux/releases/test/38_Beta/Cloud/${AYA_GUEST_ARCH}/images"
echo "Downloading: ${IMAGE}, this may take a while..."
curl -o "${AYA_IMGDIR}/fedora38.${AYA_GUEST_ARCH}.qcow2" -sSL "${IMAGE_URL}/${IMAGE}"
fi
;;
centos8) centos8)
if [ ! -f "${AYA_IMGDIR}/centos8.${AYA_GUEST_ARCH}.qcow2" ]; then if [ ! -f "${AYA_IMGDIR}/centos8.${AYA_GUEST_ARCH}.qcow2" ]; then
IMAGE="CentOS-8-GenericCloud-8.4.2105-20210603.0.${AYA_GUEST_ARCH}.qcow2" IMAGE="CentOS-8-GenericCloud-8.4.2105-20210603.0.${AYA_GUEST_ARCH}.qcow2"
@ -181,6 +189,9 @@ EOF
echo "VM launched" echo "VM launched"
exec_vm uname -a exec_vm uname -a
echo "Enabling testing repositories"
exec_vm sudo dnf config-manager --set-enabled updates-testing
exec_vm sudo dnf config-manager --set-enabled updates-testing-modular
echo "Installing dependencies" echo "Installing dependencies"
exec_vm sudo dnf install -qy bpftool llvm llvm-devel clang clang-devel zlib-devel exec_vm sudo dnf install -qy bpftool llvm llvm-devel clang clang-devel zlib-devel
exec_vm 'curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ exec_vm 'curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \

@ -41,9 +41,6 @@ pub struct BuildEbpfOptions {
/// Set the endianness of the BPF target /// Set the endianness of the BPF target
#[clap(default_value = "bpfel-unknown-none", long)] #[clap(default_value = "bpfel-unknown-none", long)]
pub target: Architecture, pub target: Architecture,
/// Build the release target
#[clap(long)]
pub release: bool,
/// Libbpf dir, required for compiling C code /// Libbpf dir, required for compiling C code
#[clap(long, action)] #[clap(long, action)]
pub libbpf_dir: PathBuf, pub libbpf_dir: PathBuf,
@ -59,17 +56,15 @@ fn build_rust_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
dir.push("test/integration-ebpf"); dir.push("test/integration-ebpf");
let target = format!("--target={}", opts.target); let target = format!("--target={}", opts.target);
let mut args = vec![ let args = vec![
"+nightly", "+nightly",
"build", "build",
"--release",
"--verbose", "--verbose",
target.as_str(), target.as_str(),
"-Z", "-Z",
"build-std=core", "build-std=core",
]; ];
if opts.release {
args.push("--release")
}
let status = Command::new("cargo") let status = Command::new("cargo")
.current_dir(&dir) .current_dir(&dir)
.args(&args) .args(&args)
@ -99,7 +94,7 @@ fn build_c_ebpf(opts: &BuildEbpfOptions) -> anyhow::Result<()> {
let mut out_path = PathBuf::from(WORKSPACE_ROOT.to_string()); let mut out_path = PathBuf::from(WORKSPACE_ROOT.to_string());
out_path.push("target"); out_path.push("target");
out_path.push(opts.target.to_string()); out_path.push(opts.target.to_string());
out_path.push(if opts.release { "release " } else { "debug" }); out_path.push("release");
let include_path = out_path.join("include"); let include_path = out_path.join("include");
get_libbpf_headers(&opts.libbpf_dir, &include_path)?; get_libbpf_headers(&opts.libbpf_dir, &include_path)?;

@ -45,7 +45,6 @@ pub fn run(opts: Options) -> Result<(), anyhow::Error> {
// build our ebpf program followed by our application // build our ebpf program followed by our application
build_ebpf(BuildOptions { build_ebpf(BuildOptions {
target: opts.bpf_target, target: opts.bpf_target,
release: opts.release,
libbpf_dir: PathBuf::from(&opts.libbpf_dir), libbpf_dir: PathBuf::from(&opts.libbpf_dir),
}) })
.context("Error while building eBPF program")?; .context("Error while building eBPF program")?;

Loading…
Cancel
Save