You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aya/aya/src/util.rs

183 lines
5.3 KiB
Rust

//! Utility functions.
use std::{
collections::BTreeMap,
ffi::CString,
fs::{self, File},
io::{self, BufReader},
str::FromStr,
};
use crate::generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK};
use libc::if_nametoindex;
use io::BufRead;
const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online";
pub(crate) const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible";
/// Returns the numeric IDs of the CPUs currently online.
pub fn online_cpus() -> Result<Vec<u32>, io::Error> {
let data = fs::read_to_string(ONLINE_CPUS)?;
parse_cpu_ranges(data.trim()).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!("unexpected {} format", ONLINE_CPUS),
)
})
}
/// Get the number of possible cpus.
///
/// See `/sys/devices/system/cpu/possible`.
pub fn nr_cpus() -> Result<usize, io::Error> {
Ok(possible_cpus()?.len())
}
/// Get the list of possible cpus.
///
/// See `/sys/devices/system/cpu/possible`.
pub(crate) fn possible_cpus() -> Result<Vec<u32>, io::Error> {
let data = fs::read_to_string(POSSIBLE_CPUS)?;
parse_cpu_ranges(data.trim()).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!("unexpected {} format", POSSIBLE_CPUS),
)
})
}
fn parse_cpu_ranges(data: &str) -> Result<Vec<u32>, ()> {
let mut cpus = Vec::new();
for range in data.split(',') {
cpus.extend({
match range
.splitn(2, '-')
.map(u32::from_str)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| ())?
.as_slice()
{
&[] | &[_, _, _, ..] => return Err(()),
&[start] => start..=start,
&[start, end] => start..=end,
}
})
}
Ok(cpus)
}
/// Loads kernel symbols from `/proc/kallsyms`.
///
/// The symbols can be passed to [`StackTrace::resolve`](crate::maps::stack_trace::StackTrace::resolve).
pub fn kernel_symbols() -> Result<BTreeMap<u64, String>, io::Error> {
let mut reader = BufReader::new(File::open("/proc/kallsyms")?);
parse_kernel_symbols(&mut reader)
}
fn parse_kernel_symbols(reader: &mut dyn BufRead) -> Result<BTreeMap<u64, String>, io::Error> {
let mut syms = BTreeMap::new();
for line in reader.lines() {
let line = line?;
let parts = line.splitn(4, ' ').collect::<Vec<_>>();
let addr = u64::from_str_radix(parts[0], 16)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, line.clone()))?;
let name = parts[2].to_owned();
syms.insert(addr, name);
}
Ok(syms)
}
pub(crate) fn ifindex_from_ifname(if_name: &str) -> Result<u32, io::Error> {
let c_str_if_name = CString::new(if_name)?;
let c_if_name = c_str_if_name.as_ptr();
// Safety: libc wrapper
let if_index = unsafe { if_nametoindex(c_if_name) };
if if_index == 0 {
return Err(io::Error::last_os_error());
}
Ok(if_index)
}
pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 {
(major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK)
}
/// Include bytes from a file for use in a subsequent [`crate::Bpf::load`].
///
/// This macro differs from the standard `include_bytes!` macro since it also ensures that
/// the bytes are correctly aligned to be parsed as an ELF binary. This avoid some nasty
/// compilation errors when the resulting byte array is not the correct alignment.
///
/// # Examples
/// ```ignore
/// use aya::{Bpf, include_bytes_aligned};
///
/// let mut bpf = Bpf::load(include_bytes_aligned!(
/// "/path/to/bpf.o"
/// ))?;
///
/// # Ok::<(), aya::BpfError>(())
/// ```
#[macro_export]
macro_rules! include_bytes_aligned {
($path:literal) => {{
#[repr(C)]
pub struct Aligned<Bytes: ?Sized> {
pub _align: [u32; 0],
pub bytes: Bytes,
}
static ALIGNED: &Aligned<[u8]> = &Aligned {
_align: [],
bytes: *include_bytes!($path),
};
&ALIGNED.bytes
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_online_cpus() {
assert_eq!(parse_cpu_ranges("0").unwrap(), vec![0]);
assert_eq!(parse_cpu_ranges("0,1").unwrap(), vec![0, 1]);
assert_eq!(parse_cpu_ranges("0,1,2").unwrap(), vec![0, 1, 2]);
assert_eq!(
parse_cpu_ranges("0-7").unwrap(),
(0..=7).collect::<Vec<_>>()
);
assert_eq!(
parse_cpu_ranges("0-3,4-7").unwrap(),
(0..=7).collect::<Vec<_>>()
);
assert_eq!(
parse_cpu_ranges("0-5,6,7").unwrap(),
(0..=7).collect::<Vec<_>>()
);
assert!(parse_cpu_ranges("").is_err());
assert!(parse_cpu_ranges("0-1,2-").is_err());
assert!(parse_cpu_ranges("foo").is_err());
}
#[test]
fn test_parse_kernel_symbols() {
let data = "0000000000002000 A irq_stack_backing_store\n\
0000000000006000 A cpu_tss_rw [foo bar]\n"
.as_bytes();
let syms = parse_kernel_symbols(&mut BufReader::new(data)).unwrap();
assert_eq!(syms.keys().collect::<Vec<_>>(), vec![&0x2000, &0x6000]);
assert_eq!(
syms.get(&0x2000u64).unwrap().as_str(),
"irq_stack_backing_store"
);
assert_eq!(syms.get(&0x6000u64).unwrap().as_str(), "cpu_tss_rw");
}
}