pub mod bpf;
pub mod kprobe;
pub mod raw_tracepoint;
pub mod tracepoint;
pub mod uprobe;
use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec};
use core::{any::Any, ffi::c_void, fmt::Debug};
use ax_errno::{AxError, AxResult};
use ax_io::Read;
use ax_kspin::{SpinNoPreempt, SpinNoPreemptGuard};
use ax_lazyinit::LazyInit;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr, PhysAddrRange, VirtAddr, VirtAddrRange};
use ax_runtime::hal::paging::MappingFlags;
use axpoll::Pollable;
pub use bpf::BpfPerfEventWrapper;
use hashbrown::HashMap;
use kbpf_basic::{
linux_bpf::{PERF_FLAG_FD_CLOEXEC, perf_event_attr},
perf::{PerfEventIoc, PerfProbeArgs, PerfTypeId},
};
use crate::{
ebpf::{error::BpfResultExt, transform::EbpfKernelAuxiliary},
file::{FileLike, Kstat, add_file_like, get_file_like},
mm::VmBytes,
pseudofs::DeviceMmap,
};
pub trait PerfEventOps: Pollable + Send + Sync + Debug {
fn enable(&mut self) -> AxResult<()>;
fn disable(&mut self) -> AxResult<()>;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_bpf_prog(&mut self, _bpf_prog: Arc<dyn FileLike>) -> AxResult<()> {
Err(AxError::Unsupported)
}
fn device_mmap(&mut self, _len: usize) -> AxResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
Err(AxError::Unsupported)
}
}
pub struct PerfEvent {
event: SpinNoPreempt<Box<dyn PerfEventOps>>,
}
impl Debug for PerfEvent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PerfEvent").finish()
}
}
impl PerfEvent {
pub fn new(event: Box<dyn PerfEventOps>) -> Self {
PerfEvent {
event: SpinNoPreempt::new(event),
}
}
pub fn event(&self) -> SpinNoPreemptGuard<'_, Box<dyn PerfEventOps>> {
self.event.lock()
}
}
impl Pollable for PerfEvent {
fn poll(&self) -> axpoll::IoEvents {
self.event.lock().poll()
}
fn register(&self, context: &mut core::task::Context<'_>, events: axpoll::IoEvents) {
self.event.lock().register(context, events)
}
}
impl FileLike for PerfEvent {
fn read(&self, _dst: &mut crate::file::IoDst) -> AxResult<usize> {
Err(AxError::Unsupported)
}
fn write(&self, _src: &mut crate::file::IoSrc) -> AxResult<usize> {
Err(AxError::Unsupported)
}
fn stat(&self) -> AxResult<Kstat> {
Ok(Kstat::default())
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[perf_event]".into()
}
fn ioctl(&self, cmd: u32, arg: usize) -> AxResult<usize> {
let req = PerfEventIoc::try_from(cmd).map_err(|_| AxError::InvalidInput)?;
match req {
PerfEventIoc::Enable => {
self.event.lock().enable()?;
}
PerfEventIoc::Disable => {
self.event.lock().disable()?;
}
PerfEventIoc::SetBpf => {
let bpf_prog_fd = arg as i32;
let file = get_file_like(bpf_prog_fd)?;
self.event.lock().set_bpf_prog(file)?;
}
}
Ok(0)
}
fn device_mmap(&self, offset: u64, length: u64) -> AxResult<DeviceMmap> {
if offset != 0 {
return Err(AxError::InvalidInput);
}
let len = length as usize;
let (paddr, anchor) = self.event.lock().device_mmap(len)?;
Ok(DeviceMmap::Physical(
PhysAddrRange::from_start_size(paddr, len),
Some(anchor),
))
}
}
pub fn sys_perf_event_open(
attr_uptr: usize,
pid: i32,
cpu: i32,
group_fd: i32,
flags: u64,
) -> AxResult<isize> {
let mut buf = vec![0u8; core::mem::size_of::<perf_event_attr>()];
VmBytes::new(attr_uptr as *mut u8, buf.len()).read(&mut buf)?;
let attr = unsafe { &*(buf.as_ptr() as *const perf_event_attr) };
perf_event_open(attr, pid, cpu, group_fd, flags as u32)
}
pub fn perf_event_open(
attr: &perf_event_attr,
pid: i32,
cpu: i32,
group_fd: i32,
flags: u32,
) -> AxResult<isize> {
let args =
PerfProbeArgs::try_from_perf_attr::<EbpfKernelAuxiliary>(attr, pid, cpu, group_fd, flags)
.into_ax_result()?;
let event: Box<dyn PerfEventOps> = match args.type_ {
PerfTypeId::PERF_TYPE_KPROBE => Box::new(kprobe::perf_event_open_kprobe(args)?),
PerfTypeId::PERF_TYPE_SOFTWARE => Box::new(bpf::perf_event_open_bpf(args)),
PerfTypeId::PERF_TYPE_TRACEPOINT => Box::new(tracepoint::perf_event_open_tracepoint(args)?),
PerfTypeId::PERF_TYPE_UPROBE => Box::new(uprobe::perf_event_open_uprobe(args)?),
_ => {
warn!("perf_event_open: unsupported type {:?}", args.type_);
return Err(AxError::Unsupported);
}
};
let event_arc: Arc<dyn FileLike> = Arc::new(PerfEvent::new(event));
let cloexec = flags & PERF_FLAG_FD_CLOEXEC != 0;
let fd = add_file_like(event_arc.clone(), cloexec)?;
PERF_FILE
.get()
.expect("perf subsystem not initialized")
.lock()
.insert(fd as usize, Arc::downgrade(&event_arc));
Ok(fd as isize)
}
static PERF_FILE: LazyInit<SpinNoPreempt<HashMap<usize, alloc::sync::Weak<dyn FileLike>>>> =
LazyInit::new();
pub fn perf_event_init() {
PERF_FILE.init_once(SpinNoPreempt::new(HashMap::new()));
}
pub fn perf_event_output(_ctx: *mut c_void, fd: usize, _flags: u32, data: &[u8]) -> AxResult<()> {
let table = PERF_FILE.get().ok_or(AxError::NotFound)?;
let mut map = table.lock();
let weak = map.get(&fd).ok_or(AxError::NotFound)?;
let Some(file) = weak.upgrade() else {
map.remove(&fd);
return Err(AxError::NotFound);
};
drop(map);
let perf_event = file
.into_any_arc()
.downcast::<PerfEvent>()
.map_err(|_| AxError::InvalidInput)?;
let mut inner = perf_event.event();
let bpf_event = inner
.as_any_mut()
.downcast_mut::<BpfPerfEventWrapper>()
.ok_or(AxError::InvalidInput)?;
bpf_event.write_event(data)?;
Ok(())
}
#[allow(unused)]
struct BPFJitMemory {
num_pages: usize,
pages: VirtAddr,
}
#[allow(unused)]
impl BPFJitMemory {
fn new(num_pages: usize) -> AxResult<Self> {
let kspace = ax_mm::kernel_aspace();
let mut guard = kspace.lock();
let virt_start = guard
.find_free_area(
guard.base(),
num_pages * PAGE_SIZE_4K,
VirtAddrRange::new(guard.base(), guard.end()),
)
.ok_or(AxError::NoMemory)?;
guard.map_alloc(
virt_start,
num_pages * PAGE_SIZE_4K,
MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE,
true,
)?;
Ok(BPFJitMemory {
num_pages,
pages: virt_start,
})
}
unsafe fn as_static_mut_slice(&mut self) -> &'static mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(
self.pages.as_ptr() as *mut u8,
self.num_pages * PAGE_SIZE_4K,
)
}
}
}
impl Drop for BPFJitMemory {
fn drop(&mut self) {
let kspace = ax_mm::kernel_aspace();
let mut guard = kspace.lock();
guard
.unmap(self.pages, self.num_pages * PAGE_SIZE_4K)
.expect("failed to unmap BPF JIT memory");
}
}