improve signal implementation to pass occlum signal test

This commit is contained in:
Jianfeng Jiang
2022-11-08 17:39:49 +08:00
parent f944a5ead1
commit c98b9d360f
45 changed files with 636 additions and 220 deletions

4
.gitattributes vendored
View File

@ -4,6 +4,4 @@ src/kxos-user/hello_c/hello filter=lfs diff=lfs merge=lfs -text
src/kxos-user/execve/execve filter=lfs diff=lfs merge=lfs -text
src/kxos-user/execve/hello filter=lfs diff=lfs merge=lfs -text
src/kxos-user/fork_c/fork filter=lfs diff=lfs merge=lfs -text
src/kxos-user/signal_c/divide_zero filter=lfs diff=lfs merge=lfs -text
src/kxos-user/signal_c/sig_procmask filter=lfs diff=lfs merge=lfs -text
src/kxos-user/signal_c/sig_action filter=lfs diff=lfs merge=lfs -text
src/kxos-user/signal_c/signal_test filter=lfs diff=lfs merge=lfs -text

View File

@ -15,4 +15,4 @@ pub const PAGE_SIZE_BITS: usize = 0xc;
pub const KVA_START: usize = (usize::MAX) << PAGE_SIZE_BITS;
pub const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
pub const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Close;

View File

@ -1,6 +1,10 @@
//! CPU.
use crate::trap::{CalleeRegs, CallerRegs, SyscallFrame, TrapFrame};
use crate::vm::Pod;
use crate::{
impl_pod_for,
trap::{CalleeRegs, CallerRegs, SyscallFrame, TrapFrame},
};
/// Defines a CPU-local variable.
#[macro_export]
@ -65,6 +69,8 @@ pub struct GpRegs {
pub rflag: u64,
}
impl_pod_for!(GpRegs, TrapInformation, CpuContext, FpRegs);
impl From<SyscallFrame> for CpuContext {
fn from(syscall: SyscallFrame) -> Self {
Self {

View File

@ -138,4 +138,5 @@ pub enum LogLevel {
Info,
Warn,
Error,
Close,
}

View File

@ -10,7 +10,7 @@ bitflags = "1.3"
spin = "0.9.4"
kxos-frame = {path = "../kxos-frame"}
kxos-util = {path="../kxos-util"}
kxos-frame-pod-derive= {path="../kxos-frame-pod-derive"}
kxos-frame-pod-derive = {path = "../kxos-frame-pod-derive"}
[dependencies.lazy_static]
version = "1.0"

View File

@ -7,6 +7,8 @@ pub mod msix;
pub mod util;
extern crate alloc;
use kxos_frame::info;
#[macro_use]
extern crate kxos_frame_pod_derive;
use alloc::{sync::Arc, vec::Vec};
use lazy_static::lazy_static;

View File

@ -5,7 +5,7 @@ use kxos_frame_pod_derive::Pod;
use super::capability::msix::CapabilityMSIXData;
use kxos_frame::{offset_of, vm::Pod, IrqAllocateHandle};
use kxos_frame::{offset_of, IrqAllocateHandle};
use kxos_util::frame_ptr::InFramePtr;
#[derive(Debug, Default)]

View File

@ -6,37 +6,44 @@ pub mod vm_page;
use crate::process::Process;
/// copy bytes from user space of current process. The bytes len is the len of dest.
pub fn read_bytes_from_user(src: Vaddr, dest: &mut [u8]) {
pub fn read_bytes_from_user(src: Vaddr, dest: &mut [u8]) -> Result<()> {
let current = Process::current();
let vm_space = current
.vm_space()
.expect("[Internal error]Current should have vm space to copy bytes from user");
vm_space.read_bytes(src, dest).expect("read bytes failed");
let vm_space = current.vm_space().ok_or(Error::with_message(
Errno::ESRCH,
"[Internal error]Current should have vm space to copy bytes from user",
))?;
vm_space.read_bytes(src, dest)?;
Ok(())
}
/// copy val (Plain of Data type) from user space of current process.
pub fn read_val_from_user<T: Pod>(src: Vaddr) -> T {
pub fn read_val_from_user<T: Pod>(src: Vaddr) -> Result<T> {
let current = Process::current();
let vm_space = current
.vm_space()
.expect("[Internal error]Current should have vm space to copy val from user");
vm_space.read_val(src).expect("read val failed")
let vm_space = current.vm_space().ok_or(Error::with_message(
Errno::ESRCH,
"[Internal error]Current should have vm space to copy val from user",
))?;
Ok(vm_space.read_val(src)?)
}
/// write bytes from user space of current process. The bytes len is the len of src.
pub fn write_bytes_to_user(dest: Vaddr, src: &[u8]) {
pub fn write_bytes_to_user(dest: Vaddr, src: &[u8]) -> Result<()> {
let current = Process::current();
let vm_space = current
.vm_space()
.expect("[Internal error]Current should have vm space to write bytes to user");
vm_space.write_bytes(dest, src).expect("write bytes failed")
let vm_space = current.vm_space().ok_or(Error::with_message(
Errno::ESRCH,
"[Internal error]Current should have vm space to write bytes to user",
))?;
vm_space.write_bytes(dest, src)?;
Ok(())
}
/// write val (Plain of Data type) to user space of current process.
pub fn write_val_to_user<T: Pod>(dest: Vaddr, val: &T) {
pub fn write_val_to_user<T: Pod>(dest: Vaddr, val: &T) -> Result<()> {
let current = Process::current();
let vm_space = current
.vm_space()
.expect("[Internal error]Current should have vm space to write val to user");
vm_space.write_val(dest, val).expect("write val failed");
let vm_space = current.vm_space().ok_or(Error::with_message(
Errno::ESRCH,
"[Internal error]Current should have vm space to write val to user",
))?;
vm_space.write_val(dest, val)?;
Ok(())
}

View File

@ -13,7 +13,7 @@ pub(crate) use bitflags::bitflags;
pub(crate) use core::ffi::CStr;
pub(crate) use kxos_frame::config::PAGE_SIZE;
pub(crate) use kxos_frame::vm::Vaddr;
pub(crate) use kxos_frame::{debug, error, info, trace, warn};
pub(crate) use kxos_frame::{debug, error, info, print, println, trace, warn};
pub(crate) use spin::Mutex;
#[macro_export]

View File

@ -2,7 +2,7 @@ use kxos_frame::cpu::CpuContext;
use crate::{prelude::*, process::signal::signals::fault::FaultSignal};
/// We can't handle most exceptions, just send self a signal to force the process exit before return to user space.
/// We can't handle most exceptions, just send self a fault signal before return to user space.
pub fn handle_exception(context: &mut CpuContext) {
let trap_info = context.trap_information.clone();
let current = current!();

View File

@ -14,7 +14,6 @@ use self::signal::sig_disposition::SigDispositions;
use self::signal::sig_mask::SigMask;
use self::signal::sig_queues::SigQueues;
use self::signal::signals::kernel::KernelSignal;
use self::signal::SigContext;
use self::status::ProcessStatus;
use self::task::create_user_task_from_elf;
@ -65,8 +64,8 @@ pub struct Process {
sig_queues: Mutex<SigQueues>,
/// Process-level sigmask
sig_mask: Mutex<SigMask>,
/// Signal handler Context
sig_context: Mutex<Option<SigContext>>,
/// Signal handler ucontext address
sig_context: Mutex<VecDeque<Vaddr>>,
}
impl Process {
@ -119,7 +118,7 @@ impl Process {
sig_dispositions: Mutex::new(sig_dispositions),
sig_queues: Mutex::new(sig_queues),
sig_mask: Mutex::new(sig_mask),
sig_context: Mutex::new(None),
sig_context: Mutex::new(VecDeque::new()),
}
}
@ -226,7 +225,7 @@ impl Process {
&self.process_group
}
pub fn sig_context(&self) -> &Mutex<Option<SigContext>> {
pub fn sig_context(&self) -> &Mutex<VecDeque<Vaddr>> {
&self.sig_context
}

View File

@ -1,6 +1,12 @@
#![allow(non_camel_case_types)]
use core::mem;
use kxos_frame::cpu::GpRegs;
use crate::prelude::*;
use super::sig_num::SigNum;
pub type sigset_t = u64;
#[derive(Debug, Clone, Copy, Pod)]
@ -11,3 +17,80 @@ pub struct sigaction_t {
pub restorer_ptr: Vaddr,
pub mask: sigset_t,
}
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
pub struct siginfo_t {
pub si_signo: i32,
pub si_errno: i32,
pub si_code: i32,
_padding: i32,
/// siginfo_fields should be a union type ( See occlum definition ). But union type have unsafe interfaces.
/// Here we use a simple byte array.
pub siginfo_fields: [u8; 128 - mem::size_of::<i32>() * 4],
}
impl siginfo_t {
pub fn new(num: SigNum, code: i32) -> Self {
let zero_fields = [0u8; 128 - mem::size_of::<i32>() * 4];
siginfo_t {
si_signo: num.as_u8() as i32,
si_errno: 0,
si_code: code,
_padding: 0,
siginfo_fields: zero_fields,
}
}
}
#[derive(Clone, Copy, Debug, Pod)]
#[repr(C)]
pub struct ucontext_t {
pub uc_flags: u64,
pub uc_link: Vaddr, // *mut ucontext_t
pub uc_stack: stack_t,
pub uc_mcontext: mcontext_t,
pub uc_sigmask: sigset_t,
pub fpregs: [u8; 64 * 8], //fxsave structure
}
impl Default for ucontext_t {
fn default() -> Self {
Self {
uc_flags: Default::default(),
uc_link: Default::default(),
uc_stack: Default::default(),
uc_mcontext: Default::default(),
uc_sigmask: Default::default(),
fpregs: [0u8; 64 * 8],
}
}
}
pub type stack_t = sigaltstack_t;
#[derive(Debug, Clone, Copy, Pod, Default)]
#[repr(C)]
pub struct sigaltstack_t {
pub ss_sp: Vaddr, // *mut c_void
pub ss_flags: i32,
pub ss_size: usize,
}
#[derive(Debug, Clone, Copy, Pod, Default)]
#[repr(C)]
pub struct mcontext_t {
pub inner: SignalCpuContext,
// 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],
}
#[derive(Debug, Clone, Copy, Pod, Default)]
pub struct SignalCpuContext {
pub gp_regs: GpRegs,
pub fpregs_on_heap: u64,
pub fpregs: Vaddr, // *mut FpRegs,
}

View File

@ -7,11 +7,16 @@ pub mod sig_num;
pub mod sig_queues;
pub mod signals;
use core::mem;
use kxos_frame::AlignExt;
use kxos_frame::{cpu::CpuContext, task::Task};
use self::c_types::siginfo_t;
use self::sig_mask::SigMask;
use self::sig_num::SigNum;
use crate::memory::{write_bytes_to_user, write_val_to_user};
use crate::process::signal::c_types::ucontext_t;
use crate::process::signal::sig_action::SigActionFlags;
use crate::{
prelude::*,
@ -19,7 +24,7 @@ use crate::{
};
/// Handle pending signal for current process
pub fn handle_pending_signal(context: &mut CpuContext) {
pub fn handle_pending_signal(context: &mut CpuContext) -> Result<()> {
let current = current!();
let pid = current.pid();
let process_name = current.filename().unwrap();
@ -47,7 +52,8 @@ pub fn handle_pending_signal(context: &mut CpuContext) {
restorer_addr,
mask,
context,
),
signal.to_info(),
)?,
SigAction::Dfl => {
let sig_default_action = SigDefaultAction::from_signum(sig_num);
debug!("sig_default_action: {:?}", sig_default_action);
@ -86,6 +92,7 @@ pub fn handle_pending_signal(context: &mut CpuContext) {
}
}
}
Ok(())
}
pub fn handle_user_signal_handler(
@ -93,32 +100,54 @@ pub fn handle_user_signal_handler(
handler_addr: Vaddr,
flags: SigActionFlags,
restorer_addr: Vaddr,
mask: SigMask,
mut mask: SigMask,
context: &mut CpuContext,
) {
sig_info: siginfo_t,
) -> Result<()> {
debug!("sig_num = {:?}", sig_num);
debug!("handler_addr = 0x{:x}", handler_addr);
debug!("flags = {:?}", flags);
debug!("restorer_addr = 0x{:x}", restorer_addr);
// FIXME: How to respect flags
if flags.intersects(!(SigActionFlags::SA_RESTART | SigActionFlags::SA_RESTORER)) {
// FIXME: How to respect flags?
if flags.contains_unsupported_flag() {
panic!("Unsupported Signal flags");
}
if !flags.contains(SigActionFlags::SA_NODEFER) {
// add current signal to mask
let current_mask = SigMask::from(sig_num);
mask.block(current_mask.as_u64());
}
let current = current!();
// block signals in sigmask when running signal handler
current.sig_mask().lock().block(mask.as_u64());
// store context in current process
let sig_context = SigContext::new(context.clone(), mask);
*(current.sig_context().lock()) = Some(sig_context);
// set up signal stack in user stack
// set up signal stack in user stack. // avoid corrupt user stack, we minus 128 first.
let mut user_rsp = context.gp_regs.rsp;
// avoid corrupt user stack, we minus 128 first.
user_rsp = user_rsp - 128;
// Copy the trampoline code.
// 1. write siginfo_t
user_rsp = user_rsp - mem::size_of::<siginfo_t>() as u64;
write_val_to_user(user_rsp as _, &sig_info)?;
let siginfo_addr = user_rsp;
debug!("siginfo_addr = 0x{:x}", siginfo_addr);
// 2. write ucontext_t.
user_rsp = alloc_aligned_in_user_stack(user_rsp, mem::size_of::<ucontext_t>(), 16)?;
let mut ucontext = ucontext_t::default();
ucontext.uc_sigmask = mask.as_u64();
ucontext.uc_mcontext.inner.gp_regs = context.gp_regs;
// TODO: store fp regs in ucontext
write_val_to_user(user_rsp as _, &ucontext)?;
let ucontext_addr = user_rsp;
debug!("ucontext addr = 0x{:x}", ucontext_addr);
// Store the ucontext addr in sig context of current process.
current.sig_context().lock().push_back(ucontext_addr as _);
// 3. Set the address of the trampoline code.
if flags.contains(SigActionFlags::SA_RESTORER) {
// If contains SA_RESTORER flag, trampoline code is provided by libc in restorer_addr.
// We just store restorer_addr on user stack to allow user code just to trampoline code.
user_rsp = write_u64_to_user_stack(user_rsp, restorer_addr as u64);
user_rsp = write_u64_to_user_stack(user_rsp, restorer_addr as u64)?;
} else {
// Otherwise we create
const TRAMPOLINE: &[u8] = &[
@ -128,44 +157,36 @@ pub fn handle_user_signal_handler(
];
user_rsp = user_rsp - TRAMPOLINE.len() as u64;
let trampoline_rip = user_rsp;
write_bytes_to_user(user_rsp as Vaddr, TRAMPOLINE);
user_rsp = write_u64_to_user_stack(user_rsp, trampoline_rip);
write_bytes_to_user(user_rsp as Vaddr, TRAMPOLINE)?;
user_rsp = write_u64_to_user_stack(user_rsp, trampoline_rip)?;
}
// 4. Set correct register values
context.gp_regs.rip = handler_addr as _;
context.gp_regs.rsp = user_rsp;
// parameters of signal handler
context.gp_regs.rdi = sig_num.as_u8() as u64; // signal number
context.gp_regs.rsi = 0; // siginfo_t* siginfo
context.gp_regs.rdx = 0; // void* ctx
if flags.contains(SigActionFlags::SA_SIGINFO) {
context.gp_regs.rsi = siginfo_addr; // siginfo_t* siginfo
context.gp_regs.rdx = ucontext_addr; // void* ctx
} else {
context.gp_regs.rsi = 0;
context.gp_regs.rdx = 0;
}
Ok(())
}
fn write_u64_to_user_stack(rsp: u64, value: u64) -> u64 {
fn write_u64_to_user_stack(rsp: u64, value: u64) -> Result<u64> {
let rsp = rsp - 8;
write_val_to_user(rsp as Vaddr, &value);
rsp
write_val_to_user(rsp as Vaddr, &value)?;
Ok(rsp)
}
/// Used to store process context before running signal handler.
/// In rt_sigreturn, this context is used to restore process context.
#[derive(Debug, Clone, Copy)]
pub struct SigContext {
cpu_context: CpuContext,
sig_mask: SigMask,
}
impl SigContext {
pub const fn new(cpu_context: CpuContext, sig_mask: SigMask) -> SigContext {
Self {
cpu_context,
sig_mask,
}
}
pub fn cpu_context(&self) -> &CpuContext {
&self.cpu_context
}
pub fn sig_mask(&self) -> &SigMask {
&self.sig_mask
}
/// alloc memory of size on user stack, the return address should respect the align argument.
fn alloc_aligned_in_user_stack(rsp: u64, size: usize, align: usize) -> Result<u64> {
if !align.is_power_of_two() {
return_errno_with_message!(Errno::EINVAL, "align must be power of two");
}
let start = (rsp - size as u64).align_down(align as u64);
Ok(start)
}

View File

@ -104,6 +104,15 @@ impl SigActionFlags {
pub fn to_u32(&self) -> u32 {
self.bits()
}
pub fn contains_unsupported_flag(&self) -> bool {
self.intersects(
SigActionFlags::SA_NOCLDSTOP
| SigActionFlags::SA_NOCLDWAIT
| SigActionFlags::SA_ONSTACK
| SigActionFlags::SA_RESETHAND,
)
}
}
/// The default action to signals

View File

@ -11,6 +11,14 @@ impl From<u64> for SigMask {
}
}
impl From<SigNum> for SigMask {
fn from(sig_num: SigNum) -> Self {
let idx = SigMask::num_to_idx(sig_num);
let bits = 1u64 << idx;
SigMask { bits }
}
}
impl SigMask {
pub fn new_empty() -> Self {
SigMask::from(0u64)

View File

@ -5,6 +5,7 @@ use kxos_frame::trap::{
};
use crate::prelude::*;
use crate::process::signal::c_types::siginfo_t;
use crate::process::signal::constants::*;
use crate::process::signal::sig_num::SigNum;
@ -48,4 +49,10 @@ impl Signal for FaultSignal {
fn num(&self) -> SigNum {
self.num
}
fn to_info(&self) -> siginfo_t {
siginfo_t::new(self.num, self.code)
// info.set_si_addr(self.addr.unwrap_or_default() as *const c_void);
// info
}
}

View File

@ -1,4 +1,4 @@
use crate::process::signal::sig_num::SigNum;
use crate::process::signal::{c_types::siginfo_t, constants::SI_KERNEL, sig_num::SigNum};
use super::Signal;
@ -17,4 +17,9 @@ 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
}
}

View File

@ -4,9 +4,11 @@ pub mod user;
use core::fmt::Debug;
use super::sig_num::SigNum;
use super::{c_types::siginfo_t, sig_num::SigNum};
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;
}

View File

@ -1,4 +1,11 @@
use crate::process::{signal::sig_num::SigNum, Pid};
use crate::process::{
signal::{
c_types::siginfo_t,
constants::{SI_QUEUE, SI_TKILL, SI_USER},
sig_num::SigNum,
},
Pid,
};
use super::Signal;
@ -46,4 +53,21 @@ 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 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
}
}

View File

@ -57,7 +57,7 @@ pub fn create_new_task(userspace: Arc<UserSpace>, parent: Weak<Process>) -> Arc<
if current.status().lock().is_zombie() {
break;
}
handle_pending_signal(context);
handle_pending_signal(context).unwrap();
if current.status().lock().is_zombie() {
debug!("exit due to signal");
break;
@ -66,10 +66,12 @@ pub fn create_new_task(userspace: Arc<UserSpace>, parent: Weak<Process>) -> Arc<
while current.status().lock().is_suspend() {
Process::yield_now();
debug!("{} is suspended.", current.pid());
handle_pending_signal(context);
handle_pending_signal(context).unwrap();
}
}
debug!("exit user loop");
// Work around: exit in kernel task entry may be not called. Why this will happen?
Task::current().exit();
}
Task::new(user_task_entry, parent, Some(userspace)).expect("spawn task failed")

View File

@ -4,7 +4,7 @@ use crate::{memory::read_bytes_from_user, prelude::*, syscall::SYS_ACCESS};
pub fn sys_access(filename_ptr: Vaddr, file_mode: u64) -> Result<SyscallReturn> {
debug!("[syscall][id={}][SYS_ACCESS]", SYS_ACCESS);
let mut filename_buffer = vec![0u8; MAX_FILENAME_LEN];
read_bytes_from_user(filename_ptr, &mut filename_buffer);
read_bytes_from_user(filename_ptr, &mut filename_buffer)?;
let filename = CString::from(CStr::from_bytes_until_nul(&filename_buffer).unwrap());
debug!("filename: {:?}", filename);
warn!("access currenly does not check and just return success");

View File

@ -12,7 +12,7 @@ pub fn sys_execve(
) -> Result<SyscallReturn> {
debug!("[syscall][id={}][SYS_EXECVE]", SYS_EXECVE);
let mut filename_buffer = vec![0u8; MAX_FILENAME_LEN];
read_bytes_from_user(filename_ptr, &mut filename_buffer);
read_bytes_from_user(filename_ptr, &mut filename_buffer)?;
let filename = CString::from(CStr::from_bytes_until_nul(&filename_buffer).unwrap());
debug!("filename: {:?}", filename);

View File

@ -333,7 +333,7 @@ impl FutexKey {
pub fn load_val(&self) -> i32 {
// FIXME: how to implement a atomic load?
warn!("implement an atomic load");
read_val_from_user(self.0)
read_val_from_user(self.0).unwrap()
}
pub fn addr(&self) -> Vaddr {

View File

@ -20,20 +20,24 @@ pub fn sys_readlink(
filename_ptr as Vaddr,
user_buf_ptr as Vaddr,
user_buf_len as usize,
);
)?;
Ok(SyscallReturn::Return(res as _))
}
/// do sys readlink
/// write the content to user buffer, returns the actual write len
pub fn do_sys_readlink(filename_ptr: Vaddr, user_buf_ptr: Vaddr, user_buf_len: usize) -> usize {
pub fn do_sys_readlink(
filename_ptr: Vaddr,
user_buf_ptr: Vaddr,
user_buf_len: usize,
) -> Result<usize> {
debug!("filename ptr = 0x{:x}", filename_ptr);
debug!("user_buf_ptr = 0x{:x}", user_buf_ptr);
debug!("user_buf_len = 0x{:x}", user_buf_len);
let mut filename_buffer = [0u8; MAX_FILENAME_LEN];
let current = Process::current();
read_bytes_from_user(filename_ptr, &mut filename_buffer);
read_bytes_from_user(filename_ptr, &mut filename_buffer)?;
let filename = CStr::from_bytes_until_nul(&filename_buffer).expect("Invalid filename");
debug!("filename = {:?}", filename);
if filename == CString::new("/proc/self/exe").unwrap().as_c_str() {
@ -44,8 +48,8 @@ pub fn do_sys_readlink(filename_ptr: Vaddr, user_buf_ptr: Vaddr, user_buf_len: u
let bytes_len = bytes.len();
let write_len = bytes_len.min(user_buf_len);
write_bytes_to_user(user_buf_ptr, &bytes[..write_len]);
return write_len;
write_bytes_to_user(user_buf_ptr, &bytes[..write_len])?;
return Ok(write_len);
}
panic!("does not support linkname other than /proc/self/exe")

View File

@ -19,7 +19,7 @@ pub fn sys_rt_sigaction(
debug!("sig_action_ptr = 0x{:x}", sig_action_ptr);
debug!("old_sig_action_ptr = 0x{:x}", old_sig_action_ptr);
debug!("sigset_size = {}", sigset_size);
let sig_action_c = read_val_from_user::<sigaction_t>(sig_action_ptr);
let sig_action_c = read_val_from_user::<sigaction_t>(sig_action_ptr)?;
debug!("sig_action_c = {:?}", sig_action_c);
let sig_action = SigAction::try_from(sig_action_c).unwrap();
debug!("sig_action = {:x?}", sig_action);
@ -31,6 +31,8 @@ pub fn sys_rt_sigaction(
let old_action_c = old_action.to_c();
debug!("old_action_c = {:x?}", old_action_c);
sig_dispositions.set(sig_num, sig_action);
write_val_to_user(old_sig_action_ptr, &old_action_c);
if old_sig_action_ptr != 0 {
write_val_to_user(old_sig_action_ptr, &old_action_c)?;
}
Ok(SyscallReturn::Return(0))
}

View File

@ -1,14 +1,15 @@
use crate::prelude::*;
use crate::{memory::read_val_from_user, prelude::*, process::signal::c_types::ucontext_t};
use kxos_frame::cpu::CpuContext;
use super::SyscallReturn;
pub fn sys_rt_sigreturn(context: &mut CpuContext) -> Result<SyscallReturn> {
let current = current!();
let sig_context = current.sig_context().lock().as_ref().unwrap().clone();
*context = *sig_context.cpu_context();
let sig_context = current.sig_context().lock().pop_back().unwrap();
let ucontext = read_val_from_user::<ucontext_t>(sig_context)?;
context.gp_regs = ucontext.uc_mcontext.inner.gp_regs;
// unblock sig mask
let sig_mask = sig_context.sig_mask();
current.sig_mask().lock().unblock(sig_mask.as_u64());
let sig_mask = ucontext.uc_sigmask;
current.sig_mask().lock().unblock(sig_mask);
Ok(SyscallReturn::NoReturn)
}

View File

@ -1,13 +1,44 @@
use crate::prelude::*;
use crate::process::signal::sig_num::SigNum;
use crate::process::signal::signals::user::{UserSignal, UserSignalKind};
use crate::process::{table, Pgid, Pid};
use crate::syscall::SYS_TGKILL;
use super::SyscallReturn;
pub fn sys_tgkill(tgid: u64, pid: u64, signal: u64) -> Result<SyscallReturn> {
/// tgkill send a signal to a thread with pid as its thread id, and tgid as its thread group id.
/// Since kxos only supports one-thread process now, tgkill will send signal to process with pid as its process id,
/// and tgid as its process group id.
pub fn sys_tgkill(tgid: Pgid, pid: Pid, sig_num: u8) -> Result<SyscallReturn> {
debug!("[syscall][id={}][SYS_TGKILL]", SYS_TGKILL);
debug!("tgid = {}", tgid);
debug!("pid = {}", pid);
warn!("TODO: tgkill do nothing now");
let sig_num = SigNum::from_u8(sig_num);
debug!("sig_num = {:?}", sig_num);
let target_process =
table::pid_to_process(pid).ok_or(Error::with_message(Errno::EINVAL, "Invalid pid"))?;
let pgid = target_process.pgid();
if pgid != tgid {
return_errno_with_message!(
Errno::EINVAL,
"the combination of tgid and pid is not valid"
);
}
if target_process.status().lock().is_zombie() {
return Ok(SyscallReturn::Return(0));
}
let signal = {
let src_pid = current!().pid();
let src_uid = 0;
Box::new(UserSignal::new(
sig_num,
UserSignalKind::Tkill,
src_pid,
src_uid,
))
};
let mut sig_queue = target_process.sig_queues().lock();
sig_queue.enqueue(signal);
Ok(SyscallReturn::Return(0))
}

View File

@ -60,15 +60,15 @@ fn copy_cstring_to_u8_slice(src: &CStr, dst: &mut [u8]) {
pub fn sys_uname(old_uname_addr: u64) -> Result<SyscallReturn> {
debug!("[syscall][id={}][SYS_UNAME]", SYS_UNAME);
do_sys_uname(old_uname_addr as Vaddr);
do_sys_uname(old_uname_addr as Vaddr)?;
Ok(SyscallReturn::Return(0))
}
pub fn do_sys_uname(old_uname_addr: Vaddr) -> usize {
pub fn do_sys_uname(old_uname_addr: Vaddr) -> Result<usize> {
debug!("old_uname_addr: 0x{:x}", old_uname_addr);
debug!("uts name size: {}", core::mem::size_of::<UtsName>());
debug!("uts name align: {}", core::mem::align_of::<UtsName>());
write_val_to_user(old_uname_addr, &*UTS_NAME);
0
write_val_to_user(old_uname_addr, &*UTS_NAME)?;
Ok(0)
}

View File

@ -18,7 +18,7 @@ pub fn sys_wait4(wait_pid: u64, exit_status_ptr: u64, wait_options: u64) -> Resu
let process_filter = ProcessFilter::from_id(wait_pid as _);
let (return_pid, exit_code) = wait_child_exit(process_filter, wait_options);
if return_pid != 0 && exit_status_ptr != 0 {
write_val_to_user(exit_status_ptr as _, &exit_code);
write_val_to_user(exit_status_ptr as _, &exit_code)?;
}
Ok(SyscallReturn::Return(return_pid as _))
}

View File

@ -13,12 +13,12 @@ pub fn sys_write(fd: u64, user_buf_ptr: u64, user_buf_len: u64) -> Result<Syscal
if fd == STDOUT || fd == STDERR {
let mut buffer = vec![0u8; user_buf_len as usize];
read_bytes_from_user(user_buf_ptr as usize, &mut buffer);
let content = alloc::str::from_utf8(buffer.as_slice()).expect("Invalid content"); // TODO: print content
read_bytes_from_user(user_buf_ptr as usize, &mut buffer)?;
let content = alloc::str::from_utf8(buffer.as_slice())?; // TODO: print content
if fd == STDOUT {
info!("Message from user mode: {:?}", content);
print!("{}", content);
} else {
info!("Error message from user mode: {:?}", content);
print!("{}", content);
}
Ok(SyscallReturn::Return(user_buf_len as _))
} else {

View File

@ -18,23 +18,23 @@ pub struct IoVec {
pub fn sys_writev(fd: u64, io_vec_ptr: u64, io_vec_count: u64) -> Result<SyscallReturn> {
debug!("[syscall][id={}][SYS_WRITEV]", SYS_WRITEV);
let res = do_sys_writev(fd, io_vec_ptr as Vaddr, io_vec_count as usize);
let res = do_sys_writev(fd, io_vec_ptr as Vaddr, io_vec_count as usize)?;
Ok(SyscallReturn::Return(res as _))
}
pub fn do_sys_writev(fd: u64, io_vec_ptr: Vaddr, io_vec_count: usize) -> usize {
pub fn do_sys_writev(fd: u64, io_vec_ptr: Vaddr, io_vec_count: usize) -> Result<usize> {
debug!("fd = {}", fd);
debug!("io_vec_ptr = 0x{:x}", io_vec_ptr);
debug!("io_vec_counter = 0x{:x}", io_vec_count);
let mut write_len = 0;
for i in 0..io_vec_count {
let io_vec = read_val_from_user::<IoVec>(io_vec_ptr + i * 8);
let io_vec = read_val_from_user::<IoVec>(io_vec_ptr + i * 8)?;
let base = io_vec.base;
let len = io_vec.len;
debug!("base = 0x{:x}", base);
debug!("len = {}", len);
let mut buffer = vec![0u8; len];
read_bytes_from_user(base, &mut buffer);
read_bytes_from_user(base, &mut buffer)?;
let content = alloc::str::from_utf8(&buffer).unwrap();
write_len += len;
if fd == 1 {
@ -46,5 +46,5 @@ pub fn do_sys_writev(fd: u64, io_vec_ptr: Vaddr, io_vec_count: usize) -> usize {
panic!("Unsupported fd {}", fd);
}
}
write_len
Ok(write_len)
}

View File

@ -27,38 +27,30 @@ pub fn get_all_apps() -> Vec<UserApp> {
let mut res = Vec::with_capacity(16);
// Most simple hello world, written in assembly
let app1 = UserApp::new("hello_world", read_hello_world_content());
res.push(app1);
let asm_hello_world = UserApp::new("hello_world", read_hello_world_content());
res.push(asm_hello_world);
// Hello world, written in C language.
// Since glibc requires the app name starts with "/", and we don't have filesystem now.
// So we manually add a leading "/" for app written in C language.
let app2 = UserApp::new("/hello_c", read_hello_c_content());
res.push(app2);
let hello_c = UserApp::new("/hello_c", read_hello_c_content());
res.push(hello_c);
// Fork process, written in assembly
let app3 = UserApp::new("fork", read_fork_content());
res.push(app3);
let asm_fork = UserApp::new("fork", read_fork_content());
res.push(asm_fork);
// Execve, written in C language.
let app4 = UserApp::new("/execve", read_execve_content());
res.push(app4);
let execve_c = UserApp::new("/execve", read_execve_content());
res.push(execve_c);
// Fork new process, written in C language. (Fork in glibc uses syscall clone actually)
let app5 = UserApp::new("/fork", read_fork_c_content());
res.push(app5);
let fork_c = UserApp::new("/fork", read_fork_c_content());
res.push(fork_c);
// Set sig procmask
let app6 = UserApp::new("/sig_procmask", read_sig_procmask_content());
res.push(app6);
// divide zero
let app7 = UserApp::new("/divide_zero", read_divide_zero_content());
res.push(app7);
// sig_action
let app8 = UserApp::new("/sig_action", read_sig_action_content());
res.push(app8);
// signal test
let signal_test = UserApp::new("/signal_test", read_signal_test_content());
res.push(signal_test);
res
}
@ -87,14 +79,6 @@ fn read_fork_c_content() -> &'static [u8] {
include_bytes!("../../kxos-user/fork_c/fork")
}
fn read_sig_procmask_content() -> &'static [u8] {
include_bytes!("../../kxos-user/signal_c/sig_procmask")
}
fn read_divide_zero_content() -> &'static [u8] {
include_bytes!("../../kxos-user/signal_c/divide_zero")
}
fn read_sig_action_content() -> &'static [u8] {
include_bytes!("../../kxos-user/signal_c/sig_action")
fn read_signal_test_content() -> &'static [u8] {
include_bytes!("../../kxos-user/signal_c/signal_test")
}

View File

@ -1,10 +1,7 @@
.PHONY: build clean run
build: divide_zero.c sig_procmask.c sig_action.c
@gcc -static divide_zero.c -o divide_zero
@gcc -static sig_procmask.c -o sig_procmask
@gcc -static sig_action.c -o sig_action
build: signal_test.c
@gcc -static signal_test.c -o signal_test
clean:
@rm divide_zero sig_procmask sig_action
@rm signal_test
run: build
@./sig_procmask
@./sig_action
@./signal_test

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:07eb07c6387a9afd68a5b8a8bba9b48e1362b9c3b8f18ce30e744640a4dfa546
size 871776

View File

@ -1,7 +0,0 @@
#include<stdio.h>
int main() {
int a = 2 - 2;
int b = 1 / a;
return 0;
}

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1938c6e8abd70c400780dd8aa089d768a8662d54bac2a658936790d5537e868c
size 877544

View File

@ -1,29 +0,0 @@
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
int sigchld = 0;
void proc_exit() {
sigchld = sigchld + 1;
}
int main() {
signal(SIGCHLD, proc_exit);
printf("Run a parent process has pid = %d\n", getpid());
fflush(stdout);
int pid = fork();
if(pid == 0) {
// child process
printf("create a new proces successfully (pid = %d)\n", getpid());
fflush(stdout);
} else {
// parent process
wait(NULL);
printf("sigchld = %d\n", sigchld);
fflush(stdout);
}
return 0;
}

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6faf986f058a2480297fb323a536319f9c7dde76efe4ed0472f09a4ad2960c9b
size 871848

View File

@ -1,19 +0,0 @@
/// This code is from CSAPP
/// We use this codes to test sigprocmask
#include <stdio.h>
#include <signal.h>
int main() {
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGCHLD);
/* Block SIGINT and save previous blocked set */
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
// Code region that will not be interrupted by SIGINT and SIGCHILD
/* Restore previous blocked set, unblocking SIGINT */
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
return 0;
}

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:25894b343f222eea5074a83e16a7140868a992a66e7bc8e363fabf2c23e19451
size 882520

View File

@ -0,0 +1,285 @@
// This test file is from occlum, to test whether we implement signal correctly.
#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 <pthread.h>
#include <errno.h>
#include <time.h>
// ============================================================================
// Helper functions
// ============================================================================
#define THROW_ERROR(fmt, ...) do { \
printf("\t\tERROR:" fmt " in func %s at line %d of file %s with errno %d: %s\n", \
##__VA_ARGS__, __func__, __LINE__, __FILE__, errno, strerror(errno)); \
return -1; \
} while (0)
// ============================================================================
// Test sigprocmask
// ============================================================================
#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);
fflush(stdout);
recursion_level++;
if (recursion_level <= MAX_RECURSION_LEVEL) {
raise(SIGIO);
}
recursion_level--;
}
int test_raise() {
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(struct sigaction));
memset(&old_action, 0, sizeof(struct sigaction));
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 catching and handling hardware exception
// ============================================================================
static void handle_sigfpe(int num, siginfo_t *info, void *_context) {
printf("SIGFPE Caught\n");
fflush(stdout);
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;
}
#define fxsave(addr) __asm __volatile("fxsave %0" : "=m" (*(addr)))
int test_handle_sigfpe() {
// Set up a signal handler that handles divide-by-zero exception
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(struct sigaction));
memset(&old_action, 0, sizeof(struct sigaction));
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");
}
char x[512] __attribute__((aligned(16))) = {};
char y[512] __attribute__((aligned(16))) = {};
// Trigger divide-by-zero exception
int a = 1;
int b = 0;
// Use volatile to prevent compiler optimization
volatile int c;
fxsave(x);
c = div_maybe_zero(a, b);
fxsave(y);
// kxos does not save and restore fpregs now, so we emit this check.
// if (memcmp(x, y, 512) != 0) {
// THROW_ERROR("floating point registers are modified");
// }
printf("Signal handler successfully jumped over the divide-by-zero instruction\n");
fflush(stdout);
if (sigaction(SIGFPE, &old_action, NULL) < 0) {
THROW_ERROR("restoring old signal handler failed");
}
return 0;
}
// TODO: rewrite this in assembly
int read_maybe_null(int *p) {
return *p;
}
static void handle_sigsegv(int num, siginfo_t *info, void *_context) {
printf("SIGSEGV Caught\n");
fflush(stdout);
assert(num == SIGSEGV);
assert(info->si_signo == SIGSEGV);
ucontext_t *ucontext = _context;
mcontext_t *mcontext = &ucontext->uc_mcontext;
// TODO: how long is the instruction?
// The faulty instruction should be `idiv %esi` (f7 fe)
mcontext->gregs[REG_RIP] += 2;
return;
}
int test_handle_sigsegv() {
// Set up a signal handler that handles divide-by-zero exception
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(struct sigaction));
memset(&old_action, 0, sizeof(struct sigaction));
new_action.sa_sigaction = handle_sigsegv;
new_action.sa_flags = SA_SIGINFO;
if (sigaction(SIGSEGV, &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");
}
int *addr = NULL;
volatile int val = read_maybe_null(addr);
(void)val; // to suppress "unused variables" warning
printf("Signal handler successfully jumped over a null-dereferencing instruction\n");
fflush(stdout);
if (sigaction(SIGSEGV, &old_action, NULL) < 0) {
THROW_ERROR("restoring old signal handler failed");
}
return 0;
}
// ============================================================================
// Test SIGCHLD signal
// ============================================================================
int sigchld = 0;
void proc_exit() {
sigchld = 1;
}
int test_sigchld() {
signal(SIGCHLD, proc_exit);
printf("Run a parent process has pid = %d\n", getpid());
fflush(stdout);
int pid = fork();
if(pid == 0) {
// child process
printf("create a new proces successfully (pid = %d)\n", getpid());
fflush(stdout);
} else {
// parent process
wait(NULL);
printf("sigchld = %d\n", sigchld);
fflush(stdout);
}
return 0;
}
int main() {
test_sigprocmask();
test_raise();
test_handle_sigfpe();
test_handle_sigsegv();
test_sigchld();
return 0;
}

View File

@ -11,7 +11,8 @@ spin = "0.9.4"
kxos-frame = {path = "../kxos-frame"}
kxos-pci = {path="../kxos-pci"}
kxos-util = {path="../kxos-util"}
kxos-frame-pod-derive= {path="../kxos-frame-pod-derive"}
kxos-frame-pod-derive = {path = "../kxos-frame-pod-derive"}
[features]

View File

@ -1,5 +1,4 @@
use kxos_frame::Pod;
use kxos_frame_pod_derive::Pod;
use kxos_pci::capability::vendor::virtio::CapabilityVirtioData;
use kxos_pci::util::BAR;
use kxos_util::frame_ptr::InFramePtr;

View File

@ -14,9 +14,9 @@ use kxos_util::frame_ptr::InFramePtr;
use spin::{mutex::Mutex, MutexGuard};
use self::{block::VirtioBLKConfig, queue::VirtQueue};
use kxos_frame::Pod;
use kxos_pci::{capability::vendor::virtio::CapabilityVirtioData, msix::MSIX};
#[macro_use]
extern crate kxos_frame_pod_derive;
pub mod block;
pub mod queue;

View File

@ -6,7 +6,6 @@ use bitflags::bitflags;
use core::sync::atomic::{fence, Ordering};
use kxos_frame::offset_of;
use kxos_frame::Pod;
use kxos_frame_pod_derive::Pod;
use kxos_util::frame_ptr::InFramePtr;
#[derive(Debug)]
pub enum QueueError {