From 596f12e7764b92afbe07e03e2c4605de4947d560 Mon Sep 17 00:00:00 2001 From: Godones Date: Sat, 13 Jun 2026 11:57:23 +0800 Subject: [PATCH 1/2] fix(starry): align app qemu boot flow and own BPF JIT memory Run Starry app QEMU cases through the same dynamic platform boot adjustment used by regular Starry QEMU runs, so x86_64 app cases get the correct UEFI/bin handling and snapshot drive rewriting. Also make x86_64 BPF JIT executable memory owned by OwnedEbpfVm instead of leaking it. Keep the JIT mapping alive with RAII field ordering and fall back to interpreter execution on non-x86 targets. Signed-off-by: Godones --- os/StarryOS/kernel/src/perf/bpf.rs | 58 +++++++++++++++++++++++----- os/StarryOS/kernel/src/perf/mod.rs | 61 +++++++++++++++++++++++++++++- scripts/axbuild/src/starry/mod.rs | 2 + 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/os/StarryOS/kernel/src/perf/bpf.rs b/os/StarryOS/kernel/src/perf/bpf.rs index 321adc45af..5e3d569498 100644 --- a/os/StarryOS/kernel/src/perf/bpf.rs +++ b/os/StarryOS/kernel/src/perf/bpf.rs @@ -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, @@ -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`] 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, @@ -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. @@ -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 { - 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 @@ -263,7 +301,7 @@ impl OwnedEbpfVm { core::mem::size_of::(), ) }; - self.vm.execute_program(probe_context) + self.execute_program(probe_context) } } diff --git a/os/StarryOS/kernel/src/perf/mod.rs b/os/StarryOS/kernel/src/perf/mod.rs index 4c186c1c1a..fb41bf449b 100644 --- a/os/StarryOS/kernel/src/perf/mod.rs +++ b/os/StarryOS/kernel/src/perf/mod.rs @@ -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; @@ -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 { + 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, + }) + } + + /// 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"); + } +} diff --git a/scripts/axbuild/src/starry/mod.rs b/scripts/axbuild/src/starry/mod.rs index befd9b8718..857bbd4dda 100644 --- a/scripts/axbuild/src/starry/mod.rs +++ b/scripts/axbuild/src/starry/mod.rs @@ -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={})", @@ -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(), From 6e4c6c9318cab18509d5904f947fb8ac27435d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Mon, 15 Jun 2026 10:20:44 +0800 Subject: [PATCH 2/2] chore: trigger ci