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
68 changes: 66 additions & 2 deletions os/StarryOS/kernel/src/ebpf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -44,13 +45,71 @@ 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
/// `init_ebpf()` at kernel start so map/prog/jit code can resolve helpers
/// without re-running the kbpf-basic init on every call.
pub static BPF_HELPER_FUN_SET: LazyInit<BTreeMap<u32, RawBPFHelperFn>> = 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
Comment thread
ZR233 marked this conversation as resolved.
/// 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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不能把 size_of_buf == 0 当作成功返回 0。Linux 的 bpf_get_current_comm(buf, size) 要求 size_of_buf 严格大于 0,成功时才返回 0;失败路径应返回负 errno(并按语义清零可写 buffer)。当前返回 0 会让 BPF 程序误以为 helper 已经成功写入一个 NUL-terminated comm,但实际上没有写任何字节。若 verifier 已保证 size > 0,可以去掉这个分支;若保留运行时防御,请改为 -EINVAL 语义并补一个覆盖 size == 0/短 buffer 的回归用例。

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.
Expand All @@ -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);
}

Expand Down
14 changes: 11 additions & 3 deletions os/StarryOS/kernel/src/perf/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 /
Comment thread
ZR233 marked this conversation as resolved.
Comment thread
ZR233 marked this conversation as resolved.
// map / stack ranges once kbpf-basic exposes per-program
// bounds.
vm.register_allowed_memory(0..u64::MAX);
Comment thread
ZR233 marked this conversation as resolved.

#[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.
Expand Down
35 changes: 21 additions & 14 deletions os/StarryOS/kernel/src/perf/kprobe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -182,25 +190,24 @@ fn perf_probe_arg_to_kretprobe_builder(
) -> AxResult<KretprobeBuilder<KernelRawMutex>> {
let symbol = &args.name;
let addr = lookup_symbol_addr(symbol)?;
Ok(KretprobeBuilder::<KernelRawMutex>::new(10)
.with_symbol(symbol.clone())
.with_symbol_addr(addr))
Ok(
KretprobeBuilder::<KernelRawMutex>::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<ProbePerfEvent> {
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),
};
Expand Down
16 changes: 15 additions & 1 deletion os/StarryOS/kernel/src/perf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand All @@ -220,6 +224,7 @@ impl PerfEvent {
PerfEvent {
event: SpinNoPreempt::new(event),
id,
nonblocking: AtomicBool::new(false),
}
}

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions os/StarryOS/kernel/src/perf/uprobe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<ProbePerfEvent> {
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 => {
Expand Down
9 changes: 7 additions & 2 deletions os/StarryOS/kernel/src/tracepoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(())
Expand Down
2 changes: 2 additions & 0 deletions test-suit/starryos/qemu-smp1/system/KNOWN_FAILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Loading