Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 48 additions & 10 deletions os/StarryOS/kernel/src/perf/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use kprobe::PtRegs;
use rbpf::EbpfVmRaw;

use super::PerfEventOps;
#[cfg(target_arch = "x86_64")]
use crate::perf::BPFJitMemory;
use crate::{
ebpf::{BPF_HELPER_FUN_SET, error::BpfResultExt, prog::BpfProg},
file::FileLike,
Expand Down Expand Up @@ -191,15 +193,16 @@ pub fn perf_event_open_bpf(args: PerfProbeArgs) -> BpfPerfEventWrapper {
/// A loaded BPF program bundled with an `rbpf` interpreter that borrows
/// into the program's instruction buffer.
///
/// Soundness: the interpreter holds a `'static`-typed slice into the
/// instruction bytes owned by `_prog`; the only thing keeping those bytes
/// alive is the [`Arc<BpfProg>`] in `_prog`. Field order in this struct is
/// therefore load-bearing — `vm` is declared first, `_prog` last, so the
/// struct's drop glue runs `vm`'s destructor before `_prog`'s, and the
/// instruction buffer is freed strictly after the borrower is gone. Do not
/// reorder the fields.
/// Soundness: the interpreter holds `'static`-typed borrows into resources
/// owned by this struct. Field order is therefore load-bearing: `vm` must be
/// declared first so its destructor runs before the JIT memory and program
/// instruction buffer are released. Do not reorder the fields.
pub struct OwnedEbpfVm {
vm: EbpfVmRaw<'static>,
#[cfg(target_arch = "x86_64")]
/// MUST be declared after `vm` (drop order). Keeps the JIT executable
/// memory alive for the entire lifetime of `vm`.
_jit_exec_memory: BPFJitMemory,
/// MUST be declared after `vm` (drop order). Keeps the instruction
/// buffer alive for the entire lifetime of `vm`.
_prog: Arc<BpfProg>,
Expand Down Expand Up @@ -238,7 +241,35 @@ impl OwnedEbpfVm {
// map / stack ranges once kbpf-basic exposes the per-program bounds.
vm.register_allowed_memory(0..u64::MAX);

Ok(Self { vm, _prog: prog })
#[cfg(target_arch = "x86_64")]
{
// TODO: calculate a more precise size.
let mut jit_exec_memory = BPFJitMemory::new(4)?;
// SAFETY: `jit_exec_memory` is moved into the returned
// `OwnedEbpfVm` after `vm`; field drop order guarantees `vm` is
// destroyed before the executable mapping is unmapped.
let jit_slice = unsafe { jit_exec_memory.as_static_mut_slice() };
vm.set_jit_exec_memory(jit_slice).map_err(|e| {
error!("rbpf::EbpfVmRaw::set_jit_exec_memory failed: {e:?}");
AxError::InvalidInput
})?;

vm.jit_compile().map_err(|e| {
error!("rbpf::EbpfVmRaw::jit_compile failed: {e:?}");
AxError::InvalidInput
})?;

Ok(Self {
vm,
_jit_exec_memory: jit_exec_memory,
_prog: prog,
})
}

#[cfg(not(target_arch = "x86_64"))]
{
Ok(Self { vm, _prog: prog })
}
}

/// Execute the wrapped BPF program with the supplied context bytes.
Expand All @@ -248,7 +279,14 @@ impl OwnedEbpfVm {
/// exterior mutability — and therefore no lock — is required around an
/// `OwnedEbpfVm`.
pub fn execute_program(&self, ctx: &mut [u8]) -> Result<u64, rbpf::lib::Error> {
self.vm.execute_program(ctx)
#[cfg(not(target_arch = "x86_64"))]
{
self.vm.execute_program(ctx)
}
#[cfg(target_arch = "x86_64")]
{
unsafe { self.vm.execute_program_jit(ctx) }
}
}

/// Execute the wrapped BPF program with a `PtRegs` as the single-pointer
Expand All @@ -263,7 +301,7 @@ impl OwnedEbpfVm {
core::mem::size_of::<PtRegs>(),
)
};
self.vm.execute_program(probe_context)
self.execute_program(probe_context)
}
}

Expand Down
61 changes: 60 additions & 1 deletion os/StarryOS/kernel/src/perf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use ax_errno::{AxError, AxResult};
use ax_io::Read;
use ax_kspin::{SpinNoPreempt, SpinNoPreemptGuard};
use ax_lazyinit::LazyInit;
use ax_memory_addr::{PhysAddr, PhysAddrRange};
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;
Expand Down Expand Up @@ -251,3 +252,61 @@ pub fn perf_event_output(_ctx: *mut c_void, fd: usize, _flags: u32, data: &[u8])
bpf_event.write_event(data)?;
Ok(())
}

/// Executable kernel mapping used by rbpf JIT programs on x86_64.
#[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,
)?;
Comment thread
Godones marked this conversation as resolved.

Ok(BPFJitMemory {
num_pages,
pages: virt_start,
})
}

/// Returns a `'static` mutable slice for rbpf's JIT memory registration.
///
/// SAFETY: the caller must keep `self` alive and exclusively owned for at
/// least as long as the returned slice may be used. The slice must not be
/// used after this `BPFJitMemory` is dropped, because drop unmaps the
/// backing pages.
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");
}
}
Comment thread
Godones marked this conversation as resolved.
2 changes: 2 additions & 0 deletions scripts/axbuild/src/starry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ impl Starry {
if !qemu.args.iter().any(|arg| arg == "-snapshot") {
qemu.args.push("-snapshot".to_string());
}
qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo);
println!(" prepare assets: 0ns (pipeline=plain, cache=miss)");
println!(
" qemu config: {} (timeout={})",
Expand Down Expand Up @@ -605,6 +606,7 @@ impl Starry {
rootfs::RootfsPatchMode::EnsureDiskBootNet,
);
qemu.args.extend(prepared_assets.extra_qemu_args.clone());
qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo);
println!(
" prepare assets: {:.2?} (pipeline={}, cache={})",
prepare_started.elapsed(),
Expand Down