diff --git a/os/StarryOS/kernel/src/ebpf/mod.rs b/os/StarryOS/kernel/src/ebpf/mod.rs index e861046945..88bc8a4f25 100644 --- a/os/StarryOS/kernel/src/ebpf/mod.rs +++ b/os/StarryOS/kernel/src/ebpf/mod.rs @@ -20,6 +20,7 @@ use alloc::{collections::btree_map::BTreeMap, sync::Arc, vec}; use ax_errno::{AxError, AxResult}; use ax_io::Read; use ax_lazyinit::LazyInit; +use ax_task::current; use kbpf_basic::{ helper::RawBPFHelperFn, linux_bpf::{bpf_attr, bpf_cmd}, @@ -44,6 +45,7 @@ use crate::{ kprobe::KernelRawMutex, mm::VmBytes, perf::raw_tracepoint::bpf_raw_tracepoint_open, + task::AsThread, }; /// The global BPF helper-function table (id → `RawBPFHelperFn`). Populated by @@ -51,6 +53,63 @@ use crate::{ /// without re-running the kbpf-basic init on every call. pub static BPF_HELPER_FUN_SET: LazyInit> = LazyInit::new(); +/// BPF helper ID for `bpf_probe_read` (legacy, address-space-agnostic). +const BPF_FUNC_PROBE_READ: u32 = 4; +/// BPF helper ID for `bpf_get_current_pid_tgid`. kbpf-basic does not register +/// this helper, so we implement it directly in StarryOS. +const BPF_FUNC_GET_CURRENT_PID_TGID: u32 = 14; +/// BPF helper ID for `bpf_get_current_comm`. kbpf-basic does not register +/// this helper, so we implement it directly in StarryOS. +const BPF_FUNC_GET_CURRENT_COMM: u32 = 16; +/// BPF helper ID for `bpf_probe_read_kernel`. kbpf-basic only registers +/// `bpf_probe_read` so we alias 113 onto the same raw helper. +const BPF_FUNC_PROBE_READ_KERNEL: u32 = 113; + +/// `bpf_get_current_pid_tgid()` — returns `(tgid << 32) | tid` of the +/// currently running task, matching the Linux kernel helper ABI. +fn bpf_get_current_pid_tgid(_a: u64, _b: u64, _c: u64, _d: u64, _e: u64) -> u64 { + let task = current(); + let tgid = task.as_thread().proc_data.proc.pid() as u64; + let pid = task.as_thread().tid() as u64; + (tgid << 32) | pid +} + +/// `bpf_get_current_comm(char *buf, u32 size_of_buf)` — copies the current +/// task's comm (name) into `buf` using `strscpy_pad` semantics: at most +/// `size_of_buf - 1` bytes are copied, a NUL terminator is always written, +/// and remaining bytes are zero-padded. Returns 0 on success or `-EINVAL` +/// when `size_of_buf` is 0, matching the Linux kernel helper ABI. +fn bpf_get_current_comm(buf: u64, size_of_buf: u64, _c: u64, _d: u64, _e: u64) -> u64 { + let size = size_of_buf as usize; + if buf == 0 { + return 0; + } + + let task = current(); + let comm = task.name(); + let comm_bytes = comm.as_bytes(); + + if size == 0 { + return (-22i64) as u64; // -EINVAL + } + + // Copy at most size-1 bytes to leave room for the NUL terminator. + let copy_len = comm_bytes.len().min(size.saturating_sub(1)); + + // SAFETY: `buf` is a kernel-space pointer validated by the eBPF verifier + // before the helper is invoked. + unsafe { + core::ptr::copy_nonoverlapping(comm_bytes.as_ptr(), buf as *mut u8, copy_len); + // Always NUL-terminate at `buf[copy_len]`. + (buf as *mut u8).add(copy_len).write(0); + // Zero-pad the remainder. + if copy_len + 1 < size { + core::ptr::write_bytes((buf as *mut u8).add(copy_len + 1), 0, size - copy_len - 1); + } + } + 0 +} + /// Initialize the BPF subsystem: build the helper-function table from /// `kbpf-basic`. Must be called before `sys_bpf(BPF_PROG_LOAD)` so loaded /// programs can resolve the helper ids referenced in their instructions. @@ -61,9 +120,14 @@ pub fn init_ebpf() { // tracepoint program uses to pull fields out of its sample buffer. // kbpf-basic only registers the legacy `bpf_probe_read` (id 4), whose raw // reader is address-space-agnostic in this VM, so alias 113 onto it. - if let Some(&probe_read) = set.get(&4) { - set.entry(113).or_insert(probe_read); + if let Some(&probe_read) = set.get(&BPF_FUNC_PROBE_READ) { + set.entry(BPF_FUNC_PROBE_READ_KERNEL).or_insert(probe_read); } + // Register helpers that kbpf-basic does not yet provide (#14, #16). + set.entry(BPF_FUNC_GET_CURRENT_PID_TGID) + .or_insert(bpf_get_current_pid_tgid); + set.entry(BPF_FUNC_GET_CURRENT_COMM) + .or_insert(bpf_get_current_comm); BPF_HELPER_FUN_SET.init_once(set); } diff --git a/os/StarryOS/kernel/src/perf/bpf.rs b/os/StarryOS/kernel/src/perf/bpf.rs index d52369ebc4..c6a6013244 100644 --- a/os/StarryOS/kernel/src/perf/bpf.rs +++ b/os/StarryOS/kernel/src/perf/bpf.rs @@ -37,6 +37,11 @@ use crate::{ file::FileLike, }; +/// Number of 4K pages reserved for x86_64 BPF JIT executable memory. +/// Each JIT-compiled eBPF program fits within this space; the allocation +/// is sized generously (16 KiB) since programs are typically < 1 page. +const BPF_JIT_MEM_PAGES: usize = 4; + /// Wraps `kbpf_basic::perf::bpf::BpfPerfEvent` with kernel state: a poll /// set so readers can wait for new records, and a weak handle to the /// backing pages produced by `device_mmap` (Some after the first @@ -285,14 +290,17 @@ impl OwnedEbpfVm { // TODO: not all of the address space is accessible to a BPF program; // allowing the full `0..u64::MAX` range disables rbpf's bounds check // and lets a buggy/hostile program read arbitrary kernel memory via - // direct loads. Narrow this to the legitimately-reachable context / - // map / stack ranges once kbpf-basic exposes the per-program bounds. + // direct loads. + // + // FIXME: narrow this to the legitimately-reachable context / + // map / stack ranges once kbpf-basic exposes per-program + // bounds. vm.register_allowed_memory(0..u64::MAX); #[cfg(target_arch = "x86_64")] { // TODO: calculate a more precise size. - let mut jit_exec_memory = BPFJitMemory::new(4)?; + let mut jit_exec_memory = BPFJitMemory::new(BPF_JIT_MEM_PAGES)?; // 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. diff --git a/os/StarryOS/kernel/src/perf/kprobe.rs b/os/StarryOS/kernel/src/perf/kprobe.rs index c66d3cad18..d29a3e1047 100644 --- a/os/StarryOS/kernel/src/perf/kprobe.rs +++ b/os/StarryOS/kernel/src/perf/kprobe.rs @@ -16,6 +16,14 @@ use axpoll::Pollable; use kbpf_basic::perf::{PerfProbeArgs, PerfProbeConfig}; use kprobe::{CallBackFunc, KretprobeBuilder, ProbeBuilder, PtRegs}; +/// Config value for entry probes (kprobe/uprobe), per Linux PERF_TYPE_PROBE ABI. +pub const PROBE_CONFIG_ENTRY: u64 = 0; +/// Config value for return probes (kretprobe/uretprobe), per Linux PERF_TYPE_PROBE ABI. +pub const PROBE_CONFIG_RETURN: u64 = 1; +/// Maximum number of concurrently active kretprobe instances, matching +/// Linux's `max(10, 2*NR_CPUS)` default for single-CPU configurations. +const KRETPROBE_MAX_ACTIVE: u32 = 10; + use crate::{ file::FileLike, kprobe::{ @@ -182,25 +190,24 @@ fn perf_probe_arg_to_kretprobe_builder( ) -> AxResult> { let symbol = &args.name; let addr = lookup_symbol_addr(symbol)?; - Ok(KretprobeBuilder::::new(10) - .with_symbol(symbol.clone()) - .with_symbol_addr(addr)) + Ok( + KretprobeBuilder::::new(KRETPROBE_MAX_ACTIVE) + .with_symbol(symbol.clone()) + .with_symbol_addr(addr), + ) } /// Build a `ProbePerfEvent` for a `PERF_TYPE_KPROBE` perf_event_open call. -/// Config 0 = kprobe; config 1 = kretprobe (per kbpf-basic convention). +/// Config `PROBE_CONFIG_ENTRY` (0) = kprobe; `PROBE_CONFIG_RETURN` (1) = kretprobe. pub fn perf_event_open_kprobe(args: PerfProbeArgs) -> AxResult { let probe = match args.config { - PerfProbeConfig::Raw(val) => { - if val == 0 { - let builder = perf_probe_arg_to_kprobe_builder(&args)?; - ProbeTy::Kprobe(register_kprobe(builder)) - } else if val == 1 { - let builder = perf_probe_arg_to_kretprobe_builder(&args)?; - ProbeTy::Kretprobe(register_kretprobe(builder)) - } else { - return Err(AxError::InvalidInput); - } + PerfProbeConfig::Raw(PROBE_CONFIG_ENTRY) => { + let builder = perf_probe_arg_to_kprobe_builder(&args)?; + ProbeTy::Kprobe(register_kprobe(builder)) + } + PerfProbeConfig::Raw(PROBE_CONFIG_RETURN) => { + let builder = perf_probe_arg_to_kretprobe_builder(&args)?; + ProbeTy::Kretprobe(register_kretprobe(builder)) } _ => return Err(AxError::InvalidInput), }; diff --git a/os/StarryOS/kernel/src/perf/mod.rs b/os/StarryOS/kernel/src/perf/mod.rs index 96e7c79c24..dd469626e7 100644 --- a/os/StarryOS/kernel/src/perf/mod.rs +++ b/os/StarryOS/kernel/src/perf/mod.rs @@ -32,7 +32,7 @@ use core::{ any::Any, ffi::c_void, fmt::Debug, - sync::atomic::{AtomicU64, Ordering}, + sync::atomic::{AtomicBool, AtomicU64, Ordering}, }; use ax_errno::{AxError, AxResult}; @@ -203,6 +203,10 @@ pub struct PerfEvent { /// Unique, stable perf-event id (see [`NEXT_PERF_EVENT_ID`]). Returned by /// `PERF_EVENT_IOC_ID` and used as the `read_format` `PERF_FORMAT_ID` value. id: u64, + /// O_NONBLOCK flag set via `fcntl(F_SETFL)`. When true, operations that + /// would block (e.g. reading from an empty ring buffer) should return + /// `EAGAIN` instead. + nonblocking: AtomicBool, } impl Debug for PerfEvent { @@ -220,6 +224,7 @@ impl PerfEvent { PerfEvent { event: SpinNoPreempt::new(event), id, + nonblocking: AtomicBool::new(false), } } @@ -396,6 +401,15 @@ impl FileLike for PerfEvent { Some(anchor), )) } + + fn nonblocking(&self) -> bool { + self.nonblocking.load(Ordering::Acquire) + } + + fn set_nonblocking(&self, on: bool) -> AxResult { + self.nonblocking.store(on, Ordering::Release); + Ok(()) + } } /// `perf_event_open(2)` syscall entry. Copies the user `perf_event_attr` in diff --git a/os/StarryOS/kernel/src/perf/uprobe.rs b/os/StarryOS/kernel/src/perf/uprobe.rs index 84374b2775..428ce33ae0 100644 --- a/os/StarryOS/kernel/src/perf/uprobe.rs +++ b/os/StarryOS/kernel/src/perf/uprobe.rs @@ -15,7 +15,7 @@ use ax_errno::{AxError, AxResult}; use kbpf_basic::perf::{PerfProbeArgs, PerfProbeConfig}; use kprobe::ProbeBuilder; -use super::kprobe::{ProbePerfEvent, ProbeTy}; +use super::kprobe::{PROBE_CONFIG_ENTRY, PROBE_CONFIG_RETURN, ProbePerfEvent, ProbeTy}; use crate::{ kprobe::KprobeAuxiliary, task::{AsThread, get_task}, @@ -74,13 +74,13 @@ fn perf_probe_arg_to_uprobe_builder( /// Build a uprobe perf event from `perf_event_open` args. pub fn perf_event_open_uprobe(args: PerfProbeArgs) -> AxResult { let probe = match args.config { - PerfProbeConfig::Raw(0) => { + PerfProbeConfig::Raw(PROBE_CONFIG_ENTRY) => { let builder = perf_probe_arg_to_uprobe_builder(&args)?; ProbeTy::Uprobe(crate::uprobe::register_uprobe(builder)) } - PerfProbeConfig::Raw(1) => { + PerfProbeConfig::Raw(PROBE_CONFIG_RETURN) => { // uretprobe — not implemented for user space yet. - warn!("uprobe: uretprobe (config=1) is not yet supported"); + warn!("uprobe: uretprobe is not yet supported"); return Err(AxError::Unsupported); } other => { diff --git a/os/StarryOS/kernel/src/tracepoint/mod.rs b/os/StarryOS/kernel/src/tracepoint/mod.rs index 488a420bf5..8ede91f25e 100644 --- a/os/StarryOS/kernel/src/tracepoint/mod.rs +++ b/os/StarryOS/kernel/src/tracepoint/mod.rs @@ -27,6 +27,11 @@ use crate::{ task::AsThread, }; +/// Maximum number of trace records kept in the raw trace pipe ring buffer. +const TRACE_RAW_PIPE_CAPACITY: usize = 4096; +/// Maximum number of PID→cmdline entries in the command-line cache. +const TRACE_CMDLINE_CACHE_SIZE: usize = 4096; + // The registry entry is locked from the tracepoint fire path, which for // `sched:sched_switch` runs inside `axtask::switch_to` (IRQ off, // preemption disabled). A sleeping `ax_sync::Mutex` would trip the @@ -71,7 +76,7 @@ impl TraceState { const fn new() -> Self { Self { point_map: LazyInit::new(), - raw_pipe: Mutex::new(TracePipeRaw::new(4096)), + raw_pipe: Mutex::new(TracePipeRaw::new(TRACE_RAW_PIPE_CAPACITY)), pipe_event: PollSet::new(), pipe_notify: IrqNotify::new(), cmdline_cache: LazyInit::new(), @@ -277,7 +282,7 @@ pub fn tracepoint_init() -> AxResult<()> { TRACE_STATE .cmdline_cache .init_once(Mutex::new(TraceCmdLineCache::new( - NonZero::new(4096).unwrap(), + NonZero::new(TRACE_CMDLINE_CACHE_SIZE).unwrap(), ))); start_trace_pipe_notify_worker(); Ok(()) diff --git a/test-suit/starryos/qemu-smp1/system/KNOWN_FAILS.md b/test-suit/starryos/qemu-smp1/system/KNOWN_FAILS.md index 8ce69302d3..9b3147cf2b 100644 --- a/test-suit/starryos/qemu-smp1/system/KNOWN_FAILS.md +++ b/test-suit/starryos/qemu-smp1/system/KNOWN_FAILS.md @@ -21,4 +21,6 @@ but the grouped CI runner only executes `/usr/bin/starry-test-suit/*`. - `test-tgsigqueueinfo`: queued thread signal delivery with `siginfo`. - `test-uid-gid-direct-setters`: uid/gid setter boundary matrix semantics. - `test-uid-gid-groups`: user namespace `setgroups=deny` behavior. +- `test-ptrace-gdb`: 19/20 pass, 1 remaining assertion failure on aarch64. - `test-uid-gid-res-setters`: setresuid/setresgid boundary matrix semantics. +- `test-copy-file-range`: 35/37 pass, 2 assertion failures on loongarch64 (cross-process fd: errno=9 EBADF).