Add the signal subsystem

In this commit, we add eight signal-related syscalls
* kill
* tkill
* tgkill
* rt_sigaction
* rt_sigreturn
* rt_sigprocmask
* rt_sigpending
* exit_group

We implement the following major features for signals:
* Generate, mask, and deliver signals
* Support user-defined signal handlers
    * Support nested invocation of signal handlers
    * Support passing arguments: signum, sigaction, and ucontext
* Support both process-directed and thread-directed signals
* Capture hardware exceptions and convert them to signals
* Deliver fatal signals (like SIGKILL) to kill processes gracefully

But we still have gaps, including but not limited to the points below:
* Convert #PF (page fault) and #GP (general protection) exceptions to signals
* Force delivery of signals via interrupt
* Support simulation mode
This commit is contained in:
Tate, Hongliang Tian 2020-04-17 16:21:02 +08:00
parent 1172c25677
commit e166382923
49 changed files with 2774 additions and 311 deletions

@ -213,24 +213,16 @@ fn do_new_process(
} }
fn do_exec_thread(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> { fn do_exec_thread(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> {
let exit_status = process::task::exec(libos_tid, host_tid)?; let status = process::task::exec(libos_tid, host_tid)?;
// sync file system // sync file system
// TODO: only sync when all processes exit // TODO: only sync when all processes exit
use rcore_fs::vfs::FileSystem; use rcore_fs::vfs::FileSystem;
crate::fs::ROOT_INODE.fs().sync()?; crate::fs::ROOT_INODE.fs().sync()?;
// Only return the least significant 8 bits of the exit status // Not to be confused with the return value of a main function.
// // The exact meaning of status is described in wait(2) man page.
// From The Open Group Base Specifications Issue 7, 2018 edition: Ok(status)
// > The shell shall recognize the entire status value retrieved for the
// > command by the equivalent of the wait() function WEXITSTATUS macro...
//
// From the man page of wait() syscall:
// > WEXITSTATUS macro returns the exit status of the child. This consists of the least
// > significant 8 bits of the status
let exit_status = exit_status & 0x0000_00FF_i32;
Ok(exit_status)
} }
fn validate_program_path(target_path: &PathBuf) -> Result<()> { fn validate_program_path(target_path: &PathBuf) -> Result<()> {

@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::syscall::CpuContext;
use sgx_types::*; use sgx_types::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::rsgx_cpuidex; use std::rsgx_cpuidex;
@ -261,17 +262,17 @@ pub fn setup_cpuid_info() {
let max_basic_leaf = CPUID.get_max_basic_leaf(); let max_basic_leaf = CPUID.get_max_basic_leaf();
} }
pub fn handle_cpuid_exception(info: &mut sgx_exception_info_t) -> u32 { pub fn handle_cpuid_exception(user_context: &mut CpuContext) -> Result<isize> {
debug!("handle CPUID exception"); debug!("handle CPUID exception");
let leaf = info.cpu_context.rax as u32; let leaf = user_context.rax as u32;
let subleaf = info.cpu_context.rcx as u32; let subleaf = user_context.rcx as u32;
let cpuid_result = CPUID.get_cpuid_info(leaf, subleaf); let cpuid_result = CPUID.get_cpuid_info(leaf, subleaf);
trace!("cpuid result: {:?}", cpuid_result); trace!("cpuid result: {:?}", cpuid_result);
info.cpu_context.rax = cpuid_result.eax as u64; user_context.rax = cpuid_result.eax as u64;
info.cpu_context.rbx = cpuid_result.ebx as u64; user_context.rbx = cpuid_result.ebx as u64;
info.cpu_context.rcx = cpuid_result.ecx as u64; user_context.rcx = cpuid_result.ecx as u64;
info.cpu_context.rdx = cpuid_result.edx as u64; user_context.rdx = cpuid_result.edx as u64;
info.cpu_context.rip += 2; user_context.rip += 2;
EXCEPTION_CONTINUE_EXECUTION Ok(0)
} }

@ -1,50 +1,74 @@
//! Exception handling subsystem.
use self::cpuid::{handle_cpuid_exception, setup_cpuid_info, CPUID_OPCODE}; use self::cpuid::{handle_cpuid_exception, setup_cpuid_info, CPUID_OPCODE};
use self::rdtsc::{handle_rdtsc_exception, RDTSC_OPCODE}; use self::rdtsc::{handle_rdtsc_exception, RDTSC_OPCODE};
use self::syscall::{handle_syscall_exception, SYSCALL_OPCODE}; use self::syscall::{handle_syscall_exception, SYSCALL_OPCODE};
use super::*; use super::*;
use crate::syscall::SyscallNum; use crate::signal::{FaultSignal, SigSet};
use crate::syscall::{CpuContext, SyscallNum};
use sgx_types::*; use sgx_types::*;
// Modules for instruction simulation
mod cpuid;
mod rdtsc;
mod syscall;
pub fn register_exception_handlers() { pub fn register_exception_handlers() {
setup_cpuid_info(); setup_cpuid_info();
// Register handlers whose priorities go from low to high
unsafe { unsafe {
sgx_register_exception_handler(1, handle_exception); let is_first = 1;
sgx_register_exception_handler(is_first, handle_exception);
} }
} }
#[no_mangle] #[no_mangle]
extern "C" fn handle_exception(info: *mut sgx_exception_info_t) -> u32 { extern "C" fn handle_exception(info: *mut sgx_exception_info_t) -> u32 {
let ret = unsafe { __occlum_syscall(SyscallNum::Exception as u32, info) };
assert!(ret == EXCEPTION_CONTINUE_EXECUTION);
ret
}
pub fn do_handle_exception(info: *mut sgx_exception_info_t) -> Result<isize> {
let mut info = unsafe { &mut *info };
// Assume the length of opcode is 2 bytes
let ip_opcode = unsafe { *(info.cpu_context.rip as *const u16) };
if info.exception_vector != sgx_exception_vector_t::SGX_EXCEPTION_VECTOR_UD
|| info.exception_type != sgx_exception_type_t::SGX_EXCEPTION_HARDWARE
{
panic!(
"unable to process the exception, vector:{} type:{}",
info.exception_vector as u32, info.exception_type as u32
);
}
let ret = match ip_opcode {
#![deny(unreachable_patterns)]
CPUID_OPCODE => handle_cpuid_exception(&mut info),
RDTSC_OPCODE => handle_rdtsc_exception(&mut info),
SYSCALL_OPCODE => handle_syscall_exception(&mut info),
_ => panic!("unable to process the exception, opcode: {:#x}", ip_opcode),
};
Ok(ret as isize)
}
extern "C" { extern "C" {
fn __occlum_syscall(num: u32, info: *mut sgx_exception_info_t) -> u32; fn __occlum_syscall_c_abi(num: u32, info: *mut sgx_exception_info_t) -> u32;
}
unsafe { __occlum_syscall_c_abi(SyscallNum::HandleException as u32, info) };
unreachable!();
} }
mod cpuid; /// Exceptions are handled as a special kind of system calls.
mod rdtsc; pub fn do_handle_exception(
mod syscall; info: *mut sgx_exception_info_t,
user_context: *mut CpuContext,
) -> Result<isize> {
let info = unsafe { &mut *info };
if info.exception_type != sgx_exception_type_t::SGX_EXCEPTION_HARDWARE {
return_errno!(EINVAL, "Can only handle hardware exceptions");
}
let user_context = unsafe { &mut *user_context };
*user_context = CpuContext::from_sgx(&info.cpu_context);
// Try to do instruction emulation first
if info.exception_vector == sgx_exception_vector_t::SGX_EXCEPTION_VECTOR_UD {
// Assume the length of opcode is 2 bytes
let ip_opcode = unsafe { *(user_context.rip as *const u16) };
if ip_opcode == RDTSC_OPCODE {
return handle_rdtsc_exception(user_context);
} else if ip_opcode == SYSCALL_OPCODE {
return handle_syscall_exception(user_context);
} else if ip_opcode == CPUID_OPCODE {
return handle_cpuid_exception(user_context);
}
}
// Then, it must be a "real" exception. Convert it to signal and force delivering it.
// The generated signal is SIGBUS, SIGFPE, SIGILL, or SIGSEGV.
//
// So what happens if the signal is masked? The man page of sigprocmask(2) states:
//
// > If SIGBUS, SIGFPE, SIGILL, or SIGSEGV are generated while they are blocked, the result is
// undefined, unless the signal was generated by kill(2), sigqueue(3), or raise(3).
//
// As the thread cannot proceed without handling the exception, we choose to force
// delivering the signal regardless of the current signal mask.
let signal = Box::new(FaultSignal::new(info));
crate::signal::force_signal(signal, user_context);
Ok(0)
}

@ -1,15 +1,15 @@
use super::*; use crate::prelude::*;
use sgx_types::*; use crate::syscall::CpuContext;
pub const RDTSC_OPCODE: u16 = 0x310F; pub const RDTSC_OPCODE: u16 = 0x310F;
pub fn handle_rdtsc_exception(info: &mut sgx_exception_info_t) -> u32 { pub fn handle_rdtsc_exception(user_context: &mut CpuContext) -> Result<isize> {
debug!("handle RDTSC exception"); debug!("handle RDTSC exception");
let (low, high) = time::do_rdtsc(); let (low, high) = crate::time::do_rdtsc();
trace!("do_rdtsc result {{ low: {:#x} high: {:#x}}}", low, high); trace!("do_rdtsc result {{ low: {:#x} high: {:#x}}}", low, high);
info.cpu_context.rax = low as u64; user_context.rax = low as u64;
info.cpu_context.rdx = high as u64; user_context.rdx = high as u64;
info.cpu_context.rip += 2; user_context.rip += 2;
EXCEPTION_CONTINUE_EXECUTION Ok(0)
} }

@ -1,30 +1,25 @@
use super::*; use super::*;
use crate::syscall::{occlum_syscall, SyscallNum}; use crate::syscall::{occlum_syscall, CpuContext, SyscallNum};
use sgx_types::*; use sgx_types::*;
pub const SYSCALL_OPCODE: u16 = 0x050F; pub const SYSCALL_OPCODE: u16 = 0x050F;
pub fn handle_syscall_exception(info: &mut sgx_exception_info_t) -> u32 { pub fn handle_syscall_exception(user_context: &mut CpuContext) -> ! {
debug!("handle SYSCALL exception"); debug!("handle SYSCALL exception");
// SYSCALL, save RIP into RCX and RFLAGS into R11
info.cpu_context.rcx = info.cpu_context.rip + 2;
info.cpu_context.r11 = info.cpu_context.rflags;
let num = info.cpu_context.rax as u32;
let arg0 = info.cpu_context.rdi as isize;
let arg1 = info.cpu_context.rsi as isize;
let arg2 = info.cpu_context.rdx as isize;
let arg3 = info.cpu_context.r10 as isize;
let arg4 = info.cpu_context.r8 as isize;
let arg5 = info.cpu_context.r9 as isize;
// syscall should not be an exception in Occlum
assert!(num != SyscallNum::Exception as u32);
let ret = occlum_syscall(num, arg0, arg1, arg2, arg3, arg4, arg5);
info.cpu_context.rax = ret as u64;
// SYSRET, load RIP from RCX and loading RFLAGS from R11 // SYSCALL instruction saves RIP into RCX and RFLAGS into R11. This is to
info.cpu_context.rip = info.cpu_context.rcx; // comply with hardware's behavoir. Not useful for us.
// Clear RF, VM, reserved bits; set bit 1 user_context.rcx = user_context.rip;
info.cpu_context.rflags = (info.cpu_context.r11 & 0x3C7FD7) | 2; user_context.r11 = user_context.rflags;
EXCEPTION_CONTINUE_EXECUTION // The target RIP should be the next instruction
user_context.rip += 2;
// Set target RFLAGS: clear RF, VM, reserved bits; set bit 1
user_context.rflags = (user_context.rflags & 0x3C7FD7) | 2;
let num = user_context.rax as u32;
assert!(num != SyscallNum::HandleException as u32);
// FIXME: occlum syscall must use Linux ABI
occlum_syscall(user_context);
} }

@ -61,6 +61,7 @@ mod misc;
mod net; mod net;
mod process; mod process;
mod sched; mod sched;
mod signal;
mod syscall; mod syscall;
mod time; mod time;
mod untrusted; mod untrusted;

@ -16,7 +16,7 @@ pub use std::sync::{
pub use crate::error::Result; pub use crate::error::Result;
pub use crate::error::*; pub use crate::error::*;
pub use crate::fs::{File, FileDesc, FileRef}; pub use crate::fs::{File, FileDesc, FileRef};
pub use crate::process::pid_t; pub use crate::process::{pid_t, uid_t};
macro_rules! debug_trace { macro_rules! debug_trace {
() => { () => {

@ -1,14 +1,39 @@
use std::intrinsics::atomic_store; use std::intrinsics::atomic_store;
use super::do_futex::futex_wake; use super::do_futex::futex_wake;
use super::process::ChildProcessFilter; use super::process::ProcessFilter;
use super::{table, ThreadRef}; use super::{table, TermStatus, ThreadRef, ThreadStatus};
use crate::prelude::*; use crate::prelude::*;
use crate::signal::SigNum;
pub fn do_exit(exit_status: i32) { pub fn do_exit_group(status: i32) {
let term_status = TermStatus::Exited(status as u8);
current!().process().force_exit(term_status);
exit_thread(term_status);
}
pub fn do_exit(status: i32) {
let term_status = TermStatus::Exited(status as u8);
exit_thread(term_status);
}
/// Exit this thread if its has been forced to exit.
///
/// A thread may be forced to exit for two reasons: 1) a fatal signal; 2)
/// exit_group syscall.
pub fn handle_force_exit() {
if let Some(term_status) = current!().process().is_forced_exit() {
exit_thread(term_status);
}
}
fn exit_thread(term_status: TermStatus) {
let thread = current!(); let thread = current!();
if thread.status() == ThreadStatus::Exited {
return;
}
let num_remaining_threads = thread.exit(exit_status); let num_remaining_threads = thread.exit(term_status);
// Notify a thread, if any, that waits on ctid. See set_tid_address(2) for more info. // Notify a thread, if any, that waits on ctid. See set_tid_address(2) for more info.
if let Some(ctid_ptr) = thread.clear_ctid() { if let Some(ctid_ptr) = thread.clear_ctid() {
@ -28,11 +53,11 @@ pub fn do_exit(exit_status: i32) {
// If this thread is the last thread, then exit the process // If this thread is the last thread, then exit the process
if num_remaining_threads == 0 { if num_remaining_threads == 0 {
do_exit_process(&thread, exit_status); exit_process(&thread, term_status);
} }
} }
fn do_exit_process(thread: &ThreadRef, exit_status: i32) { fn exit_process(thread: &ThreadRef, term_status: TermStatus) {
let process = thread.process(); let process = thread.process();
// If the parent process is the idle process, we can release the process directly. // If the parent process is the idle process, we can release the process directly.
@ -44,7 +69,7 @@ fn do_exit_process(thread: &ThreadRef, exit_status: i32) {
table::del_thread(thread.tid()).expect("tid must be in the table"); table::del_thread(thread.tid()).expect("tid must be in the table");
table::del_process(process.pid()).expect("pid must be in the table"); table::del_process(process.pid()).expect("pid must be in the table");
process_inner.exit(exit_status); process_inner.exit(term_status);
parent_inner.remove_zombie_child(process.pid()); parent_inner.remove_zombie_child(process.pid());
return; return;
} }
@ -55,19 +80,19 @@ fn do_exit_process(thread: &ThreadRef, exit_status: i32) {
// Deadlock note: Always lock parent then child. // Deadlock note: Always lock parent then child.
let parent = process.parent(); let parent = process.parent();
let mut parent_inner = parent.inner(); let mut parent_inner = parent.inner();
process.inner().exit(exit_status); process.inner().exit(term_status);
// Wake up the parent if it is waiting on this child // Wake up the parent if it is waiting on this child
let waiting_children = parent_inner.waiting_children_mut().unwrap(); let waiting_children = parent_inner.waiting_children_mut().unwrap();
waiting_children.del_and_wake_one_waiter(|waiter_data| -> Option<pid_t> { waiting_children.del_and_wake_one_waiter(|waiter_data| -> Option<pid_t> {
match waiter_data { match waiter_data {
ChildProcessFilter::WithAnyPid => {} ProcessFilter::WithAnyPid => {}
ChildProcessFilter::WithPid(required_pid) => { ProcessFilter::WithPid(required_pid) => {
if process.pid() != *required_pid { if process.pid() != *required_pid {
return None; return None;
} }
} }
ChildProcessFilter::WithPgid(required_pgid) => { ProcessFilter::WithPgid(required_pgid) => {
if process.pgid() != *required_pgid { if process.pgid() != *required_pgid {
return None; return None;
} }

@ -311,8 +311,8 @@ fn init_auxvec(process_vm: &ProcessVM, exec_elf_file: &ElfFile) -> Result<AuxVec
let ldso_elf_base = process_vm.get_elf_ranges()[1].start() as u64; let ldso_elf_base = process_vm.get_elf_ranges()[1].start() as u64;
auxvec.set(AuxKey::AT_BASE, ldso_elf_base)?; auxvec.set(AuxKey::AT_BASE, ldso_elf_base)?;
let syscall_native_addr = __occlum_syscall_native as *const () as u64; let syscall_addr = __occlum_syscall_linux_abi as *const () as u64;
auxvec.set(AuxKey::AT_OCCLUM_ENTRY, syscall_native_addr)?; auxvec.set(AuxKey::AT_OCCLUM_ENTRY, syscall_addr)?;
// TODO: init AT_EXECFN // TODO: init AT_EXECFN
// auxvec.set_val(AuxKey::AT_EXECFN, "program_name")?; // auxvec.set_val(AuxKey::AT_EXECFN, "program_name")?;
@ -320,6 +320,6 @@ fn init_auxvec(process_vm: &ProcessVM, exec_elf_file: &ElfFile) -> Result<AuxVec
} }
extern "C" { extern "C" {
fn __occlum_syscall_native() -> i64; fn __occlum_syscall_linux_abi() -> i64;
fn occlum_gdb_hook_load_elf(elf_base: u64, elf_path: *const u8, elf_path_len: u64); fn occlum_gdb_hook_load_elf(elf_base: u64, elf_path: *const u8, elf_path_len: u64);
} }

@ -1,9 +1,9 @@
use super::process::{ChildProcessFilter, ProcessInner}; use super::process::{ProcessFilter, ProcessInner};
use super::wait::Waiter; use super::wait::Waiter;
use super::{table, ProcessRef, ProcessStatus}; use super::{table, ProcessRef, ProcessStatus};
use crate::prelude::*; use crate::prelude::*;
pub fn do_wait4(child_filter: &ChildProcessFilter) -> Result<(pid_t, i32)> { pub fn do_wait4(child_filter: &ProcessFilter) -> Result<(pid_t, i32)> {
// Lock the process early to ensure that we do not miss any changes in // Lock the process early to ensure that we do not miss any changes in
// children processes // children processes
let thread = current!(); let thread = current!();
@ -16,9 +16,9 @@ pub fn do_wait4(child_filter: &ChildProcessFilter) -> Result<(pid_t, i32)> {
.unwrap() .unwrap()
.iter() .iter()
.filter(|child| match child_filter { .filter(|child| match child_filter {
ChildProcessFilter::WithAnyPid => true, ProcessFilter::WithAnyPid => true,
ChildProcessFilter::WithPid(required_pid) => child.pid() == *required_pid, ProcessFilter::WithPid(required_pid) => child.pid() == *required_pid,
ChildProcessFilter::WithPgid(required_pgid) => child.pgid() == *required_pgid, ProcessFilter::WithPgid(required_pgid) => child.pgid() == *required_pgid,
}) })
.collect::<Vec<&ProcessRef>>(); .collect::<Vec<&ProcessRef>>();
@ -60,8 +60,6 @@ fn free_zombie_child(mut parent_inner: SgxMutexGuard<ProcessInner>, zombie_pid:
let zombie = parent_inner.remove_zombie_child(zombie_pid); let zombie = parent_inner.remove_zombie_child(zombie_pid);
debug_assert!(zombie.status() == ProcessStatus::Zombie); debug_assert!(zombie.status() == ProcessStatus::Zombie);
// Remove zombie from its parent
let zombie_inner = zombie.inner(); let zombie_inner = zombie.inner();
zombie_inner.exit_status().unwrap() zombie_inner.term_status().unwrap().as_u32() as i32
} }

@ -13,16 +13,19 @@ use crate::fs::{FileRef, FileTable, FsView};
use crate::misc::ResourceLimits; use crate::misc::ResourceLimits;
use crate::prelude::*; use crate::prelude::*;
use crate::sched::SchedAgent; use crate::sched::SchedAgent;
use crate::signal::{SigDispositions, SigQueues};
use crate::vm::ProcessVM; use crate::vm::ProcessVM;
use self::process::{ChildProcessFilter, ProcessBuilder, ProcessInner}; use self::process::{ProcessBuilder, ProcessInner};
use self::thread::{ThreadBuilder, ThreadId, ThreadInner}; use self::thread::{ThreadBuilder, ThreadId, ThreadInner};
use self::wait::{WaitQueue, Waiter}; use self::wait::{WaitQueue, Waiter};
pub use self::do_exit::handle_force_exit;
pub use self::do_spawn::do_spawn_without_exec; pub use self::do_spawn::do_spawn_without_exec;
pub use self::process::{Process, ProcessStatus, IDLE}; pub use self::process::{Process, ProcessFilter, ProcessStatus, IDLE};
pub use self::syscalls::*; pub use self::syscalls::*;
pub use self::task::Task; pub use self::task::Task;
pub use self::term_status::TermStatus;
pub use self::thread::{Thread, ThreadStatus}; pub use self::thread::{Thread, ThreadStatus};
mod do_arch_prctl; mod do_arch_prctl;
@ -35,6 +38,7 @@ mod do_spawn;
mod do_wait4; mod do_wait4;
mod process; mod process;
mod syscalls; mod syscalls;
mod term_status;
mod thread; mod thread;
mod wait; mod wait;
@ -43,8 +47,14 @@ pub mod elf_file;
pub mod table; pub mod table;
pub mod task; pub mod task;
// TODO: need to separate C's version pid_t with Rust version Pid.
// pid_t must be signed as negative values may have special meaning
// (check wait4 and kill for examples), while Pid should be a
// non-negative value.
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub type pid_t = u32; pub type pid_t = u32;
#[allow(non_camel_case_types)]
pub type uid_t = u32;
pub type ProcessRef = Arc<Process>; pub type ProcessRef = Arc<Process>;
pub type ThreadRef = Arc<Thread>; pub type ThreadRef = Arc<Thread>;

@ -3,6 +3,7 @@ use super::super::thread::{ThreadBuilder, ThreadId};
use super::super::{FileTableRef, FsViewRef, ProcessRef, ProcessVMRef, ResourceLimitsRef}; use super::super::{FileTableRef, FsViewRef, ProcessRef, ProcessVMRef, ResourceLimitsRef};
use super::{Process, ProcessInner}; use super::{Process, ProcessInner};
use crate::prelude::*; use crate::prelude::*;
use crate::signal::{SigDispositions, SigQueues};
#[derive(Debug)] #[derive(Debug)]
pub struct ProcessBuilder { pub struct ProcessBuilder {
@ -87,11 +88,17 @@ impl ProcessBuilder {
let exec_path = self.exec_path.take().unwrap_or_default(); let exec_path = self.exec_path.take().unwrap_or_default();
let parent = self.parent.take().map(|parent| SgxRwLock::new(parent)); let parent = self.parent.take().map(|parent| SgxRwLock::new(parent));
let inner = SgxMutex::new(ProcessInner::new()); let inner = SgxMutex::new(ProcessInner::new());
let sig_dispositions = SgxRwLock::new(SigDispositions::new());
let sig_queues = SgxMutex::new(SigQueues::new());
let forced_exit = SgxRwLock::new(None);
Arc::new(Process { Arc::new(Process {
pid, pid,
exec_path, exec_path,
parent, parent,
inner, inner,
sig_dispositions,
sig_queues,
forced_exit,
}) })
}; };

@ -1,8 +1,9 @@
use std::fmt; use std::fmt;
use super::wait::WaitQueue; use super::wait::WaitQueue;
use super::{ProcessRef, ThreadRef}; use super::{ProcessRef, TermStatus, ThreadRef};
use crate::prelude::*; use crate::prelude::*;
use crate::signal::{SigDispositions, SigNum, SigQueues};
pub use self::builder::ProcessBuilder; pub use self::builder::ProcessBuilder;
pub use self::idle::IDLE; pub use self::idle::IDLE;
@ -17,6 +18,10 @@ pub struct Process {
// Mutable info // Mutable info
parent: Option<SgxRwLock<ProcessRef>>, parent: Option<SgxRwLock<ProcessRef>>,
inner: SgxMutex<ProcessInner>, inner: SgxMutex<ProcessInner>,
// Signal
sig_dispositions: SgxRwLock<SigDispositions>,
sig_queues: SgxMutex<SigQueues>,
forced_exit: SgxRwLock<Option<TermStatus>>,
} }
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
@ -35,7 +40,7 @@ impl Process {
/// Get process group ID /// Get process group ID
// TODO: implement process group // TODO: implement process group
pub fn pgid(&self) -> pid_t { pub fn pgid(&self) -> pid_t {
0 self.pid
} }
/// Get the parent process. /// Get the parent process.
@ -76,6 +81,14 @@ impl Process {
self.inner().leader_thread() self.inner().leader_thread()
} }
/// Get threads.
pub fn threads(&self) -> Vec<ThreadRef> {
self.inner()
.threads()
.map(|vec_ref| vec_ref.clone())
.unwrap_or_else(|| Vec::new())
}
/// Get status. /// Get status.
pub fn status(&self) -> ProcessStatus { pub fn status(&self) -> ProcessStatus {
self.inner().status() self.inner().status()
@ -86,6 +99,33 @@ impl Process {
&self.exec_path &self.exec_path
} }
/// Get the signal queues for process-directed signals.
pub fn sig_queues(&self) -> &SgxMutex<SigQueues> {
&self.sig_queues
}
/// Get the process-wide signal dispositions.
pub fn sig_dispositions(&self) -> &SgxRwLock<SigDispositions> {
&self.sig_dispositions
}
/// Check whether the process has been forced to exit.
pub fn is_forced_exit(&self) -> Option<TermStatus> {
*self.forced_exit.read().unwrap()
}
/// Force a process to exit.
///
/// There are two reasons to force a process to exit:
/// 1. Receiving a fatal signal;
/// 2. Performing exit_group syscall.
///
/// A process may be forced to exit many times, but only the first time counts.
pub fn force_exit(&self, term_status: TermStatus) {
let mut forced_exit = self.forced_exit.write().unwrap();
forced_exit.get_or_insert(term_status);
}
/// Get the internal representation of the process. /// Get the internal representation of the process.
/// ///
/// For the purpose of encapsulation, this method is invisible to other subsystems. /// For the purpose of encapsulation, this method is invisible to other subsystems.
@ -98,11 +138,11 @@ pub enum ProcessInner {
Live { Live {
status: LiveStatus, status: LiveStatus,
children: Vec<ProcessRef>, children: Vec<ProcessRef>,
waiting_children: WaitQueue<ChildProcessFilter, pid_t>, waiting_children: WaitQueue<ProcessFilter, pid_t>,
threads: Vec<ThreadRef>, threads: Vec<ThreadRef>,
}, },
Zombie { Zombie {
exit_status: i32, term_status: TermStatus,
}, },
} }
@ -172,7 +212,7 @@ impl ProcessInner {
} }
} }
pub fn waiting_children_mut(&mut self) -> Option<&mut WaitQueue<ChildProcessFilter, pid_t>> { pub fn waiting_children_mut(&mut self) -> Option<&mut WaitQueue<ProcessFilter, pid_t>> {
match self { match self {
Self::Live { Self::Live {
waiting_children, .. waiting_children, ..
@ -190,7 +230,7 @@ impl ProcessInner {
children.swap_remove(zombie_i) children.swap_remove(zombie_i)
} }
pub fn exit(&mut self, exit_status: i32) { pub fn exit(&mut self, term_status: TermStatus) {
// Check preconditions // Check preconditions
debug_assert!(self.status() == ProcessStatus::Running); debug_assert!(self.status() == ProcessStatus::Running);
debug_assert!(self.num_threads() == 0); debug_assert!(self.num_threads() == 0);
@ -201,15 +241,15 @@ impl ProcessInner {
*parent = IDLE.process().clone(); *parent = IDLE.process().clone();
} }
*self = Self::Zombie { exit_status }; *self = Self::Zombie { term_status };
} }
pub fn exit_status(&self) -> Option<i32> { pub fn term_status(&self) -> Option<TermStatus> {
// Check preconditions // Check preconditions
debug_assert!(self.status() == ProcessStatus::Zombie); debug_assert!(self.status() == ProcessStatus::Zombie);
match self { match self {
Self::Zombie { exit_status } => Some(*exit_status), Self::Zombie { term_status } => Some(*term_status),
_ => None, _ => None,
} }
} }
@ -270,9 +310,9 @@ impl fmt::Debug for ProcessInner {
.collect::<Vec<pid_t>>(), .collect::<Vec<pid_t>>(),
) )
.finish(), .finish(),
ProcessInner::Zombie { exit_status, .. } => f ProcessInner::Zombie { term_status, .. } => f
.debug_struct("ProcessInner::Zombie") .debug_struct("ProcessInner::Zombie")
.field("exit_status", exit_status) .field("term_status", term_status)
.finish(), .finish(),
} }
} }
@ -294,11 +334,11 @@ impl Into<ProcessStatus> for LiveStatus {
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ChildProcessFilter { pub enum ProcessFilter {
WithAnyPid, WithAnyPid,
WithPid(pid_t), WithPid(pid_t),
WithPgid(pid_t), WithPgid(pid_t),
} }
// TODO: is this necessary? // TODO: is this necessary?
unsafe impl Send for ChildProcessFilter {} unsafe impl Send for ProcessFilter {}

@ -4,7 +4,7 @@ use super::do_arch_prctl::ArchPrctlCode;
use super::do_clone::CloneFlags; use super::do_clone::CloneFlags;
use super::do_futex::{FutexFlags, FutexOp}; use super::do_futex::{FutexFlags, FutexOp};
use super::do_spawn::FileAction; use super::do_spawn::FileAction;
use super::process::ChildProcessFilter; use super::process::ProcessFilter;
use crate::prelude::*; use crate::prelude::*;
use crate::time::timespec_t; use crate::time::timespec_t;
use crate::util::mem_util::from_user::*; use crate::util::mem_util::from_user::*;
@ -184,16 +184,16 @@ pub fn do_set_tid_address(tidptr: *mut pid_t) -> Result<isize> {
super::do_set_tid_address::do_set_tid_address(tidptr).map(|tid| tid as isize) super::do_set_tid_address::do_set_tid_address(tidptr).map(|tid| tid as isize)
} }
pub fn do_exit(status: i32) -> ! { pub fn do_exit(status: i32) -> Result<isize> {
debug!("exit: {}", status); debug!("exit: {}", status);
super::do_exit::do_exit(status); super::do_exit::do_exit(status);
Ok(0)
}
extern "C" { pub fn do_exit_group(status: i32) -> Result<isize> {
fn do_exit_task() -> !; debug!("exit_group: {}", status);
} super::do_exit::do_exit_group(status);
unsafe { Ok(0)
do_exit_task();
}
} }
pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32) -> Result<isize> { pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32) -> Result<isize> {
@ -202,16 +202,14 @@ pub fn do_wait4(pid: i32, exit_status_ptr: *mut i32) -> Result<isize> {
} }
let child_process_filter = match pid { let child_process_filter = match pid {
pid if pid < -1 => ChildProcessFilter::WithPgid((-pid) as pid_t), pid if pid < -1 => ProcessFilter::WithPgid((-pid) as pid_t),
-1 => ChildProcessFilter::WithAnyPid, -1 => ProcessFilter::WithAnyPid,
0 => { 0 => {
let pgid = current!().process().pgid(); let pgid = current!().process().pgid();
ChildProcessFilter::WithPgid(pgid) ProcessFilter::WithPgid(pgid)
}
pid if pid > 0 => ChildProcessFilter::WithPid(pid as pid_t),
_ => {
panic!("THIS SHOULD NEVER HAPPEN!");
} }
pid if pid > 0 => ProcessFilter::WithPid(pid as pid_t),
_ => unreachable!(),
}; };
let mut exit_status = 0; let mut exit_status = 0;
match super::do_wait4::do_wait4(&child_process_filter) { match super::do_wait4::do_wait4(&child_process_filter) {

@ -5,6 +5,15 @@ pub fn get_process(pid: pid_t) -> Result<ProcessRef> {
PROCESS_TABLE.lock().unwrap().get(pid) PROCESS_TABLE.lock().unwrap().get(pid)
} }
pub fn get_all_processes() -> Vec<ProcessRef> {
PROCESS_TABLE
.lock()
.unwrap()
.iter()
.map(|(_, proc_ref)| proc_ref.clone())
.collect()
}
pub(super) fn add_process(process: ProcessRef) -> Result<()> { pub(super) fn add_process(process: ProcessRef) -> Result<()> {
PROCESS_TABLE.lock().unwrap().add(process.pid(), process) PROCESS_TABLE.lock().unwrap().add(process.pid(), process)
} }
@ -50,6 +59,10 @@ impl<I: Debug + Clone + Send + Sync> Table<I> {
} }
} }
pub fn iter(&self) -> std::collections::hash_map::Iter<'_, pid_t, I> {
self.map.iter()
}
pub fn get(&self, id: pid_t) -> Result<I> { pub fn get(&self, id: pid_t) -> Result<I> {
self.map self.map
.get(&id) .get(&id)

@ -1,4 +1,4 @@
use super::super::{current, ThreadRef}; use super::super::{current, TermStatus, ThreadRef};
use super::Task; use super::Task;
use crate::prelude::*; use crate::prelude::*;
@ -33,11 +33,11 @@ fn dequeue(libos_tid: pid_t) -> Result<ThreadRef> {
/// Execute the specified LibOS thread in the current host thread. /// Execute the specified LibOS thread in the current host thread.
pub fn exec(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> { pub fn exec(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> {
let new_thread: ThreadRef = dequeue(libos_tid)?; let this_thread: ThreadRef = dequeue(libos_tid)?;
new_thread.start(host_tid); this_thread.start(host_tid);
// Enable current::get() from now on // Enable current::get() from now on
current::set(new_thread.clone()); current::set(this_thread.clone());
#[cfg(feature = "syscall_timing")] #[cfg(feature = "syscall_timing")]
GLOBAL_PROFILER GLOBAL_PROFILER
@ -48,7 +48,7 @@ pub fn exec(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> {
unsafe { unsafe {
// task may only be modified by this function; so no lock is needed // task may only be modified by this function; so no lock is needed
do_exec_task(new_thread.task() as *const Task as *mut Task); do_exec_task(this_thread.task() as *const Task as *mut Task);
} }
#[cfg(feature = "syscall_timing")] #[cfg(feature = "syscall_timing")]
@ -58,16 +58,20 @@ pub fn exec(libos_tid: pid_t, host_tid: pid_t) -> Result<i32> {
.thread_exit() .thread_exit()
.expect("unexpected error from profiler to exit thread"); .expect("unexpected error from profiler to exit thread");
let exit_status = new_thread.inner().exit_status().unwrap(); let term_status = this_thread.inner().term_status().unwrap();
info!( match term_status {
"Thread exited: tid = {}, exit_status = {}", TermStatus::Exited(status) => {
libos_tid, exit_status info!("Thread exited: tid = {}, status = {}", libos_tid, status);
); }
TermStatus::Killed(signum) => {
info!("Thread killed: tid = {}, signum = {:?}", libos_tid, signum);
}
}
// Disable current::get() // Disable current::get()
current::reset(); current::reset();
Ok(exit_status) Ok(term_status.as_u32() as i32)
} }
lazy_static! { lazy_static! {

@ -0,0 +1,22 @@
//! The termination status of a process or thread.
use crate::signal::SigNum;
// TODO: support core dump
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TermStatus {
Exited(u8),
Killed(SigNum),
//Dumped(SigNum),
}
impl TermStatus {
/// Return as a 32-bit integer encoded as specified in wait(2) man page.
pub fn as_u32(&self) -> u32 {
match *self {
TermStatus::Exited(status) => (status as u32) << 8,
TermStatus::Killed(signum) => (signum.as_u8() as u32),
//TermStatus::Dumped(signum) => (signum.as_u8() as u32) | 0x80,
}
}
}

@ -2,7 +2,7 @@ use std::ptr::NonNull;
use super::{ use super::{
FileTableRef, FsViewRef, ProcessRef, ProcessVM, ProcessVMRef, ResourceLimitsRef, SchedAgentRef, FileTableRef, FsViewRef, ProcessRef, ProcessVM, ProcessVMRef, ResourceLimitsRef, SchedAgentRef,
Task, Thread, ThreadId, ThreadInner, ThreadRef, SigQueues, SigSet, Task, Thread, ThreadId, ThreadInner, ThreadRef,
}; };
use crate::prelude::*; use crate::prelude::*;
@ -82,10 +82,12 @@ impl ThreadBuilder {
} }
pub fn build(self) -> Result<ThreadRef> { pub fn build(self) -> Result<ThreadRef> {
let tid = self.tid.unwrap_or_else(|| ThreadId::new());
let task = self let task = self
.task .task
.ok_or_else(|| errno!(EINVAL, "task is mandatory"))?; .ok_or_else(|| errno!(EINVAL, "task is mandatory"))?;
let tid = self.tid.unwrap_or_else(|| ThreadId::new());
let clear_ctid = SgxRwLock::new(self.clear_ctid);
let inner = SgxMutex::new(ThreadInner::new());
let process = self let process = self
.process .process
.ok_or_else(|| errno!(EINVAL, "process is mandatory"))?; .ok_or_else(|| errno!(EINVAL, "process is mandatory"))?;
@ -96,8 +98,9 @@ impl ThreadBuilder {
let files = self.files.unwrap_or_default(); let files = self.files.unwrap_or_default();
let sched = self.sched.unwrap_or_default(); let sched = self.sched.unwrap_or_default();
let rlimits = self.rlimits.unwrap_or_default(); let rlimits = self.rlimits.unwrap_or_default();
let clear_ctid = SgxRwLock::new(self.clear_ctid); let sig_queues = SgxMutex::new(SigQueues::new());
let inner = SgxMutex::new(ThreadInner::new()); let sig_mask = SgxRwLock::new(SigSet::new_empty());
let sig_tmp_mask = SgxRwLock::new(SigSet::new_empty());
let new_thread = Arc::new(Thread { let new_thread = Arc::new(Thread {
task, task,
@ -110,6 +113,9 @@ impl ThreadBuilder {
files, files,
sched, sched,
rlimits, rlimits,
sig_queues,
sig_mask,
sig_tmp_mask,
}); });
let mut inner = new_thread.process().inner(); let mut inner = new_thread.process().inner();

@ -4,9 +4,10 @@ use std::ptr::NonNull;
use super::task::Task; use super::task::Task;
use super::{ use super::{
FileTableRef, FsViewRef, ProcessRef, ProcessVM, ProcessVMRef, ResourceLimitsRef, SchedAgentRef, FileTableRef, FsViewRef, ProcessRef, ProcessVM, ProcessVMRef, ResourceLimitsRef, SchedAgentRef,
ThreadRef, TermStatus, ThreadRef,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::signal::{SigQueues, SigSet};
pub use self::builder::ThreadBuilder; pub use self::builder::ThreadBuilder;
pub use self::id::ThreadId; pub use self::id::ThreadId;
@ -30,6 +31,10 @@ pub struct Thread {
files: FileTableRef, files: FileTableRef,
sched: SchedAgentRef, sched: SchedAgentRef,
rlimits: ResourceLimitsRef, rlimits: ResourceLimitsRef,
// Signal
sig_queues: SgxMutex<SigQueues>,
sig_mask: SgxRwLock<SigSet>,
sig_tmp_mask: SgxRwLock<SigSet>,
} }
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
@ -68,6 +73,24 @@ impl Thread {
&self.sched &self.sched
} }
/// Get the signal queues for thread-directed signals.
pub fn sig_queues(&self) -> &SgxMutex<SigQueues> {
&self.sig_queues
}
/// Get the per-thread signal mask.
pub fn sig_mask(&self) -> &SgxRwLock<SigSet> {
&self.sig_mask
}
/// Get the per-thread, temporary signal mask.
///
/// The tmp mask is always cleared at the end of the execution
/// of a syscall.
pub fn sig_tmp_mask(&self) -> &SgxRwLock<SigSet> {
&self.sig_tmp_mask
}
/// Get a file from the file table. /// Get a file from the file table.
pub fn file(&self, fd: FileDesc) -> Result<FileRef> { pub fn file(&self, fd: FileDesc) -> Result<FileRef> {
self.files().lock().unwrap().get(fd) self.files().lock().unwrap().get(fd)
@ -99,7 +122,7 @@ impl Thread {
self.inner().start(); self.inner().start();
} }
pub(super) fn exit(&self, exit_status: i32) -> usize { pub(super) fn exit(&self, term_status: TermStatus) -> usize {
self.sched().lock().unwrap().detach(); self.sched().lock().unwrap().detach();
// Remove this thread from its owner process // Remove this thread from its owner process
@ -111,7 +134,7 @@ impl Thread {
.expect("the thread must belong to the process"); .expect("the thread must belong to the process");
threads.swap_remove(thread_i); threads.swap_remove(thread_i);
self.inner().exit(exit_status); self.inner().exit(term_status);
threads.len() threads.len()
} }
@ -153,7 +176,7 @@ unsafe impl Sync for Thread {}
pub enum ThreadInner { pub enum ThreadInner {
Init, Init,
Running, Running,
Exited { exit_status: i32 }, Exited { term_status: TermStatus },
} }
impl ThreadInner { impl ThreadInner {
@ -169,9 +192,9 @@ impl ThreadInner {
} }
} }
pub fn exit_status(&self) -> Option<i32> { pub fn term_status(&self) -> Option<TermStatus> {
match self { match self {
Self::Exited { exit_status } => Some(*exit_status), Self::Exited { term_status } => Some(*term_status),
_ => None, _ => None,
} }
} }
@ -181,8 +204,8 @@ impl ThreadInner {
*self = Self::Running; *self = Self::Running;
} }
pub fn exit(&mut self, exit_status: i32) { pub fn exit(&mut self, term_status: TermStatus) {
debug_assert!(self.status() == ThreadStatus::Running); debug_assert!(self.status() == ThreadStatus::Running);
*self = Self::Exited { exit_status }; *self = Self::Exited { term_status };
} }
} }

@ -0,0 +1,283 @@
#![allow(non_camel_case_types)]
use std::fmt;
use super::SigNum;
use crate::prelude::*;
use crate::syscall::CpuContext;
use crate::time::clock_t;
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct sigaction_t {
pub handler: *const c_void,
pub flags: u32,
pub restorer: *const c_void,
pub mask: sigset_t,
}
pub type sigset_t = u64;
#[derive(Clone, Copy)]
#[repr(C)]
pub union sigval_t {
sigval_int: i32,
sigval_ptr: *mut c_void,
}
impl fmt::Debug for sigval_t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"sigval_t = {{ {:?} or {:?} }}",
unsafe { self.sigval_int },
unsafe { self.sigval_ptr }
)
}
}
impl From<i32> for sigval_t {
fn from(val: i32) -> sigval_t {
sigval_t { sigval_int: val }
}
}
impl<T> From<*mut T> for sigval_t {
fn from(ptr: *mut T) -> sigval_t {
sigval_t {
sigval_ptr: ptr as *mut c_void,
}
}
}
impl<T> From<*const T> for sigval_t {
fn from(ptr: *const T) -> sigval_t {
sigval_t {
sigval_ptr: ptr as *const c_void as *mut c_void,
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct siginfo_t {
pub si_signo: i32,
pub si_errno: i32,
pub si_code: i32,
_padding: i32,
fields: siginfo_fields_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_fields_t {
bytes: [u8; 128 - std::mem::size_of::<i32>() * 4],
common: siginfo_common_t,
sigfault: siginfo_sigfault_t,
//sigpoll: siginfo_poll_t,
//sigsys: siginfo_sys_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_common_t {
first: siginfo_common_first_t,
second: siginfo_common_second_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_common_first_t {
piduid: siginfo_piduid_t,
timer: siginfo_timer_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct siginfo_piduid_t {
pid: pid_t,
uid: uid_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct siginfo_timer_t {
timerid: i32,
overrun: i32,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_common_second_t {
value: sigval_t,
sigchild: siginfo_sigchild_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_sigchild_t {
status: i32,
utime: clock_t,
stime: clock_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct siginfo_sigfault_t {
addr: *const c_void,
addr_lsb: i16,
first: siginfo_sigfault_first_t,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_sigfault_first_t {
addr_bnd: siginfo_addr_bnd_t,
pkey: u32,
}
#[derive(Clone, Copy)]
#[repr(C)]
union siginfo_addr_bnd_t {
lower: *const c_void,
upper: *const c_void,
}
impl siginfo_t {
pub fn new(num: SigNum, code: i32) -> Self {
let zero_fields = siginfo_fields_t {
bytes: [0_u8; std::mem::size_of::<siginfo_fields_t>()],
};
Self {
si_signo: num.as_u8() as i32,
si_code: code,
si_errno: 0,
_padding: 0,
fields: zero_fields,
}
}
}
// Use macros to implement the getter and setter functions of siginfo_t. These getters
// and setters help the user to access the values embedded inside the many unions of
// siginfo_t.
macro_rules! impl_siginfo_getters_setters {
( $( $getter:ident, $setter:ident : $val_type:ty => $( $path:ident ).* ),+, ) => {
$(
pub fn $getter(&self) -> $val_type {
unsafe {
self.$($path).*
}
}
pub fn $setter(&mut self, new_val: $val_type) {
unsafe {
self.$($path).* = new_val;
}
}
)*
}
}
impl siginfo_t {
impl_siginfo_getters_setters! {
// Format:
// getter_name, setter_name : field_type => path_to_field
si_pid, set_si_pid : pid_t => fields.common.first.piduid.pid,
si_uid, set_si_uid : uid_t => fields.common.first.piduid.uid,
si_status, set_si_satus : i32 => fields.common.second.sigchild.status,
si_utime, set_si_utime : clock_t => fields.common.second.sigchild.utime,
si_stime, set_si_stime : clock_t => fields.common.second.sigchild.stime,
si_value, set_si_value : sigval_t => fields.common.second.value,
si_addr, set_si_addr : *const c_void => fields.sigfault.addr,
si_addr_lsb, set_si_addr_lsb : i16 => fields.sigfault.addr_lsb,
si_lower, set_si_lower : *const c_void => fields.sigfault.first.addr_bnd.lower,
si_upper, set_si_upper : *const c_void => fields.sigfault.first.addr_bnd.upper,
si_pkey, set_si_pkey : u32 => fields.sigfault.first.pkey,
si_timerid, set_si_timerid : i32 => fields.common.first.timer.timerid,
si_overrune, set_si_overrune : i32 => fields.common.first.timer.overrun,
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct ucontext_t {
pub uc_flags: u64,
pub uc_link: *mut ucontext_t,
pub uc_stack: stack_t,
pub uc_mcontext: mcontext_t,
pub uc_sigmask: sigset_t,
__fpregs_mem: [u64; 64],
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct sigaltstack_t {
pub ss_sp: *mut c_void,
pub ss_flags: i32,
pub ss_size: usize,
}
pub type stack_t = sigaltstack_t;
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct mcontext_t {
pub inner: CpuContext,
// TODO: the fields should be csgsfs, err, trapno, oldmask, and cr2
_unused0: [u64; 5],
// TODO: this field should be `fpregs: fpregset_t,`
_unused1: usize,
_reserved: [u64; 8],
}
/// Special values for the user-given signal handlers
pub const SIG_ERR: *const c_void = -1_i64 as *const c_void;
pub const SIG_DFL: *const c_void = 0_i64 as *const c_void;
pub const SIG_IGN: *const c_void = 1_i64 as *const c_void;
pub const SI_ASYNCNL: i32 = -60;
pub const SI_TKILL: i32 = -6;
pub const SI_SIGIO: i32 = -5;
pub const SI_ASYNCIO: i32 = -4;
pub const SI_MESGQ: i32 = -3;
pub const SI_TIMER: i32 = -2;
pub const SI_QUEUE: i32 = -1;
pub const SI_USER: i32 = 0;
pub const SI_KERNEL: i32 = 128;
pub const FPE_INTDIV: i32 = 1;
pub const FPE_INTOVF: i32 = 2;
pub const FPE_FLTDIV: i32 = 3;
pub const FPE_FLTOVF: i32 = 4;
pub const FPE_FLTUND: i32 = 5;
pub const FPE_FLTRES: i32 = 6;
pub const FPE_FLTINV: i32 = 7;
pub const FPE_FLTSUB: i32 = 8;
pub const ILL_ILLOPC: i32 = 1;
pub const ILL_ILLOPN: i32 = 2;
pub const ILL_ILLADR: i32 = 3;
pub const ILL_ILLTRP: i32 = 4;
pub const ILL_PRVOPC: i32 = 5;
pub const ILL_PRVREG: i32 = 6;
pub const ILL_COPROC: i32 = 7;
pub const ILL_BADSTK: i32 = 8;
pub const SEGV_MAPERR: i32 = 1;
pub const SEGV_ACCERR: i32 = 2;
pub const SEGV_BNDERR: i32 = 3;
pub const SEGV_PKUERR: i32 = 4;
pub const BUS_ADRALN: i32 = 1;
pub const BUS_ADRERR: i32 = 2;
pub const BUS_OBJERR: i32 = 3;
pub const BUS_MCEERR_AR: i32 = 4;
pub const BUS_MCEERR_AO: i32 = 5;
pub const CLD_EXITED: i32 = 1;
pub const CLD_KILLED: i32 = 2;
pub const CLD_DUMPED: i32 = 3;
pub const CLD_TRAPPED: i32 = 4;
pub const CLD_STOPPED: i32 = 5;
pub const CLD_CONTINUED: i32 = 6;

@ -0,0 +1,58 @@
use super::SigNum;
use crate::prelude::*;
/// Standard signals
pub(super) const MIN_STD_SIG_NUM: u8 = 1;
pub(super) const MAX_STD_SIG_NUM: u8 = 31; // inclusive
/// Real-time signals
pub(super) const MIN_RT_SIG_NUM: u8 = 32;
pub(super) const MAX_RT_SIG_NUM: u8 = 64; // inclusive
/// Count the number of signals
pub(super) const COUNT_STD_SIGS: usize = 31;
pub(super) const COUNT_RT_SIGS: usize = 33;
pub(super) const COUNT_ALL_SIGS: usize = 64;
macro_rules! define_std_signums {
( $( $name: ident = $num: expr ),+, ) => {
$(
pub const $name : SigNum = unsafe {
SigNum::from_u8_unchecked($num)
};
)*
}
}
// Define the standard signal numbers as SigNum
define_std_signums! {
SIGHUP = 1, // Hangup detected on controlling terminal or death of controlling process
SIGINT = 2, // Interrupt from keyboard
SIGQUIT = 3, // Quit from keyboard
SIGILL = 4, // Illegal Instruction
SIGTRAP = 5, // Trace/breakpoint trap
SIGABRT = 6, // Abort signal from abort(3)
SIGBUS = 7, // Bus error (bad memory access)
SIGFPE = 8, // Floating-point exception
SIGKILL = 9, // Kill signal
SIGUSR1 = 10, // User-defined signal 1
SIGSEGV = 11, // Invalid memory reference
SIGUSR2 = 12, // User-defined signal 2
SIGPIPE = 13, // Broken pipe: write to pipe with no readers; see pipe(7)
SIGALRM = 14, // Timer signal from alarm(2)
SIGTERM = 15, // Termination signal
SIGSTKFLT = 16, // Stack fault on coprocessor (unused)
SIGCHLD = 17, // Child stopped or terminated
SIGCONT = 18, // Continue if stopped
SIGSTOP = 19, // Stop process
SIGTSTP = 20, // Stop typed at terminal
SIGTTIN = 21, // Terminal input for background process
SIGTTOU = 22, // Terminal output for background process
SIGURG = 23, // Urgent condition on socket (4.2BSD)
SIGXCPU = 24, // CPU time limit exceeded (4.2BSD); see setrlimit(2)
SIGXFSZ = 25, // File size limit exceeded (4.2BSD); see setrlimit(2)
SIGVTALRM = 26, // Virtual alarm clock (4.2BSD)
SIGPROF = 27, // Profiling timer expired
SIGWINCH = 28, // Window resize signal (4.3BSD, Sun)
SIGIO = 29, // I/O now possible (4.2BSD)
SIGPWR = 30, // Power failure (System V)
SIGSYS = 31, // Bad system call (SVr4); see also seccomp(2)
}

@ -0,0 +1,76 @@
use super::signals::{UserSignal, UserSignalKind};
use super::{SigNum, Signal};
use crate::prelude::*;
use crate::process::{table, ProcessFilter, ProcessRef, ProcessStatus, ThreadRef, ThreadStatus};
pub fn do_kill(filter: ProcessFilter, signum: SigNum) -> Result<()> {
debug!("do_kill: filter: {:?}, signum: {:?}", &filter, &signum);
let pid = current!().process().pid();
let uid = 0;
let processes = get_processes(&filter)?;
for process in processes {
if process.status() == ProcessStatus::Zombie {
continue;
}
let signal = Box::new(UserSignal::new(signum, UserSignalKind::Kill, pid, uid));
let mut sig_queues = process.sig_queues().lock().unwrap();
sig_queues.enqueue(signal);
}
Ok(())
}
fn get_processes(filter: &ProcessFilter) -> Result<Vec<ProcessRef>> {
let processes = match filter {
ProcessFilter::WithAnyPid => table::get_all_processes(),
ProcessFilter::WithPid(pid) => {
let process = table::get_process(*pid)?;
vec![process]
}
ProcessFilter::WithPgid(pgid) => {
// TODO: implement O(1) lookup for a process group
let processes: Vec<ProcessRef> = table::get_all_processes()
.into_iter()
.filter(|proc_ref| proc_ref.pgid() == *pgid)
.collect();
if processes.len() == 0 {
return_errno!(EINVAL, "invalid pgid");
}
processes
}
};
Ok(processes)
}
pub fn do_tgkill(pid: Option<pid_t>, tid: pid_t, signum: SigNum) -> Result<()> {
debug!(
"do_tgkill: pid: {:?}, tid: {:?}, signum: {:?}",
&pid, &tid, &signum
);
let thread = table::get_thread(tid)?;
if let Some(pid) = pid {
if pid != thread.process().pid() {
return_errno!(EINVAL, "the combination of pid and tid is not valid");
}
}
if thread.status() == ThreadStatus::Exited {
return Ok(());
}
let signal = {
let src_pid = current!().process().pid();
let src_uid = 0;
Box::new(UserSignal::new(
signum,
UserSignalKind::Tkill,
src_pid,
src_uid,
))
};
let mut sig_queues = thread.sig_queues().lock().unwrap();
sig_queues.enqueue(signal);
Ok(())
}

@ -0,0 +1,26 @@
use super::constants::*;
use super::{SigAction, SigNum};
use crate::prelude::*;
pub fn do_rt_sigaction(signum: SigNum, new_sa: Option<SigAction>) -> Result<SigAction> {
debug!(
"do_rt_sigaction: signum: {:?}, new_sa: {:?}",
&signum, &new_sa
);
if signum == SIGKILL || signum == SIGSTOP {
return_errno!(
EINVAL,
"The actions for SIGKILL or SIGSTOP cannot be changed"
);
}
let thread = current!();
let process = thread.process();
let mut sig_dispositions = process.sig_dispositions().write().unwrap();
let old_sa = sig_dispositions.get(signum);
if let Some(new_sa) = new_sa {
sig_dispositions.set(signum, new_sa);
}
Ok(old_sa)
}

@ -0,0 +1,13 @@
use super::SigSet;
use crate::prelude::*;
pub fn do_sigpending() -> Result<SigSet> {
debug!("do_sigpending");
let thread = current!();
let process = thread.process();
let pending = (thread.sig_queues().lock().unwrap().pending()
| process.sig_queues().lock().unwrap().pending())
& *thread.sig_mask().read().unwrap();
Ok(pending)
}

@ -0,0 +1,62 @@
use super::constants::*;
use super::{sigset_t, SigSet};
use crate::prelude::*;
pub fn do_rt_sigprocmask(
op_and_set: Option<(MaskOp, &sigset_t)>,
oldset: Option<&mut sigset_t>,
) -> Result<()> {
debug!(
"do_rt_sigprocmask: op_and_set: {:?}, oldset: {:?}",
op_and_set.map(|(op, set)| (op, SigSet::from_c(*set))),
oldset
);
let thread = current!();
let mut sig_mask = thread.sig_mask().write().unwrap();
if let Some(oldset) = oldset {
*oldset = sig_mask.to_c();
}
if let Some((op, &set)) = op_and_set {
let set = {
let mut set = SigSet::from_c(set);
// According to man pages, "it is not possible to block SIGKILL or SIGSTOP.
// Attempts to do so are silently ignored."
set -= SIGKILL;
set -= SIGSTOP;
set
};
match op {
MaskOp::Block => {
*sig_mask |= set;
}
MaskOp::Unblock => {
*sig_mask &= !set;
}
MaskOp::SetMask => {
*sig_mask = set;
}
};
}
Ok(())
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum MaskOp {
Block = 0,
Unblock = 1,
SetMask = 2,
}
impl MaskOp {
pub fn from_u32(raw: u32) -> Result<MaskOp> {
let op = match raw {
0 => MaskOp::Block,
1 => MaskOp::Unblock,
2 => MaskOp::SetMask,
_ => return_errno!(EINVAL, "invalid mask op"),
};
Ok(op)
}
}

@ -0,0 +1,346 @@
use super::c_types::{mcontext_t, siginfo_t, ucontext_t};
use super::constants::SIGKILL;
use super::{SigAction, SigActionFlags, SigDefaultAction, SigSet, Signal};
use crate::prelude::*;
use crate::process::{ProcessRef, TermStatus, ThreadRef};
use crate::syscall::CpuContext;
pub fn do_rt_sigreturn(curr_user_ctxt: &mut CpuContext) -> Result<()> {
debug!("do_rt_sigreturn");
let last_user_ctxt = {
let last_user_ctxt = PRE_USER_CONTEXTS.with(|ref_cell| {
let mut stack = ref_cell.borrow_mut();
stack.pop()
});
if last_user_ctxt.is_none() {
let term_status = TermStatus::Killed(SIGKILL);
current!().process().force_exit(term_status);
return_errno!(
EINVAL,
"sigreturn should not have been called; kill this process"
);
}
unsafe { &*last_user_ctxt.unwrap() }
};
*curr_user_ctxt = *last_user_ctxt;
Ok(())
}
/// Deliver a queued signal for the current thread, respecting the thread's
/// signal mask.
///
/// The delivery of a signal means two things: 1) dequeuing the signal from
/// the per-thread or per-process signal queue, and 2) handling the signal
/// according to the signal disposition.
///
/// When handling a signal, one of the three actions below will be done:
///
/// 1. Ignore the signal. This is the easy part.
///
/// 2. Terminate the process if the signal is fatal. This is called "force exit".
///
/// 3. Call a user-registered signal handler. In this case, the current CPU context
/// will be modified so that the user-registered signal handler will be called
/// upon returning to the user space when the current syscall is finished.
///
/// **Requirement.** This must be called only once during the execution of a
/// syscall and at a very late stage.
///
/// **Post-condition.** The temporary signal mask of the current thread is cleared.
pub fn deliver_signal(cpu_context: &mut CpuContext) {
let thread = current!();
let process = thread.process();
if process.is_forced_exit().is_none() {
do_deliver_signal(&thread, &process, cpu_context);
}
// Ensure the tmp signal mask is cleared before sysret
let mut tmp_sig_mask = thread.sig_tmp_mask().write().unwrap();
*tmp_sig_mask = SigSet::new_empty();
}
fn do_deliver_signal(thread: &ThreadRef, process: &ProcessRef, cpu_context: &mut CpuContext) {
loop {
// Dequeue a signal, respecting the signal mask and tmp mask
let sig_mask = *thread.sig_mask().read().unwrap() | *thread.sig_tmp_mask().read().unwrap();
let signal = {
#[rustfmt::skip]
let signal_opt = process.sig_queues().lock().unwrap().dequeue(&sig_mask)
.or_else(|| thread.sig_queues().lock().unwrap().dequeue(&sig_mask));
if signal_opt.is_none() {
return;
}
signal_opt.unwrap()
};
let continue_handling = handle_signal(signal, thread, process, cpu_context);
if !continue_handling {
break;
}
}
}
/// Force delivering the given signal to the current thread, without checking the thread's
/// signal mask.
///
/// **Post-condition.** The tmp signal mask of the current thread is all set. This avoids
/// delivering two signals during one execution of a syscall.
///
/// **Requirement.** This function can only be called at most once during the execution of
/// a syscall.
pub fn force_signal(signal: Box<dyn Signal>, cpu_context: &mut CpuContext) {
let thread = current!();
let process = thread.process();
handle_signal(signal, &thread, &process, cpu_context);
// Temporarily block all signals from being delivered until this syscall is
// over. This ensures that the updated curr_cpu_ctxt will not be overriden
// to deliver any other signal.
let mut tmp_sig_mask = thread.sig_tmp_mask().write().unwrap();
*tmp_sig_mask = SigSet::new_full();
}
fn handle_signal(
signal: Box<dyn Signal>,
thread: &ThreadRef,
process: &ProcessRef,
cpu_context: &mut CpuContext,
) -> bool {
let is_sig_stack_full = PRE_USER_CONTEXTS.with(|ref_cell| {
let stack = ref_cell.borrow();
stack.full()
});
if is_sig_stack_full {
panic!("the nested signal is too deep to handle");
}
let action = process.sig_dispositions().read().unwrap().get(signal.num());
debug!(
"Handle signal: signal: {:?}, action: {:?}",
&signal, &action
);
let continue_handling = match action {
SigAction::Ign => true,
SigAction::Dfl => {
let default_action = SigDefaultAction::from_signum(signal.num());
match default_action {
SigDefaultAction::Ign => true,
SigDefaultAction::Term | SigDefaultAction::Core => {
let term_status = TermStatus::Killed(signal.num());
process.force_exit(term_status);
false
}
SigDefaultAction::Stop => {
warn!("SIGSTOP is unsupported");
true
}
SigDefaultAction::Cont => {
warn!("SIGCONT is unsupported");
true
}
}
}
SigAction::User {
handler_addr,
flags,
restorer_addr,
mask,
} => {
let ret = handle_signals_by_user(
signal,
handler_addr,
flags,
restorer_addr,
mask,
cpu_context,
);
if let Err(_) = ret {
todo!("kill the process if any error");
}
false
}
};
continue_handling
}
fn handle_signals_by_user(
signal: Box<dyn Signal>,
handler_addr: usize,
flags: SigActionFlags,
restorer_addr: usize,
mask: SigSet,
curr_user_ctxt: &mut CpuContext,
) -> Result<()> {
// Represent the user stack in a memory safe way
let mut user_stack = {
const BIG_ENOUGH_GAP: u64 = 1024;
const BIG_ENOUGH_SIZE: u64 = 4096;
let stack_top = (curr_user_ctxt.rsp - BIG_ENOUGH_GAP) as usize;
let stack_size = BIG_ENOUGH_SIZE as usize;
// TODO: validate the memory range of the stack
unsafe { Stack::new(stack_top, stack_size)? }
};
// Prepare the user stack in four steps.
//
// 1. Allocate and init siginfo_t on the user stack.
let info = {
let info = user_stack.alloc::<siginfo_t>()?;
*info = signal.to_info();
info as *mut siginfo_t
};
// 2. Allocate and init ucontext_t on the user stack.
let ucontext = {
// The x86 calling convention requires rsp to be 16-byte aligned.
// The following allocation on stack is right before we "call" the
// signal handler. So we need to make sure the allocation is at least
// 16-byte aligned.
let ucontext = user_stack.alloc_aligned::<ucontext_t>(16)?;
// TODO: set all fields in ucontext
*ucontext = unsafe { std::mem::zeroed() };
ucontext as *mut ucontext_t
};
// 3. Save the current user CPU context on the stack of the signal handler
// so that we can restore the CPU context upon `sigreturn` syscall.
let saved_user_ctxt = {
let saved_user_ctxt = unsafe { &mut (*ucontext).uc_mcontext.inner };
*saved_user_ctxt = *curr_user_ctxt;
saved_user_ctxt as *mut CpuContext
};
// 4. Set up the call return address on the stack before we "call" the signal handler
let handler_stack_top = {
let handler_stack_top = user_stack.alloc::<usize>()?;
*handler_stack_top = restorer_addr;
handler_stack_top as *mut usize
};
// TODO: mask signals while the signal handler is executing
// Modify the current user CPU context so that the signal handler will
// be "called" upon returning back to the user space and when the signal
// handler finishes, the CPU will jump to the restorer.
curr_user_ctxt.rsp = handler_stack_top as u64;
curr_user_ctxt.rip = handler_addr as u64;
// Prepare the three arguments for the signal handler
curr_user_ctxt.rdi = signal.num().as_u8() as u64;
curr_user_ctxt.rsi = info as u64;
curr_user_ctxt.rdx = ucontext as u64;
PRE_USER_CONTEXTS.with(|ref_cell| {
let mut stack = ref_cell.borrow_mut();
stack.push(saved_user_ctxt).unwrap();
});
Ok(())
}
/// Represent and manipulate a stack in a memory-safe way
struct Stack {
pointer: usize,
bottom: usize,
}
impl Stack {
/// Create a new region of memory to use as stack
pub unsafe fn new(stack_top: usize, stack_size: usize) -> Result<Stack> {
if stack_top <= stack_size {
return_errno!(EINVAL, "stack address may underflow");
}
let pointer = stack_top;
let bottom = stack_top - stack_size;
Ok(Stack { pointer, bottom })
}
/// Get the size of the free space in the stack
pub fn size(&self) -> usize {
self.pointer - self.bottom
}
/// Allocate a mutable object on the stack.
///
/// The alignment of the object will be `std::mem::size_of::<T>()`.
pub fn alloc<T>(&mut self) -> Result<&mut T> {
self.do_alloc_aligned::<T>(1)
}
/// Allocate a mutable object on the stack.
///
/// The alignment of the object will be `max(align, std::mem::size_of::<T>())`.
pub fn alloc_aligned<T>(&mut self, align: usize) -> Result<&mut T> {
if !align.is_power_of_two() {
return_errno!(EINVAL, "align must be a power of two");
}
self.do_alloc_aligned::<T>(align)
}
/// Allocate a mutable object on the stack.
///
/// The alignment of the object will be `max(align, std::mem::size_of::<T>())`.
fn do_alloc_aligned<T>(&mut self, align: usize) -> Result<&mut T> {
// Check precondition
debug_assert!(align.is_power_of_two());
// Calculate the pointer of the object
let new_pointer = {
let size = std::mem::size_of::<T>();
let align = std::mem::align_of::<T>().max(align);
let mut pointer = self.pointer;
if pointer < size {
return_errno!(ENOMEM, "not enough memory");
}
pointer -= size;
pointer = align_down(pointer, align);
if pointer < self.bottom {
return_errno!(ENOMEM, "not enough memory");
}
pointer
};
self.pointer = new_pointer;
let obj_ref = unsafe { &mut *(new_pointer as *mut T) };
Ok(obj_ref)
}
}
thread_local! {
static PRE_USER_CONTEXTS: RefCell<CpuContextStack> = Default::default();
}
#[derive(Debug, Default)]
struct CpuContextStack {
stack: [Option<*mut CpuContext>; 32],
count: usize,
}
impl CpuContextStack {
pub fn new() -> Self {
Default::default()
}
pub fn full(&self) -> bool {
self.count == self.stack.len()
}
pub fn empty(&self) -> bool {
self.count == 0
}
pub fn push(&mut self, cpu_context: *mut CpuContext) -> Result<()> {
if self.full() {
return_errno!(ENOMEM, "cpu context stack is full");
}
self.stack[self.count] = Some(cpu_context);
self.count += 1;
Ok(())
}
pub fn pop(&mut self) -> Option<*mut CpuContext> {
if self.empty() {
return None;
}
self.count -= 1;
self.stack[self.count].take()
}
}

@ -0,0 +1,31 @@
//! The signal subsystem.
use crate::prelude::*;
use sig_action::{SigAction, SigActionFlags, SigDefaultAction};
pub use self::c_types::{sigaction_t, sigset_t};
pub use self::constants::*;
pub use self::do_sigreturn::{deliver_signal, force_signal};
pub use self::sig_dispositions::SigDispositions;
pub use self::sig_num::SigNum;
pub use self::sig_queues::SigQueues;
pub use self::sig_set::SigSet;
pub use self::signals::{FaultSignal, KernelSignal, Signal, UserSignal, UserSignalKind};
pub use self::syscalls::*;
mod c_types;
mod do_kill;
mod do_sigaction;
mod do_sigpending;
mod do_sigprocmask;
mod do_sigreturn;
mod sig_action;
mod sig_dispositions;
mod sig_num;
mod sig_queues;
mod sig_set;
mod signals;
mod syscalls;
pub mod constants;

@ -0,0 +1,132 @@
use super::c_types::{sigaction_t, SIG_DFL, SIG_IGN};
use super::constants::*;
use super::{SigNum, SigSet};
use crate::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SigAction {
Dfl, // Default action
Ign, // Ignore this signal
User {
// User-given handler
handler_addr: usize,
flags: SigActionFlags,
restorer_addr: usize,
mask: SigSet,
},
}
impl Default for SigAction {
fn default() -> Self {
SigAction::Dfl
}
}
impl SigAction {
pub fn from_c(sa_c: &sigaction_t) -> Result<Self> {
let sa = match sa_c.handler {
SIG_DFL => SigAction::Dfl,
SIG_IGN => SigAction::Ign,
_ => SigAction::User {
handler_addr: sa_c.handler as usize,
flags: SigActionFlags::from_u32(sa_c.flags)?,
restorer_addr: sa_c.restorer as usize,
mask: SigSet::from_c(sa_c.mask),
},
};
Ok(sa)
}
pub fn to_c(&self) -> sigaction_t {
match self {
SigAction::Dfl => sigaction_t {
handler: SIG_DFL,
flags: 0,
restorer: std::ptr::null(),
mask: 0,
},
SigAction::Ign => sigaction_t {
handler: SIG_IGN,
flags: 0,
restorer: std::ptr::null(),
mask: 0,
},
SigAction::User {
handler_addr,
flags,
restorer_addr,
mask,
} => sigaction_t {
handler: *handler_addr as *const c_void,
flags: flags.to_u32(),
restorer: *restorer_addr as *mut c_void,
mask: mask.to_c(),
},
}
}
}
bitflags! {
pub struct SigActionFlags: u32 {
const SA_NOCLDSTOP = 1;
const SA_NOCLDWAIT = 2;
const SA_SIGINFO = 4;
const SA_ONSTACK = 0x08000000;
const SA_RESTART = 0x10000000;
const SA_NODEFER = 0x40000000;
const SA_RESETHAND = 0x80000000;
const SA_RESTORER = 0x04000000;
}
}
impl SigActionFlags {
pub fn from_u32(bits: u32) -> Result<SigActionFlags> {
let flags =
Self::from_bits(bits).ok_or_else(|| errno!(EINVAL, "invalid sigaction flags"))?;
Ok(flags)
}
pub fn to_u32(&self) -> u32 {
self.bits()
}
}
#[derive(Debug, Copy, Clone)]
pub enum SigDefaultAction {
Term, // Default action is to terminate the process.
Ign, // Default action is to ignore the signal.
Core, // Default action is to terminate the process and dump core (see core(5)).
Stop, // Default action is to stop the process.
Cont, // Default action is to continue the process if it is currently stopped.
}
impl SigDefaultAction {
pub fn from_signum(num: SigNum) -> SigDefaultAction {
match num {
SIGABRT | // = SIGIOT
SIGBUS |
SIGFPE |
SIGILL |
SIGQUIT |
SIGSEGV |
SIGSYS | // = SIGUNUSED
SIGTRAP |
SIGXCPU |
SIGXFSZ
=> SigDefaultAction::Core,
SIGCHLD |
SIGURG |
SIGWINCH
=> SigDefaultAction::Ign,
SIGCONT
=> SigDefaultAction::Cont,
SIGSTOP |
SIGTSTP |
SIGTTIN |
SIGTTOU
=> SigDefaultAction::Stop,
_
=> SigDefaultAction::Term,
}
}
}

@ -0,0 +1,88 @@
use std::fmt;
use super::constants::*;
use super::{SigAction, SigNum};
use crate::prelude::*;
#[derive(Copy, Clone)]
pub struct SigDispositions {
// SigNum -> SigAction
map: [SigAction; COUNT_ALL_SIGS],
}
impl SigDispositions {
pub fn new() -> Self {
Self {
map: [Default::default(); COUNT_ALL_SIGS],
}
}
pub fn get(&self, num: SigNum) -> SigAction {
let idx = Self::num_to_idx(num);
self.map[idx]
}
pub fn set(&mut self, num: SigNum, sa: SigAction) {
let idx = Self::num_to_idx(num);
self.map[idx] = sa;
}
pub fn iter<'a>(&'a self) -> SigDispositionsIter<'a> {
SigDispositionsIter::new(self)
}
fn num_to_idx(num: SigNum) -> usize {
(num.as_u8() - MIN_STD_SIG_NUM) as usize
}
fn idx_to_num(idx: usize) -> SigNum {
unsafe { SigNum::from_u8_unchecked(idx as u8 + MIN_STD_SIG_NUM) }
}
}
pub struct SigDispositionsIter<'a> {
next_idx: usize,
dispos: &'a SigDispositions,
}
impl<'a> SigDispositionsIter<'a> {
pub fn new(dispos: &'a SigDispositions) -> Self {
SigDispositionsIter {
next_idx: 0,
dispos: dispos,
}
}
}
impl<'a> std::iter::Iterator for SigDispositionsIter<'a> {
type Item = (SigNum, &'a SigAction);
fn next(&mut self) -> Option<Self::Item> {
let map = &self.dispos.map;
if self.next_idx >= map.len() {
return None;
}
let item = {
let signum = SigDispositions::idx_to_num(self.next_idx);
let action = &map[self.next_idx];
Some((signum, action))
};
self.next_idx += 1;
item
}
}
impl Default for SigDispositions {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for SigDispositions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SigDispositions ");
let non_default_dispositions = self.iter().filter(|(_, action)| **action != SigAction::Dfl);
f.debug_map().entries(non_default_dispositions).finish()
}
}

@ -0,0 +1,90 @@
use std::fmt;
use super::constants::*;
use crate::prelude::*;
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct SigNum {
num: u8,
}
impl SigNum {
pub fn from_u8(num: u8) -> Result<SigNum> {
if num < MIN_STD_SIG_NUM || num > MAX_RT_SIG_NUM {
return_errno!(EINVAL, "not an invalid number for signal");
}
Ok(unsafe { Self::from_u8_unchecked(num) })
}
pub const unsafe fn from_u8_unchecked(num: u8) -> SigNum {
SigNum { num }
}
pub fn as_u8(&self) -> u8 {
self.num
}
pub fn is_std(&self) -> bool {
self.num <= MAX_STD_SIG_NUM
}
pub fn is_real_time(&self) -> bool {
self.num >= MIN_RT_SIG_NUM
}
}
macro_rules! std_signum_to_name {
( $std_signum: expr, { $( $sig_name: ident = $sig_num_u8: expr ),+, } ) => {
match $std_signum {
$(
$sig_name => stringify!($sig_name),
)*
_ => unreachable!(),
}
}
}
impl fmt::Debug for SigNum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#![deny(unreachable_patterns)]
if self.is_std() {
let name = std_signum_to_name!(*self, {
SIGHUP = 1, // Hangup detected on controlling terminal or death of controlling process
SIGINT = 2, // Interrupt from keyboard
SIGQUIT = 3, // Quit from keyboard
SIGILL = 4, // Illegal Instruction
SIGTRAP = 5, // Trace/breakpoint trap
SIGABRT = 6, // Abort signal from abort(3)
SIGBUS = 7, // Bus error (bad memory access)
SIGFPE = 8, // Floating-point exception
SIGKILL = 9, // Kill signal
SIGUSR1 = 10, // User-defined signal 1
SIGSEGV = 11, // Invalid memory reference
SIGUSR2 = 12, // User-defined signal 2
SIGPIPE = 13, // Broken pipe: write to pipe with no readers; see pipe(7)
SIGALRM = 14, // Timer signal from alarm(2)
SIGTERM = 15, // Termination signal
SIGSTKFLT = 16, // Stack fault on coprocessor (unused)
SIGCHLD = 17, // Child stopped or terminated
SIGCONT = 18, // Continue if stopped
SIGSTOP = 19, // Stop process
SIGTSTP = 20, // Stop typed at terminal
SIGTTIN = 21, // Terminal input for background process
SIGTTOU = 22, // Terminal output for background process
SIGURG = 23, // Urgent condition on socket (4.2BSD)
SIGXCPU = 24, // CPU time limit exceeded (4.2BSD); see setrlimit(2)
SIGXFSZ = 25, // File size limit exceeded (4.2BSD); see setrlimit(2)
SIGVTALRM = 26, // Virtual alarm clock (4.2BSD)
SIGPROF = 27, // Profiling timer expired
SIGWINCH = 28, // Window resize signal (4.3BSD, Sun)
SIGIO = 29, // I/O now possible (4.2BSD)
SIGPWR = 30, // Power failure (System V)
SIGSYS = 31, // Bad system call (SVr4); see also seccomp(2)
});
write!(f, "SigNum (#{} = {})", self.num, name)
} else {
write!(f, "SigNum (#{}, real-time)", self.num)
}
}
}

@ -0,0 +1,188 @@
use std::collections::VecDeque;
use std::fmt;
use super::constants::*;
use super::{SigNum, SigSet, Signal};
use crate::prelude::*;
pub struct SigQueues {
count: usize,
has_kill: bool,
std_queues: Vec<Option<Box<dyn Signal>>>,
rt_queues: Vec<VecDeque<Box<dyn Signal>>>,
}
impl SigQueues {
pub fn new() -> Self {
let count = 0;
let has_kill = false;
let std_queues = (0..COUNT_STD_SIGS).map(|_| None).collect();
let rt_queues = (0..COUNT_RT_SIGS).map(|_| Default::default()).collect();
SigQueues {
count,
has_kill,
std_queues,
rt_queues,
}
}
pub fn empty(&self) -> bool {
self.count == 0
}
pub fn enqueue(&mut self, signal: Box<dyn Signal>) {
let signum = signal.num();
if signum.is_std() {
// Standard signals
//
// From signal(7):
//
// Standard signals do not queue. If multiple instances of a standard
// signal are generated while that signal is blocked, then only one
// instance of the signal is marked as pending (and the signal will be
// delivered just once when it is unblocked). In the case where a
// standard signal is already pending, the siginfo_t structure (see
// sigaction(2)) associated with that signal is not overwritten on
// arrival of subsequent instances of the same signal. Thus, the
// process will receive the information associated with the first
// instance of the signal.
let queue = self.get_std_queue_mut(signum);
if queue.is_some() {
// If there is already a signal pending, just ignore all subsequent signals
return;
}
*queue = Some(signal);
self.count += 1;
} else {
// Real-time signals
let queue = self.get_rt_queue_mut(signum);
queue.push_back(signal);
self.count += 1;
}
}
pub fn dequeue(&mut self, blocked: &SigSet) -> Option<Box<dyn Signal>> {
// Fast path for the common case of no pending signals
if self.empty() {
return None;
}
// Deliver standard signals.
//
// According to signal(7):
// If both standard and real-time signals are pending for a process,
// POSIX leaves it unspecified which is delivered first. Linux, like
// many other implementations, gives priority to standard signals in
// this case.
// POSIX leaves unspecified which to deliver first if there are multiple
// pending standard signals. So we are free to define our own. The
// principle is to give more urgent signals higher priority (like SIGKILL).
const ORDERED_STD_SIGS: [SigNum; COUNT_STD_SIGS] = [
SIGKILL, SIGTERM, SIGSTOP, SIGCONT, SIGSEGV, SIGILL, SIGHUP, SIGINT, SIGQUIT, SIGTRAP,
SIGABRT, SIGBUS, SIGFPE, SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM, SIGSTKFLT, SIGCHLD,
SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH,
SIGIO, SIGPWR, SIGSYS,
];
for &signum in &ORDERED_STD_SIGS {
if blocked.contains(signum) {
continue;
}
let queue = self.get_std_queue_mut(signum);
let signal = queue.take();
if signal.is_some() {
self.count -= 1;
return signal;
}
}
// If no standard signals, then deliver real-time signals.
//
// According to signal (7):
// Real-time signals are delivered in a guaranteed order. Multiple
// real-time signals of the same type are delivered in the order
// they were sent. If different real-time signals are sent to a
// process, they are delivered starting with the lowest-numbered
// signal. (I.e., low-numbered signals have highest priority.)
for signum in MIN_RT_SIG_NUM..=MAX_RT_SIG_NUM {
let signum = unsafe { SigNum::from_u8_unchecked(signum) };
if blocked.contains(signum) {
continue;
}
let queue = self.get_rt_queue_mut(signum);
let signal = queue.pop_front();
if signal.is_some() {
self.count -= 1;
return signal;
}
}
// There must be pending but blocked signals
None
}
pub fn pending(&self) -> SigSet {
let mut pending_sigs = SigSet::new_empty();
for signum in MIN_STD_SIG_NUM..=MAX_STD_SIG_NUM {
let signum = unsafe { SigNum::from_u8_unchecked(signum) };
let queue = self.get_std_queue(signum);
if queue.is_some() {
pending_sigs += signum;
}
}
for signum in MIN_RT_SIG_NUM..=MAX_RT_SIG_NUM {
let signum = unsafe { SigNum::from_u8_unchecked(signum) };
let queue = self.get_rt_queue(signum);
if !queue.is_empty() {
pending_sigs += signum;
}
}
pending_sigs
}
fn get_std_queue(&self, signum: SigNum) -> &Option<Box<dyn Signal>> {
debug_assert!(signum.is_std());
let idx = (signum.as_u8() - MIN_STD_SIG_NUM) as usize;
&self.std_queues[idx]
}
fn get_rt_queue(&self, signum: SigNum) -> &VecDeque<Box<dyn Signal>> {
debug_assert!(signum.is_real_time());
let idx = (signum.as_u8() - MIN_RT_SIG_NUM) as usize;
&self.rt_queues[idx]
}
fn get_std_queue_mut(&mut self, signum: SigNum) -> &mut Option<Box<dyn Signal>> {
debug_assert!(signum.is_std());
let idx = (signum.as_u8() - MIN_STD_SIG_NUM) as usize;
&mut self.std_queues[idx]
}
fn get_rt_queue_mut(&mut self, signum: SigNum) -> &mut VecDeque<Box<dyn Signal>> {
debug_assert!(signum.is_real_time());
let idx = (signum.as_u8() - MIN_RT_SIG_NUM) as usize;
&mut self.rt_queues[idx]
}
}
impl Default for SigQueues {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for SigQueues {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let signals = self
.std_queues
.iter()
.flatten()
.chain(self.rt_queues.iter().flatten());
write!(f, "SigQueues {{ ");
write!(f, "queue = ");
f.debug_list().entries(signals).finish();
write!(f, " }}")
}
}

@ -0,0 +1,193 @@
use std::fmt;
use std::iter;
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign};
use super::constants::MIN_STD_SIG_NUM;
use super::{sigset_t, SigNum};
use crate::prelude::*;
#[derive(Copy, Clone, Default, PartialEq, Eq)]
pub struct SigSet {
bits: u64,
}
impl SigSet {
pub const fn new_empty() -> Self {
Self::from_c(0 as sigset_t)
}
pub const fn new_full() -> Self {
Self::from_c(!0 as sigset_t)
}
pub const fn from_c(bits: sigset_t) -> Self {
let bits = bits as u64;
SigSet { bits }
}
pub fn to_c(&self) -> sigset_t {
self.bits as sigset_t
}
pub fn as_u64(&self) -> u64 {
self.bits
}
pub fn empty(&self) -> bool {
self.bits != 0
}
pub fn full(&self) -> bool {
self.bits == !0
}
pub fn count(&self) -> usize {
self.bits.count_ones() as usize
}
pub fn contains(&self, signum: SigNum) -> bool {
let idx = Self::num_to_idx(signum);
(self.bits & (1_u64 << idx)) != 0
}
pub fn iter(&self) -> SigSetIter {
SigSetIter::new(self)
}
fn num_to_idx(num: SigNum) -> usize {
(num.as_u8() - MIN_STD_SIG_NUM) as usize
}
fn idx_to_num(idx: usize) -> SigNum {
debug_assert!(idx < 64);
unsafe { SigNum::from_u8_unchecked((idx + 1) as u8) }
}
}
pub struct SigSetIter<'a> {
sigset: &'a SigSet,
next_idx: usize,
}
impl<'a> SigSetIter<'a> {
pub fn new(sigset: &'a SigSet) -> Self {
let next_idx = 0;
Self { sigset, next_idx }
}
}
impl<'a> iter::Iterator for SigSetIter<'a> {
type Item = SigNum;
fn next(&mut self) -> Option<Self::Item> {
let bits = &self.sigset.bits;
while self.next_idx < 64 && (*bits & (1 << self.next_idx)) == 0 {
self.next_idx += 1;
}
if self.next_idx == 64 {
return None;
}
let item = SigSet::idx_to_num(self.next_idx);
self.next_idx += 1;
Some(item)
}
}
impl From<SigNum> for SigSet {
fn from(signum: SigNum) -> SigSet {
let mut sigset = SigSet::new_empty();
sigset += signum;
sigset
}
}
impl Not for SigSet {
type Output = Self;
fn not(self) -> Self::Output {
Self::from_c(!self.bits)
}
}
impl BitOr for SigSet {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self {
self |= rhs;
self
}
}
impl BitOrAssign for SigSet {
fn bitor_assign(&mut self, rhs: Self) {
self.bits |= rhs.bits;
}
}
impl BitAnd for SigSet {
type Output = Self;
fn bitand(mut self, rhs: Self) -> Self {
self &= rhs;
self
}
}
impl BitAndAssign for SigSet {
fn bitand_assign(&mut self, rhs: Self) {
self.bits &= rhs.bits;
}
}
impl Add<SigNum> for SigSet {
type Output = Self;
fn add(mut self, rhs: SigNum) -> Self {
self += rhs;
self
}
}
impl AddAssign<SigNum> for SigSet {
fn add_assign(&mut self, rhs: SigNum) {
let idx = Self::num_to_idx(rhs);
self.bits |= 1_u64 << idx;
}
}
impl Sub<SigNum> for SigSet {
type Output = Self;
fn sub(mut self, rhs: SigNum) -> Self {
self -= rhs;
self
}
}
impl SubAssign<SigNum> for SigSet {
fn sub_assign(&mut self, rhs: SigNum) {
let idx = Self::num_to_idx(rhs);
self.bits &= !(1_u64 << idx);
}
}
impl fmt::Debug for SigSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SigSet {{ ");
match self.count() {
0..=32 => {
f.debug_list().entries(self.iter()).finish();
}
33..=63 => {
write!(f, "All except ");
let except_sigset = !*self;
f.debug_list().entries(except_sigset.iter()).finish();
}
64 => {
write!(f, "None");
}
_ => unreachable!(),
}
write!(f, " }}")
}
}

@ -0,0 +1,50 @@
use super::super::c_types::*;
use super::super::constants::*;
use super::super::{SigNum, Signal};
use crate::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FaultSignal {
num: SigNum,
code: i32,
}
impl FaultSignal {
pub fn new(info: &sgx_exception_info_t) -> Self {
// FIXME: the following mapping from exception to signal is not accurate.
use sgx_exception_vector_t::*;
let (num, code) = match info.exception_vector {
// Divider exception
SGX_EXCEPTION_VECTOR_DE => (SIGFPE, FPE_INTDIV),
// Floating-point exception
SGX_EXCEPTION_VECTOR_MF |
// SIMD floating-point exception
SGX_EXCEPTION_VECTOR_XM => (SIGFPE, FPE_FLTDIV),
// Invalid opcode exception
SGX_EXCEPTION_VECTOR_UD |
// Debug exception: should not occur in enclave; treat is as #UD
SGX_EXCEPTION_VECTOR_DB |
// Break point exception: should not occur in enclave; treat is as #UD
SGX_EXCEPTION_VECTOR_BP => (SIGILL, ILL_ILLOPC),
// Bound range exception
SGX_EXCEPTION_VECTOR_BR => (SIGSEGV, SEGV_BNDERR),
// Alignment check exception
SGX_EXCEPTION_VECTOR_AC => (SIGBUS, BUS_ADRALN),
// TODO: handle page fault and general protection exceptions
_ => panic!("illegal exception: cannot be converted to signal"),
};
Self { num, code }
}
}
impl Signal for FaultSignal {
fn num(&self) -> SigNum {
self.num
}
fn to_info(&self) -> siginfo_t {
let info = siginfo_t::new(self.num, self.code);
// TODO: set info.si_addr
info
}
}

@ -0,0 +1,26 @@
use super::super::c_types::*;
use super::super::constants::*;
use super::super::{SigNum, Signal};
use crate::prelude::*;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct KernelSignal {
num: SigNum,
}
impl KernelSignal {
pub fn new(num: SigNum) -> Self {
Self { num }
}
}
impl Signal for KernelSignal {
fn num(&self) -> SigNum {
self.num
}
fn to_info(&self) -> siginfo_t {
let info = siginfo_t::new(self.num, SI_KERNEL);
info
}
}

@ -0,0 +1,20 @@
/// Implementation of signals generated from various sources.
mod fault;
mod kernel;
mod user;
pub use self::fault::FaultSignal;
pub use self::kernel::KernelSignal;
pub use self::user::{UserSignal, UserSignalKind};
use super::c_types::siginfo_t;
use super::SigNum;
use crate::prelude::*;
pub trait Signal: Send + Sync + Debug {
/// Returns the number of the signal.
fn num(&self) -> SigNum;
/// Returns the siginfo_t that gives more details about a signal.
fn to_info(&self) -> siginfo_t;
}

@ -0,0 +1,68 @@
use super::super::c_types::*;
use super::super::constants::*;
use super::super::{SigNum, Signal};
use crate::prelude::*;
#[derive(Debug, Copy, Clone)]
pub struct UserSignal {
num: SigNum,
pid: pid_t, // sender's pid
uid: uid_t, // sender's uid
kind: UserSignalKind,
}
#[derive(Debug, Copy, Clone)]
pub enum UserSignalKind {
Kill,
Tkill,
Sigqueue(sigval_t),
}
unsafe impl Sync for UserSignalKind {}
unsafe impl Send for UserSignalKind {}
impl UserSignal {
pub fn new(num: SigNum, kind: UserSignalKind, pid: pid_t, uid: uid_t) -> Self {
Self {
num,
kind,
pid,
uid,
}
}
pub fn pid(&self) -> pid_t {
self.pid
}
pub fn uid(&self) -> uid_t {
self.uid
}
pub fn kind(&self) -> UserSignalKind {
self.kind
}
}
impl Signal for UserSignal {
fn num(&self) -> SigNum {
self.num
}
fn to_info(&self) -> siginfo_t {
let code = match self.kind {
UserSignalKind::Kill => SI_USER,
UserSignalKind::Tkill => SI_TKILL,
UserSignalKind::Sigqueue(_) => SI_QUEUE,
};
let mut info = siginfo_t::new(self.num, code);
info.set_si_pid(self.pid);
info.set_si_uid(self.uid);
if let UserSignalKind::Sigqueue(val) = self.kind {
info.set_si_value(val);
}
info
}
}

@ -0,0 +1,121 @@
use super::constants::*;
use super::do_sigprocmask::MaskOp;
use super::signals::FaultSignal;
use super::{sigaction_t, sigset_t, SigAction, SigNum, SigSet};
use crate::prelude::*;
use crate::process::ProcessFilter;
use crate::syscall::CpuContext;
pub fn do_rt_sigaction(
signum_c: c_int,
new_sa_c: *const sigaction_t,
old_sa_c: *mut sigaction_t,
) -> Result<isize> {
// C types -> Rust types
let signum = SigNum::from_u8(signum_c as u8)?;
let new_sa = {
if !new_sa_c.is_null() {
let new_sa_c = unsafe { &*new_sa_c };
let new_sa = SigAction::from_c(new_sa_c)?;
Some(new_sa)
} else {
None
}
};
let mut old_sa_c = {
if !old_sa_c.is_null() {
let old_sa_c = unsafe { &mut *old_sa_c };
Some(old_sa_c)
} else {
None
}
};
// Do sigaction
let old_sa = super::do_sigaction::do_rt_sigaction(signum, new_sa)?;
// Retrieve old sigaction_t, if needed
if let Some(old_sa_c) = old_sa_c {
*old_sa_c = old_sa.to_c();
}
Ok(0)
}
pub fn do_rt_sigreturn(user_context: *mut CpuContext) -> Result<isize> {
let user_context = unsafe { &mut *user_context };
super::do_sigreturn::do_rt_sigreturn(user_context)?;
Ok(0)
}
pub fn do_kill(pid: i32, sig: c_int) -> Result<isize> {
let process_filter = match pid {
pid if pid < -1 => ProcessFilter::WithPgid((-pid) as pid_t),
-1 => ProcessFilter::WithAnyPid,
0 => {
let pgid = current!().process().pgid();
ProcessFilter::WithPgid(pgid)
}
pid if pid > 0 => ProcessFilter::WithPid(pid as pid_t),
_ => unreachable!(),
};
let signum = SigNum::from_u8(sig as u8)?;
super::do_kill::do_kill(process_filter, signum)?;
Ok(0)
}
pub fn do_tkill(tid: pid_t, sig: c_int) -> Result<isize> {
let signum = SigNum::from_u8(sig as u8)?;
super::do_kill::do_tgkill(None, tid, signum)?;
Ok(0)
}
pub fn do_tgkill(pid: i32, tid: pid_t, sig: c_int) -> Result<isize> {
let pid = if pid >= 0 { Some(pid as pid_t) } else { None };
let signum = SigNum::from_u8(sig as u8)?;
super::do_kill::do_tgkill(pid, tid, signum)?;
Ok(0)
}
pub fn do_rt_sigprocmask(
how: c_int,
set_ptr: *const sigset_t,
oldset_ptr: *mut sigset_t,
sigset_size: usize,
) -> Result<isize> {
if sigset_size != std::mem::size_of::<sigset_t>() {
return_errno!(EINVAL, "unexpected sigset size");
}
let op_and_set = {
if !set_ptr.is_null() {
let op = MaskOp::from_u32(how as u32)?;
let set = unsafe { &*set_ptr };
Some((op, set))
} else {
None
}
};
let old_set = {
if !oldset_ptr.is_null() {
Some(unsafe { &mut *oldset_ptr })
} else {
None
}
};
super::do_sigprocmask::do_rt_sigprocmask(op_and_set, old_set)?;
Ok(0)
}
pub fn do_rt_sigpending(buf_ptr: *mut sigset_t, buf_size: usize) -> Result<isize> {
let buf: &mut sigset_t = {
if buf_size < std::mem::size_of::<sigset_t>() {
return_errno!(EINVAL, "buf is not big enough");
}
if buf_ptr.is_null() {
return_errno!(EINVAL, "ptr must not be null");
}
unsafe { &mut *buf_ptr }
};
let pending = super::do_sigpending::do_sigpending()?;
*buf = pending.to_c();
Ok(0)
}

@ -32,11 +32,15 @@ use crate::net::{
SocketFile, UnixSocketFile, SocketFile, UnixSocketFile,
}; };
use crate::process::{ use crate::process::{
do_arch_prctl, do_clone, do_exit, do_futex, do_getegid, do_geteuid, do_getgid, do_getpgid, do_arch_prctl, do_clone, do_exit, do_exit_group, do_futex, do_getegid, do_geteuid, do_getgid,
do_getpid, do_getppid, do_gettid, do_getuid, do_set_tid_address, do_spawn, do_wait4, pid_t, do_getpgid, do_getpid, do_getppid, do_gettid, do_getuid, do_set_tid_address, do_spawn,
FdOp, do_wait4, pid_t, FdOp, ThreadStatus,
}; };
use crate::sched::{do_sched_getaffinity, do_sched_setaffinity, do_sched_yield}; use crate::sched::{do_sched_getaffinity, do_sched_setaffinity, do_sched_yield};
use crate::signal::{
do_kill, do_rt_sigaction, do_rt_sigpending, do_rt_sigprocmask, do_rt_sigreturn, do_tgkill,
do_tkill, sigaction_t, sigset_t,
};
use crate::vm::{MMapFlags, VMPerms}; use crate::vm::{MMapFlags, VMPerms};
use crate::{fs, process, std, vm}; use crate::{fs, process, std, vm};
@ -84,9 +88,9 @@ macro_rules! process_syscall_table_with_callback {
(Mprotect = 10) => do_mprotect(addr: usize, len: usize, prot: u32), (Mprotect = 10) => do_mprotect(addr: usize, len: usize, prot: u32),
(Munmap = 11) => do_munmap(addr: usize, size: usize), (Munmap = 11) => do_munmap(addr: usize, size: usize),
(Brk = 12) => do_brk(new_brk_addr: usize), (Brk = 12) => do_brk(new_brk_addr: usize),
(RtSigaction = 13) => do_rt_sigaction(), (RtSigaction = 13) => do_rt_sigaction(signum_c: c_int, new_sa_c: *const sigaction_t, old_sa_c: *mut sigaction_t),
(RtSigprocmask = 14) => do_rt_sigprocmask(), (RtSigprocmask = 14) => do_rt_sigprocmask(how: c_int, set: *const sigset_t, oldset: *mut sigset_t, sigset_size: size_t),
(RtSigreturn = 15) => handle_unsupported(), (RtSigreturn = 15) => do_rt_sigreturn(context: *mut CpuContext),
(Ioctl = 16) => do_ioctl(fd: FileDesc, cmd: u32, argp: *mut u8), (Ioctl = 16) => do_ioctl(fd: FileDesc, cmd: u32, argp: *mut u8),
(Pread64 = 17) => do_pread(fd: FileDesc, buf: *mut u8, size: usize, offset: off_t), (Pread64 = 17) => do_pread(fd: FileDesc, buf: *mut u8, size: usize, offset: off_t),
(Pwrite64 = 18) => do_pwrite(fd: FileDesc, buf: *const u8, size: usize, offset: off_t), (Pwrite64 = 18) => do_pwrite(fd: FileDesc, buf: *const u8, size: usize, offset: off_t),
@ -133,7 +137,7 @@ macro_rules! process_syscall_table_with_callback {
(Execve = 59) => handle_unsupported(), (Execve = 59) => handle_unsupported(),
(Exit = 60) => do_exit(exit_status: i32), (Exit = 60) => do_exit(exit_status: i32),
(Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32), (Wait4 = 61) => do_wait4(pid: i32, _exit_status: *mut i32),
(Kill = 62) => handle_unsupported(), (Kill = 62) => do_kill(pid: i32, sig: c_int),
(Uname = 63) => do_uname(name: *mut utsname_t), (Uname = 63) => do_uname(name: *mut utsname_t),
(Semget = 64) => handle_unsupported(), (Semget = 64) => handle_unsupported(),
(Semop = 65) => handle_unsupported(), (Semop = 65) => handle_unsupported(),
@ -198,7 +202,7 @@ macro_rules! process_syscall_table_with_callback {
(Getsid = 124) => handle_unsupported(), (Getsid = 124) => handle_unsupported(),
(Capget = 125) => handle_unsupported(), (Capget = 125) => handle_unsupported(),
(Capset = 126) => handle_unsupported(), (Capset = 126) => handle_unsupported(),
(RtSigpending = 127) => handle_unsupported(), (RtSigpending = 127) => do_rt_sigpending(buf_ptr: *mut sigset_t, buf_size: usize),
(RtSigtimedwait = 128) => handle_unsupported(), (RtSigtimedwait = 128) => handle_unsupported(),
(RtSigqueueinfo = 129) => handle_unsupported(), (RtSigqueueinfo = 129) => handle_unsupported(),
(RtSigsuspend = 130) => handle_unsupported(), (RtSigsuspend = 130) => handle_unsupported(),
@ -271,7 +275,7 @@ macro_rules! process_syscall_table_with_callback {
(Removexattr = 197) => handle_unsupported(), (Removexattr = 197) => handle_unsupported(),
(Lremovexattr = 198) => handle_unsupported(), (Lremovexattr = 198) => handle_unsupported(),
(Fremovexattr = 199) => handle_unsupported(), (Fremovexattr = 199) => handle_unsupported(),
(Tkill = 200) => handle_unsupported(), (Tkill = 200) => do_tkill(tid: pid_t, sig: c_int),
(Time = 201) => handle_unsupported(), (Time = 201) => handle_unsupported(),
(Futex = 202) => do_futex(futex_addr: *const i32, futex_op: u32, futex_val: i32, timeout: u64, futex_new_addr: *const i32), (Futex = 202) => do_futex(futex_addr: *const i32, futex_op: u32, futex_val: i32, timeout: u64, futex_new_addr: *const i32),
(SchedSetaffinity = 203) => do_sched_setaffinity(pid: pid_t, cpusize: size_t, buf: *const c_uchar), (SchedSetaffinity = 203) => do_sched_setaffinity(pid: pid_t, cpusize: size_t, buf: *const c_uchar),
@ -302,10 +306,10 @@ macro_rules! process_syscall_table_with_callback {
(ClockGettime = 228) => do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t), (ClockGettime = 228) => do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t),
(ClockGetres = 229) => handle_unsupported(), (ClockGetres = 229) => handle_unsupported(),
(ClockNanosleep = 230) => handle_unsupported(), (ClockNanosleep = 230) => handle_unsupported(),
(ExitGroup = 231) => handle_unsupported(), (ExitGroup = 231) => do_exit_group(exit_status: i32),
(EpollWait = 232) => do_epoll_wait(epfd: c_int, events: *mut libc::epoll_event, maxevents: c_int, timeout: c_int), (EpollWait = 232) => do_epoll_wait(epfd: c_int, events: *mut libc::epoll_event, maxevents: c_int, timeout: c_int),
(EpollCtl = 233) => do_epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *const libc::epoll_event), (EpollCtl = 233) => do_epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *const libc::epoll_event),
(Tgkill = 234) => handle_unsupported(), (Tgkill = 234) => do_tgkill(pid: i32, tid: pid_t, sig: c_int),
(Utimes = 235) => handle_unsupported(), (Utimes = 235) => handle_unsupported(),
(Vserver = 236) => handle_unsupported(), (Vserver = 236) => handle_unsupported(),
(Mbind = 237) => handle_unsupported(), (Mbind = 237) => handle_unsupported(),
@ -401,7 +405,7 @@ macro_rules! process_syscall_table_with_callback {
// Occlum-specific system calls // Occlum-specific system calls
(Spawn = 360) => do_spawn(child_pid_ptr: *mut u32, path: *const i8, argv: *const *const i8, envp: *const *const i8, fdop_list: *const FdOp), (Spawn = 360) => do_spawn(child_pid_ptr: *mut u32, path: *const i8, argv: *const *const i8, envp: *const *const i8, fdop_list: *const FdOp),
// Exception handling // Exception handling
(Exception = 361) => do_handle_exception(info: *mut sgx_exception_info_t), (HandleException = 361) => do_handle_exception(info: *mut sgx_exception_info_t, context: *mut CpuContext),
} }
}; };
} }
@ -562,7 +566,7 @@ macro_rules! impl_dispatch_syscall {
// let fd = self.args[0] as FileDesc; // let fd = self.args[0] as FileDesc;
// let buf = self.args[1] as *mut u8; // let buf = self.args[1] as *mut u8;
// let size = self.args[2] as usize; // let size = self.args[2] as usize;
// do_read(fd, buuf, size) // do_read(fd, buf, size)
// } // }
SyscallNum::$name => { SyscallNum::$name => {
impl_dispatch_syscall!(@do_syscall $fn, syscall, 0, ($($args)*,) -> ()) impl_dispatch_syscall!(@do_syscall $fn, syscall, 0, ($($args)*,) -> ())
@ -574,24 +578,36 @@ macro_rules! impl_dispatch_syscall {
} }
process_syscall_table_with_callback!(impl_dispatch_syscall); process_syscall_table_with_callback!(impl_dispatch_syscall);
/// The system call entry point in Rust.
///
/// This function is called by __occlum_syscall.
#[no_mangle] #[no_mangle]
pub extern "C" fn occlum_syscall( pub extern "C" fn occlum_syscall(user_context: *mut CpuContext) -> ! {
num: u32,
arg0: isize,
arg1: isize,
arg2: isize,
arg3: isize,
arg4: isize,
arg5: isize,
) -> isize {
// Start a new round of log messages for this system call. But we do not // Start a new round of log messages for this system call. But we do not
// set the description of this round, yet. We will do so after checking the // set the description of this round, yet. We will do so after checking the
// given system call number is a valid. // given system call number is a valid.
log::next_round(None); log::next_round(None);
let user_context = unsafe {
// TODO: validate pointer
&mut *user_context
};
// Do system call
do_syscall(user_context);
// Back to the user space
do_sysret(user_context)
}
fn do_syscall(user_context: &mut CpuContext) {
// Extract arguments from the CPU context. The arguments follows Linux's syscall ABI.
let num = user_context.rax as u32;
let arg0 = user_context.rdi as isize;
let arg1 = user_context.rsi as isize;
let arg2 = user_context.rdx as isize;
let arg3 = user_context.r10 as isize;
let arg4 = user_context.r8 as isize;
let arg5 = user_context.r9 as isize;
// TODO: the profiler will trigger panic for syscall simulation
#[cfg(feature = "syscall_timing")] #[cfg(feature = "syscall_timing")]
GLOBAL_PROFILER GLOBAL_PROFILER
.lock() .lock()
@ -599,10 +615,19 @@ pub extern "C" fn occlum_syscall(
.syscall_enter(syscall_num) .syscall_enter(syscall_num)
.expect("unexpected error from profiler to enter syscall"); .expect("unexpected error from profiler to enter syscall");
let ret = Syscall::new(num, arg0, arg1, arg2, arg3, arg4, arg5).and_then(|syscall| { let ret = Syscall::new(num, arg0, arg1, arg2, arg3, arg4, arg5).and_then(|mut syscall| {
log::set_round_desc(Some(syscall.num.as_str())); log::set_round_desc(Some(syscall.num.as_str()));
trace!("{:?}", &syscall); trace!("{:?}", &syscall);
// Pass user_context as an extra argument to two special syscalls that
// need to modify it
if syscall.num == SyscallNum::RtSigreturn {
syscall.args[0] = user_context as *mut _ as isize;
} else if syscall.num == SyscallNum::HandleException {
// syscall.args[0] == info
syscall.args[1] = user_context as *mut _ as isize;
}
dispatch_syscall(syscall) dispatch_syscall(syscall)
}); });
@ -644,7 +669,35 @@ pub extern "C" fn occlum_syscall(
} }
}; };
trace!("Retval = {:?}", retval); trace!("Retval = {:?}", retval);
retval
// Put the return value into user_context.rax, except for syscalls that may
// modify user_context directly. Currently, there are two such syscalls:
// SigReturn and HandleException.
//
// Sigreturn restores `user_context` to the state when the last signal
// handler is executed. So in the case of sigreturn, `user_context` should
// be kept intact.
if num != SyscallNum::RtSigreturn as u32 && num != SyscallNum::HandleException as u32 {
user_context.rax = retval as u64;
}
crate::signal::deliver_signal(user_context);
crate::process::handle_force_exit();
}
/// Return to the user space according to the given CPU context
fn do_sysret(user_context: &mut CpuContext) -> ! {
extern "C" {
fn __occlum_sysret(user_context: *mut CpuContext) -> !;
fn do_exit_task() -> !;
}
if current!().status() != ThreadStatus::Exited {
unsafe { __occlum_sysret(user_context) } // jump to user space
} else {
unsafe { do_exit_task() } // exit enclave
}
unreachable!("__occlum_sysret never returns!");
} }
/* /*
@ -1100,16 +1153,58 @@ fn do_prlimit(
misc::do_prlimit(pid, resource, new_limit, old_limit).map(|_| 0) misc::do_prlimit(pid, resource, new_limit, old_limit).map(|_| 0)
} }
// TODO: implement signals
fn do_rt_sigaction() -> Result<isize> {
Ok(0)
}
fn do_rt_sigprocmask() -> Result<isize> {
Ok(0)
}
fn handle_unsupported() -> Result<isize> { fn handle_unsupported() -> Result<isize> {
return_errno!(ENOSYS, "Unimplemented or unknown syscall") return_errno!(ENOSYS, "Unimplemented or unknown syscall")
} }
/// Cpu context.
///
/// Note. The definition of this struct must be kept in sync with the assembly
/// code in `syscall_entry_x86-64.S`.
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct CpuContext {
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub rdi: u64,
pub rsi: u64,
pub rbp: u64,
pub rbx: u64,
pub rdx: u64,
pub rax: u64,
pub rcx: u64,
pub rsp: u64,
pub rip: u64,
pub rflags: u64,
}
impl CpuContext {
pub fn from_sgx(src: &sgx_cpu_context_t) -> CpuContext {
Self {
r8: src.r8,
r9: src.r9,
r10: src.r10,
r11: src.r11,
r12: src.r12,
r13: src.r13,
r14: src.r14,
r15: src.r15,
rdi: src.rdi,
rsi: src.rsi,
rbp: src.rbp,
rbx: src.rbx,
rdx: src.rdx,
rax: src.rax,
rcx: src.rcx,
rsp: src.rsp,
rip: src.rip,
rflags: src.rflags,
}
}
}

@ -1,46 +0,0 @@
#define __ASSEMBLY__
#include "task.h"
.file "syscall_entry_native_x86-64.S"
.global __occlum_syscall_native
.type __occlum_syscall_native, @function
__occlum_syscall_native:
push %rbp
movq %rsp,%rbp
// Save registers
pushq %rdi
pushq %rsi
pushq %rdx
pushq %r10
pushq %r8
// arg5
pushq %r9
// arg4--arg0
movq %r8, %r9
movq %r10, %r8
movq %rdx, %rcx
movq %rsi, %rdx
movq %rdi, %rsi
// num
movq %rax, %rdi
// num - %rdi
// arg0 - %rsi
// arg1 - %rdx
// arg2 - %rcx
// arg3 - %r8
// arg4 - %r9
// arg5 - *0x8(%rsp)
call __occlum_syscall
// Restore registers
popq %r9
popq %r8
popq %r10
popq %rdx
popq %rsi
popq %rdi
popq %rbp
ret

@ -1,36 +1,57 @@
#define __ASSEMBLY__ #define __ASSEMBLY__
#include "task.h" #include "task.h"
.file "syscall_entry_x86-64.S" .file "syscall_entry_x86-64.S"
.global __occlum_syscall .global __occlum_syscall_linux_abi
.type __occlum_syscall, @function .type __occlum_syscall_linux_abi, @function
__occlum_syscall: __occlum_syscall_linux_abi:
// num - %rdi // num - %rax
// arg0 - %rsi // arg0 - %rdi
// arg1 - %rdx // arg1 - %rsi
// arg2 - %rcx // arg2 - %rdx
// arg3 - %r8 // arg3 - %r10
// arg4 - %r9 // arg4 - %r8
// arg5 - *0x8(%rsp) // arg5 - *r9
// return address - *(%rsp)
// Given by the user, the user-space stack pointer %rsp cannot be trusted. push %rbp
// So we need to check whether %rsp is within the read-write region of the
// current data domain
bndcl %rsp, %bnd0
bndcu %rsp, %bnd0
// Save the callee-saved registers
pushq %rbp
pushq %r12
// Save the user stack
movq %rsp, %rbp movq %rsp, %rbp
// The return address is now in 8(%rbp).
// The original %rbp is now in (%rbp).
// The original %rsp is now in %rbp + 8.
// Save the target CPU state when `call __occlum_syscall` is returned in
// a CpuContext struct. The registers are saved in the reverse order of
// the fields in CpuContext.
pushfq
push 8(%rbp) // save %rip
push %rbp // save %rsp, but not the final value, to be adjusted later
push %rcx
push %rax
push %rdx
push %rbx
push (%rbp) // save %rbp
push %rsi
push %rdi
push %r15
push %r14
push %r13
push %r12
push %r11
push %r10
push %r9
push %r8
// Make %rdi points to CpuContext.
mov %rsp, %rdi
// The target %rsp is actuall the saved one plus 16
addq $16, (15*8)(%rdi)
// Get current task // Get current task
movq %gs:(TD_TASK_OFFSET), %r12 movq %gs:(TD_TASK_OFFSET), %r12
// Switch to the kernel stack // Switch to the kernel stack
movq TASK_KERNEL_RSP(%r12), %rsp movq TASK_KERNEL_RSP(%r12), %rsp
// Switch to the kernel TLS by setting fsbase. Different implementation for HW and SIM modes.
// Use kernel fsbase. Different implementation for HW and SIM.
#if SGX_MODE_SIM #if SGX_MODE_SIM
pushq %rdi pushq %rdi
pushq %rsi pushq %rsi
@ -45,55 +66,98 @@ __occlum_syscall:
movq TASK_KERNEL_FS(%r12), %r11 movq TASK_KERNEL_FS(%r12), %r11
wrfsbase %r11 wrfsbase %r11
#endif #endif
// Switch to kernel stack base and limit
// Use kernel stack base and limit
movq TASK_KERNEL_STACK_BASE(%r12), %r11 movq TASK_KERNEL_STACK_BASE(%r12), %r11
movq %r11, %gs:TD_STACK_BASE movq %r11, %gs:TD_STACK_BASE
movq TASK_KERNEL_STACK_LIMIT(%r12), %r11 movq TASK_KERNEL_STACK_LIMIT(%r12), %r11
movq %r11, %gs:TD_STACK_LIMIT movq %r11, %gs:TD_STACK_LIMIT
// Make %rsp 16-byte aligned before call
sub $0x8, %rsp
// Pass arg5
pushq 0x18(%rbp)
call occlum_syscall call occlum_syscall
// Use user fsbase. Different implementation for HW and SIM. // This should never happen!
ud2
.global __occlum_sysret
.type __occlum_sysret, @function
__occlum_sysret:
// Arguments:
// %rdi - user_context: &mut CpuContext
// Jumping back to the user space itself is easy, but not so easy when
// we need to set all other registers to some specified values. To overcome
// this difficulty, the most obvious choice is using a ret instruction, which
// can set %rip and %rsp at the same time. So we must set -8(%rsp) to the
// value of the target %rip before ret, where %rsp has the value of target
// %rsp.
//
// But there is a catch: it is dangerous to modify the value at -8(%rsp),
// which may still be used by the user space (remember red zone and
// signal handler?). So we need to use a stack location outside the
// 128-byte red zone. So in this function, we store the target %rip value
// in $-136(%rsp) and do `ret 128` at the end of this function.
subq $136, (15*8)(%rdi)
movq (15*8)(%rdi), %r11
movq (16*8)(%rdi), %r12
movq %r12, (%r11)
// Get current task
movq %gs:(TD_TASK_OFFSET), %r12
// Switch to the user TLS. Different implementation for HW and SIM modes.
#if SGX_MODE_SIM #if SGX_MODE_SIM
pushq %rdi pushq %rdi
pushq %rsi
pushq %rax // RAX must be saved here otherwise the progrom may crash.
movq $ARCH_SET_FS, %rdi movq $ARCH_SET_FS, %rdi
movq TASK_USER_FS(%r12), %rsi movq TASK_USER_FS(%r12), %rsi
call __arch_prctl call __arch_prctl
popq %rax
popq %rsi
popq %rdi popq %rdi
#else // SGX_MODE_HW #else // SGX_MODE_HW
movq TASK_USER_FS(%r12), %r11 movq TASK_USER_FS(%r12), %r11
wrfsbase %r11 wrfsbase %r11
#endif #endif
// Switch to user stack base and limit
// Use user stack base and limit
movq TASK_USER_STACK_BASE(%r12), %r11 movq TASK_USER_STACK_BASE(%r12), %r11
movq %r11, %gs:TD_STACK_BASE movq %r11, %gs:TD_STACK_BASE
movq TASK_USER_STACK_LIMIT(%r12), %r11 movq TASK_USER_STACK_LIMIT(%r12), %r11
movq %r11, %gs:TD_STACK_LIMIT movq %r11, %gs:TD_STACK_LIMIT
// Switch to the user stack // Restore flags first
movq %rbp, %rsp leaq (17*8)(%rdi), %rsp
// Restore callee-saved registers popfq
popq %r12
popq %rbp
// Check return target is a valid instruction (i.e., a cfi_label) // Make %rsp points to the CPU context
popq %r10 mov %rdi, %rsp
movq (%r10), %r11 // Restore the CPU context of the user space
bndcl %r11, %bnd2 pop %r8
bndcu %r11, %bnd2 pop %r9
jmpq *%r10 pop %r10
pop %r11
pop %r12
pop %r13
pop %r14
pop %r15
pop %rdi
pop %rsi
pop %rbp
pop %rbx
pop %rdx
pop %rax
pop %rcx
pop %rsp
// Continue executing the user code
ret $128
.global __occlum_syscall_c_abi
.type __occlum_syscall_c_abi, @function
__occlum_syscall_c_abi:
movq %rdi,%rax
movq %rsi,%rdi
movq %rdx,%rsi
movq %rcx,%rdx
movq %r8,%r10
movq %r9,%r8
movq 8(%rsp),%r9
call __occlum_syscall_linux_abi
ret

@ -17,6 +17,9 @@ pub type time_t = i64;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub type suseconds_t = i64; pub type suseconds_t = i64;
#[allow(non_camel_case_types)]
pub type clock_t = i64;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]

@ -60,9 +60,11 @@ int occlum_pal_init(const struct occlum_pal_attr* attr);
* @param io_fds The file descriptors of the redirected standard I/O * @param io_fds The file descriptors of the redirected standard I/O
* (i.e., stdin, stdout, stderr), If set to NULL, will * (i.e., stdin, stdout, stderr), If set to NULL, will
* use the original standard I/O file descriptors. * use the original standard I/O file descriptors.
* @param exit_status Output. The exit status of the command. Note that the * @param exit_status Output. The exit status of the command. The semantic of
* exit status is returned if and only if the function * this value follows the one described in wait(2) man
* succeeds. * page. For example, if the program terminated normally,
* then WEXITSTATUS(exit_status) gives the value returned
* from a main function.
* *
* @retval If 0, then success; otherwise, check errno for the exact error type. * @retval If 0, then success; otherwise, check errno for the exact error type.
*/ */

@ -2,6 +2,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <occlum_pal_api.h> #include <occlum_pal_api.h>
static const char* get_instance_dir(void) { static const char* get_instance_dir(void) {
@ -40,7 +42,15 @@ int main(int argc, char* argv[]) {
}; };
int exit_status = 0; int exit_status = 0;
if (occlum_pal_exec(cmd_path, cmd_args, &io_fds, &exit_status) < 0) { if (occlum_pal_exec(cmd_path, cmd_args, &io_fds, &exit_status) < 0) {
return EXIT_FAILURE; // Command not found or other internal errors
return 127;
}
// Convert the exit status to a value in a shell-like encoding
if (WIFEXITED(exit_status)) { // terminated normally
exit_status = WEXITSTATUS(exit_status) & 0x7F; // [0, 127]
} else { // killed by signal
exit_status = 128 + WTERMSIG(exit_status); // [128 + 1, 128 + 64]
} }
// Destroy Occlum PAL // Destroy Occlum PAL

@ -14,7 +14,7 @@ TEST_DEPS := client data_sink
TESTS ?= empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ TESTS ?= empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \
truncate readdir mkdir open stat link symlink chmod chown tls pthread uname rlimit \ truncate readdir mkdir open stat link symlink chmod chown tls pthread uname rlimit \
server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \ server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \
ioctl fcntl eventfd emulate_syscall access ioctl fcntl eventfd emulate_syscall access signal
# Benchmarks: need to be compiled and run by bench-% target # Benchmarks: need to be compiled and run by bench-% target
BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput

@ -15,17 +15,15 @@
// Three types of threads that will not exit voluntarily // Three types of threads that will not exit voluntarily
// //
// FIXME: Disable this test for NOW because exit_group does not have a real implementation yet
// and SGX simlulation mode will fail this test.
// Type 1: a busy loop thread // Type 1: a busy loop thread
// static void* busyloop_thread_func(void* _) { static void* busyloop_thread_func(void* _) {
// while (1) { while (1) {
// // By calling getpid, we give the LibOS a chance to force the thread // By calling getpid, we give the LibOS a chance to force the thread
// // to terminate if exit_group is called by any thread in a thread group // to terminate if exit_group is called by any thread in a thread group
// getpid(); getpid();
// } }
// return NULL; return NULL;
// } }
// Type 2: a sleeping thread // Type 2: a sleeping thread
static void* sleeping_thread_func(void* _) { static void* sleeping_thread_func(void* _) {
@ -46,11 +44,11 @@ static void* futex_wait_thread_func(void* _) {
// exit_group syscall should terminate all threads in a thread group. // exit_group syscall should terminate all threads in a thread group.
int test_exit_group_to_force_threads_terminate(void) { int test_exit_group_to_force_threads_terminate(void) {
// Create three types of threads that will not exit voluntarily // Create three types of threads that will not exit voluntarily
// pthread_t busyloop_thread; pthread_t busyloop_thread;
// if (pthread_create(&busyloop_thread, NULL, busyloop_thread_func, NULL) < 0) { if (pthread_create(&busyloop_thread, NULL, busyloop_thread_func, NULL) < 0) {
// printf("ERROR: pthread_create failed\n"); printf("ERROR: pthread_create failed\n");
// return -1; return -1;
// } }
pthread_t sleeping_thread; pthread_t sleeping_thread;
if (pthread_create(&sleeping_thread, NULL, sleeping_thread_func, NULL) < 0) { if (pthread_create(&sleeping_thread, NULL, sleeping_thread_func, NULL) < 0) {
printf("ERROR: pthread_create failed\n"); printf("ERROR: pthread_create failed\n");

@ -207,6 +207,7 @@ int test_read_write() {
THROW_ERROR("failed to wait4 the child process"); THROW_ERROR("failed to wait4 the child process");
} }
printf("test_read_write finished!\n");
return ret; return ret;
} }
@ -314,10 +315,12 @@ int test_poll_sockets() {
static test_case_t test_cases[] = { static test_case_t test_cases[] = {
TEST_CASE(test_read_write), TEST_CASE(test_read_write),
TEST_CASE(test_send_recv), TEST_CASE(test_send_recv),
/*
TEST_CASE(test_sendmsg_recvmsg), TEST_CASE(test_sendmsg_recvmsg),
TEST_CASE(test_sendmsg_recvmsg_connectionless), TEST_CASE(test_sendmsg_recvmsg_connectionless),
TEST_CASE(test_fcntl_setfl_and_getfl), TEST_CASE(test_fcntl_setfl_and_getfl),
TEST_CASE(test_poll_sockets), TEST_CASE(test_poll_sockets),
*/
}; };
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {

5
test/signal/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS := -Wno-return-stack-address -Wno-unused-but-set-variable
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

292
test/signal/main.c Normal file

@ -0,0 +1,292 @@
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <spawn.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include "test.h"
// ============================================================================
// Helper macros
// ============================================================================
// ============================================================================
// Helper functions
// ============================================================================
// ============================================================================
// Test sigprocmask
// ============================================================================
// Add a new macro to compare two sigset. Returns 0 iff the two sigset are equal.
// Musl libc defines sigset_t to 16 bytes, but on x86 only the first 8 bytes are
// meaningful. So this comparison only takes the first 8 bytes into account.
#define sigcmpset(a, b) memcmp((a), (b), 8)
int test_sigprocmask() {
int ret;
sigset_t new, old;
sigset_t expected_old;
// Check sigmask == []
if ((ret = sigprocmask(0, NULL, &old)) < 0) {
THROW_ERROR("sigprocmask failed unexpectedly");
}
sigemptyset(&expected_old);
if (sigcmpset(&old, &expected_old) != 0) {
THROW_ERROR("unexpected old sigset");
}
// SIG_BLOCK: [] --> [SIGSEGV]
sigemptyset(&new);
sigaddset(&new, SIGSEGV);
if ((ret = sigprocmask(SIG_BLOCK, &new, &old)) < 0) {
THROW_ERROR("sigprocmask failed unexpectedly");
}
sigemptyset(&expected_old);
if (sigcmpset(&old, &expected_old) != 0) {
THROW_ERROR("unexpected old sigset");
}
// SIG_SETMASK: [SIGSEGV] --> [SIGIO]
sigemptyset(&new);
sigaddset(&new, SIGIO);
if ((ret = sigprocmask(SIG_SETMASK, &new, &old)) < 0) {
THROW_ERROR("sigprocmask failed unexpectedly");
}
sigemptyset(&expected_old);
sigaddset(&expected_old, SIGSEGV);
if (sigcmpset(&old, &expected_old) != 0) {
THROW_ERROR("unexpected old sigset");
}
// SIG_UNBLOCK: [SIGIO] -> []
if ((ret = sigprocmask(SIG_UNBLOCK, &new, &old)) < 0) {
THROW_ERROR("sigprocmask failed unexpectedly");
}
sigemptyset(&expected_old);
sigaddset(&expected_old, SIGIO);
if (sigcmpset(&old, &expected_old) != 0) {
THROW_ERROR("unexpected old sigset");
}
// Check sigmask == []
if ((ret = sigprocmask(0, NULL, &old)) < 0) {
THROW_ERROR("sigprocmask failed unexpectedly");
}
sigemptyset(&expected_old);
if (sigcmpset(&old, &expected_old) != 0) {
THROW_ERROR("unexpected old sigset");
}
return 0;
}
// ============================================================================
// Test raise syscall and user-registered signal handlers
// ============================================================================
#define MAX_RECURSION_LEVEL 3
static void handle_sigio(int num, siginfo_t* info, void* context) {
static volatile int recursion_level = 0;
printf("Hello from SIGIO signal handler (recursion_level = %d)!\n", recursion_level);
recursion_level++;
if (recursion_level <= MAX_RECURSION_LEVEL)
raise(SIGIO);
recursion_level--;
}
int test_raise() {
struct sigaction new_action, old_action;
new_action.sa_sigaction = handle_sigio;
new_action.sa_flags = SA_SIGINFO | SA_NODEFER;
if (sigaction(SIGIO, &new_action, &old_action) < 0) {
THROW_ERROR("registering new signal handler failed");
}
if (old_action.sa_handler != SIG_DFL) {
THROW_ERROR("unexpected old sig handler");
}
raise(SIGIO);
if (sigaction(SIGIO, &old_action, NULL) < 0) {
THROW_ERROR("restoring old signal handler failed");
}
return 0;
}
// ============================================================================
// Test abort, which uses SIGABRT behind the scene
// ============================================================================
int test_abort() {
pid_t child_pid;
char* child_argv[] = {"signal", "aborted_child", NULL};
int ret;
int status;
// Repeat multiple times to check that the resources of the killed child
// processes are indeed freed by the LibOS
for (int i = 0; i < 3; i++) {
ret = posix_spawn(&child_pid, "/bin/signal", NULL, NULL, child_argv, NULL);
if (ret < 0) {
THROW_ERROR("failed to spawn a child process\n");
}
ret = wait4(-1, &status, 0, NULL);
if (ret < 0) {
THROW_ERROR("failed to wait4 the child process\n");
}
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGABRT) {
THROW_ERROR("child process is expected to be killed by SIGILL\n");
}
}
return 0;
}
static int aborted_child() {
while (1) {
abort();
}
return 0;
}
// ============================================================================
// Test kill by sending SIGKILL to another process
// ============================================================================
int test_kill() {
pid_t child_pid;
char* child_argv[] = {"signal", "killed_child", NULL};
int ret;
int status;
// Repeat multiple times to check that the resources of the killed child
// processes are indeed freed by the LibOS
for (int i = 0; i < 3; i++) {
ret = posix_spawn(&child_pid, "/bin/signal", NULL, NULL, child_argv, NULL);
if (ret < 0) {
THROW_ERROR("failed to spawn a child process\n");
}
kill(child_pid, SIGKILL);
ret = wait4(-1, &status, 0, NULL);
if (ret < 0) {
THROW_ERROR("failed to wait4 the child process\n");
}
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
THROW_ERROR("child process is expected to be killed by SIGILL\n");
}
}
return 0;
}
// TODO: remove the use of getpid when we can deliver signals through interrupt
static int killed_child() {
while (1) {
getpid();
}
return 0;
}
// ============================================================================
// Test catching and handling hardware exception
// ============================================================================
static void handle_sigfpe(int num, siginfo_t* info, void* _context) {
printf("SIGFPE Caught\n");
assert(num == SIGFPE);
assert(info->si_signo == SIGFPE);
ucontext_t* ucontext = _context;
mcontext_t* mcontext = &ucontext->uc_mcontext;
// The faulty instruction should be `idiv %esi` (f7 fe)
mcontext->gregs[REG_RIP] += 2;
return;
}
// Note: this function is fragile in the sense that compiler may not always
// emit the instruction pattern that triggers divide-by-zero as we expect.
// TODO: rewrite this in assembly
int div_maybe_zero(int x, int y) {
return x / y;
}
int test_catch_fault() {
#ifdef SGX_MODE_SIM
printf("WARNING: Skip this test case as we do not support "
"capturing hardware exception in SGX simulation mode\n");
return 0;
#else
// Set up a signal handler that handles divide-by-zero exception
struct sigaction new_action, old_action;
new_action.sa_sigaction = handle_sigfpe;
new_action.sa_flags = SA_SIGINFO;
if (sigaction(SIGFPE, &new_action, &old_action) < 0) {
THROW_ERROR("registering new signal handler failed");
}
if (old_action.sa_handler != SIG_DFL) {
THROW_ERROR("unexpected old sig handler");
}
// Trigger divide-by-zero exception
int a = 1;
int b = 0;
// Use volatile to prevent compiler optimization
volatile int c;
c = div_maybe_zero(a, b);
printf("Signal handler successfully jumped over the divide-by-zero instruction\n");
if (sigaction(SIGFPE, &old_action, NULL) < 0) {
THROW_ERROR("restoring old signal handler failed");
}
return 0;
#endif /* SGX_MODE_SIM */
}
// ============================================================================
// Test suite main
// ============================================================================
static test_case_t test_cases[] = {
TEST_CASE(test_sigprocmask),
TEST_CASE(test_raise),
TEST_CASE(test_abort),
TEST_CASE(test_kill),
TEST_CASE(test_catch_fault),
};
int main(int argc, const char* argv[]) {
if (argc > 1) {
const char* cmd = argv[1];
if (strcmp(cmd, "aborted_child") == 0) {
return aborted_child();
}
else if (strcmp(cmd, "killed_child") == 0) {
return killed_child();
}
else {
fprintf(stderr, "ERROR: unknown command: %s\n", cmd);
return EXIT_FAILURE;
}
}
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}

@ -25,6 +25,13 @@ CC := occlum-gcc
CXX := occlum-g++ CXX := occlum-g++
C_FLAGS = -Wall -Wno-return-local-addr -I../include -O2 -fPIC $(EXTRA_C_FLAGS) C_FLAGS = -Wall -Wno-return-local-addr -I../include -O2 -fPIC $(EXTRA_C_FLAGS)
ifeq ($(SGX_MODE), SIM)
C_FLAGS += -D SGX_MODE_SIM
else ifeq ($(SGX_MODE), SW)
C_FLAGS += -D SGX_MODE_SIM
else
C_FLAGS += -D SGX_MODE_HW
endif
LINK_FLAGS = $(C_FLAGS) -pie $(EXTRA_LINK_FLAGS) LINK_FLAGS = $(C_FLAGS) -pie $(EXTRA_LINK_FLAGS)
.PHONY: all test test-native clean .PHONY: all test test-native clean