mod kprobe;
mod perf_event;
mod perf_event_array;
mod raw_tracepoint;
mod socket;
mod tracepoint;
mod uprobe;
mod usdt;
mod xdp;
use bcc_sys::bccapi::*;
pub(crate) use self::kprobe::Kprobe;
pub(crate) use self::perf_event::PerfEvent;
pub(crate) use self::perf_event_array::PerfEventArray;
pub(crate) use self::raw_tracepoint::RawTracepoint;
pub(crate) use self::socket::RawSocket;
pub(crate) use self::tracepoint::Tracepoint;
pub(crate) use self::uprobe::Uprobe;
pub use self::usdt::{usdt_generate_args, USDTContext};
pub(crate) use self::xdp::XDP;
use crate::helpers::to_cstring;
use crate::perf_event::{PerfMapBuilder, PerfReader};
use crate::symbol::SymbolCache;
use crate::table::Table;
use crate::types::MutPointer;
use crate::BccError;
use core::sync::atomic::{AtomicPtr, Ordering};
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::ffi::CString;
use std::fs::File;
use std::io::Error;
use std::os::raw::{c_char, c_void};
use std::os::unix::prelude::*;
use std::ptr;
const SYSCALL_PREFIXES: [&str; 7] = [
"sys_",
"__x64_sys_",
"__x32_compat_sys_",
"__ia32_compat_sys_",
"__arm64_sys_",
"__s390x_sys_",
"__s390_sys_",
];
bitflags! {
#[derive(Default)]
pub struct BccDebug: u32 {
const LLVM_IR = 0x1;
const BPF = 0x2;
const PREPROCESSOR = 0x4;
const SOURCE = 0x8;
const BPF_REGISTER_STATE = 0x10;
const BTF = 0x20;
}
}
#[repr(u32)]
pub enum BpfProgType {
Kprobe = bpf_prog_type_BPF_PROG_TYPE_KPROBE,
Tracepoint = bpf_prog_type_BPF_PROG_TYPE_TRACEPOINT,
PerfEvent = bpf_prog_type_BPF_PROG_TYPE_PERF_EVENT,
}
#[derive(Debug)]
pub struct BPF {
p: AtomicPtr<c_void>,
pub(crate) kprobes: HashSet<Kprobe>,
pub(crate) uprobes: HashSet<Uprobe>,
pub(crate) tracepoints: HashSet<Tracepoint>,
pub(crate) raw_tracepoints: HashSet<RawTracepoint>,
pub(crate) perf_events: HashSet<PerfEvent>,
pub(crate) perf_events_array: HashSet<PerfEventArray>,
pub(crate) xdp: HashSet<XDP>,
perf_readers: Vec<PerfReader>,
sym_caches: HashMap<pid_t, SymbolCache>,
cflags: Vec<CString>,
functions: HashMap<String, i32>,
debug: BccDebug,
}
pub(crate) fn make_alphanumeric(s: &str) -> String {
s.replace(
|c| !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')),
"_",
)
}
fn null_or_mut_ptr<T>(s: &mut Vec<u8>) -> *mut T {
if s.capacity() == 0 {
ptr::null_mut()
} else {
s.as_mut_ptr() as *mut T
}
}
pub struct BPFBuilder {
code: CString,
cflags: Vec<CString>,
device: Option<CString>,
debug: BccDebug,
usdt_contexts: Vec<USDTContext>,
attach_usdt_ignore_pid: bool,
}
impl BPFBuilder {
pub fn new(code: &str) -> Result<Self, BccError> {
Ok(Self {
code: to_cstring(code, "code")?,
cflags: Vec::new(),
device: None,
debug: Default::default(),
usdt_contexts: Vec::new(),
attach_usdt_ignore_pid: false,
})
}
pub fn cflags<T: AsRef<str>>(mut self, cflags: &[T]) -> Result<Self, BccError> {
self.cflags.clear();
for f in cflags {
let cs = to_cstring(f.as_ref(), "cflags")?;
self.cflags.push(cs);
}
Ok(self)
}
pub fn device<T: AsRef<str>>(mut self, device: T) -> Result<Self, BccError> {
self.device = Some(to_cstring(device.as_ref(), "device")?);
Ok(self)
}
pub fn debug(mut self, debug: BccDebug) -> Self {
self.debug = debug;
self
}
pub fn attach_usdt_ignore_pid(mut self, ignore: bool) -> Result<Self, BccError> {
self.attach_usdt_ignore_pid = ignore;
Ok(self)
}
pub fn add_usdt_context(mut self, context: USDTContext) -> Result<Self, BccError> {
self.usdt_contexts.push(context);
Ok(self)
}
#[cfg(any(
feature = "v0_4_0",
feature = "v0_5_0",
feature = "v0_6_0",
feature = "v0_6_1",
feature = "v0_7_0",
feature = "v0_8_0",
))]
fn create_module(&self) -> Result<MutPointer, BccError> {
let ptr = unsafe {
bpf_module_create_c_from_string(
self.code.as_ptr(),
self.debug.bits(),
self.cflags
.iter()
.map(|v| v.as_ptr())
.collect::<Vec<*const c_char>>()
.as_mut_ptr(),
self.cflags.len().try_into().unwrap(),
)
};
if ptr.is_null() {
Err(BccError::Compilation)
} else {
Ok(ptr)
}
}
#[cfg(any(feature = "v0_9_0", feature = "v0_10_0"))]
fn create_module(&self) -> Result<MutPointer, BccError> {
let ptr = unsafe {
bpf_module_create_c_from_string(
self.code.as_ptr(),
self.debug.bits(),
self.cflags
.iter()
.map(|v| v.as_ptr())
.collect::<Vec<*const c_char>>()
.as_mut_ptr(),
self.cflags.len().try_into().unwrap(),
true,
)
};
if ptr.is_null() {
Err(BccError::Compilation)
} else {
Ok(ptr)
}
}
#[cfg(any(
feature = "v0_11_0",
feature = "v0_12_0",
feature = "v0_13_0",
feature = "v0_14_0",
feature = "v0_15_0",
feature = "v0_16_0",
feature = "v0_17_0",
feature = "v0_18_0",
feature = "v0_19_0",
feature = "v0_20_0",
feature = "v0_21_0",
feature = "v0_22_0",
feature = "v0_23_0",
not(feature = "specific")
))]
fn create_module(&self) -> Result<MutPointer, BccError> {
let ptr = unsafe {
bpf_module_create_c_from_string(
self.code.as_ptr(),
self.debug.bits(),
self.cflags
.iter()
.map(|v| v.as_ptr())
.collect::<Vec<*const c_char>>()
.as_mut_ptr(),
self.cflags.len().try_into().unwrap(),
true,
self.device
.as_ref()
.map(|name| name.as_ptr())
.unwrap_or(ptr::null_mut()),
)
};
if ptr.is_null() {
Err(BccError::Compilation)
} else {
Ok(ptr)
}
}
pub fn build(mut self) -> Result<BPF, BccError> {
let contexts = if self.usdt_contexts.is_empty() {
Vec::new()
} else {
let (mut code, contexts) = usdt_generate_args(self.usdt_contexts.drain(..).collect())?;
let base_code = self.code.to_str().map(|s| s.to_string())?;
code.push_str(base_code.as_str());
self.code = to_cstring(code.as_str(), "code")?;
contexts
};
let attach_usdt_ignore_pid = self.attach_usdt_ignore_pid;
let ptr = self.create_module()?;
let mut bpf = BPF {
p: AtomicPtr::new(ptr),
uprobes: HashSet::new(),
kprobes: HashSet::new(),
tracepoints: HashSet::new(),
raw_tracepoints: HashSet::new(),
perf_events: HashSet::new(),
perf_events_array: HashSet::new(),
perf_readers: Vec::new(),
sym_caches: HashMap::new(),
xdp: HashSet::new(),
cflags: self.cflags,
functions: HashMap::new(),
debug: self.debug,
};
for context in contexts {
let _ = context.attach(&mut bpf, attach_usdt_ignore_pid)?;
}
Ok(bpf)
}
}
impl BPF {
pub fn new(code: &str) -> Result<BPF, BccError> {
BPFBuilder::new(code)?.build()
}
fn ptr(&self) -> MutPointer {
self.p.load(Ordering::SeqCst)
}
pub fn table(&self, name: &str) -> Result<Table, BccError> {
let cname = to_cstring(name, "name")?;
let id = unsafe { bpf_table_id(self.ptr(), cname.as_ptr()) };
Ok(Table::new(id, self.ptr()))
}
#[cfg(any(feature = "v0_9_0", feature = "v0_10_0",))]
unsafe fn load_func_impl(
&self,
program: *mut ::std::os::raw::c_void,
prog_type: ::std::os::raw::c_int,
name: *const ::std::os::raw::c_char,
insns: *const bpf_insn,
prog_len: ::std::os::raw::c_int,
license: *const ::std::os::raw::c_char,
kern_version: ::std::os::raw::c_uint,
log_level: ::std::os::raw::c_int,
log_buf: *mut ::std::os::raw::c_char,
log_buf_size: ::std::os::raw::c_uint,
_dev_name: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
bcc_func_load(
program,
prog_type,
name,
insns,
prog_len,
license,
kern_version,
log_level,
log_buf,
log_buf_size,
)
}
#[cfg(any(
feature = "v0_11_0",
feature = "v0_12_0",
feature = "v0_13_0",
feature = "v0_14_0",
feature = "v0_15_0",
feature = "v0_16_0",
feature = "v0_17_0",
feature = "v0_18_0",
feature = "v0_19_0",
feature = "v0_20_0",
feature = "v0_21_0",
feature = "v0_22_0",
feature = "v0_23_0",
not(feature = "specific"),
))]
unsafe fn load_func_impl(
&self,
program: *mut ::std::os::raw::c_void,
prog_type: ::std::os::raw::c_int,
name: *const ::std::os::raw::c_char,
insns: *const bpf_insn,
prog_len: ::std::os::raw::c_int,
license: *const ::std::os::raw::c_char,
kern_version: ::std::os::raw::c_uint,
log_level: ::std::os::raw::c_int,
log_buf: *mut ::std::os::raw::c_char,
log_buf_size: ::std::os::raw::c_uint,
dev_name: *const ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
bcc_func_load(
program,
prog_type,
name,
insns,
prog_len,
license,
kern_version,
log_level,
log_buf,
log_buf_size,
dev_name,
)
}
#[cfg(any(
feature = "v0_6_0",
feature = "v0_6_1",
feature = "v0_7_0",
feature = "v0_8_0",
))]
pub fn load_func(&mut self, name: &str, bpf_prog_type: BpfProgType) -> Result<i32, BccError> {
Err(BccError::BccVersionTooLow {
cause: "load_func".to_owned(),
min_version: "0.9.0".to_owned(),
})
}
#[cfg(any(
feature = "v0_9_0",
feature = "v0_10_0",
feature = "v0_11_0",
feature = "v0_12_0",
feature = "v0_13_0",
feature = "v0_14_0",
feature = "v0_15_0",
feature = "v0_16_0",
feature = "v0_17_0",
feature = "v0_18_0",
feature = "v0_19_0",
feature = "v0_20_0",
feature = "v0_21_0",
feature = "v0_22_0",
feature = "v0_23_0",
not(feature = "specific"),
))]
pub fn load_func(&mut self, name: &str, bpf_prog_type: BpfProgType) -> Result<i32, BccError> {
let name_cstring = CString::new(name).unwrap();
let name_ptr = name_cstring.as_ptr();
if let Some(fd) = self.functions.get(name) {
return Ok(*fd);
}
let log_level = if self.debug.contains(BccDebug::BPF_REGISTER_STATE) {
2
} else if self.debug.contains(BccDebug::BPF) {
1
} else {
0
};
unsafe {
let f_start = bpf_function_start(self.ptr(), name_ptr);
if f_start as usize == 0 {
return Err(BccError::Loading {
name: name.to_string(),
message: String::from("The specified function could not be found."),
});
}
let fd = self.load_func_impl(
self.ptr(),
bpf_prog_type as i32,
name_ptr,
f_start as *const bcc_sys::bccapi::bpf_insn,
bpf_function_size(self.ptr(), name_ptr).try_into().unwrap(),
bpf_module_license(self.ptr()),
bpf_module_kern_version(self.ptr()),
log_level,
ptr::null_mut(),
0,
ptr::null_mut(),
);
self.functions.insert(name.to_string(), fd);
Ok(fd)
}
}
pub(crate) fn table_fd(&self, name: &str) -> Result<i32, BccError> {
let cname = to_cstring(name, "name")?;
Ok(unsafe { bpf_table_fd(self.ptr(), cname.as_ptr()) })
}
pub fn load_net(&mut self, name: &str) -> Result<File, BccError> {
self.load(name, bpf_prog_type_BPF_PROG_TYPE_SCHED_ACT, 0, 0)
}
#[cfg(feature = "v0_4_0")]
pub fn load(
&mut self,
name: &str,
prog_type: u32,
_log_level: i32,
log_size: u32,
) -> Result<File, BccError> {
let cname = to_cstring(name, "name")?;
unsafe {
let start: *mut bpf_insn =
bpf_function_start(self.ptr(), cname.as_ptr()) as *mut bpf_insn;
let size = bpf_function_size(self.ptr(), cname.as_ptr()) as i32;
let license = bpf_module_license(self.ptr());
let version = bpf_module_kern_version(self.ptr());
if start.is_null() {
return Err(BccError::Loading {
name: name.to_string(),
message: String::from("start instruction is null"),
});
}
let mut log_buf: Vec<u8> = Vec::with_capacity(log_size as usize);
let fd = bpf_prog_load(
prog_type,
start,
size,
license,
version,
null_or_mut_ptr(&mut log_buf),
log_buf.capacity() as u32,
);
if fd < 0 {
return Err(BccError::Loading {
name: name.to_string(),
message: Error::last_os_error().to_string(),
});
}
Ok(File::from_raw_fd(fd))
}
}
#[cfg(any(
feature = "v0_5_0",
feature = "v0_6_0",
feature = "v0_6_1",
feature = "v0_7_0",
feature = "v0_8_0"
))]
pub fn load(
&mut self,
name: &str,
prog_type: u32,
log_level: i32,
log_size: u32,
) -> Result<File, BccError> {
let cname = to_cstring(name, "name")?;
unsafe {
let start: *mut bpf_insn =
bpf_function_start(self.ptr(), cname.as_ptr()) as *mut bpf_insn;
let size = bpf_function_size(self.ptr(), cname.as_ptr()) as i32;
let license = bpf_module_license(self.ptr());
let version = bpf_module_kern_version(self.ptr());
if start.is_null() {
return Err(BccError::Loading {
name: name.to_string(),
message: String::from("start instruction is null"),
});
}
let mut log_buf: Vec<u8> = Vec::with_capacity(log_size as usize);
let fd = bpf_prog_load(
prog_type,
cname.as_ptr(),
start,
size,
license,
version,
log_level,
null_or_mut_ptr(&mut log_buf),
log_buf.capacity() as u32,
);
if fd < 0 {
return Err(BccError::Loading {
name: name.to_string(),
message: Error::last_os_error().to_string(),
});
}
Ok(File::from_raw_fd(fd))
}
}
#[cfg(any(
feature = "v0_9_0",
feature = "v0_10_0",
feature = "v0_11_0",
feature = "v0_12_0",
feature = "v0_13_0",
feature = "v0_14_0",
feature = "v0_15_0",
feature = "v0_16_0",
feature = "v0_17_0",
feature = "v0_18_0",
feature = "v0_19_0",
feature = "v0_20_0",
feature = "v0_21_0",
feature = "v0_22_0",
feature = "v0_23_0",
not(feature = "specific"),
))]
pub fn load(
&mut self,
name: &str,
prog_type: u32,
log_level: i32,
log_size: u32,
) -> Result<File, BccError> {
let cname = to_cstring(name, "name")?;
unsafe {
let start: *mut bpf_insn =
bpf_function_start(self.ptr(), cname.as_ptr()) as *mut bpf_insn;
let size = bpf_function_size(self.ptr(), cname.as_ptr()) as i32;
let license = bpf_module_license(self.ptr());
let version = bpf_module_kern_version(self.ptr());
if start.is_null() {
return Err(BccError::Loading {
name: name.to_string(),
message: String::from("start instruction is null"),
});
}
let mut log_buf: Vec<u8> = Vec::with_capacity(log_size as usize);
let fd = bcc_prog_load(
prog_type,
cname.as_ptr(),
start,
size,
license,
version,
log_level,
null_or_mut_ptr(&mut log_buf),
log_buf.capacity() as u32,
);
if fd < 0 {
return Err(BccError::Loading {
name: name.to_string(),
message: Error::last_os_error().to_string(),
});
}
Ok(File::from_raw_fd(fd))
}
}
pub fn get_syscall_prefix(&mut self) -> String {
for prefix in SYSCALL_PREFIXES.iter() {
if self.ksymname(&format!("{}bpf", prefix)).is_ok() {
return (*prefix).to_string();
}
}
SYSCALL_PREFIXES[0].to_string()
}
pub fn get_syscall_fnname(&mut self, name: &str) -> String {
self.get_syscall_prefix() + name
}
pub fn get_kprobe_functions(&mut self, event_re: &str) -> Result<Vec<String>, BccError> {
crate::kprobe::get_kprobe_functions(event_re)
}
pub fn ksymname(&mut self, name: &str) -> Result<u64, BccError> {
self.sym_caches
.entry(-1)
.or_insert_with(|| SymbolCache::new(-1));
let cache = self.sym_caches.get(&-1).unwrap();
cache.resolve_name("", name)
}
pub fn dump_func(&self, func_name: CString) -> Result<Vec<u8>, BccError> {
unsafe {
let raw_func_name = func_name.as_ptr();
let start = bpf_function_start(self.ptr(), raw_func_name);
if start.is_null() {
return Err(BccError::UnknownSymbol {
name: func_name.as_c_str().to_str().unwrap().to_owned(),
module: "".into(),
});
}
let size = bpf_function_size(self.ptr(), raw_func_name);
let slice = std::ptr::slice_from_raw_parts(start as *const u8, size);
let slice = &*slice;
return Ok(slice.into());
}
}
#[cfg(any(
feature = "v0_6_0",
feature = "v0_6_1",
feature = "v0_7_0",
feature = "v0_8_0",
feature = "v0_9_0",
feature = "v0_10_0",
feature = "v0_11_0",
feature = "v0_12_0",
feature = "v0_13_0",
feature = "v0_14_0",
feature = "v0_15_0",
feature = "v0_16_0",
feature = "v0_17_0",
feature = "v0_18_0",
feature = "v0_19_0",
feature = "v0_20_0",
feature = "v0_21_0",
feature = "v0_22_0",
feature = "v0_23_0",
not(feature = "specific"),
))]
pub fn support_raw_tracepoint(&mut self) -> bool {
self.ksymname("bpf_find_raw_tracepoint").is_ok()
|| self.ksymname("bpf_get_raw_tracepoint").is_ok()
}
pub fn init_perf_map<F>(&mut self, table: Table, cb: F) -> Result<(), BccError>
where
F: Fn() -> Box<dyn FnMut(&[u8]) + Send>,
{
let perf_map = PerfMapBuilder::new(table, cb).build()?;
self.perf_readers.extend(perf_map.readers);
Ok(())
}
pub fn perf_map_poll(&mut self, timeout: i32) {
unsafe {
perf_reader_poll(
self.perf_readers.len() as i32,
self.perf_readers.as_ptr() as *mut *mut perf_reader,
timeout,
);
};
}
}
impl Drop for BPF {
fn drop(&mut self) {
unsafe {
bpf_module_destroy(self.ptr());
for (_, fd) in self.functions.iter() {
File::from_raw_fd(*fd);
}
};
}
}