WIP: userspace signal handling

This commit is contained in:
4lDO2
2024-06-18 16:58:28 +02:00
parent 198151a355
commit 295cc820e6
12 changed files with 91 additions and 448 deletions
+18 -40
View File
@@ -140,6 +140,7 @@ pub struct Context {
/// The effective namespace id
pub ens: SchemeNamespace,
/// Signal handler
pub sig: SignalState,
/// Process umask
@@ -191,8 +192,6 @@ pub struct Context {
pub name: Cow<'static, str>,
/// The open files in the scheme
pub files: Arc<RwLock<Vec<Option<FileDescriptor>>>>,
/// Signal actions
pub actions: Arc<RwLock<Vec<(SigAction, usize)>>>,
/// All contexts except kmain will primarily live in userspace, and enter the kernel only when
/// interrupts or syscalls occur. This flag is set for all contexts but kmain.
pub userspace: bool,
@@ -203,25 +202,20 @@ pub struct Context {
pub fmap_ret: Option<Frame>,
}
#[derive(Clone, Copy, Debug)]
#[derive(Debug)]
pub struct SignalState {
/// Bitset of pending signals.
pub pending: u64,
/// Bitset of procmasked signals.
pub procmask: u64,
/// Offset to jump to when a signal is received.
pub user_handler: Option<NonZeroUsize>,
/// Offset to jump to when a program fault occurs.
pub excp_handler: Option<NonZeroUsize>,
/// Signal control page, required for signals to work.
pub control: Option<RaiiFrame>,
/// Offset within the control page of a naturally aligned, semiatomically accessed, u128.
pub sigword_off: u16,
/// A function pointer to the userspace signal handler.
pub handler: Option<SignalHandler>,
}
#[derive(Clone, Copy, Debug)]
pub struct SignalHandler {
pub handler: NonZeroUsize,
pub altstack: Option<Altstack>,
}
#[derive(Clone, Copy, Debug)]
pub struct Altstack {
pub base: NonZeroUsize,
pub len: NonZeroUsize,
/// Set whenever the kernel is about to have the context jump to its signal trampoline, but the
/// context is currently running.
pub is_pending: bool,
}
impl Context {
@@ -238,9 +232,11 @@ impl Context {
egid: 0,
ens: SchemeNamespace::from(0),
sig: SignalState {
pending: 0,
procmask: !0,
handler: None,
user_handler: None,
excp_handler: None,
control: None,
sigword_off: 0,
is_pending: false,
},
umask: 0o022,
status: Status::HardBlocked { reason: HardBlockedReason::NotYetStarted },
@@ -261,7 +257,6 @@ impl Context {
addr_space: None,
name: Cow::Borrowed(""),
files: Arc::new(RwLock::new(Vec::new())),
actions: Self::empty_actions(),
userspace: false,
ptrace_stop: false,
fmap_ret: None,
@@ -431,16 +426,6 @@ impl Context {
core::mem::replace(&mut self.addr_space, addr_space)
}
pub fn empty_actions() -> Arc<RwLock<Vec<(SigAction, usize)>>> {
Arc::new(RwLock::new(vec![(
SigAction {
sa_handler: unsafe { mem::transmute(SIG_DFL) },
sa_mask: 0,
sa_flags: SigActionFlags::empty(),
},
0
); 128]))
}
pub fn caller_ctx(&self) -> CallerCtx {
CallerCtx {
pid: self.id.into(),
@@ -473,13 +458,6 @@ impl Context {
}
}
impl SignalState {
pub fn deliverable(&self) -> u64 {
const CANT_BLOCK: u64 = (1 << (SIGKILL - 1)) | (1 << (SIGSTOP - 1));
self.pending & (CANT_BLOCK | !self.procmask)
}
}
/// Wrapper struct for borrowing the syscall head or tail buf.
#[derive(Debug)]
pub struct BorrowedHtBuf {
+32 -1
View File
@@ -8,7 +8,7 @@ use syscall::{error::*, flag::MapFlags, GrantFlags, MunmapFlags};
use crate::{
arch::paging::PAGE_SIZE, cpu_set::LogicalCpuSet, memory::{
deallocate_frame, deallocate_p2frame, get_page_info, init_frame, the_zeroed_frame, AddRefError, Enomem, Frame, PageInfo, RefCount, RefKind
deallocate_frame, deallocate_p2frame, get_page_info, init_frame, the_zeroed_frame, AddRefError, Enomem, Frame, PageInfo, RaiiFrame, RefCount, RefKind
}, paging::{
Page, PageFlags, PageMapper, RmmA, TableKind, VirtualAddress,
}, percpu::PercpuBlock, scheme::{self, KernelSchemes}
@@ -466,6 +466,37 @@ impl AddrSpaceWrapper {
Ok(dst_base)
}
/// Borrows a page from user memory, requiring that the frame be Allocated and read/write. This
/// is intended to be used for user-kernel shared memory.
pub fn borrow_frame_enforce_rw_allocated(self: &Arc<Self>, page: Page) -> Result<RaiiFrame> {
let mut guard = self.acquire_write();
let (_start_page, info) = guard.grants.contains(page).ok_or(Error::new(EINVAL))?;
if !info.can_have_flags(MapFlags::PROT_READ | MapFlags::PROT_WRITE) {
return Err(Error::new(EPERM));
}
if !matches!(info.provider, Provider::Allocated { .. }) {
return Err(Error::new(EPERM));
}
let frame = if let Some((f, fl)) = guard.table.utable.translate(page.start_address()) && fl.has_write() {
Frame::containing(f)
} else {
let (frame, flush, new_guard) = correct_inner(self, guard, page, AccessMode::Write, 0).map_err(|_| Error::new(ENOMEM))?;
flush.flush();
guard = new_guard;
frame
};
match get_page_info(frame).expect("missing page info for Allocated grant").add_ref(RefKind::Shared) {
Ok(_) => Ok(unsafe { RaiiFrame::new_unchecked(frame) }),
Err(AddRefError::RcOverflow) => Err(Error::new(ENOMEM)),
Err(AddRefError::SharedToCow) => unreachable!(),
Err(AddRefError::CowToShared) => unreachable!("if it was CoW, it was read-only, but in that case we already called correct_inner"),
}
}
}
impl AddrSpace {
pub fn current() -> Result<Arc<AddrSpaceWrapper>> {
-1
View File
@@ -76,7 +76,6 @@ pub fn init() {
context.sched_affinity = LogicalCpuSet::empty();
context.sched_affinity.atomic_set(crate::cpu_id());
context.name = Cow::Borrowed("kmain");
context.sig.procmask = 0;
self::arch::EMPTY_CR3.call_once(|| unsafe { RmmA::table(TableKind::User) });
+6 -185
View File
@@ -17,7 +17,7 @@ use crate::{
use super::ContextId;
pub fn kmain_signal_handler() {
if context::context_id() != ContextId::new(1) {
/*if context::context_id() != ContextId::new(1) {
log::warn!("kmain signal didn't target PID 1, ignoring");
return;
}
@@ -37,194 +37,15 @@ pub fn kmain_signal_handler() {
}
} else {
log::warn!("Spurious kmain signal, bitmask {bits:#0x}.");
}
}*/
}
// TODO: Move everything but SIGKILL to userspace. SIGCONT and SIGSTOP do not necessarily need to
// be done from this current context.
pub fn signal_handler() {
let (action, sig) = {
// FIXME: Can any low-level state become corrupt if a panic occurs here?
let context_lock = context::current().expect("context::signal_handler not inside of context");
let mut context = context_lock.write();
// Lowest-numbered signal first.
// TODO: randomly?
if context.sig.deliverable() == 0 {
return;
}
let bits = context.sig.deliverable();
// Always prioritize SIGKILL and SIGSTOP, so processes can't simply keep themselves alive
// by killing themselves.
let selected = if bits & (1 << (SIGKILL - 1)) != 0 {
SIGKILL
} else if bits & (1 << (SIGSTOP - 1)) != 0 {
SIGSTOP
} else {
bits.trailing_zeros() as usize + 1
};
context.sig.pending &= !(1 << (selected - 1));
let actions = context.actions.read();
(actions[selected - 1].0, selected)
};
let handler = action.sa_handler.map(|ptr| ptr as usize).unwrap_or(0);
let thumbs_down = ptrace::breakpoint_callback(
/*let thumbs_down = ptrace::breakpoint_callback(
PTRACE_STOP_SIGNAL,
Some(ptrace_event!(PTRACE_STOP_SIGNAL, sig, handler)),
)
.and_then(|_| ptrace::next_breakpoint().map(|f| f.contains(PTRACE_FLAG_IGNORE)));
if sig != SIGKILL && thumbs_down.unwrap_or(false) {
// If signal can be and was ignored
return;
}
if handler == SIG_DFL {
match sig {
SIGCHLD => {
// println!("SIGCHLD");
}
SIGCONT => {
// println!("Continue");
{
let contexts = context::contexts();
let (pid, pgid, ppid) = {
let context_lock = contexts
.current()
.expect("context::signal_handler not inside of context");
let mut context = context_lock.write();
context.status = Status::Runnable;
(context.id, context.pgid, context.ppid)
};
if let Some(parent_lock) = contexts.get(ppid) {
let waitpid = {
let parent = parent_lock.write();
Arc::clone(&parent.waitpid)
};
waitpid.send(
WaitpidKey {
pid: Some(pid),
pgid: Some(pgid),
},
(pid, 0xFFFF),
);
} else {
println!("{}: {} not found for continue", pid.get(), ppid.get());
}
}
}
// FIXME: SIGSTOP shouldn't be overridable
SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU => {
// println!("Stop {}", sig);
{
let contexts = context::contexts();
let (pid, pgid, ppid) = {
let context_lock = contexts
.current()
.expect("context::signal_handler not inside of context");
let mut context = context_lock.write();
context.status = Status::Stopped(sig);
(context.id, context.pgid, context.ppid)
};
if let Some(parent_lock) = contexts.get(ppid) {
let waitpid = {
let parent = parent_lock.write();
Arc::clone(&parent.waitpid)
};
waitpid.send(
WaitpidKey {
pid: Some(pid),
pgid: Some(pgid),
},
(pid, (sig << 8) | 0x7F),
);
} else {
println!("{}: {} not found for stop", pid.get(), ppid.get());
}
}
switch();
}
_ => {
// println!("Exit {}", sig);
crate::syscall::exit(sig);
}
}
} else if handler == SIG_IGN {
// println!("Ignore");
} else {
// println!("Call {:X}", handler);
// TODO: Move more of this to userspace
let context_lock = context::current()
.expect("context::signal_handler not inside of context");
let mut context = context_lock.write();
let Some(handler) = context.sig.handler else {
log::debug!("signal ignored since context did not setup sighandler");
return;
};
let Some(regs) = context.regs_mut() else {
log::warn!("cannot send signal to context without userspace registers");
return;
};
let mut intregs = IntRegisters::default();
regs.save(&mut intregs);
const STACK_ADJUST: usize = 256;
// TODO: 16 bytes alignment is sufficient unless XSAVE is enabled.
const STACK_ALIGN: usize = 64;
let new_sp_unless_altstack = (regs.stack_pointer() - STACK_ADJUST) / STACK_ALIGN * STACK_ALIGN;
let new_sp = match handler.altstack {
Some(altstack) if !(altstack.base.get()..altstack.base.get() + altstack.len.get()).contains(&regs.stack_pointer()) => altstack.base.get() + altstack.len.get(),
_ => new_sp_unless_altstack,
} - size_of::<SignalStack>();
let old_procmask = context.sig.procmask;
context.sig.procmask |= action.sa_mask;
if !action.sa_flags.contains(SigActionFlags::SA_NODEFER) {
context.sig.procmask |= 1 << (sig - 1);
}
let Some(regs) = context.regs_mut() else {
return;
};
regs.set_stack_pointer(new_sp);
regs.set_instr_pointer(handler.handler.get());
drop(context);
let Ok(slice) = UserSlice::wo(new_sp, size_of::<SignalStack>()) else {
return;
};
let stack = SignalStack {
intregs,
old_procmask,
sa_mask: action.sa_mask,
sa_flags: action.sa_flags.bits() as u32,
sig_num: sig as u32,
sa_handler: action.sa_handler.map_or(0, |h| h as usize),
};
let Ok(()) = slice.copy_from_slice(&stack) else {
return;
};
}
.and_then(|_| ptrace::next_breakpoint().map(|f| f.contains(PTRACE_FLAG_IGNORE)));*/
}
pub fn excp_handler(signal: usize) {
}
+1 -8
View File
@@ -37,13 +37,6 @@ unsafe fn update_runnable(context: &mut Context, cpu_id: LogicalCpuId) -> Update
return UpdateResult::Skip;
}
let signal = context.sig.deliverable() != 0;
// Unblock when there are pending nonmasked signals.
if matches!(context.status, Status::Blocked) && signal {
context.unblock_no_ipi();
}
// Wake from sleep
if context.status.is_soft_blocked() && context.wake.is_some() {
let wake = context.wake.expect("context::switch: wake not set");
@@ -57,7 +50,7 @@ unsafe fn update_runnable(context: &mut Context, cpu_id: LogicalCpuId) -> Update
// Switch to context if it needs to run
if context.status.is_runnable() {
UpdateResult::CanSwitch { signal }
UpdateResult::CanSwitch { signal: mem::take(&mut context.sig.is_pending) }
} else {
UpdateResult::Skip
}