Initial commit

pull/1/head
Alessandro Decina 4 years ago
commit 15be301f8c

2
.gitignore vendored

@ -0,0 +1,2 @@
Cargo.lock
target/

@ -0,0 +1,12 @@
[package]
name = "aya"
version = "0.1.0"
authors = ["Alessandro Decina <alessandro.d@gmail.com>"]
edition = "2018"
[dependencies]
libc = "0.2"
thiserror = "1"
object = "0.23"
bytes = "1"
lazy_static = "1"

@ -0,0 +1,64 @@
#!/usr/bin/env sh
LIBBPF_DIR=$1
OUTPUT_DIR=$2
if test -z "$LIBBPF_DIR"; then
echo "error: no libbpf dir provided"
exit 1
fi
if test -z "$OUTPUT_DIR"; then
echo "error: no output dir provided"
exit 1
fi
BPF_TYPES="\
bpf_cmd \
bpf_insn \
bpf_attr \
bpf_map_type \
bpf_prog_type \
bpf_attach_type
"
BPF_VARS="\
BPF_PSEUDO_.*
"
PERF_TYPES="\
perf_event_attr \
perf_sw_ids \
perf_event_sample_format \
perf_event_mmap_page \
perf_event_header \
perf_type_id \
perf_event_type
"
PERF_VARS="\
PERF_FLAG_.* \
PERF_EVENT_.*
"
bindgen $LIBBPF_DIR/include/uapi/linux/bpf.h \
--no-layout-tests \
--default-enum-style moduleconsts \
$(for ty in $BPF_TYPES; do
echo --whitelist-type "$ty"
done) \
$(for var in $BPF_VARS; do
echo --whitelist-var "$var"
done) \
> $OUTPUT_DIR/bpf_bindings.rs
bindgen include/perf_wrapper.h \
--no-layout-tests \
--default-enum-style moduleconsts \
$(for ty in $PERF_TYPES; do
echo --whitelist-type "$ty"
done) \
$(for var in $PERF_VARS; do
echo --whitelist-var "$var"
done) \
> $OUTPUT_DIR/perf_bindings.rs

@ -0,0 +1,187 @@
use std::collections::HashMap;
use thiserror::Error;
use crate::{
generated::bpf_insn,
maps::{Map, MapError},
obj::{relocate, Object, ParseError, RelocationError},
programs::{KProbe, Program, ProgramData, ProgramError, SocketFilter, TracePoint, UProbe, Xdp},
syscalls::bpf_map_update_elem_ptr,
};
pub use object::Pod;
unsafe impl object::Pod for bpf_insn {}
pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
/* FIXME: these are arch dependent */
pub(crate) const PERF_EVENT_IOC_ENABLE: libc::c_ulong = 9216;
pub(crate) const PERF_EVENT_IOC_DISABLE: libc::c_ulong = 9217;
pub(crate) const PERF_EVENT_IOC_SET_BPF: libc::c_ulong = 1074013192;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub(crate) struct bpf_map_def {
pub(crate) map_type: u32,
pub(crate) key_size: u32,
pub(crate) value_size: u32,
pub(crate) max_entries: u32,
pub(crate) map_flags: u32,
}
unsafe impl object::Pod for bpf_map_def {}
#[derive(Debug)]
pub struct Bpf {
maps: HashMap<String, Map>,
programs: HashMap<String, Program>,
}
impl Bpf {
pub fn load(data: &[u8]) -> Result<Bpf, BpfError> {
let mut obj = Object::parse(data)?;
let mut maps = Vec::new();
for (_, obj) in obj.maps.drain() {
let mut map = Map { obj, fd: None };
let fd = map.create()?;
if !map.obj.data.is_empty() && map.obj.name != ".bss" {
bpf_map_update_elem_ptr(fd, &0 as *const _, map.obj.data.as_ptr(), 0)
.map_err(|(code, io_error)| MapError::UpdateElementFailed { code, io_error })?;
}
maps.push(map);
}
relocate(&mut obj, maps.as_slice())?;
let programs = obj
.programs
.drain()
.map(|(name, obj)| {
let kind = obj.kind;
let data = ProgramData {
obj,
name: name.clone(),
fd: None,
links: Vec::new(),
};
let program = match kind {
crate::obj::ProgramKind::KProbe => Program::KProbe(KProbe { data }),
crate::obj::ProgramKind::UProbe => Program::UProbe(UProbe { data }),
crate::obj::ProgramKind::TracePoint => Program::TracePoint(TracePoint { data }),
crate::obj::ProgramKind::Xdp => Program::Xdp(Xdp { data }),
};
(name, program)
})
.collect();
Ok(Bpf {
maps: maps
.drain(..)
.map(|map| (map.obj.name.clone(), map))
.collect(),
programs,
})
}
pub fn map(&self, name: &str) -> Option<&Map> {
self.maps.get(name)
}
pub fn map_mut(&mut self, name: &str) -> Option<&mut Map> {
self.maps.get_mut(name)
}
pub fn program(&self, name: &str) -> Option<&Program> {
self.programs.get(name)
}
pub fn program_mut(&mut self, name: &str) -> Option<&mut Program> {
self.programs.get_mut(name)
}
pub fn kprobe(&self, name: &str) -> Option<&KProbe> {
match self.programs.get(name) {
Some(Program::KProbe(kprobe)) => Some(kprobe),
_ => None,
}
}
pub fn kprobe_mut(&mut self, name: &str) -> Option<&mut KProbe> {
match self.programs.get_mut(name) {
Some(Program::KProbe(kprobe)) => Some(kprobe),
_ => None,
}
}
pub fn uprobe(&self, name: &str) -> Option<&UProbe> {
match self.programs.get(name) {
Some(Program::UProbe(uprobe)) => Some(uprobe),
_ => None,
}
}
pub fn uprobe_mut(&mut self, name: &str) -> Option<&mut UProbe> {
match self.programs.get_mut(name) {
Some(Program::UProbe(uprobe)) => Some(uprobe),
_ => None,
}
}
pub fn trace_point(&self, name: &str) -> Option<&TracePoint> {
match self.programs.get(name) {
Some(Program::TracePoint(trace_point)) => Some(trace_point),
_ => None,
}
}
pub fn trace_point_mut(&mut self, name: &str) -> Option<&mut TracePoint> {
match self.programs.get_mut(name) {
Some(Program::TracePoint(trace_point)) => Some(trace_point),
_ => None,
}
}
pub fn socket_filter(&self, name: &str) -> Option<&SocketFilter> {
match self.programs.get(name) {
Some(Program::SocketFilter(socket_filter)) => Some(socket_filter),
_ => None,
}
}
pub fn socket_filter_mut(&mut self, name: &str) -> Option<&mut SocketFilter> {
match self.programs.get_mut(name) {
Some(Program::SocketFilter(socket_filter)) => Some(socket_filter),
_ => None,
}
}
pub fn xdp(&self, name: &str) -> Option<&Xdp> {
match self.programs.get(name) {
Some(Program::Xdp(xdp)) => Some(xdp),
_ => None,
}
}
pub fn xdp_mut(&mut self, name: &str) -> Option<&mut Xdp> {
match self.programs.get_mut(name) {
Some(Program::Xdp(xdp)) => Some(xdp),
_ => None,
}
}
}
#[derive(Debug, Error)]
pub enum BpfError {
#[error("error parsing BPF object: {0}")]
ParseError(#[from] ParseError),
#[error("error relocating BPF object: {0}")]
RelocationError(#[from] RelocationError),
#[error("map error: {0}")]
MapError(#[from] MapError),
#[error("program error: {0}")]
ProgramError(#[from] ProgramError),
}

@ -0,0 +1,542 @@
/* automatically generated by rust-bindgen 0.55.1 */
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct __BindgenBitfieldUnit<Storage, Align> {
storage: Storage,
align: [Align; 0],
}
impl<Storage, Align> __BindgenBitfieldUnit<Storage, Align> {
#[inline]
pub const fn new(storage: Storage) -> Self {
Self { storage, align: [] }
}
}
impl<Storage, Align> __BindgenBitfieldUnit<Storage, Align>
where
Storage: AsRef<[u8]> + AsMut<[u8]>,
{
#[inline]
pub fn get_bit(&self, index: usize) -> bool {
debug_assert!(index / 8 < self.storage.as_ref().len());
let byte_index = index / 8;
let byte = self.storage.as_ref()[byte_index];
let bit_index = if cfg!(target_endian = "big") {
7 - (index % 8)
} else {
index % 8
};
let mask = 1 << bit_index;
byte & mask == mask
}
#[inline]
pub fn set_bit(&mut self, index: usize, val: bool) {
debug_assert!(index / 8 < self.storage.as_ref().len());
let byte_index = index / 8;
let byte = &mut self.storage.as_mut()[byte_index];
let bit_index = if cfg!(target_endian = "big") {
7 - (index % 8)
} else {
index % 8
};
let mask = 1 << bit_index;
if val {
*byte |= mask;
} else {
*byte &= !mask;
}
}
#[inline]
pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 {
debug_assert!(bit_width <= 64);
debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
let mut val = 0;
for i in 0..(bit_width as usize) {
if self.get_bit(i + bit_offset) {
let index = if cfg!(target_endian = "big") {
bit_width as usize - 1 - i
} else {
i
};
val |= 1 << index;
}
}
val
}
#[inline]
pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) {
debug_assert!(bit_width <= 64);
debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
for i in 0..(bit_width as usize) {
let mask = 1 << i;
let val_bit_is_set = val & mask == mask;
let index = if cfg!(target_endian = "big") {
bit_width as usize - 1 - i
} else {
i
};
self.set_bit(index + bit_offset, val_bit_is_set);
}
}
}
pub const BPF_PSEUDO_MAP_FD: u32 = 1;
pub const BPF_PSEUDO_MAP_VALUE: u32 = 2;
pub const BPF_PSEUDO_BTF_ID: u32 = 3;
pub const BPF_PSEUDO_CALL: u32 = 1;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_insn {
pub code: __u8,
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize], u8>,
pub off: __s16,
pub imm: __s32,
}
impl bpf_insn {
#[inline]
pub fn dst_reg(&self) -> __u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 4u8) as u8) }
}
#[inline]
pub fn set_dst_reg(&mut self, val: __u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(0usize, 4u8, val as u64)
}
}
#[inline]
pub fn src_reg(&self) -> __u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 4u8) as u8) }
}
#[inline]
pub fn set_src_reg(&mut self, val: __u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(4usize, 4u8, val as u64)
}
}
#[inline]
pub fn new_bitfield_1(dst_reg: __u8, src_reg: __u8) -> __BindgenBitfieldUnit<[u8; 1usize], u8> {
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize], u8> =
Default::default();
__bindgen_bitfield_unit.set(0usize, 4u8, {
let dst_reg: u8 = unsafe { ::std::mem::transmute(dst_reg) };
dst_reg as u64
});
__bindgen_bitfield_unit.set(4usize, 4u8, {
let src_reg: u8 = unsafe { ::std::mem::transmute(src_reg) };
src_reg as u64
});
__bindgen_bitfield_unit
}
}
pub mod bpf_cmd {
pub type Type = ::std::os::raw::c_uint;
pub const BPF_MAP_CREATE: Type = 0;
pub const BPF_MAP_LOOKUP_ELEM: Type = 1;
pub const BPF_MAP_UPDATE_ELEM: Type = 2;
pub const BPF_MAP_DELETE_ELEM: Type = 3;
pub const BPF_MAP_GET_NEXT_KEY: Type = 4;
pub const BPF_PROG_LOAD: Type = 5;
pub const BPF_OBJ_PIN: Type = 6;
pub const BPF_OBJ_GET: Type = 7;
pub const BPF_PROG_ATTACH: Type = 8;
pub const BPF_PROG_DETACH: Type = 9;
pub const BPF_PROG_TEST_RUN: Type = 10;
pub const BPF_PROG_GET_NEXT_ID: Type = 11;
pub const BPF_MAP_GET_NEXT_ID: Type = 12;
pub const BPF_PROG_GET_FD_BY_ID: Type = 13;
pub const BPF_MAP_GET_FD_BY_ID: Type = 14;
pub const BPF_OBJ_GET_INFO_BY_FD: Type = 15;
pub const BPF_PROG_QUERY: Type = 16;
pub const BPF_RAW_TRACEPOINT_OPEN: Type = 17;
pub const BPF_BTF_LOAD: Type = 18;
pub const BPF_BTF_GET_FD_BY_ID: Type = 19;
pub const BPF_TASK_FD_QUERY: Type = 20;
pub const BPF_MAP_LOOKUP_AND_DELETE_ELEM: Type = 21;
pub const BPF_MAP_FREEZE: Type = 22;
pub const BPF_BTF_GET_NEXT_ID: Type = 23;
pub const BPF_MAP_LOOKUP_BATCH: Type = 24;
pub const BPF_MAP_LOOKUP_AND_DELETE_BATCH: Type = 25;
pub const BPF_MAP_UPDATE_BATCH: Type = 26;
pub const BPF_MAP_DELETE_BATCH: Type = 27;
pub const BPF_LINK_CREATE: Type = 28;
pub const BPF_LINK_UPDATE: Type = 29;
pub const BPF_LINK_GET_FD_BY_ID: Type = 30;
pub const BPF_LINK_GET_NEXT_ID: Type = 31;
pub const BPF_ENABLE_STATS: Type = 32;
pub const BPF_ITER_CREATE: Type = 33;
pub const BPF_LINK_DETACH: Type = 34;
pub const BPF_PROG_BIND_MAP: Type = 35;
}
pub mod bpf_map_type {
pub type Type = ::std::os::raw::c_uint;
pub const BPF_MAP_TYPE_UNSPEC: Type = 0;
pub const BPF_MAP_TYPE_HASH: Type = 1;
pub const BPF_MAP_TYPE_ARRAY: Type = 2;
pub const BPF_MAP_TYPE_PROG_ARRAY: Type = 3;
pub const BPF_MAP_TYPE_PERF_EVENT_ARRAY: Type = 4;
pub const BPF_MAP_TYPE_PERCPU_HASH: Type = 5;
pub const BPF_MAP_TYPE_PERCPU_ARRAY: Type = 6;
pub const BPF_MAP_TYPE_STACK_TRACE: Type = 7;
pub const BPF_MAP_TYPE_CGROUP_ARRAY: Type = 8;
pub const BPF_MAP_TYPE_LRU_HASH: Type = 9;
pub const BPF_MAP_TYPE_LRU_PERCPU_HASH: Type = 10;
pub const BPF_MAP_TYPE_LPM_TRIE: Type = 11;
pub const BPF_MAP_TYPE_ARRAY_OF_MAPS: Type = 12;
pub const BPF_MAP_TYPE_HASH_OF_MAPS: Type = 13;
pub const BPF_MAP_TYPE_DEVMAP: Type = 14;
pub const BPF_MAP_TYPE_SOCKMAP: Type = 15;
pub const BPF_MAP_TYPE_CPUMAP: Type = 16;
pub const BPF_MAP_TYPE_XSKMAP: Type = 17;
pub const BPF_MAP_TYPE_SOCKHASH: Type = 18;
pub const BPF_MAP_TYPE_CGROUP_STORAGE: Type = 19;
pub const BPF_MAP_TYPE_REUSEPORT_SOCKARRAY: Type = 20;
pub const BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE: Type = 21;
pub const BPF_MAP_TYPE_QUEUE: Type = 22;
pub const BPF_MAP_TYPE_STACK: Type = 23;
pub const BPF_MAP_TYPE_SK_STORAGE: Type = 24;
pub const BPF_MAP_TYPE_DEVMAP_HASH: Type = 25;
pub const BPF_MAP_TYPE_STRUCT_OPS: Type = 26;
pub const BPF_MAP_TYPE_RINGBUF: Type = 27;
pub const BPF_MAP_TYPE_INODE_STORAGE: Type = 28;
pub const BPF_MAP_TYPE_TASK_STORAGE: Type = 29;
}
pub mod bpf_prog_type {
pub type Type = ::std::os::raw::c_uint;
pub const BPF_PROG_TYPE_UNSPEC: Type = 0;
pub const BPF_PROG_TYPE_SOCKET_FILTER: Type = 1;
pub const BPF_PROG_TYPE_KPROBE: Type = 2;
pub const BPF_PROG_TYPE_SCHED_CLS: Type = 3;
pub const BPF_PROG_TYPE_SCHED_ACT: Type = 4;
pub const BPF_PROG_TYPE_TRACEPOINT: Type = 5;
pub const BPF_PROG_TYPE_XDP: Type = 6;
pub const BPF_PROG_TYPE_PERF_EVENT: Type = 7;
pub const BPF_PROG_TYPE_CGROUP_SKB: Type = 8;
pub const BPF_PROG_TYPE_CGROUP_SOCK: Type = 9;
pub const BPF_PROG_TYPE_LWT_IN: Type = 10;
pub const BPF_PROG_TYPE_LWT_OUT: Type = 11;
pub const BPF_PROG_TYPE_LWT_XMIT: Type = 12;
pub const BPF_PROG_TYPE_SOCK_OPS: Type = 13;
pub const BPF_PROG_TYPE_SK_SKB: Type = 14;
pub const BPF_PROG_TYPE_CGROUP_DEVICE: Type = 15;
pub const BPF_PROG_TYPE_SK_MSG: Type = 16;
pub const BPF_PROG_TYPE_RAW_TRACEPOINT: Type = 17;
pub const BPF_PROG_TYPE_CGROUP_SOCK_ADDR: Type = 18;
pub const BPF_PROG_TYPE_LWT_SEG6LOCAL: Type = 19;
pub const BPF_PROG_TYPE_LIRC_MODE2: Type = 20;
pub const BPF_PROG_TYPE_SK_REUSEPORT: Type = 21;
pub const BPF_PROG_TYPE_FLOW_DISSECTOR: Type = 22;
pub const BPF_PROG_TYPE_CGROUP_SYSCTL: Type = 23;
pub const BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE: Type = 24;
pub const BPF_PROG_TYPE_CGROUP_SOCKOPT: Type = 25;
pub const BPF_PROG_TYPE_TRACING: Type = 26;
pub const BPF_PROG_TYPE_STRUCT_OPS: Type = 27;
pub const BPF_PROG_TYPE_EXT: Type = 28;
pub const BPF_PROG_TYPE_LSM: Type = 29;
pub const BPF_PROG_TYPE_SK_LOOKUP: Type = 30;
}
pub mod bpf_attach_type {
pub type Type = ::std::os::raw::c_uint;
pub const BPF_CGROUP_INET_INGRESS: Type = 0;
pub const BPF_CGROUP_INET_EGRESS: Type = 1;
pub const BPF_CGROUP_INET_SOCK_CREATE: Type = 2;
pub const BPF_CGROUP_SOCK_OPS: Type = 3;
pub const BPF_SK_SKB_STREAM_PARSER: Type = 4;
pub const BPF_SK_SKB_STREAM_VERDICT: Type = 5;
pub const BPF_CGROUP_DEVICE: Type = 6;
pub const BPF_SK_MSG_VERDICT: Type = 7;
pub const BPF_CGROUP_INET4_BIND: Type = 8;
pub const BPF_CGROUP_INET6_BIND: Type = 9;
pub const BPF_CGROUP_INET4_CONNECT: Type = 10;
pub const BPF_CGROUP_INET6_CONNECT: Type = 11;
pub const BPF_CGROUP_INET4_POST_BIND: Type = 12;
pub const BPF_CGROUP_INET6_POST_BIND: Type = 13;
pub const BPF_CGROUP_UDP4_SENDMSG: Type = 14;
pub const BPF_CGROUP_UDP6_SENDMSG: Type = 15;
pub const BPF_LIRC_MODE2: Type = 16;
pub const BPF_FLOW_DISSECTOR: Type = 17;
pub const BPF_CGROUP_SYSCTL: Type = 18;
pub const BPF_CGROUP_UDP4_RECVMSG: Type = 19;
pub const BPF_CGROUP_UDP6_RECVMSG: Type = 20;
pub const BPF_CGROUP_GETSOCKOPT: Type = 21;
pub const BPF_CGROUP_SETSOCKOPT: Type = 22;
pub const BPF_TRACE_RAW_TP: Type = 23;
pub const BPF_TRACE_FENTRY: Type = 24;
pub const BPF_TRACE_FEXIT: Type = 25;
pub const BPF_MODIFY_RETURN: Type = 26;
pub const BPF_LSM_MAC: Type = 27;
pub const BPF_TRACE_ITER: Type = 28;
pub const BPF_CGROUP_INET4_GETPEERNAME: Type = 29;
pub const BPF_CGROUP_INET6_GETPEERNAME: Type = 30;
pub const BPF_CGROUP_INET4_GETSOCKNAME: Type = 31;
pub const BPF_CGROUP_INET6_GETSOCKNAME: Type = 32;
pub const BPF_XDP_DEVMAP: Type = 33;
pub const BPF_CGROUP_INET_SOCK_RELEASE: Type = 34;
pub const BPF_XDP_CPUMAP: Type = 35;
pub const BPF_SK_LOOKUP: Type = 36;
pub const BPF_XDP: Type = 37;
pub const __MAX_BPF_ATTACH_TYPE: Type = 38;
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr {
pub __bindgen_anon_1: bpf_attr__bindgen_ty_1,
pub __bindgen_anon_2: bpf_attr__bindgen_ty_2,
pub batch: bpf_attr__bindgen_ty_3,
pub __bindgen_anon_3: bpf_attr__bindgen_ty_4,
pub __bindgen_anon_4: bpf_attr__bindgen_ty_5,
pub __bindgen_anon_5: bpf_attr__bindgen_ty_6,
pub test: bpf_attr__bindgen_ty_7,
pub __bindgen_anon_6: bpf_attr__bindgen_ty_8,
pub info: bpf_attr__bindgen_ty_9,
pub query: bpf_attr__bindgen_ty_10,
pub raw_tracepoint: bpf_attr__bindgen_ty_11,
pub __bindgen_anon_7: bpf_attr__bindgen_ty_12,
pub task_fd_query: bpf_attr__bindgen_ty_13,
pub link_create: bpf_attr__bindgen_ty_14,
pub link_update: bpf_attr__bindgen_ty_15,
pub link_detach: bpf_attr__bindgen_ty_16,
pub enable_stats: bpf_attr__bindgen_ty_17,
pub iter_create: bpf_attr__bindgen_ty_18,
pub prog_bind_map: bpf_attr__bindgen_ty_19,
_bindgen_union_align: [u64; 15usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_1 {
pub map_type: __u32,
pub key_size: __u32,
pub value_size: __u32,
pub max_entries: __u32,
pub map_flags: __u32,
pub inner_map_fd: __u32,
pub numa_node: __u32,
pub map_name: [::std::os::raw::c_char; 16usize],
pub map_ifindex: __u32,
pub btf_fd: __u32,
pub btf_key_type_id: __u32,
pub btf_value_type_id: __u32,
pub btf_vmlinux_value_type_id: __u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct bpf_attr__bindgen_ty_2 {
pub map_fd: __u32,
pub key: __u64,
pub __bindgen_anon_1: bpf_attr__bindgen_ty_2__bindgen_ty_1,
pub flags: __u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr__bindgen_ty_2__bindgen_ty_1 {
pub value: __u64,
pub next_key: __u64,
_bindgen_union_align: u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_3 {
pub in_batch: __u64,
pub out_batch: __u64,
pub keys: __u64,
pub values: __u64,
pub count: __u32,
pub map_fd: __u32,
pub elem_flags: __u64,
pub flags: __u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct bpf_attr__bindgen_ty_4 {
pub prog_type: __u32,
pub insn_cnt: __u32,
pub insns: __u64,
pub license: __u64,
pub log_level: __u32,
pub log_size: __u32,
pub log_buf: __u64,
pub kern_version: __u32,
pub prog_flags: __u32,
pub prog_name: [::std::os::raw::c_char; 16usize],
pub prog_ifindex: __u32,
pub expected_attach_type: __u32,
pub prog_btf_fd: __u32,
pub func_info_rec_size: __u32,
pub func_info: __u64,
pub func_info_cnt: __u32,
pub line_info_rec_size: __u32,
pub line_info: __u64,
pub line_info_cnt: __u32,
pub attach_btf_id: __u32,
pub __bindgen_anon_1: bpf_attr__bindgen_ty_4__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr__bindgen_ty_4__bindgen_ty_1 {
pub attach_prog_fd: __u32,
pub attach_btf_obj_fd: __u32,
_bindgen_union_align: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_5 {
pub pathname: __u64,
pub bpf_fd: __u32,
pub file_flags: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_6 {
pub target_fd: __u32,
pub attach_bpf_fd: __u32,
pub attach_type: __u32,
pub attach_flags: __u32,
pub replace_bpf_fd: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_7 {
pub prog_fd: __u32,
pub retval: __u32,
pub data_size_in: __u32,
pub data_size_out: __u32,
pub data_in: __u64,
pub data_out: __u64,
pub repeat: __u32,
pub duration: __u32,
pub ctx_size_in: __u32,
pub ctx_size_out: __u32,
pub ctx_in: __u64,
pub ctx_out: __u64,
pub flags: __u32,
pub cpu: __u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct bpf_attr__bindgen_ty_8 {
pub __bindgen_anon_1: bpf_attr__bindgen_ty_8__bindgen_ty_1,
pub next_id: __u32,
pub open_flags: __u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr__bindgen_ty_8__bindgen_ty_1 {
pub start_id: __u32,
pub prog_id: __u32,
pub map_id: __u32,
pub btf_id: __u32,
pub link_id: __u32,
_bindgen_union_align: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_9 {
pub bpf_fd: __u32,
pub info_len: __u32,
pub info: __u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_10 {
pub target_fd: __u32,
pub attach_type: __u32,
pub query_flags: __u32,
pub attach_flags: __u32,
pub prog_ids: __u64,
pub prog_cnt: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_11 {
pub name: __u64,
pub prog_fd: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_12 {
pub btf: __u64,
pub btf_log_buf: __u64,
pub btf_size: __u32,
pub btf_log_size: __u32,
pub btf_log_level: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_13 {
pub pid: __u32,
pub fd: __u32,
pub flags: __u32,
pub buf_len: __u32,
pub buf: __u64,
pub prog_id: __u32,
pub fd_type: __u32,
pub probe_offset: __u64,
pub probe_addr: __u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct bpf_attr__bindgen_ty_14 {
pub prog_fd: __u32,
pub __bindgen_anon_1: bpf_attr__bindgen_ty_14__bindgen_ty_1,
pub attach_type: __u32,
pub flags: __u32,
pub __bindgen_anon_2: bpf_attr__bindgen_ty_14__bindgen_ty_2,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr__bindgen_ty_14__bindgen_ty_1 {
pub target_fd: __u32,
pub target_ifindex: __u32,
_bindgen_union_align: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union bpf_attr__bindgen_ty_14__bindgen_ty_2 {
pub target_btf_id: __u32,
pub __bindgen_anon_1: bpf_attr__bindgen_ty_14__bindgen_ty_2__bindgen_ty_1,
_bindgen_union_align: [u64; 2usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_14__bindgen_ty_2__bindgen_ty_1 {
pub iter_info: __u64,
pub iter_info_len: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_15 {
pub link_fd: __u32,
pub new_prog_fd: __u32,
pub flags: __u32,
pub old_prog_fd: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_16 {
pub link_fd: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_17 {
pub type_: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_18 {
pub link_fd: __u32,
pub flags: __u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bpf_attr__bindgen_ty_19 {
pub prog_fd: __u32,
pub map_fd: __u32,
pub flags: __u32,
}

@ -0,0 +1,9 @@
#![allow(dead_code, non_camel_case_types, non_snake_case)]
// FIXME: generate for x86_64 and aarch64
mod bpf_bindings;
mod perf_bindings;
pub use bpf_bindings::*;
pub use perf_bindings::*;

@ -0,0 +1,917 @@
/* automatically generated by rust-bindgen 0.55.1 */
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct __BindgenBitfieldUnit<Storage, Align> {
storage: Storage,
align: [Align; 0],
}
impl<Storage, Align> __BindgenBitfieldUnit<Storage, Align> {
#[inline]
pub const fn new(storage: Storage) -> Self {
Self { storage, align: [] }
}
}
impl<Storage, Align> __BindgenBitfieldUnit<Storage, Align>
where
Storage: AsRef<[u8]> + AsMut<[u8]>,
{
#[inline]
pub fn get_bit(&self, index: usize) -> bool {
debug_assert!(index / 8 < self.storage.as_ref().len());
let byte_index = index / 8;
let byte = self.storage.as_ref()[byte_index];
let bit_index = if cfg!(target_endian = "big") {
7 - (index % 8)
} else {
index % 8
};
let mask = 1 << bit_index;
byte & mask == mask
}
#[inline]
pub fn set_bit(&mut self, index: usize, val: bool) {
debug_assert!(index / 8 < self.storage.as_ref().len());
let byte_index = index / 8;
let byte = &mut self.storage.as_mut()[byte_index];
let bit_index = if cfg!(target_endian = "big") {
7 - (index % 8)
} else {
index % 8
};
let mask = 1 << bit_index;
if val {
*byte |= mask;
} else {
*byte &= !mask;
}
}
#[inline]
pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 {
debug_assert!(bit_width <= 64);
debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
let mut val = 0;
for i in 0..(bit_width as usize) {
if self.get_bit(i + bit_offset) {
let index = if cfg!(target_endian = "big") {
bit_width as usize - 1 - i
} else {
i
};
val |= 1 << index;
}
}
val
}
#[inline]
pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) {
debug_assert!(bit_width <= 64);
debug_assert!(bit_offset / 8 < self.storage.as_ref().len());
debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len());
for i in 0..(bit_width as usize) {
let mask = 1 << i;
let val_bit_is_set = val & mask == mask;
let index = if cfg!(target_endian = "big") {
bit_width as usize - 1 - i
} else {
i
};
self.set_bit(index + bit_offset, val_bit_is_set);
}
}
}
pub const PERF_FLAG_FD_NO_GROUP: u32 = 1;
pub const PERF_FLAG_FD_OUTPUT: u32 = 2;
pub const PERF_FLAG_PID_CGROUP: u32 = 4;
pub const PERF_FLAG_FD_CLOEXEC: u32 = 8;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __u16 = ::std::os::raw::c_ushort;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __s64 = ::std::os::raw::c_longlong;
pub type __u64 = ::std::os::raw::c_ulonglong;
pub mod perf_type_id {
pub type Type = ::std::os::raw::c_uint;
pub const PERF_TYPE_HARDWARE: Type = 0;
pub const PERF_TYPE_SOFTWARE: Type = 1;
pub const PERF_TYPE_TRACEPOINT: Type = 2;
pub const PERF_TYPE_HW_CACHE: Type = 3;
pub const PERF_TYPE_RAW: Type = 4;
pub const PERF_TYPE_BREAKPOINT: Type = 5;
pub const PERF_TYPE_MAX: Type = 6;
}
pub mod perf_sw_ids {
pub type Type = ::std::os::raw::c_uint;
pub const PERF_COUNT_SW_CPU_CLOCK: Type = 0;
pub const PERF_COUNT_SW_TASK_CLOCK: Type = 1;
pub const PERF_COUNT_SW_PAGE_FAULTS: Type = 2;
pub const PERF_COUNT_SW_CONTEXT_SWITCHES: Type = 3;
pub const PERF_COUNT_SW_CPU_MIGRATIONS: Type = 4;
pub const PERF_COUNT_SW_PAGE_FAULTS_MIN: Type = 5;
pub const PERF_COUNT_SW_PAGE_FAULTS_MAJ: Type = 6;
pub const PERF_COUNT_SW_ALIGNMENT_FAULTS: Type = 7;
pub const PERF_COUNT_SW_EMULATION_FAULTS: Type = 8;
pub const PERF_COUNT_SW_DUMMY: Type = 9;
pub const PERF_COUNT_SW_BPF_OUTPUT: Type = 10;
pub const PERF_COUNT_SW_MAX: Type = 11;
}
pub mod perf_event_sample_format {
pub type Type = ::std::os::raw::c_ulong;
pub const PERF_SAMPLE_IP: Type = 1;
pub const PERF_SAMPLE_TID: Type = 2;
pub const PERF_SAMPLE_TIME: Type = 4;
pub const PERF_SAMPLE_ADDR: Type = 8;
pub const PERF_SAMPLE_READ: Type = 16;
pub const PERF_SAMPLE_CALLCHAIN: Type = 32;
pub const PERF_SAMPLE_ID: Type = 64;
pub const PERF_SAMPLE_CPU: Type = 128;
pub const PERF_SAMPLE_PERIOD: Type = 256;
pub const PERF_SAMPLE_STREAM_ID: Type = 512;
pub const PERF_SAMPLE_RAW: Type = 1024;
pub const PERF_SAMPLE_BRANCH_STACK: Type = 2048;
pub const PERF_SAMPLE_REGS_USER: Type = 4096;
pub const PERF_SAMPLE_STACK_USER: Type = 8192;
pub const PERF_SAMPLE_WEIGHT: Type = 16384;
pub const PERF_SAMPLE_DATA_SRC: Type = 32768;
pub const PERF_SAMPLE_IDENTIFIER: Type = 65536;
pub const PERF_SAMPLE_TRANSACTION: Type = 131072;
pub const PERF_SAMPLE_REGS_INTR: Type = 262144;
pub const PERF_SAMPLE_PHYS_ADDR: Type = 524288;
pub const PERF_SAMPLE_AUX: Type = 1048576;
pub const PERF_SAMPLE_CGROUP: Type = 2097152;
pub const PERF_SAMPLE_MAX: Type = 4194304;
pub const __PERF_SAMPLE_CALLCHAIN_EARLY: Type = 9223372036854775808;
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct perf_event_attr {
pub type_: __u32,
pub size: __u32,
pub config: __u64,
pub __bindgen_anon_1: perf_event_attr__bindgen_ty_1,
pub sample_type: __u64,
pub read_format: __u64,
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 8usize], u32>,
pub __bindgen_anon_2: perf_event_attr__bindgen_ty_2,
pub bp_type: __u32,
pub __bindgen_anon_3: perf_event_attr__bindgen_ty_3,
pub __bindgen_anon_4: perf_event_attr__bindgen_ty_4,
pub branch_sample_type: __u64,
pub sample_regs_user: __u64,
pub sample_stack_user: __u32,
pub clockid: __s32,
pub sample_regs_intr: __u64,
pub aux_watermark: __u32,
pub sample_max_stack: __u16,
pub __reserved_2: __u16,
pub aux_sample_size: __u32,
pub __reserved_3: __u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union perf_event_attr__bindgen_ty_1 {
pub sample_period: __u64,
pub sample_freq: __u64,
_bindgen_union_align: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union perf_event_attr__bindgen_ty_2 {
pub wakeup_events: __u32,
pub wakeup_watermark: __u32,
_bindgen_union_align: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union perf_event_attr__bindgen_ty_3 {
pub bp_addr: __u64,
pub kprobe_func: __u64,
pub uprobe_path: __u64,
pub config1: __u64,
_bindgen_union_align: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union perf_event_attr__bindgen_ty_4 {
pub bp_len: __u64,
pub kprobe_addr: __u64,
pub probe_offset: __u64,
pub config2: __u64,
_bindgen_union_align: u64,
}
impl perf_event_attr {
#[inline]
pub fn disabled(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u64) }
}
#[inline]
pub fn set_disabled(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(0usize, 1u8, val as u64)
}
}
#[inline]
pub fn inherit(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u64) }
}
#[inline]
pub fn set_inherit(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(1usize, 1u8, val as u64)
}
}
#[inline]
pub fn pinned(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u64) }
}
#[inline]
pub fn set_pinned(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(2usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclusive(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclusive(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(3usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_user(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_user(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(4usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_kernel(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_kernel(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(5usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_hv(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(6usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_hv(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(6usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_idle(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_idle(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(7usize, 1u8, val as u64)
}
}
#[inline]
pub fn mmap(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u64) }
}
#[inline]
pub fn set_mmap(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(8usize, 1u8, val as u64)
}
}
#[inline]
pub fn comm(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u64) }
}
#[inline]
pub fn set_comm(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(9usize, 1u8, val as u64)
}
}
#[inline]
pub fn freq(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u64) }
}
#[inline]
pub fn set_freq(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(10usize, 1u8, val as u64)
}
}
#[inline]
pub fn inherit_stat(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u64) }
}
#[inline]
pub fn set_inherit_stat(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(11usize, 1u8, val as u64)
}
}
#[inline]
pub fn enable_on_exec(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(12usize, 1u8) as u64) }
}
#[inline]
pub fn set_enable_on_exec(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(12usize, 1u8, val as u64)
}
}
#[inline]
pub fn task(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(13usize, 1u8) as u64) }
}
#[inline]
pub fn set_task(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(13usize, 1u8, val as u64)
}
}
#[inline]
pub fn watermark(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(14usize, 1u8) as u64) }
}
#[inline]
pub fn set_watermark(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(14usize, 1u8, val as u64)
}
}
#[inline]
pub fn precise_ip(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(15usize, 2u8) as u64) }
}
#[inline]
pub fn set_precise_ip(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(15usize, 2u8, val as u64)
}
}
#[inline]
pub fn mmap_data(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(17usize, 1u8) as u64) }
}
#[inline]
pub fn set_mmap_data(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(17usize, 1u8, val as u64)
}
}
#[inline]
pub fn sample_id_all(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(18usize, 1u8) as u64) }
}
#[inline]
pub fn set_sample_id_all(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(18usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_host(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(19usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_host(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(19usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_guest(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(20usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_guest(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(20usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_callchain_kernel(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(21usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_callchain_kernel(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(21usize, 1u8, val as u64)
}
}
#[inline]
pub fn exclude_callchain_user(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(22usize, 1u8) as u64) }
}
#[inline]
pub fn set_exclude_callchain_user(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(22usize, 1u8, val as u64)
}
}
#[inline]
pub fn mmap2(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(23usize, 1u8) as u64) }
}
#[inline]
pub fn set_mmap2(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(23usize, 1u8, val as u64)
}
}
#[inline]
pub fn comm_exec(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(24usize, 1u8) as u64) }
}
#[inline]
pub fn set_comm_exec(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(24usize, 1u8, val as u64)
}
}
#[inline]
pub fn use_clockid(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(25usize, 1u8) as u64) }
}
#[inline]
pub fn set_use_clockid(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(25usize, 1u8, val as u64)
}
}
#[inline]
pub fn context_switch(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(26usize, 1u8) as u64) }
}
#[inline]
pub fn set_context_switch(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(26usize, 1u8, val as u64)
}
}
#[inline]
pub fn write_backward(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(27usize, 1u8) as u64) }
}
#[inline]
pub fn set_write_backward(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(27usize, 1u8, val as u64)
}
}
#[inline]
pub fn namespaces(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(28usize, 1u8) as u64) }
}
#[inline]
pub fn set_namespaces(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(28usize, 1u8, val as u64)
}
}
#[inline]
pub fn ksymbol(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(29usize, 1u8) as u64) }
}
#[inline]
pub fn set_ksymbol(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(29usize, 1u8, val as u64)
}
}
#[inline]
pub fn bpf_event(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(30usize, 1u8) as u64) }
}
#[inline]
pub fn set_bpf_event(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(30usize, 1u8, val as u64)
}
}
#[inline]
pub fn aux_output(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(31usize, 1u8) as u64) }
}
#[inline]
pub fn set_aux_output(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(31usize, 1u8, val as u64)
}
}
#[inline]
pub fn cgroup(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(32usize, 1u8) as u64) }
}
#[inline]
pub fn set_cgroup(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(32usize, 1u8, val as u64)
}
}
#[inline]
pub fn __reserved_1(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(33usize, 31u8) as u64) }
}
#[inline]
pub fn set___reserved_1(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(33usize, 31u8, val as u64)
}
}
#[inline]
pub fn new_bitfield_1(
disabled: __u64,
inherit: __u64,
pinned: __u64,
exclusive: __u64,
exclude_user: __u64,
exclude_kernel: __u64,
exclude_hv: __u64,
exclude_idle: __u64,
mmap: __u64,
comm: __u64,
freq: __u64,
inherit_stat: __u64,
enable_on_exec: __u64,
task: __u64,
watermark: __u64,
precise_ip: __u64,
mmap_data: __u64,
sample_id_all: __u64,
exclude_host: __u64,
exclude_guest: __u64,
exclude_callchain_kernel: __u64,
exclude_callchain_user: __u64,
mmap2: __u64,
comm_exec: __u64,
use_clockid: __u64,
context_switch: __u64,
write_backward: __u64,
namespaces: __u64,
ksymbol: __u64,
bpf_event: __u64,
aux_output: __u64,
cgroup: __u64,
__reserved_1: __u64,
) -> __BindgenBitfieldUnit<[u8; 8usize], u32> {
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 8usize], u32> =
Default::default();
__bindgen_bitfield_unit.set(0usize, 1u8, {
let disabled: u64 = unsafe { ::std::mem::transmute(disabled) };
disabled as u64
});
__bindgen_bitfield_unit.set(1usize, 1u8, {
let inherit: u64 = unsafe { ::std::mem::transmute(inherit) };
inherit as u64
});
__bindgen_bitfield_unit.set(2usize, 1u8, {
let pinned: u64 = unsafe { ::std::mem::transmute(pinned) };
pinned as u64
});
__bindgen_bitfield_unit.set(3usize, 1u8, {
let exclusive: u64 = unsafe { ::std::mem::transmute(exclusive) };
exclusive as u64
});
__bindgen_bitfield_unit.set(4usize, 1u8, {
let exclude_user: u64 = unsafe { ::std::mem::transmute(exclude_user) };
exclude_user as u64
});
__bindgen_bitfield_unit.set(5usize, 1u8, {
let exclude_kernel: u64 = unsafe { ::std::mem::transmute(exclude_kernel) };
exclude_kernel as u64
});
__bindgen_bitfield_unit.set(6usize, 1u8, {
let exclude_hv: u64 = unsafe { ::std::mem::transmute(exclude_hv) };
exclude_hv as u64
});
__bindgen_bitfield_unit.set(7usize, 1u8, {
let exclude_idle: u64 = unsafe { ::std::mem::transmute(exclude_idle) };
exclude_idle as u64
});
__bindgen_bitfield_unit.set(8usize, 1u8, {
let mmap: u64 = unsafe { ::std::mem::transmute(mmap) };
mmap as u64
});
__bindgen_bitfield_unit.set(9usize, 1u8, {
let comm: u64 = unsafe { ::std::mem::transmute(comm) };
comm as u64
});
__bindgen_bitfield_unit.set(10usize, 1u8, {
let freq: u64 = unsafe { ::std::mem::transmute(freq) };
freq as u64
});
__bindgen_bitfield_unit.set(11usize, 1u8, {
let inherit_stat: u64 = unsafe { ::std::mem::transmute(inherit_stat) };
inherit_stat as u64
});
__bindgen_bitfield_unit.set(12usize, 1u8, {
let enable_on_exec: u64 = unsafe { ::std::mem::transmute(enable_on_exec) };
enable_on_exec as u64
});
__bindgen_bitfield_unit.set(13usize, 1u8, {
let task: u64 = unsafe { ::std::mem::transmute(task) };
task as u64
});
__bindgen_bitfield_unit.set(14usize, 1u8, {
let watermark: u64 = unsafe { ::std::mem::transmute(watermark) };
watermark as u64
});
__bindgen_bitfield_unit.set(15usize, 2u8, {
let precise_ip: u64 = unsafe { ::std::mem::transmute(precise_ip) };
precise_ip as u64
});
__bindgen_bitfield_unit.set(17usize, 1u8, {
let mmap_data: u64 = unsafe { ::std::mem::transmute(mmap_data) };
mmap_data as u64
});
__bindgen_bitfield_unit.set(18usize, 1u8, {
let sample_id_all: u64 = unsafe { ::std::mem::transmute(sample_id_all) };
sample_id_all as u64
});
__bindgen_bitfield_unit.set(19usize, 1u8, {
let exclude_host: u64 = unsafe { ::std::mem::transmute(exclude_host) };
exclude_host as u64
});
__bindgen_bitfield_unit.set(20usize, 1u8, {
let exclude_guest: u64 = unsafe { ::std::mem::transmute(exclude_guest) };
exclude_guest as u64
});
__bindgen_bitfield_unit.set(21usize, 1u8, {
let exclude_callchain_kernel: u64 =
unsafe { ::std::mem::transmute(exclude_callchain_kernel) };
exclude_callchain_kernel as u64
});
__bindgen_bitfield_unit.set(22usize, 1u8, {
let exclude_callchain_user: u64 =
unsafe { ::std::mem::transmute(exclude_callchain_user) };
exclude_callchain_user as u64
});
__bindgen_bitfield_unit.set(23usize, 1u8, {
let mmap2: u64 = unsafe { ::std::mem::transmute(mmap2) };
mmap2 as u64
});
__bindgen_bitfield_unit.set(24usize, 1u8, {
let comm_exec: u64 = unsafe { ::std::mem::transmute(comm_exec) };
comm_exec as u64
});
__bindgen_bitfield_unit.set(25usize, 1u8, {
let use_clockid: u64 = unsafe { ::std::mem::transmute(use_clockid) };
use_clockid as u64
});
__bindgen_bitfield_unit.set(26usize, 1u8, {
let context_switch: u64 = unsafe { ::std::mem::transmute(context_switch) };
context_switch as u64
});
__bindgen_bitfield_unit.set(27usize, 1u8, {
let write_backward: u64 = unsafe { ::std::mem::transmute(write_backward) };
write_backward as u64
});
__bindgen_bitfield_unit.set(28usize, 1u8, {
let namespaces: u64 = unsafe { ::std::mem::transmute(namespaces) };
namespaces as u64
});
__bindgen_bitfield_unit.set(29usize, 1u8, {
let ksymbol: u64 = unsafe { ::std::mem::transmute(ksymbol) };
ksymbol as u64
});
__bindgen_bitfield_unit.set(30usize, 1u8, {
let bpf_event: u64 = unsafe { ::std::mem::transmute(bpf_event) };
bpf_event as u64
});
__bindgen_bitfield_unit.set(31usize, 1u8, {
let aux_output: u64 = unsafe { ::std::mem::transmute(aux_output) };
aux_output as u64
});
__bindgen_bitfield_unit.set(32usize, 1u8, {
let cgroup: u64 = unsafe { ::std::mem::transmute(cgroup) };
cgroup as u64
});
__bindgen_bitfield_unit.set(33usize, 31u8, {
let __reserved_1: u64 = unsafe { ::std::mem::transmute(__reserved_1) };
__reserved_1 as u64
});
__bindgen_bitfield_unit
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct perf_event_mmap_page {
pub version: __u32,
pub compat_version: __u32,
pub lock: __u32,
pub index: __u32,
pub offset: __s64,
pub time_enabled: __u64,
pub time_running: __u64,
pub __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1,
pub pmc_width: __u16,
pub time_shift: __u16,
pub time_mult: __u32,
pub time_offset: __u64,
pub time_zero: __u64,
pub size: __u32,
pub __reserved: [__u8; 948usize],
pub data_head: __u64,
pub data_tail: __u64,
pub data_offset: __u64,
pub data_size: __u64,
pub aux_head: __u64,
pub aux_tail: __u64,
pub aux_offset: __u64,
pub aux_size: __u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union perf_event_mmap_page__bindgen_ty_1 {
pub capabilities: __u64,
pub __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1__bindgen_ty_1,
_bindgen_union_align: u64,
}
#[repr(C)]
#[repr(align(8))]
#[derive(Debug, Copy, Clone)]
pub struct perf_event_mmap_page__bindgen_ty_1__bindgen_ty_1 {
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 8usize], u64>,
}
impl perf_event_mmap_page__bindgen_ty_1__bindgen_ty_1 {
#[inline]
pub fn cap_bit0(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u64) }
}
#[inline]
pub fn set_cap_bit0(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(0usize, 1u8, val as u64)
}
}
#[inline]
pub fn cap_bit0_is_deprecated(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u64) }
}
#[inline]
pub fn set_cap_bit0_is_deprecated(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(1usize, 1u8, val as u64)
}
}
#[inline]
pub fn cap_user_rdpmc(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u64) }
}
#[inline]
pub fn set_cap_user_rdpmc(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(2usize, 1u8, val as u64)
}
}
#[inline]
pub fn cap_user_time(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u64) }
}
#[inline]
pub fn set_cap_user_time(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(3usize, 1u8, val as u64)
}
}
#[inline]
pub fn cap_user_time_zero(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u64) }
}
#[inline]
pub fn set_cap_user_time_zero(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(4usize, 1u8, val as u64)
}
}
#[inline]
pub fn cap_____res(&self) -> __u64 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 59u8) as u64) }
}
#[inline]
pub fn set_cap_____res(&mut self, val: __u64) {
unsafe {
let val: u64 = ::std::mem::transmute(val);
self._bitfield_1.set(5usize, 59u8, val as u64)
}
}
#[inline]
pub fn new_bitfield_1(
cap_bit0: __u64,
cap_bit0_is_deprecated: __u64,
cap_user_rdpmc: __u64,
cap_user_time: __u64,
cap_user_time_zero: __u64,
cap_____res: __u64,
) -> __BindgenBitfieldUnit<[u8; 8usize], u64> {
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 8usize], u64> =
Default::default();
__bindgen_bitfield_unit.set(0usize, 1u8, {
let cap_bit0: u64 = unsafe { ::std::mem::transmute(cap_bit0) };
cap_bit0 as u64
});
__bindgen_bitfield_unit.set(1usize, 1u8, {
let cap_bit0_is_deprecated: u64 =
unsafe { ::std::mem::transmute(cap_bit0_is_deprecated) };
cap_bit0_is_deprecated as u64
});
__bindgen_bitfield_unit.set(2usize, 1u8, {
let cap_user_rdpmc: u64 = unsafe { ::std::mem::transmute(cap_user_rdpmc) };
cap_user_rdpmc as u64
});
__bindgen_bitfield_unit.set(3usize, 1u8, {
let cap_user_time: u64 = unsafe { ::std::mem::transmute(cap_user_time) };
cap_user_time as u64
});
__bindgen_bitfield_unit.set(4usize, 1u8, {
let cap_user_time_zero: u64 = unsafe { ::std::mem::transmute(cap_user_time_zero) };
cap_user_time_zero as u64
});
__bindgen_bitfield_unit.set(5usize, 59u8, {
let cap_____res: u64 = unsafe { ::std::mem::transmute(cap_____res) };
cap_____res as u64
});
__bindgen_bitfield_unit
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct perf_event_header {
pub type_: __u32,
pub misc: __u16,
pub size: __u16,
}
pub mod perf_event_type {
pub type Type = ::std::os::raw::c_uint;
pub const PERF_RECORD_MMAP: Type = 1;
pub const PERF_RECORD_LOST: Type = 2;
pub const PERF_RECORD_COMM: Type = 3;
pub const PERF_RECORD_EXIT: Type = 4;
pub const PERF_RECORD_THROTTLE: Type = 5;
pub const PERF_RECORD_UNTHROTTLE: Type = 6;
pub const PERF_RECORD_FORK: Type = 7;
pub const PERF_RECORD_READ: Type = 8;
pub const PERF_RECORD_SAMPLE: Type = 9;
pub const PERF_RECORD_MMAP2: Type = 10;
pub const PERF_RECORD_AUX: Type = 11;
pub const PERF_RECORD_ITRACE_START: Type = 12;
pub const PERF_RECORD_LOST_SAMPLES: Type = 13;
pub const PERF_RECORD_SWITCH: Type = 14;
pub const PERF_RECORD_SWITCH_CPU_WIDE: Type = 15;
pub const PERF_RECORD_NAMESPACES: Type = 16;
pub const PERF_RECORD_KSYMBOL: Type = 17;
pub const PERF_RECORD_BPF_EVENT: Type = 18;
pub const PERF_RECORD_CGROUP: Type = 19;
pub const PERF_RECORD_MAX: Type = 20;
}

@ -0,0 +1,13 @@
#![deny(clippy::all)]
#[macro_use]
extern crate lazy_static;
mod bpf;
mod generated;
pub mod maps;
mod obj;
pub mod programs;
mod syscalls;
pub use bpf::*;

@ -0,0 +1,725 @@
use std::{convert::TryFrom, marker::PhantomData, mem};
use crate::{
generated::bpf_map_type::BPF_MAP_TYPE_HASH,
syscalls::{
bpf_map_delete_elem, bpf_map_get_next_key, bpf_map_lookup_and_delete_elem,
bpf_map_lookup_elem, bpf_map_update_elem,
},
};
use super::{Map, MapError};
use crate::Pod;
pub struct HashMap<T: AsRef<Map>, K, V> {
inner: T,
_k: PhantomData<K>,
_v: PhantomData<V>,
}
impl<T: AsRef<Map>, K: Pod, V: Pod> HashMap<T, K, V> {
pub fn new(map: T) -> Result<HashMap<T, K, V>, MapError> {
let inner = map.as_ref();
let map_type = inner.obj.def.map_type;
if map_type != BPF_MAP_TYPE_HASH {
return Err(MapError::InvalidMapType {
map_type: map_type as u32,
})?;
}
let size = mem::size_of::<K>();
let expected = inner.obj.def.key_size as usize;
if size != expected {
return Err(MapError::InvalidKeySize { size, expected });
}
let size = mem::size_of::<V>();
let expected = inner.obj.def.value_size as usize;
if size != expected {
return Err(MapError::InvalidValueSize { size, expected });
}
Ok(HashMap {
inner: map,
_k: PhantomData,
_v: PhantomData,
})
}
pub unsafe fn get(&self, key: &K, flags: u64) -> Result<Option<V>, MapError> {
let fd = self.inner.as_ref().fd_or_err()?;
bpf_map_lookup_elem(fd, key, flags)
.map_err(|(code, io_error)| MapError::LookupElementFailed { code, io_error })
}
pub unsafe fn iter<'coll>(&'coll self) -> MapIter<'coll, T, K, V> {
MapIter::new(self)
}
pub unsafe fn keys<'coll>(&'coll self) -> MapKeys<'coll, T, K, V> {
MapKeys::new(self)
}
}
impl<T: AsRef<Map> + AsMut<Map>, K: Pod, V: Pod> HashMap<T, K, V> {
pub fn insert(&mut self, key: K, value: V, flags: u64) -> Result<(), MapError> {
let fd = self.inner.as_ref().fd_or_err()?;
bpf_map_update_elem(fd, &key, &value, flags)
.map_err(|(code, io_error)| MapError::UpdateElementFailed { code, io_error })?;
Ok(())
}
pub unsafe fn pop(&mut self, key: &K) -> Result<Option<V>, MapError> {
let fd = self.inner.as_ref().fd_or_err()?;
bpf_map_lookup_and_delete_elem(fd, key)
.map_err(|(code, io_error)| MapError::LookupAndDeleteElementFailed { code, io_error })
}
pub fn remove(&mut self, key: &K) -> Result<(), MapError> {
let fd = self.inner.as_ref().fd_or_err()?;
bpf_map_delete_elem(fd, key)
.map(|_| ())
.map_err(|(code, io_error)| MapError::DeleteElementFailed { code, io_error })
}
}
impl<'a, K: Pod, V: Pod> TryFrom<&'a Map> for HashMap<&'a Map, K, V> {
type Error = MapError;
fn try_from(inner: &'a Map) -> Result<HashMap<&'a Map, K, V>, MapError> {
HashMap::new(inner)
}
}
impl<'a, K: Pod, V: Pod> TryFrom<&'a mut Map> for HashMap<&'a mut Map, K, V> {
type Error = MapError;
fn try_from(inner: &'a mut Map) -> Result<HashMap<&'a mut Map, K, V>, MapError> {
HashMap::new(inner)
}
}
pub struct MapKeys<'coll, T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> {
map: &'coll HashMap<T, K, V>,
err: bool,
key: Option<K>,
}
impl<'coll, T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> MapKeys<'coll, T, K, V> {
fn new(map: &'coll HashMap<T, K, V>) -> MapKeys<'coll, T, K, V> {
MapKeys {
map,
err: false,
key: None,
}
}
}
impl<T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> Iterator for MapKeys<'_, T, K, V> {
type Item = Result<K, MapError>;
fn next(&mut self) -> Option<Result<K, MapError>> {
if self.err {
return None;
}
let fd = match self.map.inner.as_ref().fd_or_err() {
Ok(fd) => fd,
Err(e) => {
self.err = true;
return Some(Err(e));
}
};
match bpf_map_get_next_key(fd, self.key.as_ref()) {
Ok(Some(key)) => {
self.key = Some(key);
return Some(Ok(key));
}
Ok(None) => {
self.key = None;
return None;
}
Err((code, io_error)) => {
self.err = true;
return Some(Err(MapError::GetNextKeyFailed { code, io_error }));
}
}
}
}
pub struct MapIter<'coll, T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> {
inner: MapKeys<'coll, T, K, V>,
}
impl<'coll, T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> MapIter<'coll, T, K, V> {
fn new(map: &'coll HashMap<T, K, V>) -> MapIter<'coll, T, K, V> {
MapIter {
inner: MapKeys::new(map),
}
}
}
impl<T: AsRef<Map>, K: Clone + Pod, V: Clone + Pod> Iterator for MapIter<'_, T, K, V> {
type Item = Result<(K, V), MapError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some(Ok(key)) => {
let value = unsafe { self.inner.map.get(&key, 0) };
match value {
Ok(None) => continue,
Ok(Some(value)) => return Some(Ok((key, value))),
Err(e) => return Some(Err(e)),
}
}
Some(Err(e)) => return Some(Err(e)),
None => return None,
}
}
}
}
impl AsRef<Map> for &Map {
fn as_ref(&self) -> &Map {
self
}
}
impl AsRef<Map> for &mut Map {
fn as_ref(&self) -> &Map {
self
}
}
impl AsMut<Map> for &mut Map {
fn as_mut(&mut self) -> &mut Map {
self
}
}
#[cfg(test)]
mod tests {
use std::io;
use libc::{EFAULT, ENOENT};
use crate::{
bpf_map_def,
generated::{
bpf_attr, bpf_cmd,
bpf_map_type::{BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY},
},
obj,
syscalls::{override_syscall, SysResult, Syscall},
};
use super::*;
fn new_obj_map(name: &str) -> obj::Map {
obj::Map {
name: name.to_string(),
def: bpf_map_def {
map_type: BPF_MAP_TYPE_HASH,
key_size: 4,
value_size: 4,
max_entries: 1024,
map_flags: 0,
},
section_index: 0,
data: Vec::new(),
}
}
fn sys_error(value: i32) -> SysResult {
Err((-1, io::Error::from_raw_os_error(value)))
}
#[test]
fn test_wrong_key_size() {
let map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
assert!(matches!(
HashMap::<_, u8, u32>::new(&map),
Err(MapError::InvalidKeySize {
size: 1,
expected: 4
})
));
}
#[test]
fn test_wrong_value_size() {
let map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
assert!(matches!(
HashMap::<_, u32, u16>::new(&map),
Err(MapError::InvalidValueSize {
size: 2,
expected: 4
})
));
}
#[test]
fn test_try_from_wrong_map() {
let map = Map {
obj: obj::Map {
name: "TEST".to_string(),
def: bpf_map_def {
map_type: BPF_MAP_TYPE_PERF_EVENT_ARRAY,
key_size: 4,
value_size: 4,
max_entries: 1024,
map_flags: 0,
},
section_index: 0,
data: Vec::new(),
},
fd: None,
};
assert!(matches!(
HashMap::<_, u32, u32>::try_from(&map),
Err(MapError::InvalidMapType { .. })
));
}
#[test]
fn test_try_from_ok() {
let map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok())
}
#[test]
fn test_insert_not_created() {
let mut map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(
hm.insert(1, 42, 0),
Err(MapError::NotCreated { .. })
));
}
#[test]
fn test_insert_syscall_error() {
override_syscall(|_| sys_error(EFAULT));
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(
hm.insert(1, 42, 0),
Err(MapError::UpdateElementFailed { code: -1, io_error }) if io_error.raw_os_error() == Some(EFAULT)
));
}
#[test]
fn test_insert_ok() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM,
..
} => Ok(1),
_ => sys_error(EFAULT),
});
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(hm.insert(1, 42, 0).is_ok());
}
#[test]
fn test_remove_not_created() {
let mut map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(hm.remove(&1), Err(MapError::NotCreated { .. })));
}
#[test]
fn test_remove_syscall_error() {
override_syscall(|_| sys_error(EFAULT));
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(
hm.remove(&1),
Err(MapError::DeleteElementFailed { code: -1, io_error }) if io_error.raw_os_error() == Some(EFAULT)
));
}
#[test]
fn test_remove_ok() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_DELETE_ELEM,
..
} => Ok(1),
_ => sys_error(EFAULT),
});
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(hm.remove(&1).is_ok());
}
#[test]
fn test_get_not_created() {
let map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
assert!(matches!(
unsafe { hm.get(&1, 0) },
Err(MapError::NotCreated { .. })
));
}
#[test]
fn test_get_syscall_error() {
override_syscall(|_| sys_error(EFAULT));
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
assert!(matches!(
unsafe { hm.get(&1, 0) },
Err(MapError::LookupElementFailed { code: -1, io_error }) if io_error.raw_os_error() == Some(EFAULT)
));
}
#[test]
fn test_get_not_found() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
..
} => sys_error(ENOENT),
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
assert!(matches!(unsafe { hm.get(&1, 0) }, Ok(None)));
}
#[test]
fn test_pop_not_created() {
let mut map = Map {
obj: new_obj_map("TEST"),
fd: None,
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(
unsafe { hm.pop(&1) },
Err(MapError::NotCreated { .. })
));
}
#[test]
fn test_pop_syscall_error() {
override_syscall(|_| sys_error(EFAULT));
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(
unsafe { hm.pop(&1) },
Err(MapError::LookupAndDeleteElementFailed { code: -1, io_error }) if io_error.raw_os_error() == Some(EFAULT)
));
}
#[test]
fn test_pop_not_found() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_AND_DELETE_ELEM,
..
} => sys_error(ENOENT),
_ => sys_error(EFAULT),
});
let mut map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap();
assert!(matches!(unsafe { hm.pop(&1) }, Ok(None)));
}
fn bpf_key<T: Copy>(attr: &bpf_attr) -> Option<T> {
match unsafe { attr.__bindgen_anon_2.key } as *const T {
p if p.is_null() => None,
p => Some(unsafe { *p }),
}
}
fn set_next_key<T: Copy>(attr: &bpf_attr, next: T) {
let key = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.next_key } as *const T as *mut T;
unsafe { *key = next };
}
fn set_ret<T: Copy>(attr: &bpf_attr, ret: T) {
let value = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.value } as *const T as *mut T;
unsafe { *value = ret };
}
#[test]
fn test_keys_empty() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
..
} => sys_error(ENOENT),
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let keys = unsafe { hm.keys() }.collect::<Result<Vec<_>, _>>();
assert!(matches!(keys, Ok(ks) if ks.is_empty()))
}
fn get_next_key(attr: &bpf_attr) -> SysResult {
match bpf_key(&attr) {
None => set_next_key(&attr, 10),
Some(10) => set_next_key(&attr, 20),
Some(20) => set_next_key(&attr, 30),
Some(30) => return sys_error(ENOENT),
Some(_) => return sys_error(EFAULT),
};
Ok(1)
}
fn lookup_elem(attr: &bpf_attr) -> SysResult {
match bpf_key(&attr) {
Some(10) => set_ret(&attr, 100),
Some(20) => set_ret(&attr, 200),
Some(30) => set_ret(&attr, 300),
Some(_) => return sys_error(ENOENT),
None => return sys_error(EFAULT),
};
Ok(1)
}
#[test]
fn test_keys() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => get_next_key(&attr),
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let keys = unsafe { hm.keys() }.collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(&keys, &[10, 20, 30])
}
#[test]
fn test_keys_error() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => {
match bpf_key(&attr) {
None => set_next_key(&attr, 10),
Some(10) => set_next_key(&attr, 20),
Some(_) => return sys_error(EFAULT),
};
Ok(1)
}
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let mut keys = unsafe { hm.keys() };
assert!(matches!(keys.next(), Some(Ok(10))));
assert!(matches!(keys.next(), Some(Ok(20))));
assert!(matches!(keys.next(), Some(Err(MapError::GetNextKeyFailed { .. }))));
assert!(matches!(keys.next(), None));
}
#[test]
fn test_iter() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => get_next_key(&attr),
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
attr,
} => lookup_elem(&attr),
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let items = unsafe { hm.iter() }.collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(&items, &[(10, 100), (20, 200), (30, 300)])
}
#[test]
fn test_iter_key_deleted() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => get_next_key(&attr),
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
attr,
} => {
match bpf_key(&attr) {
Some(10) => set_ret(&attr, 100),
Some(20) => return sys_error(ENOENT),
Some(30) => set_ret(&attr, 300),
Some(_) => return sys_error(ENOENT),
None => return sys_error(EFAULT),
};
Ok(1)
}
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let items = unsafe { hm.iter() }.collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(&items, &[(10, 100), (30, 300)])
}
#[test]
fn test_iter_key_error() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => {
match bpf_key(&attr) {
None => set_next_key(&attr, 10),
Some(10) => set_next_key(&attr, 20),
Some(20) => return sys_error(EFAULT),
Some(30) => return sys_error(ENOENT),
Some(_) => panic!(),
};
Ok(1)
}
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
attr,
} => lookup_elem(&attr),
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let mut iter = unsafe { hm.iter() };
assert!(matches!(iter.next(), Some(Ok((10, 100)))));
assert!(matches!(iter.next(), Some(Ok((20, 200)))));
assert!(matches!(iter.next(), Some(Err(MapError::GetNextKeyFailed { .. }))));
assert!(matches!(iter.next(), None));
}
#[test]
fn test_iter_value_error() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY,
attr,
} => get_next_key(&attr),
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM,
attr,
} => {
match bpf_key(&attr) {
Some(10) => set_ret(&attr, 100),
Some(20) => return sys_error(EFAULT),
Some(30) => set_ret(&attr, 300),
Some(_) => return sys_error(ENOENT),
None => return sys_error(EFAULT),
};
Ok(1)
}
_ => sys_error(EFAULT),
});
let map = Map {
obj: new_obj_map("TEST"),
fd: Some(42),
};
let hm = HashMap::<_, u32, u32>::new(&map).unwrap();
let mut iter = unsafe { hm.iter() };
assert!(matches!(iter.next(), Some(Ok((10, 100)))));
assert!(matches!(iter.next(), Some(Err(MapError::LookupElementFailed { .. }))));
assert!(matches!(iter.next(), Some(Ok((30, 300)))));
assert!(matches!(iter.next(), None));
}
}

@ -0,0 +1,164 @@
use std::{ffi::CString, io};
use thiserror::Error;
use crate::{obj, syscalls::bpf_create_map, RawFd};
mod hash_map;
pub use hash_map::*;
mod perf_map;
pub use perf_map::*;
#[derive(Error, Debug)]
pub enum MapError {
#[error("invalid map type {map_type}")]
InvalidMapType { map_type: u32 },
#[error("invalid map name `{name}`")]
InvalidName { name: String },
#[error("the map `{name}` has not been created")]
NotCreated { name: String },
#[error("the map `{name}` has already been created")]
AlreadyCreated { name: String },
#[error("failed to create map `{name}`: {code}")]
CreateFailed {
name: String,
code: i64,
io_error: io::Error,
},
#[error("invalid key size {size}, expected {expected}")]
InvalidKeySize { size: usize, expected: usize },
#[error("invalid value size {size}, expected {expected}")]
InvalidValueSize { size: usize, expected: usize },
#[error("the BPF_MAP_UPDATE_ELEM syscall failed with code {code} io_error {io_error}")]
UpdateElementFailed { code: i64, io_error: io::Error },
#[error("the BPF_MAP_LOOKUP_ELEM syscall failed with code {code} io_error {io_error}")]
LookupElementFailed { code: i64, io_error: io::Error },
#[error("the BPF_MAP_DELETE_ELEM syscall failed with code {code} io_error {io_error}")]
DeleteElementFailed { code: i64, io_error: io::Error },
#[error(
"the BPF_MAP_LOOKUP_AND_DELETE_ELEM syscall failed with code {code} io_error {io_error}"
)]
LookupAndDeleteElementFailed { code: i64, io_error: io::Error },
#[error("the BPF_MAP_GET_NEXT_KEY syscall failed with code {code} io_error {io_error}")]
GetNextKeyFailed { code: i64, io_error: io::Error },
}
#[derive(Debug)]
pub struct Map {
pub(crate) obj: obj::Map,
pub(crate) fd: Option<RawFd>,
}
impl Map {
pub fn create(&mut self) -> Result<RawFd, MapError> {
let name = self.obj.name.clone();
if self.fd.is_some() {
return Err(MapError::AlreadyCreated { name: name.clone() });
}
let c_name =
CString::new(name.clone()).map_err(|_| MapError::InvalidName { name: name.clone() })?;
let fd = bpf_create_map(&c_name, &self.obj.def).map_err(|(code, io_error)| {
MapError::CreateFailed {
name,
code,
io_error,
}
})? as RawFd;
self.fd = Some(fd);
Ok(fd)
}
pub(crate) fn fd_or_err(&self) -> Result<RawFd, MapError> {
self.fd.ok_or_else(|| MapError::NotCreated {
name: self.obj.name.clone(),
})
}
}
#[cfg(test)]
mod tests {
use libc::EFAULT;
use crate::{
bpf_map_def,
generated::{bpf_cmd, bpf_map_type::BPF_MAP_TYPE_HASH},
syscalls::{override_syscall, Syscall},
};
use super::*;
fn new_obj_map(name: &str) -> obj::Map {
obj::Map {
name: name.to_string(),
def: bpf_map_def {
map_type: BPF_MAP_TYPE_HASH,
key_size: 4,
value_size: 4,
max_entries: 1024,
map_flags: 0,
},
section_index: 0,
data: Vec::new(),
}
}
fn new_map(name: &str) -> Map {
Map {
obj: new_obj_map(name),
fd: None,
}
}
#[test]
fn test_create() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_CREATE,
..
} => Ok(42),
_ => Err((-1, io::Error::from_raw_os_error(EFAULT))),
});
let mut map = new_map("foo");
assert!(matches!(map.create(), Ok(42)));
assert_eq!(map.fd, Some(42));
assert!(matches!(map.create(), Err(MapError::AlreadyCreated { .. })));
}
#[test]
fn test_create_failed() {
override_syscall(|_| {
return Err((-42, io::Error::from_raw_os_error(EFAULT)));
});
let mut map = new_map("foo");
let ret = map.create();
assert!(matches!(ret, Err(MapError::CreateFailed { .. })));
if let Err(MapError::CreateFailed {
name,
code,
io_error,
}) = ret
{
assert_eq!(name, "foo");
assert_eq!(code, -42);
assert_eq!(io_error.raw_os_error(), Some(EFAULT));
}
assert_eq!(map.fd, None);
}
}

@ -0,0 +1,700 @@
use std::{
convert::TryFrom,
ffi::c_void,
fs, io, mem, ptr, slice,
str::FromStr,
sync::atomic::{self, AtomicPtr, Ordering},
};
use bytes::BytesMut;
use libc::{
c_int, close, munmap, sysconf, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, _SC_PAGESIZE,
};
use thiserror::Error;
use crate::{
generated::{
bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, perf_event_header, perf_event_mmap_page,
perf_event_type::*,
},
maps::{Map, MapError},
syscalls::{bpf_map_update_elem, perf_event_ioctl, perf_event_open},
RawFd, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE,
};
const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online";
#[derive(Error, Debug)]
pub enum PerfBufferError {
#[error("invalid page count {page_count}, the value must be a power of two")]
InvalidPageCount { page_count: usize },
#[error("perf_event_open failed: {io_error}")]
OpenFailed {
#[source]
io_error: io::Error,
},
#[error("mmap failed: {io_error}")]
MMapFailed {
#[source]
io_error: io::Error,
},
#[error("PERF_EVENT_IOC_ENABLE failed: {io_error}")]
PerfEventEnableFailed {
#[source]
io_error: io::Error,
},
#[error("read_events() was called with no output buffers")]
NoBuffers,
#[error("the buffer needs to be of at least {size} bytes")]
MoreSpaceNeeded { size: usize },
}
#[derive(Debug, PartialEq)]
pub struct Events {
pub read: usize,
pub lost: usize,
}
struct PerfBuffer {
buf: AtomicPtr<perf_event_mmap_page>,
size: usize,
page_size: usize,
fd: RawFd,
}
impl PerfBuffer {
fn open(
cpu_id: u32,
page_size: usize,
page_count: usize,
) -> Result<PerfBuffer, PerfBufferError> {
if !page_count.is_power_of_two() {
return Err(PerfBufferError::InvalidPageCount { page_count });
}
let fd = perf_event_open(cpu_id as i32)
.map_err(|(_, io_error)| PerfBufferError::OpenFailed { io_error })?
as RawFd;
let size = page_size * page_count;
let buf = unsafe {
mmap(
ptr::null_mut(),
size + page_size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0,
)
};
if buf == MAP_FAILED {
return Err(PerfBufferError::MMapFailed {
io_error: io::Error::last_os_error(),
});
}
let perf_buf = PerfBuffer {
buf: AtomicPtr::new(buf as *mut perf_event_mmap_page),
fd,
size,
page_size,
};
perf_event_ioctl(fd, PERF_EVENT_IOC_ENABLE, 0)
.map_err(|(_, io_error)| PerfBufferError::PerfEventEnableFailed { io_error })?;
Ok(perf_buf)
}
pub fn read_events(&mut self, buffers: &mut [BytesMut]) -> Result<Events, PerfBufferError> {
if buffers.is_empty() {
return Err(PerfBufferError::NoBuffers);
}
let header = self.buf.load(Ordering::SeqCst);
let base = header as usize + self.page_size;
let mut events = Events { read: 0, lost: 0 };
let mut buf_n = 0;
let fill_buf = |start_off, base, mmap_size, out_buf: &mut [u8]| {
let len = out_buf.len();
let end = (start_off + len) % mmap_size;
let start = start_off % mmap_size;
if start < end {
out_buf.copy_from_slice(unsafe {
slice::from_raw_parts((base + start) as *const u8, len)
});
} else {
let size = mmap_size - start;
unsafe {
out_buf[..size]
.copy_from_slice(slice::from_raw_parts((base + start) as *const u8, size));
out_buf[size..]
.copy_from_slice(slice::from_raw_parts(base as *const u8, len - size));
}
}
};
let read_event = |event_start, event_type, base, buf: &mut BytesMut| {
let sample_size = match event_type {
PERF_RECORD_SAMPLE | PERF_RECORD_LOST => {
let mut size = [0u8; mem::size_of::<u32>()];
fill_buf(
event_start + mem::size_of::<perf_event_header>(),
base,
self.size,
&mut size,
);
u32::from_ne_bytes(size)
}
_ => return Ok(None),
} as usize;
let sample_start =
(event_start + mem::size_of::<perf_event_header>() + mem::size_of::<u32>())
% self.size;
match event_type {
PERF_RECORD_SAMPLE => {
buf.clear();
if sample_size > buf.capacity() {
return Err(PerfBufferError::MoreSpaceNeeded { size: sample_size });
}
unsafe { buf.set_len(sample_size) };
fill_buf(sample_start, base, self.size, buf);
Ok(Some((1, 0)))
}
PERF_RECORD_LOST => {
let mut count = [0u8; mem::size_of::<u64>()];
fill_buf(
event_start + mem::size_of::<perf_event_header>() + mem::size_of::<u64>(),
base,
self.size,
&mut count,
);
Ok(Some((0, u64::from_ne_bytes(count) as usize)))
}
_ => Ok(None),
}
};
let head = unsafe { (*header).data_head } as usize;
let mut tail = unsafe { (*header).data_tail } as usize;
while head != tail {
if buf_n == buffers.len() {
break;
}
let buf = &mut buffers[buf_n];
let event_start = tail % self.size;
let event =
unsafe { ptr::read_unaligned((base + event_start) as *const perf_event_header) };
let event_size = event.size as usize;
match read_event(event_start, event.type_, base, buf) {
Ok(Some((read, lost))) => {
if read > 0 {
buf_n += 1;
events.read += read;
}
events.lost += lost;
}
Ok(None) => { /* skip unknown event type */ }
Err(PerfBufferError::MoreSpaceNeeded { .. }) if events.read > 0 => {
// we have processed some events so we're going to return those. In the
// next read_events() we'll return an error unless the caller increases the
// buffer size
break;
}
Err(e) => {
// we got an error and we didn't process any events, propagate the error
// and give the caller a chance to increase buffers
atomic::fence(Ordering::SeqCst);
unsafe { (*header).data_tail = tail as u64 };
return Err(e);
}
}
tail += event_size;
}
atomic::fence(Ordering::SeqCst);
unsafe { (*header).data_tail = tail as u64 };
return Ok(events);
}
}
impl Drop for PerfBuffer {
fn drop(&mut self) {
unsafe {
let _ = perf_event_ioctl(self.fd, PERF_EVENT_IOC_DISABLE, 0);
munmap(
self.buf.load(Ordering::SeqCst) as *mut c_void,
self.size + self.page_size,
);
close(self.fd);
}
}
}
#[derive(Error, Debug)]
pub enum PerfMapError {
#[error("error parsing /sys/devices/system/cpu/online")]
InvalidOnlineCpuFile,
#[error("no CPUs specified")]
NoCpus,
#[error("invalid cpu {cpu_id}")]
InvalidCpu { cpu_id: u32 },
#[error("map error: {map_error}")]
MapError {
#[from]
map_error: MapError,
},
#[error("perf buffer error: {buf_error}")]
PerfBufferError {
#[from]
buf_error: PerfBufferError,
},
#[error("bpf_map_update_elem failed: {io_error}")]
UpdateElementFailed {
#[source]
io_error: io::Error,
},
}
pub struct PerfMap<'map> {
map: &'map Map,
cpu_fds: Vec<(u32, RawFd)>,
buffers: Vec<Option<PerfBuffer>>,
}
impl<'map> PerfMap<'map> {
pub fn new(
map: &'map Map,
cpu_ids: Option<Vec<u32>>,
page_count: Option<usize>,
) -> Result<PerfMap<'map>, PerfMapError> {
let map_type = map.obj.def.map_type;
if map_type != BPF_MAP_TYPE_PERF_EVENT_ARRAY {
return Err(MapError::InvalidMapType {
map_type: map_type as u32,
})?;
}
let mut cpu_ids = match cpu_ids {
Some(ids) => ids,
None => online_cpus().map_err(|_| PerfMapError::InvalidOnlineCpuFile)?,
};
if cpu_ids.is_empty() {
return Err(PerfMapError::NoCpus);
}
cpu_ids.sort();
let min_cpu = cpu_ids.first().unwrap();
let max_cpu = cpu_ids.last().unwrap();
let mut buffers = (*min_cpu..=*max_cpu).map(|_| None).collect::<Vec<_>>();
let map_fd = map.fd_or_err()?;
let page_size = unsafe { sysconf(_SC_PAGESIZE) } as usize;
let mut cpu_fds = Vec::new();
for cpu_id in &cpu_ids {
let buf = PerfBuffer::open(*cpu_id, page_size, page_count.unwrap_or(2))?;
bpf_map_update_elem(map_fd, cpu_id, &buf.fd, 0)
.map_err(|(_, io_error)| PerfMapError::UpdateElementFailed { io_error })?;
cpu_fds.push((*cpu_id, buf.fd));
buffers[*cpu_id as usize] = Some(buf);
}
Ok(PerfMap {
map,
cpu_fds,
buffers,
})
}
pub fn cpu_file_descriptors(&self) -> &[(u32, RawFd)] {
self.cpu_fds.as_slice()
}
pub fn read_cpu_events(
&mut self,
cpu_id: u32,
buffers: &mut [BytesMut],
) -> Result<Events, PerfMapError> {
let buf = match self.buffers.get_mut(cpu_id as usize) {
None | Some(None) => return Err(PerfMapError::InvalidCpu { cpu_id }),
Some(Some(buf)) => buf,
};
Ok(buf.read_events(buffers)?)
}
}
impl<'inner> TryFrom<&'inner Map> for PerfMap<'inner> {
type Error = PerfMapError;
fn try_from(inner: &'inner Map) -> Result<PerfMap<'inner>, PerfMapError> {
PerfMap::new(inner, None, None)
}
}
fn online_cpus() -> Result<Vec<u32>, ()> {
let data = fs::read_to_string(ONLINE_CPUS).map_err(|_| ())?;
parse_online_cpus(data.trim())
}
fn parse_online_cpus(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)
}
#[cfg_attr(test, allow(unused_variables))]
unsafe fn mmap(
addr: *mut c_void,
len: usize,
prot: c_int,
flags: c_int,
fd: i32,
offset: i64,
) -> *mut c_void {
#[cfg(not(test))]
return libc::mmap(addr, len, prot, flags, fd, offset);
#[cfg(test)]
use crate::syscalls::TEST_MMAP_RET;
#[cfg(test)]
TEST_MMAP_RET.with(|ret| *ret.borrow())
}
#[derive(Debug)]
#[repr(C)]
pub struct Sample {
header: perf_event_header,
pub size: u32,
}
#[repr(C)]
#[derive(Debug)]
pub struct LostSamples {
header: perf_event_header,
pub id: u64,
pub count: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
generated::perf_event_mmap_page,
syscalls::{override_syscall, Syscall, TEST_MMAP_RET},
};
use std::{convert::TryInto, fmt::Debug, iter::FromIterator, mem};
#[test]
fn test_parse_online_cpus() {
assert_eq!(parse_online_cpus("0").unwrap(), vec![0]);
assert_eq!(parse_online_cpus("0,1").unwrap(), vec![0, 1]);
assert_eq!(parse_online_cpus("0,1,2").unwrap(), vec![0, 1, 2]);
assert_eq!(parse_online_cpus("0-7").unwrap(), Vec::from_iter(0..=7));
assert_eq!(parse_online_cpus("0-3,4-7").unwrap(), Vec::from_iter(0..=7));
assert_eq!(parse_online_cpus("0-5,6,7").unwrap(), Vec::from_iter(0..=7));
assert!(parse_online_cpus("").is_err());
assert!(parse_online_cpus("0-1,2-").is_err());
assert!(parse_online_cpus("foo").is_err());
}
const PAGE_SIZE: usize = 4096;
union MMappedBuf {
mmap_page: perf_event_mmap_page,
data: [u8; PAGE_SIZE * 2],
}
fn fake_mmap(buf: &mut MMappedBuf) {
override_syscall(|call| match call {
Syscall::PerfEventOpen { .. } | Syscall::PerfEventIoctl { .. } => Ok(42),
_ => panic!(),
});
TEST_MMAP_RET.with(|ret| *ret.borrow_mut() = buf as *const _ as *mut _);
}
#[test]
fn test_invalid_page_count() {
assert!(matches!(
PerfBuffer::open(1, PAGE_SIZE, 0),
Err(PerfBufferError::InvalidPageCount { .. })
));
assert!(matches!(
PerfBuffer::open(1, PAGE_SIZE, 3),
Err(PerfBufferError::InvalidPageCount { .. })
));
assert!(matches!(
PerfBuffer::open(1, PAGE_SIZE, 5),
Err(PerfBufferError::InvalidPageCount { .. })
));
}
#[test]
fn test_no_out_bufs() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
assert!(matches!(
buf.read_events(&mut []),
Err(PerfBufferError::NoBuffers)
))
}
#[test]
fn test_no_events() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let out_buf = BytesMut::with_capacity(4);
assert_eq!(
buf.read_events(&mut [out_buf]).unwrap(),
Events { read: 0, lost: 0 }
);
}
#[test]
fn test_read_first_lost() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let evt = LostSamples {
header: perf_event_header {
type_: PERF_RECORD_LOST,
misc: 0,
size: mem::size_of::<LostSamples>() as u16,
},
id: 1,
count: 0xCAFEBABE,
};
write(&mut mmapped_buf, 0, evt);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let out_buf = BytesMut::with_capacity(0);
let events = buf.read_events(&mut [out_buf]).unwrap();
assert_eq!(events.lost, 0xCAFEBABE);
}
#[repr(C)]
#[derive(Debug)]
struct PerfSample<T: Debug> {
s_hdr: Sample,
value: T,
}
fn write<T: Debug>(mmapped_buf: &mut MMappedBuf, offset: usize, value: T) -> usize {
let dst = (mmapped_buf as *const _ as usize + PAGE_SIZE + offset) as *const PerfSample<T>
as *mut T;
unsafe {
ptr::write_unaligned(dst, value);
mmapped_buf.mmap_page.data_head = (offset + mem::size_of::<T>()) as u64;
mmapped_buf.mmap_page.data_head as usize
}
}
fn write_sample<T: Debug>(mmapped_buf: &mut MMappedBuf, offset: usize, value: T) -> usize {
let sample = PerfSample {
s_hdr: Sample {
header: perf_event_header {
type_: PERF_RECORD_SAMPLE,
misc: 0,
size: mem::size_of::<PerfSample<T>>() as u16,
},
size: mem::size_of::<T>() as u32,
},
value,
};
write(mmapped_buf, offset, sample)
}
fn u32_from_buf(buf: &[u8]) -> u32 {
u32::from_ne_bytes(buf[..4].try_into().unwrap())
}
fn u64_from_buf(buf: &[u8]) -> u64 {
u64::from_ne_bytes(buf[..8].try_into().unwrap())
}
#[test]
fn test_read_first_sample() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32);
let mut out_bufs = [BytesMut::with_capacity(4)];
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE);
}
#[test]
fn test_read_many_with_many_reads() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let next = write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32);
write_sample(&mut mmapped_buf, next, 0xBADCAFEu32);
let mut out_bufs = [BytesMut::with_capacity(4)];
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE);
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xBADCAFE);
}
#[test]
fn test_read_many_with_one_read() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let next = write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32);
write_sample(&mut mmapped_buf, next, 0xBADCAFEu32);
let mut out_bufs = (0..3)
.map(|_| BytesMut::with_capacity(4))
.collect::<Vec<_>>();
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 2 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE);
assert_eq!(u32_from_buf(&out_bufs[1]), 0xBADCAFE);
}
#[test]
fn test_read_last_sample() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let offset = PAGE_SIZE - mem::size_of::<PerfSample<u32>>();
mmapped_buf.mmap_page.data_tail = offset as u64;
write_sample(&mut mmapped_buf, offset, 0xCAFEBABEu32);
let mut out_bufs = [BytesMut::with_capacity(4)];
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE);
}
#[test]
fn test_read_wrapping_sample_size() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let header = perf_event_header {
type_: PERF_RECORD_SAMPLE,
misc: 0,
size: mem::size_of::<PerfSample<u64>>() as u16,
};
let offset = PAGE_SIZE - mem::size_of::<perf_event_header>() - 2;
mmapped_buf.mmap_page.data_tail = offset as u64;
write(&mut mmapped_buf, offset, header);
write(&mut mmapped_buf, PAGE_SIZE - 2, 0x0004u16);
write(&mut mmapped_buf, 0, 0x0000u16);
write(&mut mmapped_buf, 2, 0xBAADCAFEu32);
let mut out_bufs = [BytesMut::with_capacity(8)];
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u32_from_buf(&out_bufs[0]), 0xBAADCAFE);
}
#[test]
fn test_read_wrapping_value() {
let mut mmapped_buf = MMappedBuf {
data: [0; PAGE_SIZE * 2],
};
fake_mmap(&mut mmapped_buf);
let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap();
let sample = PerfSample {
s_hdr: Sample {
header: perf_event_header {
type_: PERF_RECORD_SAMPLE,
misc: 0,
size: mem::size_of::<PerfSample<u64>>() as u16,
},
size: mem::size_of::<u64>() as u32,
},
value: 0xCAFEBABEu32,
};
let offset = PAGE_SIZE - mem::size_of::<PerfSample<u32>>();
mmapped_buf.mmap_page.data_tail = offset as u64;
write(&mut mmapped_buf, offset, sample);
write(&mut mmapped_buf, 0, 0xBAADCAFEu32);
let mut out_bufs = [BytesMut::with_capacity(8)];
let events = buf.read_events(&mut out_bufs).unwrap();
assert_eq!(events, Events { lost: 0, read: 1 });
assert_eq!(u64_from_buf(&out_bufs[0]), 0xBAADCAFECAFEBABE);
}
}

@ -0,0 +1,414 @@
mod relocation;
use object::{
pod,
read::{Object as ElfObject, ObjectSection, Section},
Endianness, ObjectSymbol, ObjectSymbolTable, SectionIndex, SymbolIndex,
};
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
ffi::{CStr, CString},
mem,
str::FromStr,
};
use thiserror::Error;
pub use self::relocation::{relocate, RelocationError};
use crate::{
bpf_map_def,
generated::{bpf_insn, bpf_map_type::BPF_MAP_TYPE_ARRAY},
obj::relocation::{Relocation, Symbol},
};
const KERNEL_VERSION_ANY: u32 = 0xFFFF_FFFE;
#[derive(Debug, Clone)]
pub struct Object {
pub(crate) endianness: Endianness,
pub license: CString,
pub kernel_version: KernelVersion,
pub(crate) maps: HashMap<String, Map>,
pub(crate) programs: HashMap<String, Program>,
pub(crate) relocations: HashMap<SectionIndex, Vec<Relocation>>,
pub(crate) symbol_table: HashMap<SymbolIndex, Symbol>,
}
#[derive(Debug, Clone)]
pub struct Map {
pub(crate) name: String,
pub(crate) def: bpf_map_def,
pub(crate) section_index: usize,
pub(crate) data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub(crate) struct Program {
pub(crate) license: CString,
pub(crate) kernel_version: KernelVersion,
pub(crate) instructions: Vec<bpf_insn>,
pub(crate) kind: ProgramKind,
pub(crate) section_index: SectionIndex,
}
#[derive(Debug, Copy, Clone)]
pub enum ProgramKind {
KProbe,
UProbe,
Xdp,
TracePoint,
}
impl FromStr for ProgramKind {
type Err = ParseError;
fn from_str(kind: &str) -> Result<ProgramKind, ParseError> {
use ProgramKind::*;
Ok(match kind {
"kprobe" => KProbe,
"uprobe" => UProbe,
"xdp" => Xdp,
"trace_point" => TracePoint,
_ => {
return Err(ParseError::InvalidProgramKind {
kind: kind.to_string(),
})
}
})
}
}
impl Object {
pub(crate) fn parse(data: &[u8]) -> Result<Object, ParseError> {
let obj = object::read::File::parse(data).map_err(|source| ParseError::Error { source })?;
let endianness = obj.endianness();
let section = obj
.section_by_name("license")
.ok_or(ParseError::MissingLicense)?;
let license = parse_license(BPFSection::try_from(&section)?.data)?;
let section = obj
.section_by_name("version")
.ok_or(ParseError::MissingKernelVersion)?;
let kernel_version = parse_version(BPFSection::try_from(&section)?.data, endianness)?;
let mut bpf_obj = Object {
endianness: endianness.into(),
license,
kernel_version,
maps: HashMap::new(),
programs: HashMap::new(),
relocations: HashMap::new(),
symbol_table: HashMap::new(),
};
for s in obj.sections() {
parse_section(&mut bpf_obj, BPFSection::try_from(&s)?)?;
}
if let Some(symbol_table) = obj.symbol_table() {
for symbol in symbol_table.symbols() {
bpf_obj.symbol_table.insert(
symbol.index(),
Symbol {
name: symbol.name().ok().map(String::from),
section_index: symbol.section().index(),
address: symbol.address(),
},
);
}
}
return Ok(bpf_obj);
}
}
#[derive(Debug, Clone, Error)]
pub enum ParseError {
#[error("error parsing ELF data")]
Error {
#[source]
source: object::read::Error,
},
#[error("no license specified")]
MissingLicense,
#[error("invalid license `{data:?}`: missing NULL terminator")]
MissingLicenseNullTerminator { data: Vec<u8> },
#[error("invalid license `{data:?}`")]
InvalidLicense { data: Vec<u8> },
#[error("missing kernel version")]
MissingKernelVersion,
#[error("invalid kernel version `{data:?}`")]
InvalidKernelVersion { data: Vec<u8> },
#[error("error parsing section with index {index}")]
SectionError {
index: usize,
#[source]
source: object::read::Error,
},
#[error("unsupported relocation")]
UnsupportedRelocationKind,
#[error("invalid program kind `{kind}`")]
InvalidProgramKind { kind: String },
#[error("error parsing program `{name}`")]
InvalidProgramCode { name: String },
#[error("error parsing map `{name}`")]
InvalidMapDefinition { name: String },
}
struct BPFSection<'s> {
index: SectionIndex,
name: &'s str,
data: &'s [u8],
relocations: Vec<Relocation>,
}
impl<'data, 'file, 's> TryFrom<&'s Section<'data, 'file>> for BPFSection<'s> {
type Error = ParseError;
fn try_from(section: &'s Section) -> Result<BPFSection<'s>, ParseError> {
let index = section.index();
let map_err = |source| ParseError::SectionError {
index: index.0,
source,
};
Ok(BPFSection {
index,
name: section.name().map_err(map_err)?,
data: section.data().map_err(map_err)?,
relocations: section
.relocations()
.map(|(offset, r)| {
Ok(Relocation {
kind: r.kind(),
target: r.target(),
addend: r.addend(),
offset,
})
})
.collect::<Result<Vec<_>, _>>()?,
})
}
}
fn parse_license(data: &[u8]) -> Result<CString, ParseError> {
if data.len() < 2 {
return Err(ParseError::InvalidLicense {
data: data.to_vec(),
});
}
if data[data.len() - 1] != 0 {
return Err(ParseError::MissingLicenseNullTerminator {
data: data.to_vec(),
});
}
Ok(CStr::from_bytes_with_nul(data)
.map_err(|_| ParseError::InvalidLicense {
data: data.to_vec(),
})?
.to_owned())
}
fn parse_version(data: &[u8], endianness: object::Endianness) -> Result<KernelVersion, ParseError> {
let data = match data.len() {
4 => data.try_into().unwrap(),
_ => {
return Err(ParseError::InvalidKernelVersion {
data: data.to_vec(),
})
}
};
let v = match endianness {
object::Endianness::Big => u32::from_be_bytes(data),
object::Endianness::Little => u32::from_le_bytes(data),
};
Ok(match v {
KERNEL_VERSION_ANY => KernelVersion::Any,
v => KernelVersion::Version(v),
})
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum KernelVersion {
Version(u32),
Any,
}
impl From<KernelVersion> for u32 {
fn from(version: KernelVersion) -> u32 {
match version {
KernelVersion::Any => KERNEL_VERSION_ANY,
KernelVersion::Version(v) => v,
}
}
}
fn parse_map(section: &BPFSection, name: &str) -> Result<Map, ParseError> {
let (def, data) = if name == ".bss" || name.starts_with(".data") || name.starts_with(".rodata")
{
let def = bpf_map_def {
map_type: BPF_MAP_TYPE_ARRAY,
key_size: mem::size_of::<u32>() as u32,
value_size: section.data.len() as u32,
max_entries: 1,
map_flags: 0, /* FIXME: set rodata readonly */
};
(def, section.data.to_vec())
} else {
(parse_map_def(name, section.data)?, Vec::new())
};
Ok(Map {
section_index: section.index.0,
name: name.to_string(),
def,
data,
})
}
fn parse_map_def(name: &str, data: &[u8]) -> Result<bpf_map_def, ParseError> {
let (def, rest) =
pod::from_bytes::<bpf_map_def>(data).map_err(|_| ParseError::InvalidMapDefinition {
name: name.to_string(),
})?;
if !rest.is_empty() {
return Err(ParseError::InvalidMapDefinition {
name: name.to_string(),
});
}
Ok(*def)
}
fn parse_program(bpf: &Object, section: &BPFSection, ty: &str) -> Result<Program, ParseError> {
let (code, rest) = pod::slice_from_bytes::<bpf_insn>(
section.data,
section.data.len() / mem::size_of::<bpf_insn>(),
)
.map_err(|_| ParseError::InvalidProgramCode {
name: section.name.to_string(),
})?;
if !rest.is_empty() {
return Err(ParseError::InvalidProgramCode {
name: section.name.to_string(),
});
}
Ok(Program {
section_index: section.index,
license: bpf.license.clone(),
kernel_version: bpf.kernel_version,
instructions: code.to_vec(),
kind: ProgramKind::from_str(ty)?,
})
}
fn parse_section(bpf: &mut Object, section: BPFSection) -> Result<(), ParseError> {
let parts = section.name.split("/").collect::<Vec<_>>();
match parts.as_slice() {
&[name] if name == ".bss" || name.starts_with(".data") || name.starts_with(".rodata") => {
bpf.maps
.insert(name.to_string(), parse_map(&section, name)?);
}
&["maps", name] => {
bpf.maps
.insert(name.to_string(), parse_map(&section, name)?);
}
&[ty @ "kprobe", name]
| &[ty @ "uprobe", name]
| &[ty @ "xdp", name]
| &[ty @ "trace_point", name] => {
bpf.programs
.insert(name.to_string(), parse_program(bpf, &section, ty)?);
if !section.relocations.is_empty() {
bpf.relocations.insert(section.index, section.relocations);
}
}
_ => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use object::Endianness;
#[test]
fn test_parse_generic_error() {
assert!(matches!(
Object::parse(&b"foo"[..]),
Err(ParseError::Error { .. })
))
}
#[test]
fn test_parse_license() {
assert!(matches!(
parse_license(b""),
Err(ParseError::InvalidLicense { .. })
));
assert!(matches!(
parse_license(b"\0"),
Err(ParseError::InvalidLicense { .. })
));
assert!(matches!(
parse_license(b"GPL"),
Err(ParseError::MissingLicenseNullTerminator { .. })
));
assert_eq!(parse_license(b"GPL\0").unwrap().to_str().unwrap(), "GPL");
}
#[test]
fn test_parse_version() {
assert!(matches!(
parse_version(b"", Endianness::Little),
Err(ParseError::InvalidKernelVersion { .. })
));
assert!(matches!(
parse_version(b"123", Endianness::Little),
Err(ParseError::InvalidKernelVersion { .. })
));
assert_eq!(
parse_version(&0xFFFF_FFFEu32.to_le_bytes(), Endianness::Little)
.expect("failed to parse magic version"),
KernelVersion::Any
);
assert_eq!(
parse_version(&0xFFFF_FFFEu32.to_be_bytes(), Endianness::Big)
.expect("failed to parse magic version"),
KernelVersion::Any
);
assert_eq!(
parse_version(&1234u32.to_le_bytes(), Endianness::Little)
.expect("failed to parse magic version"),
KernelVersion::Version(1234)
);
}
}

@ -0,0 +1,108 @@
use std::collections::HashMap;
use object::{RelocationKind, RelocationTarget, SectionIndex};
use thiserror::Error;
use super::Object;
use crate::{
generated::{bpf_insn, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE},
maps::Map,
};
#[derive(Debug, Clone, Error)]
pub enum RelocationError {
#[error("unknown symbol, index `{index}`")]
UnknownSymbol { index: usize },
#[error("unknown symbol section, index `{index}`")]
UnknownSymbolSection { index: usize },
#[error("section `{section_index}` not found, referenced by symbol `{}`",
.symbol_name.clone().unwrap_or_else(|| .symbol_index.to_string()))]
RelocationSectionNotFound {
section_index: usize,
symbol_index: usize,
symbol_name: Option<String>,
},
#[error("the map `{name}` at section `{section_index}` has not been created")]
MapNotCreated { section_index: usize, name: String },
#[error("invalid relocation offset `{offset}`")]
InvalidRelocationOffset { offset: u64 },
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct Relocation {
pub(crate) kind: RelocationKind,
pub(crate) target: RelocationTarget,
pub(crate) offset: u64,
pub(crate) addend: i64,
}
#[derive(Debug, Clone)]
pub(crate) struct Symbol {
pub(crate) section_index: Option<SectionIndex>,
pub(crate) name: Option<String>,
pub(crate) address: u64,
}
pub fn relocate(obj: &mut Object, maps: &[Map]) -> Result<(), RelocationError> {
let maps_by_section = maps
.iter()
.map(|map| (map.obj.section_index, map))
.collect::<HashMap<_, _>>();
for program in obj.programs.values_mut() {
if let Some(relocations) = obj.relocations.get(&program.section_index) {
for rel in relocations {
match rel.target {
RelocationTarget::Symbol(index) => {
let sym = obj
.symbol_table
.get(&index)
.ok_or(RelocationError::UnknownSymbol { index: index.0 })?;
let section_index = sym
.section_index
.ok_or(RelocationError::UnknownSymbolSection { index: index.0 })?;
let map = maps_by_section.get(&section_index.0).ok_or(
RelocationError::RelocationSectionNotFound {
symbol_index: index.0,
symbol_name: sym.name.clone(),
section_index: section_index.0,
},
)?;
let map_fd = map.fd.ok_or_else(|| RelocationError::MapNotCreated {
name: map.obj.name.clone(),
section_index: section_index.0,
})?;
let instructions = &mut program.instructions;
let ins_index =
(rel.offset / std::mem::size_of::<bpf_insn>() as u64) as usize;
if ins_index >= instructions.len() {
return Err(RelocationError::InvalidRelocationOffset {
offset: rel.offset,
});
}
if !map.obj.data.is_empty() {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8);
instructions[ins_index + 1].imm =
instructions[ins_index].imm + sym.address as i32;
} else {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8);
}
instructions[ins_index].imm = map_fd;
}
RelocationTarget::Section(_index) => {}
RelocationTarget::Absolute => todo!(),
}
}
}
}
Ok(())
}

@ -0,0 +1,262 @@
mod perf_attach;
mod probe;
mod socket_filter;
mod trace_point;
mod xdp;
use libc::ENOSPC;
use perf_attach::*;
pub use probe::*;
pub use socket_filter::*;
pub use trace_point::*;
pub use xdp::*;
use std::{
cell::RefCell,
cmp,
ffi::CStr,
io,
os::raw::c_uint,
path::PathBuf,
rc::{Rc, Weak},
};
use thiserror::Error;
use crate::{obj, syscalls::bpf_load_program, RawFd};
#[derive(Debug, Error)]
pub enum ProgramError {
#[error("the program {program} is already loaded")]
AlreadyLoaded { program: String },
#[error("the program {program} is not loaded")]
NotLoaded { program: String },
#[error("the BPF_PROG_LOAD syscall for `{program}` failed: {io_error}\nVerifier output:\n{verifier_log}")]
LoadFailed {
program: String,
io_error: io::Error,
verifier_log: String,
},
#[error("FIXME")]
AlreadyDetached,
#[error("the perf_event_open syscall failed: {io_error}")]
PerfEventOpenFailed { io_error: io::Error },
#[error("PERF_EVENT_IOC_SET_BPF/PERF_EVENT_IOC_ENABLE failed: {io_error}")]
PerfEventAttachFailed { io_error: io::Error },
#[error("the program {program} is not attached")]
NotAttached { program: String },
#[error("error attaching {program}: BPF_LINK_CREATE failed with {io_error}")]
BpfLinkCreateFailed {
program: String,
#[source]
io_error: io::Error,
},
#[error("unkown network interface {name}")]
UnkownInterface { name: String },
#[error("error reading ld.so.cache file")]
InvalidLdSoCache { error_kind: io::ErrorKind },
#[error("could not resolve uprobe target {path}")]
InvalidUprobeTarget { path: PathBuf },
#[error("error resolving symbol: {error}")]
UprobeSymbolError { symbol: String, error: String },
#[error("setsockopt SO_ATTACH_BPF failed: {io_error}")]
SocketFilterError { io_error: io::Error },
#[error("{message}")]
Other { message: String },
}
#[derive(Debug)]
pub(crate) struct ProgramData {
pub(crate) name: String,
pub(crate) obj: obj::Program,
pub(crate) fd: Option<RawFd>,
pub(crate) links: Vec<Rc<RefCell<dyn Link>>>,
}
#[derive(Debug)]
pub enum Program {
KProbe(KProbe),
UProbe(UProbe),
TracePoint(TracePoint),
SocketFilter(SocketFilter),
Xdp(Xdp),
}
impl Program {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(self.prog_type(), self.data_mut())
}
fn prog_type(&self) -> c_uint {
use crate::generated::bpf_prog_type::*;
match self {
Program::KProbe(_) => BPF_PROG_TYPE_KPROBE,
Program::UProbe(_) => BPF_PROG_TYPE_KPROBE,
Program::TracePoint(_) => BPF_PROG_TYPE_TRACEPOINT,
Program::SocketFilter(_) => BPF_PROG_TYPE_SOCKET_FILTER,
Program::Xdp(_) => BPF_PROG_TYPE_XDP,
}
}
fn data_mut(&mut self) -> &mut ProgramData {
match self {
Program::KProbe(p) => &mut p.data,
Program::UProbe(p) => &mut p.data,
Program::TracePoint(p) => &mut p.data,
Program::SocketFilter(p) => &mut p.data,
Program::Xdp(p) => &mut p.data,
}
}
}
impl ProgramData {
fn fd_or_err(&self) -> Result<RawFd, ProgramError> {
self.fd.ok_or(ProgramError::NotLoaded {
program: self.name.clone(),
})
}
}
impl Drop for ProgramData {
fn drop(&mut self) {}
}
const MAX_LOG_BUF_SIZE: usize = (std::u32::MAX >> 8) as usize;
pub struct VerifierLog {
buf: Vec<u8>,
}
impl VerifierLog {
fn new() -> VerifierLog {
VerifierLog { buf: Vec::new() }
}
pub(crate) fn buf(&mut self) -> &mut Vec<u8> {
&mut self.buf
}
fn grow(&mut self) {
self.buf.reserve(cmp::max(
1024 * 4,
cmp::min(MAX_LOG_BUF_SIZE, self.buf.capacity() * 2),
));
self.buf.resize(self.buf.capacity(), 0);
}
fn reset(&mut self) {
if !self.buf.is_empty() {
self.buf[0] = 0;
}
}
fn truncate(&mut self) {
if self.buf.is_empty() {
return;
}
let pos = self
.buf
.iter()
.position(|b| *b == 0)
.unwrap_or(self.buf.len() - 1);
self.buf.truncate(pos + 1);
}
pub fn as_c_str(&self) -> Option<&CStr> {
if self.buf.is_empty() {
None
} else {
Some(CStr::from_bytes_with_nul(&self.buf).unwrap())
}
}
}
fn load_program(prog_type: c_uint, data: &mut ProgramData) -> Result<(), ProgramError> {
let ProgramData { obj, fd, name, .. } = data;
if fd.is_some() {
return Err(ProgramError::AlreadyLoaded {
program: name.to_string(),
});
}
let crate::obj::Program {
instructions,
license,
kernel_version,
..
} = obj;
let mut ret = Ok(1);
let mut log_buf = VerifierLog::new();
for i in 0..3 {
log_buf.reset();
ret = match bpf_load_program(
prog_type,
instructions,
license,
(*kernel_version).into(),
&mut log_buf,
) {
Ok(prog_fd) => {
*fd = Some(prog_fd as RawFd);
return Ok(());
}
Err((_, io_error)) if i == 0 || io_error.raw_os_error() == Some(ENOSPC) => {
log_buf.grow();
continue;
}
x => x,
};
}
if let Err((_, io_error)) = ret {
log_buf.truncate();
return Err(ProgramError::LoadFailed {
program: name.clone(),
io_error,
verifier_log: log_buf.as_c_str().unwrap().to_string_lossy().to_string(),
});
}
Ok(())
}
pub trait Link: std::fmt::Debug {
fn detach(&mut self) -> Result<(), ProgramError>;
}
#[derive(Debug)]
pub(crate) struct LinkRef<T: Link> {
inner: Weak<RefCell<T>>,
}
impl<T: Link> LinkRef<T> {
fn new(inner: &Rc<RefCell<T>>) -> LinkRef<T> {
LinkRef {
inner: Rc::downgrade(inner),
}
}
}
impl<T: Link> Link for LinkRef<T> {
fn detach(&mut self) -> Result<(), ProgramError> {
if let Some(inner) = self.inner.upgrade() {
inner.borrow_mut().detach()
} else {
Err(ProgramError::AlreadyDetached)
}
}
}

@ -0,0 +1,46 @@
use std::{cell::RefCell, rc::Rc};
use libc::close;
use crate::{
syscalls::perf_event_ioctl, RawFd, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE,
PERF_EVENT_IOC_SET_BPF,
};
use super::{Link, LinkRef, ProgramData, ProgramError};
#[derive(Debug)]
struct PerfLink {
perf_fd: Option<RawFd>,
}
impl Link for PerfLink {
fn detach(&mut self) -> Result<(), ProgramError> {
if let Some(fd) = self.perf_fd.take() {
let _ = perf_event_ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
unsafe { close(fd) };
Ok(())
} else {
Err(ProgramError::AlreadyDetached)
}
}
}
impl Drop for PerfLink {
fn drop(&mut self) {
let _ = self.detach();
}
}
pub(crate) fn perf_attach(data: &mut ProgramData, fd: RawFd) -> Result<impl Link, ProgramError> {
let link = Rc::new(RefCell::new(PerfLink { perf_fd: Some(fd) }));
data.links.push(link.clone());
let prog_fd = data.fd_or_err()?;
perf_event_ioctl(fd, PERF_EVENT_IOC_SET_BPF, prog_fd)
.map_err(|(_, io_error)| ProgramError::PerfEventAttachFailed { io_error })?;
perf_event_ioctl(fd, PERF_EVENT_IOC_ENABLE, 0)
.map_err(|(_, io_error)| ProgramError::PerfEventAttachFailed { io_error })?;
Ok(LinkRef::new(&link))
}

@ -0,0 +1,326 @@
use libc::pid_t;
use object::{Object, ObjectSymbol};
use std::{
ffi::CStr,
fs,
io::{self, BufRead, Cursor, Read},
mem,
os::raw::c_char,
path::{Path, PathBuf},
};
use thiserror::Error;
use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
programs::{load_program, ProgramData, ProgramError},
syscalls::perf_event_open_probe,
};
use super::{perf_attach, Link};
lazy_static! {
static ref LD_SO_CACHE: Result<LdSoCache, io::Error> = LdSoCache::load("/etc/ld.so.cache");
}
const LD_SO_CACHE_HEADER: &str = "glibc-ld.so.cache1.1";
#[derive(Debug)]
pub struct KProbe {
pub(crate) data: ProgramData,
}
#[derive(Debug)]
pub struct UProbe {
pub(crate) data: ProgramData,
}
impl KProbe {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
}
pub fn name(&self) -> String {
self.data.name.to_string()
}
pub fn attach(
&mut self,
fn_name: &str,
offset: u64,
pid: Option<pid_t>,
) -> Result<impl Link, ProgramError> {
attach(&mut self.data, ProbeKind::KProbe, fn_name, offset, pid)
}
}
impl UProbe {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
}
pub fn name(&self) -> String {
self.data.name.to_string()
}
pub fn attach<T: AsRef<Path>>(
&mut self,
fn_name: Option<&str>,
offset: u64,
target: T,
pid: Option<pid_t>,
) -> Result<impl Link, ProgramError> {
let target = target.as_ref();
let target_str = &*target.as_os_str().to_string_lossy();
let mut path = if let Some(pid) = pid {
find_lib_in_proc_maps(pid, &target_str).map_err(|io_error| ProgramError::Other {
message: format!("error parsing /proc/{}/maps: {}", pid, io_error),
})?
} else {
None
};
if path.is_none() {
path = if target.is_absolute() {
Some(target_str)
} else {
let cache =
LD_SO_CACHE
.as_ref()
.map_err(|io_error| ProgramError::InvalidLdSoCache {
error_kind: io_error.kind(),
})?;
cache.resolve(target_str)
}
.map(String::from)
};
let path = path.ok_or(ProgramError::InvalidUprobeTarget {
path: target.to_owned(),
})?;
let sym_offset = if let Some(fn_name) = fn_name {
resolve_symbol(&path, fn_name).map_err(|error| ProgramError::UprobeSymbolError {
symbol: fn_name.to_string(),
error: error.to_string(),
})?
} else {
0
};
attach(
&mut self.data,
ProbeKind::UProbe,
&path,
sym_offset + offset,
pid,
)
}
}
enum ProbeKind {
KProbe,
KRetProbe,
UProbe,
URetProbe,
}
fn attach(
program_data: &mut ProgramData,
kind: ProbeKind,
name: &str,
offset: u64,
pid: Option<pid_t>,
) -> Result<impl Link, ProgramError> {
use ProbeKind::*;
let perf_ty = read_sys_fs_perf_type(match kind {
KProbe | KRetProbe => "kprobe",
UProbe | URetProbe => "uprobe",
})?;
let ret_bit = match kind {
KRetProbe => Some(read_sys_fs_perf_ret_probe("kprobe")?),
URetProbe => Some(read_sys_fs_perf_ret_probe("uprobe")?),
_ => None,
};
let fd = perf_event_open_probe(perf_ty, ret_bit, name, offset, pid)
.map_err(|(_code, io_error)| ProgramError::PerfEventOpenFailed { io_error })?
as i32;
perf_attach(program_data, fd)
}
fn proc_maps_libs(pid: pid_t) -> Result<Vec<(String, String)>, io::Error> {
let maps_file = format!("/proc/{}/maps", pid);
let data = fs::read_to_string(maps_file)?;
Ok(data
.lines()
.filter_map(|line| {
let line = line.split_whitespace().last()?;
if line.starts_with('/') {
let path = PathBuf::from(line);
let key = path.file_name().unwrap().to_string_lossy().into_owned();
Some((key, path.to_string_lossy().to_string()))
} else {
None
}
})
.collect())
}
fn find_lib_in_proc_maps(pid: pid_t, lib: &str) -> Result<Option<String>, io::Error> {
let libs = proc_maps_libs(pid)?;
let ret = if lib.contains(".so") {
libs.iter().find(|(k, _)| k.as_str().starts_with(lib))
} else {
let lib = lib.to_string();
let lib1 = lib.clone() + ".so";
let lib2 = lib + "-";
libs.iter()
.find(|(k, _)| k.starts_with(&lib1) || k.starts_with(&lib2))
};
Ok(ret.map(|(_, v)| v.clone()))
}
#[derive(Debug)]
pub(crate) struct CacheEntry {
key: String,
value: String,
flags: i32,
}
#[derive(Debug)]
pub(crate) struct LdSoCache {
entries: Vec<CacheEntry>,
}
impl LdSoCache {
pub fn load<T: AsRef<Path>>(path: T) -> Result<Self, io::Error> {
let data = fs::read(path)?;
Self::parse(&data)
}
fn parse(data: &[u8]) -> Result<Self, io::Error> {
let mut cursor = Cursor::new(data);
let read_u32 = |cursor: &mut Cursor<_>| -> Result<u32, io::Error> {
let mut buf = [0u8; mem::size_of::<u32>()];
cursor.read_exact(&mut buf)?;
Ok(u32::from_ne_bytes(buf))
};
let read_i32 = |cursor: &mut Cursor<_>| -> Result<i32, io::Error> {
let mut buf = [0u8; mem::size_of::<i32>()];
cursor.read_exact(&mut buf)?;
Ok(i32::from_ne_bytes(buf))
};
let mut buf = [0u8; LD_SO_CACHE_HEADER.len()];
cursor.read_exact(&mut buf)?;
let header = std::str::from_utf8(&buf).or(Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid ld.so.cache header",
)))?;
if header != LD_SO_CACHE_HEADER {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid ld.so.cache header",
));
}
let num_entries = read_u32(&mut cursor)?;
let _str_tab_len = read_u32(&mut cursor)?;
cursor.consume(5 * mem::size_of::<u32>());
let mut entries = Vec::new();
for _ in 0..num_entries {
let flags = read_i32(&mut cursor)?;
let k_pos = read_u32(&mut cursor)? as usize;
let v_pos = read_u32(&mut cursor)? as usize;
cursor.consume(12);
let key =
unsafe { CStr::from_ptr(cursor.get_ref()[k_pos..].as_ptr() as *const c_char) }
.to_string_lossy()
.into_owned();
let value =
unsafe { CStr::from_ptr(cursor.get_ref()[v_pos..].as_ptr() as *const c_char) }
.to_string_lossy()
.into_owned();
entries.push(CacheEntry { key, value, flags });
}
Ok(LdSoCache { entries })
}
pub fn resolve(&self, lib: &str) -> Option<&str> {
let lib = if !lib.contains(".so") {
lib.to_string() + ".so"
} else {
lib.to_string()
};
self.entries
.iter()
.find(|entry| entry.key.starts_with(&lib))
.map(|entry| entry.value.as_str())
}
}
#[derive(Error, Debug)]
enum ResolveSymbolError {
#[error("io error {0}")]
Io(#[from] io::Error),
#[error("error parsing ELF {0}")]
Object(#[from] object::Error),
#[error("unknown symbol {0}")]
Unknown(String),
}
fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> {
let data = fs::read(path)?;
let obj = object::read::File::parse(&data)?;
obj.dynamic_symbols()
.chain(obj.symbols())
.find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false))
.map(|s| s.address())
.ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))
}
pub fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, ProgramError> {
let file = format!("/sys/bus/event_source/devices/{}/type", pmu);
let perf_ty = fs::read_to_string(&file).map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?;
let perf_ty = perf_ty
.trim()
.parse::<u32>()
.map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?;
Ok(perf_ty)
}
pub fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, ProgramError> {
let file = format!("/sys/bus/event_source/devices/{}/format/retprobe", pmu);
let data = fs::read_to_string(&file).map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?;
let mut parts = data.trim().splitn(2, ":").skip(1);
let config = parts.next().ok_or(ProgramError::Other {
message: format!("error parsing {}: `{}'", file, data),
})?;
config.parse::<u32>().map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})
}

@ -0,0 +1,39 @@
use std::{io, mem, os::unix::prelude::RawFd};
use libc::{setsockopt, SOL_SOCKET, SO_ATTACH_BPF};
use crate::generated::bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER;
use super::{load_program, ProgramData, ProgramError};
#[derive(Debug)]
pub struct SocketFilter {
pub(crate) data: ProgramData,
}
impl SocketFilter {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data)
}
pub fn attach(&self, socket: RawFd) -> Result<(), ProgramError> {
let prog_fd = self.data.fd_or_err()?;
let ret = unsafe {
setsockopt(
socket,
SOL_SOCKET,
SO_ATTACH_BPF,
&prog_fd as *const _ as *const _,
mem::size_of::<RawFd>() as u32,
)
};
if ret < 0 {
return Err(ProgramError::SocketFilterError {
io_error: io::Error::last_os_error(),
});
}
Ok(())
}
}

@ -0,0 +1,40 @@
use std::fs;
use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT, syscalls::perf_event_open_trace_point,
};
use super::{load_program, perf_attach, Link, ProgramData, ProgramError};
#[derive(Debug)]
pub struct TracePoint {
pub(crate) data: ProgramData,
}
impl TracePoint {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data)
}
pub fn attach(&mut self, category: &str, name: &str) -> Result<impl Link, ProgramError> {
let id = read_sys_fs_trace_point_id(category, name)?;
let fd = perf_event_open_trace_point(id)
.map_err(|(_code, io_error)| ProgramError::PerfEventOpenFailed { io_error })?
as i32;
perf_attach(&mut self.data, fd)
}
}
fn read_sys_fs_trace_point_id(category: &str, name: &str) -> Result<u32, ProgramError> {
let file = format!("/sys/kernel/debug/tracing/events/{}/{}/id", category, name);
let id = fs::read_to_string(&file).map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?;
let id = id.trim().parse::<u32>().map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?;
Ok(id)
}

@ -0,0 +1,45 @@
use std::ffi::CString;
use libc::if_nametoindex;
use crate::generated::{bpf_attach_type::BPF_XDP, bpf_prog_type::BPF_PROG_TYPE_XDP};
use crate::syscalls::bpf_link_create;
use crate::RawFd;
use super::{load_program, ProgramData, ProgramError};
#[derive(Debug)]
pub struct Xdp {
pub(crate) data: ProgramData,
}
impl Xdp {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_XDP, &mut self.data)
}
pub fn name(&self) -> String {
self.data.name.to_string()
}
pub fn attach(&self, interface: &str) -> Result<(), ProgramError> {
let prog_fd = self.data.fd_or_err()?;
let c_interface = CString::new(interface).unwrap();
let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) } as RawFd;
if if_index == 0 {
return Err(ProgramError::UnkownInterface {
name: interface.to_string(),
})?;
}
let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, 0).map_err(|(_, io_error)| {
ProgramError::BpfLinkCreateFailed {
program: self.name(),
io_error,
}
})?;
Ok(())
}
}

@ -0,0 +1,180 @@
use std::{
cmp,
ffi::CStr,
io,
mem::{self, MaybeUninit},
slice,
};
use libc::{c_long, c_uint, ENOENT};
use crate::{
bpf_map_def,
generated::{bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn},
programs::VerifierLog,
syscalls::SysResult,
RawFd, BPF_OBJ_NAME_LEN,
};
use super::{syscall, Syscall};
pub(crate) fn bpf_create_map(name: &CStr, def: &bpf_map_def) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_1 };
u.map_type = def.map_type;
u.key_size = def.key_size;
u.value_size = def.value_size;
u.max_entries = def.max_entries;
u.map_flags = def.map_flags;
// u.map_name is 16 bytes max and must be NULL terminated
let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1);
u.map_name[..name_len]
.copy_from_slice(unsafe { slice::from_raw_parts(name.as_ptr(), name_len) });
sys_bpf(bpf_cmd::BPF_MAP_CREATE, &attr)
}
pub(crate) fn bpf_load_program(
ty: c_uint,
insns: &[bpf_insn],
license: &CStr,
kernel_version: u32,
log: &mut VerifierLog,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = ty;
u.expected_attach_type = 0;
u.insns = insns.as_ptr() as u64;
u.insn_cnt = insns.len() as u32;
u.license = license.as_ptr() as u64;
u.kern_version = kernel_version;
let log_buf = log.buf();
if log_buf.capacity() > 0 {
u.log_level = 7;
u.log_buf = log_buf.as_mut_ptr() as u64;
u.log_size = log_buf.capacity() as u32;
}
sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr)
}
fn lookup<K, V: crate::Pod>(
fd: RawFd,
key: &K,
flags: u64,
cmd: bpf_cmd::Type,
) -> Result<Option<V>, (c_long, io::Error)> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let mut value = MaybeUninit::uninit();
let u = unsafe { &mut attr.__bindgen_anon_2 };
u.map_fd = fd as u32;
u.key = key as *const _ as u64;
u.__bindgen_anon_1.value = &mut value as *mut _ as u64;
u.flags = flags;
match sys_bpf(cmd, &attr) {
Ok(_) => Ok(Some(unsafe { value.assume_init() })),
Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
Err(e) => Err(e),
}
}
pub(crate) fn bpf_map_lookup_elem<K, V: crate::Pod>(
fd: RawFd,
key: &K,
flags: u64,
) -> Result<Option<V>, (c_long, io::Error)> {
lookup(fd, key, flags, bpf_cmd::BPF_MAP_LOOKUP_ELEM)
}
pub(crate) fn bpf_map_lookup_and_delete_elem<K, V: crate::Pod>(
fd: RawFd,
key: &K,
) -> Result<Option<V>, (c_long, io::Error)> {
lookup(fd, key, 0, bpf_cmd::BPF_MAP_LOOKUP_AND_DELETE_ELEM)
}
pub(crate) fn bpf_map_update_elem<K, V>(fd: RawFd, key: &K, value: &V, flags: u64) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_2 };
u.map_fd = fd as u32;
u.key = key as *const _ as u64;
u.__bindgen_anon_1.value = value as *const _ as u64;
u.flags = flags;
sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &attr)
}
pub(crate) fn bpf_map_update_elem_ptr<K, V>(
fd: RawFd,
key: *const K,
value: *const V,
flags: u64,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_2 };
u.map_fd = fd as u32;
u.key = key as u64;
u.__bindgen_anon_1.value = value as u64;
u.flags = flags;
sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &attr)
}
pub(crate) fn bpf_map_delete_elem<K>(fd: RawFd, key: &K) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_2 };
u.map_fd = fd as u32;
u.key = key as *const _ as u64;
sys_bpf(bpf_cmd::BPF_MAP_DELETE_ELEM, &attr)
}
pub(crate) fn bpf_map_get_next_key<K>(
fd: RawFd,
key: Option<&K>,
) -> Result<Option<K>, (c_long, io::Error)> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let mut next_key = MaybeUninit::uninit();
let u = unsafe { &mut attr.__bindgen_anon_2 };
u.map_fd = fd as u32;
if let Some(key) = key {
u.key = key as *const _ as u64;
}
u.__bindgen_anon_1.next_key = &mut next_key as *mut _ as u64;
match sys_bpf(bpf_cmd::BPF_MAP_GET_NEXT_KEY, &attr) {
Ok(_) => Ok(Some(unsafe { next_key.assume_init() })),
Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None),
Err(e) => Err(e),
}
}
pub(crate) fn bpf_link_create(
prog_fd: RawFd,
target_fd: RawFd,
attach_type: bpf_attach_type::Type,
flags: u32,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
attr.link_create.prog_fd = prog_fd as u32;
attr.link_create.__bindgen_anon_1.target_fd = target_fd as u32;
attr.link_create.attach_type = attach_type;
attr.link_create.flags = flags;
sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr)
}
fn sys_bpf<'a>(cmd: bpf_cmd::Type, attr: &'a bpf_attr) -> SysResult {
syscall(Syscall::Bpf { cmd, attr })
}

@ -0,0 +1,22 @@
use std::{cell::RefCell, io, ptr};
use libc::c_void;
use super::{SysResult, Syscall};
type SyscallFn = unsafe fn(Syscall) -> SysResult;
#[cfg(test)]
thread_local! {
pub(crate) static TEST_SYSCALL: RefCell<SyscallFn> = RefCell::new(test_syscall);
pub(crate) static TEST_MMAP_RET: RefCell<*mut c_void> = RefCell::new(ptr::null_mut());
}
#[cfg(test)]
unsafe fn test_syscall(_call: Syscall) -> SysResult {
return Err((-1, io::Error::from_raw_os_error(libc::EINVAL)));
}
pub(crate) fn override_syscall(call: unsafe fn(Syscall) -> SysResult) {
TEST_SYSCALL.with(|test_impl| *test_impl.borrow_mut() = call);
}

@ -0,0 +1,71 @@
mod bpf;
mod perf_event;
#[cfg(test)]
mod fake;
use std::io;
use libc::{c_int, c_long, c_ulong, pid_t};
pub(crate) use bpf::*;
#[cfg(test)]
pub(crate) use fake::*;
pub(crate) use perf_event::*;
use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr};
pub(crate) type SysResult = Result<c_long, (c_long, io::Error)>;
#[cfg_attr(test, allow(dead_code))]
pub(crate) enum Syscall<'a> {
Bpf {
cmd: bpf_cmd::Type,
attr: &'a bpf_attr,
},
PerfEventOpen {
attr: perf_event_attr,
pid: pid_t,
cpu: i32,
group: i32,
flags: u32,
},
PerfEventIoctl {
fd: c_int,
request: c_ulong,
arg: c_int,
},
}
fn syscall(call: Syscall) -> SysResult {
#[cfg(not(test))]
return unsafe { syscall_impl(call) };
#[cfg(test)]
return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
}
#[cfg(not(test))]
unsafe fn syscall_impl(call: Syscall) -> SysResult {
use libc::{SYS_bpf, SYS_perf_event_open};
use std::mem;
use Syscall::*;
let ret = match call {
Bpf { cmd, attr } => libc::syscall(SYS_bpf, cmd, attr, mem::size_of::<bpf_attr>()),
PerfEventOpen {
attr,
pid,
cpu,
group,
flags,
} => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags),
PerfEventIoctl { fd, request, arg } => libc::ioctl(fd, request, arg) as i64,
};
if ret < 0 {
return Err((ret, io::Error::last_os_error()));
}
Ok(ret)
}

@ -0,0 +1,89 @@
use std::{ffi::CString, mem};
use libc::{c_int, c_ulong, pid_t};
use crate::generated::{
perf_event_attr,
perf_event_sample_format::PERF_SAMPLE_RAW,
perf_sw_ids::PERF_COUNT_SW_BPF_OUTPUT,
perf_type_id::{PERF_TYPE_SOFTWARE, PERF_TYPE_TRACEPOINT},
PERF_FLAG_FD_CLOEXEC,
};
use super::{syscall, SysResult, Syscall};
pub(crate) fn perf_event_open(cpu: c_int) -> SysResult {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
attr.config = PERF_COUNT_SW_BPF_OUTPUT as u64;
attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = PERF_TYPE_SOFTWARE;
attr.sample_type = PERF_SAMPLE_RAW as u64;
attr.__bindgen_anon_1.sample_period = 1;
attr.__bindgen_anon_2.wakeup_events = 1;
syscall(Syscall::PerfEventOpen {
attr,
pid: -1,
cpu,
group: -1,
flags: PERF_FLAG_FD_CLOEXEC,
})
}
pub(crate) fn perf_event_open_probe(
ty: u32,
ret_bit: Option<u32>,
name: &str,
offset: u64,
pid: Option<pid_t>,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
if let Some(ret_bit) = ret_bit {
attr.config = 1 << ret_bit;
}
let c_name = CString::new(name).unwrap();
attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = ty;
attr.__bindgen_anon_3.config1 = c_name.as_ptr() as u64;
attr.__bindgen_anon_4.config2 = offset;
let cpu = if pid.is_some() { -1 } else { 0 };
let pid = pid.unwrap_or(-1);
syscall(Syscall::PerfEventOpen {
attr,
pid,
cpu,
group: -1,
flags: PERF_FLAG_FD_CLOEXEC,
})
}
pub(crate) fn perf_event_open_trace_point(id: u32) -> SysResult {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
attr.size = mem::size_of::<perf_event_attr>() as u32;
attr.type_ = PERF_TYPE_TRACEPOINT;
attr.config = id as u64;
syscall(Syscall::PerfEventOpen {
attr,
pid: -1,
cpu: 0,
group: -1,
flags: PERF_FLAG_FD_CLOEXEC,
})
}
pub(crate) fn perf_event_ioctl(fd: c_int, request: c_ulong, arg: c_int) -> SysResult {
let call = Syscall::PerfEventIoctl { fd, request, arg };
#[cfg(not(test))]
return syscall(call);
#[cfg(test)]
return crate::syscalls::TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
}
Loading…
Cancel
Save