WIP: userspace signal handling
This commit is contained in:
+18
-40
@@ -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
@@ -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>> {
|
||||
|
||||
@@ -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
@@ -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(®s.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) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user