diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index be45f1c8..d9d89610 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -30,6 +30,7 @@ luna_app(uname.cpp uname) luna_app(base64.cpp base64) luna_app(login.cpp login) luna_app(ipc-test.cpp ipc-test) +luna_app(signal-test.cpp signal-test) luna_app(mount.cpp mount) luna_app(umount.cpp umount) luna_app(ps.cpp ps) diff --git a/apps/init.cpp b/apps/init.cpp index 4f5d268a..131c2166 100644 --- a/apps/init.cpp +++ b/apps/init.cpp @@ -328,7 +328,14 @@ int main() { if (service.pid.has_value() && service.pid.value() == child) { - do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(status)); + if (WIFEXITED(status)) + { + do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(status)); + } + else + { + do_log("[init] service %s was terminated by signal %d\n", service.name.chars(), WTERMSIG(status)); + } if (service.restart) { diff --git a/apps/sh.cpp b/apps/sh.cpp index 7f247eb9..6bca57cf 100644 --- a/apps/sh.cpp +++ b/apps/sh.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -117,7 +118,14 @@ Result luna_main(int argc, char** argv) if (child == 0) { TRY(execute_command(cmd.view())); } - TRY(os::Process::wait(child, nullptr)); + int status; + TRY(os::Process::wait(child, &status)); + + if (WIFSIGNALED(status)) + { + int sig = WTERMSIG(status); + if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig)); + } } return 0; diff --git a/apps/signal-test.cpp b/apps/signal-test.cpp new file mode 100644 index 00000000..b9f6df9a --- /dev/null +++ b/apps/signal-test.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +void handler(int) +{ + puts("I caught a segfault!"); +} + +int main() +{ + struct sigaction sa; + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESETHAND; + sigaction(SIGSEGV, &sa, NULL); + +#pragma GCC diagnostic ignored "-Wnonnull" + char* str = nullptr; + memset(str, 0, 2); + + return 0; +} diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index cc3d5c82..10c3b425 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES src/sys/uname.cpp src/sys/mount.cpp src/sys/resource.cpp + src/sys/signal.cpp src/fs/VFS.cpp src/fs/Pipe.cpp src/fs/Mount.cpp diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index 1ac1d957..3b5739c8 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -9,6 +9,7 @@ #include "sys/Syscall.h" #include "thread/Scheduler.h" #include "video/TextConsole.h" +#include #include #include #include @@ -70,64 +71,65 @@ void decode_page_fault_error_code(u64 code) (code & PF_RESERVED) ? " | Reserved bits set" : "", (code & PF_NX_VIOLATION) ? " | NX violation" : ""); } -[[noreturn]] void handle_page_fault(Registers* regs) +void handle_cpu_exception(int signo, const char* err, Registers* regs) { - CPU::disable_interrupts(); + if (err) kerrorln("Caught CPU exception: %s", err); - u64 cr2; - asm volatile("mov %%cr2, %0" : "=r"(cr2)); - kerrorln("Page fault at RIP %lx while accessing %lx!", regs->rip, cr2); - - decode_page_fault_error_code(regs->error); + kerrorln("RAX: %.16lx RBX: %.16lx RCX: %.16lx RDX: %.16lx", regs->rax, regs->rbx, regs->rcx, regs->rdx); + kerrorln("RBP: %.16lx RSP: %.16lx RDI: %.16lx RSI: %.16lx", regs->rbp, regs->rsp, regs->rdi, regs->rsi); + kerrorln("R8: %.16lx R9: %.16lx R10: %.16lx R11: %.16lx", regs->r8, regs->r9, regs->r10, regs->r11); + kerrorln("R12: %.16lx R13: %.16lx R14: %.16lx R15: %.16lx", regs->r12, regs->r13, regs->r14, regs->r15); + kerrorln("RIP: %.16lx CS: %.16lx SS: %.16lx FLAGS: %.16lx", regs->rip, regs->cs, regs->ss, regs->rflags); CPU::print_stack_trace_at(regs); if (!is_in_kernel(regs)) { - // FIXME: Kill this process with SIGSEGV once we have signals and all that. - kerrorln("Current task %zu was terminated because of a page fault", Scheduler::current()->id); - Scheduler::current()->exit_and_signal_parent(127); + Scheduler::current()->send_signal(signo); + Scheduler::current()->process_pending_signals(regs); + return; } CPU::efficient_halt(); } -[[noreturn]] void handle_general_protection_fault(Registers* regs) +void handle_page_fault(Registers* regs) { - CPU::disable_interrupts(); + u64 cr2; + asm volatile("mov %%cr2, %0" : "=r"(cr2)); + kerrorln("Page fault while accessing %lx!", cr2); + decode_page_fault_error_code(regs->error); + + handle_cpu_exception(SIGSEGV, nullptr, regs); +} + +void handle_general_protection_fault(Registers* regs) +{ kerrorln("General protection fault at RIP %lx, error code %lx!", regs->rip, regs->error); - CPU::print_stack_trace_at(regs); - - if (!is_in_kernel(regs)) - { - // FIXME: Kill this process with SIGSEGV once we have signals and all that. - kerrorln("Current task %zu was terminated because of a general protection fault", Scheduler::current()->id); - Scheduler::current()->exit_and_signal_parent(127); - } - - CPU::efficient_halt(); + handle_cpu_exception(SIGSEGV, nullptr, regs); } extern "C" void handle_x86_exception(Registers* regs) { + CPU::disable_interrupts(); switch (regs->isr) { - case 0: FIXME_UNHANDLED_INTERRUPT("Division by zero"); + case 0: handle_cpu_exception(SIGFPE, "Division by zero", regs); return; case 1: FIXME_UNHANDLED_INTERRUPT("Debug interrupt"); case 2: FIXME_UNHANDLED_INTERRUPT("NMI (Non-maskable interrupt)"); case 3: FIXME_UNHANDLED_INTERRUPT("Breakpoint"); case 4: FIXME_UNHANDLED_INTERRUPT("Overflow"); case 5: FIXME_UNHANDLED_INTERRUPT("Bound range exceeded"); - case 6: FIXME_UNHANDLED_INTERRUPT("Invalid opcode"); + case 6: handle_cpu_exception(SIGILL, "Invalid opcode", regs); return; case 7: FIXME_UNHANDLED_INTERRUPT("Device not available"); case 10: FIXME_UNHANDLED_INTERRUPT("Invalid TSS"); case 11: FIXME_UNHANDLED_INTERRUPT("Segment not present"); case 12: FIXME_UNHANDLED_INTERRUPT("Stack-segment fault"); - case 13: handle_general_protection_fault(regs); - case 14: handle_page_fault(regs); - case 16: FIXME_UNHANDLED_INTERRUPT("x87 floating-point exception"); + case 13: handle_general_protection_fault(regs); return; + case 14: handle_page_fault(regs); return; + case 16: handle_cpu_exception(SIGFPE, "x87 floating-point exception", regs); return; case 17: FIXME_UNHANDLED_INTERRUPT("Alignment check"); case 19: FIXME_UNHANDLED_INTERRUPT("SIMD floating-point exception"); case 20: FIXME_UNHANDLED_INTERRUPT("Virtualization exception"); diff --git a/kernel/src/arch/x86_64/Thread.cpp b/kernel/src/arch/x86_64/Thread.cpp index 671e9eca..c17cd7d4 100644 --- a/kernel/src/arch/x86_64/Thread.cpp +++ b/kernel/src/arch/x86_64/Thread.cpp @@ -1,5 +1,9 @@ #include "thread/Thread.h" +#include "Log.h" +#include "memory/MemoryManager.h" +#include #include +#include bool is_in_kernel(Registers* regs) { @@ -31,6 +35,11 @@ void Thread::set_return(u64 ret) regs.rax = ret; } +u64 Thread::return_register() +{ + return regs.rax; +} + void Thread::init_regs_kernel() { memset(®s, 0, sizeof(Registers)); @@ -61,3 +70,95 @@ void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs) memcpy(regs, &new_thread->regs, sizeof(Registers)); } + +// FIXME: Move this function to a common location (also used in ThreadImage) +Result Thread::push_mem_on_stack(const u8* mem, usize size) +{ + if ((regs.rsp - size) < stack.bottom()) return err(E2BIG); + + if (!MemoryManager::validate_user_write((void*)(regs.rsp - size), size)) return err(EFAULT); + + regs.rsp -= size; + + memcpy((void*)regs.rsp, mem, size); + + return regs.rsp; +} + +Result Thread::pop_mem_from_stack(u8* mem, usize size) +{ + if ((regs.rsp + size) > stack.top()) return err(E2BIG); + + if (!MemoryManager::validate_user_read((void*)regs.rsp, size)) return err(EFAULT); + + memcpy(mem, (void*)regs.rsp, size); + + regs.rsp += size; + + return regs.rsp; +} + +bool Thread::deliver_signal(int signo, Registers* current_regs) +{ + auto handler = signal_handlers[signo - 1]; + check(handler.sa_handler != SIG_IGN); + check(handler.sa_handler != SIG_DFL); + + memcpy(®s, current_regs, sizeof(regs)); + + kinfoln("signal: delivering signal %d for thread %ld, ip=%p, sp=%p, handler=%p, sigreturn=%p", signo, id, + (void*)regs.rip, (void*)regs.rsp, (void*)handler.sa_handler, (void*)handler.__sa_sigreturn); + + regs.rsp -= 128; // Skip the red zone + + if (push_mem_on_stack((u8*)current_regs, sizeof(*current_regs)).has_error()) return false; + if (push_mem_on_stack((u8*)&signal_mask, sizeof(signal_mask)).has_error()) return false; + + u64 rsp = regs.rsp; + + regs.rsp = align_down<16>(regs.rsp); + if (push_mem_on_stack((u8*)&rsp, sizeof(u64)).has_error()) return false; + if (push_mem_on_stack((u8*)&handler.__sa_sigreturn, sizeof(void*)).has_error()) return false; + + signal_mask = handler.sa_mask; + if ((handler.sa_flags & SA_NODEFER) == 0) signal_mask |= (1 << (signo - 1)); + + rsp = regs.rsp; + + init_regs_user(); + regs.rsp = rsp; + regs.rip = (u64)handler.sa_handler; + regs.rdi = signo; + + memcpy(current_regs, ®s, sizeof(regs)); + + if (handler.sa_flags & SA_RESETHAND) + { + handler.sa_handler = SIG_DFL; + handler.sa_mask = 0; + handler.sa_flags = 0; + signal_handlers[signo - 1] = handler; + } + + return true; +} + +void Thread::sigreturn(Registers* current_regs) +{ + memcpy(®s, current_regs, sizeof(regs)); + + u64 rsp; + pop_mem_from_stack((u8*)&rsp, sizeof(rsp)); + regs.rsp = rsp; + pop_mem_from_stack((u8*)&signal_mask, sizeof(signal_mask)); + pop_mem_from_stack((u8*)current_regs, sizeof(*current_regs)); + memcpy(®s, current_regs, sizeof(regs)); + regs.cs = 0x18 | 3; + regs.ss = 0x20 | 3; + // FIXME: Using this, a program can craft a special RFLAGS that gives them a higher IOPL or other stuff. Find out + // exactly what bits to block from modifying. + + kinfoln("sigreturn: restored program state, sp=%p, ip=%p", (void*)regs.rsp, (void*)regs.rip); + + memcpy(current_regs, ®s, sizeof(regs)); +} diff --git a/kernel/src/sys/Syscall.cpp b/kernel/src/sys/Syscall.cpp index d45552aa..a19a35be 100644 --- a/kernel/src/sys/Syscall.cpp +++ b/kernel/src/sys/Syscall.cpp @@ -1,4 +1,5 @@ #include "sys/Syscall.h" +#include "thread/Scheduler.h" #include syscall_func_t syscalls[] = { @@ -13,6 +14,9 @@ i64 invoke_syscall(Registers* regs, SyscallArgs args, u64 syscall) if (syscall >= Syscalls::__count) { return -ENOSYS; } auto rc = syscalls[syscall](regs, args); + + Scheduler::current()->process_pending_signals(regs); + if (rc.has_error()) return -rc.error(); return (i64)rc.value(); } diff --git a/kernel/src/sys/exec.cpp b/kernel/src/sys/exec.cpp index 5ef59522..fe6a8623 100644 --- a/kernel/src/sys/exec.cpp +++ b/kernel/src/sys/exec.cpp @@ -112,6 +112,11 @@ Result sys_execve(Registers* regs, SyscallArgs args) memcpy(regs, ¤t->regs, sizeof(*regs)); + for (int i = 0; i < NSIG; i++) + { + current->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 }; + } + kinfoln("exec: thread %lu was replaced with %s", current->id, path.chars()); return 0; @@ -151,6 +156,13 @@ Result sys_fork(Registers* regs, SyscallArgs) memcpy(&thread->regs, regs, sizeof(*regs)); + for (int i = 0; i < NSIG; i++) + { + auto sighandler = current->signal_handlers[i].sa_handler; + thread->signal_handlers[i] = { .sa_handler = sighandler, .sa_mask = 0, .sa_flags = 0 }; + } + thread->signal_mask = current->signal_mask; + thread->set_return(0); Scheduler::add_thread(thread); diff --git a/kernel/src/sys/signal.cpp b/kernel/src/sys/signal.cpp new file mode 100644 index 00000000..2bde914b --- /dev/null +++ b/kernel/src/sys/signal.cpp @@ -0,0 +1,99 @@ +#include "Log.h" +#include "memory/MemoryManager.h" +#include "sys/Syscall.h" +#include "thread/Scheduler.h" +#include + +Result sys_sigreturn(Registers* regs, SyscallArgs) +{ + auto* current = Scheduler::current(); + + current->sigreturn(regs); + + // Since we could be returning to anywhere in the program, we don't want to be changing the return value register + // (RAX on x86_64). But our syscall framework doesn't allow that, so we just set it to its current value. + return current->return_register(); +} + +Result sys_sigaction(Registers*, SyscallArgs args) +{ + auto* current = Scheduler::current(); + + int signo = (int)args[0]; + const struct sigaction* act = (const struct sigaction*)args[1]; + struct sigaction* oldact = (struct sigaction*)args[2]; + void* sigreturn = (void*)args[3]; + + if (signo > NSIG) return err(EINVAL); + if (signo <= 0) return err(EINVAL); + + if (oldact) + { + if (!MemoryManager::copy_to_user_typed(oldact, ¤t->signal_handlers[signo - 1])) return err(EFAULT); + } + + if (act) + { + struct sigaction kact; + if (!MemoryManager::copy_from_user_typed(act, &kact)) return err(EFAULT); + kact.__sa_sigreturn = sigreturn; + + kinfoln("sigaction: installing signal handler for signo=%d: handler=%p, sigreturn=%p", signo, + (void*)kact.sa_handler, sigreturn); + + current->signal_handlers[signo - 1] = kact; + } + + return 0; +} + +Result sys_kill(Registers*, SyscallArgs args) +{ + auto* current = Scheduler::current(); + + pid_t pid = (pid_t)args[0]; + int signo = (int)args[1]; + + // FIXME: Support this case. + if (pid <= 0) return err(ENOTSUP); + + auto* target = TRY(Result::from_option(Scheduler::find_by_pid(pid), ESRCH)); + if (current->auth.euid != 0 && current->auth.euid != target->auth.euid && current->auth.egid != target->auth.egid) + return err(EPERM); + if (target->is_kernel) return 0; + if (signo == 0) return 0; + + target->send_signal(signo); + + return 0; +} + +Result sys_sigprocmask(Registers*, SyscallArgs args) +{ + auto* current = Scheduler::current(); + + int how = (int)args[0]; + const sigset_t* set = (const sigset_t*)args[1]; + sigset_t* oldset = (sigset_t*)args[2]; + + if (oldset) + { + if (!MemoryManager::copy_to_user_typed(oldset, ¤t->signal_mask)) return err(EFAULT); + } + + if (set) + { + sigset_t kset; + if (!MemoryManager::copy_from_user_typed(set, &kset)) return err(EFAULT); + + switch (how) + { + case SIG_BLOCK: current->signal_mask |= kset; break; + case SIG_UNBLOCK: current->signal_mask &= ~kset; break; + case SIG_SETMASK: current->signal_mask = kset; break; + default: return err(EINVAL); + } + } + + return 0; +} diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp index 77d94238..e033d1b2 100644 --- a/kernel/src/thread/Scheduler.cpp +++ b/kernel/src/thread/Scheduler.cpp @@ -155,6 +155,11 @@ namespace Scheduler image->apply(thread); thread->set_arguments(args.size(), argv, env.size(), envp); + for (int i = 0; i < NSIG; i++) + { + thread->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 }; + } + kinfoln("Created userspace thread: id %lu with ip %#.16lx and sp %#.16lx (ksp %#lx)", thread->id, thread->ip(), thread->sp(), thread->kernel_stack.top()); @@ -259,6 +264,7 @@ namespace Scheduler Thread* old_thread = g_current; Thread* new_thread = pick_task(); generic_switch_context(old_thread, new_thread, regs); + if (!is_in_kernel(regs)) new_thread->process_pending_signals(regs); } void invoke(Registers* regs) diff --git a/kernel/src/thread/Thread.cpp b/kernel/src/thread/Thread.cpp index 8c7d1eb4..aa235d46 100644 --- a/kernel/src/thread/Thread.cpp +++ b/kernel/src/thread/Thread.cpp @@ -1,8 +1,11 @@ #include "thread/Thread.h" +#include "Log.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" #include #include +#include +#include #include #include #include @@ -69,7 +72,7 @@ Result> Thread::resolve_atfile(int dirfd, const String& pa return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink); } -[[noreturn]] void Thread::exit_and_signal_parent(u8 _status) +[[noreturn]] void Thread::exit_and_signal_parent(int _status) { if (this->id == 1) fail("the init process exited"); if (is_kernel) state = ThreadState::Dying; @@ -80,14 +83,18 @@ Result> Thread::resolve_atfile(int dirfd, const String& pa return true; }); - if (parent && parent->state == ThreadState::Waiting) + if (parent) { - auto child = *parent->child_being_waited_for; - if (child == -1 || child == (pid_t)id) + if (parent->state == ThreadState::Waiting) { - parent->child_being_waited_for = (pid_t)id; - parent->wake_up(); + auto child = *parent->child_being_waited_for; + if (child == -1 || child == (pid_t)id) + { + parent->child_being_waited_for = (pid_t)id; + parent->wake_up(); + } } + else { parent->send_signal(SIGCHLD); } } state = ThreadState::Exited; @@ -97,6 +104,80 @@ Result> Thread::resolve_atfile(int dirfd, const String& pa unreachable(); } +enum class DefaultSignalAction +{ + Ignore, + Terminate, +}; + +static constexpr DefaultSignalAction default_actions[] = { + DefaultSignalAction::Terminate, // SIGHUP + DefaultSignalAction::Terminate, // SIGINT + DefaultSignalAction::Terminate, // SIGQUIT (dump core) + DefaultSignalAction::Terminate, // SIGILL (dump core) + DefaultSignalAction::Terminate, // SIGTRAP (dump core) + DefaultSignalAction::Terminate, // SIGABRT (dump core) + DefaultSignalAction::Ignore, // SIGCHLD + DefaultSignalAction::Terminate, // SIGFPE (dump core) + DefaultSignalAction::Terminate, // SIGKILL + DefaultSignalAction::Terminate, // SIGSTOP (FIXME: Support stopping and continuing) + DefaultSignalAction::Terminate, // SIGSEGV (dump core) + DefaultSignalAction::Ignore, // SIGCONT (FIXME: Support stopping and continuing) + DefaultSignalAction::Terminate, // SIGPIPE + DefaultSignalAction::Terminate, // SIGALRM + DefaultSignalAction::Terminate, // SIGTERM +}; + +void Thread::process_pending_signals(Registers* current_regs) +{ + for (int i = 0; i < NSIG; i++) + { + int signo = i + 1; + if (signo != SIGKILL && signo != SIGSTOP && signal_mask & (1 << i)) continue; + if (pending_signals & (1 << i)) + { + pending_signals &= ~(1 << i); + kinfoln("signal: executing signal %d for thread %ld", signo, id); + auto handler = signal_handlers[i]; + if (signo != SIGKILL && signo != SIGSTOP && handler.sa_handler == SIG_IGN) + { + kinfoln("signal: ignoring signal (handler=SIG_IGN)"); + return; + } + if (handler.sa_handler == SIG_DFL || signo == SIGKILL || signo == SIGSTOP) + { + default_signal: + if (id == 1) + { + kwarnln("signal: init got a signal it has no handler for, ignoring"); + return; + } + + kinfoln("signal: using default behavior (handler=SIG_DFL)"); + + auto action = default_actions[i]; + switch (action) + { + case DefaultSignalAction::Ignore: return; + // FIXME: Add signal exit codes. + case DefaultSignalAction::Terminate: exit_and_signal_parent(signo | _SIGBIT); + default: return; + } + } + // If we fail to deliver the signal (usually because there's not enough space on the stack), execute the + // default action. + if (!deliver_signal(signo, current_regs)) goto default_signal; + return; + } + } +} + +void Thread::send_signal(int signo) +{ + check(signo > 0 && signo <= NSIG); + pending_signals |= 1 << (signo - 1); +} + bool FileDescriptor::should_append() { return flags & O_APPEND; diff --git a/kernel/src/thread/Thread.h b/kernel/src/thread/Thread.h index 6e979095..be90fab1 100644 --- a/kernel/src/thread/Thread.h +++ b/kernel/src/thread/Thread.h @@ -3,6 +3,7 @@ #include "arch/MMU.h" #include "fs/VFS.h" #include "memory/AddressSpace.h" +#include #include #include #include @@ -79,13 +80,18 @@ struct Thread : public LinkedListNode bool follow_last_symlink, SharedPtr* parent_inode = nullptr); + struct sigaction signal_handlers[NSIG]; + sigset_t signal_mask { 0 }; + + int pending_signals { 0 }; + FPData fp_data; ThreadState state = ThreadState::Runnable; bool is_kernel { true }; - u8 status { 0 }; + int status { 0 }; mode_t umask { 0 }; @@ -104,7 +110,7 @@ struct Thread : public LinkedListNode PageDirectory* active_directory { nullptr }; - [[noreturn]] void exit_and_signal_parent(u8 status); + [[noreturn]] void exit_and_signal_parent(int status); bool is_idle() { @@ -128,6 +134,17 @@ struct Thread : public LinkedListNode u64 sp(); void set_return(u64 ret); + u64 return_register(); + + void process_pending_signals(Registers* current_regs); + + bool deliver_signal(int signo, Registers* current_regs); + void sigreturn(Registers* current_regs); + + Result push_mem_on_stack(const u8* mem, usize size); + Result pop_mem_from_stack(u8* mem, usize size); + + void send_signal(int signo); static void init(); }; diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt index 307e1523..5edda97a 100644 --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCES src/grp.cpp src/locale.cpp src/scanf.cpp + src/signal.cpp src/sys/stat.cpp src/sys/mman.cpp src/sys/wait.cpp @@ -36,6 +37,7 @@ if(${LUNA_ARCH} STREQUAL "x86_64") ${SOURCES} src/arch/x86_64/syscall.S src/arch/x86_64/setjmp.S + src/arch/x86_64/sigreturn.S ) endif() diff --git a/libc/include/bits/signal.h b/libc/include/bits/signal.h new file mode 100644 index 00000000..792f9840 --- /dev/null +++ b/libc/include/bits/signal.h @@ -0,0 +1,58 @@ +/* bits/signal.h: Signal-related definitions. */ + +#ifndef _BITS_SIGNAL_H +#define _BITS_SIGNAL_H + +typedef void (*__simple_sighandler_t)(int); + +#define SIG_IGN (__simple_sighandler_t)(-1) +#define SIG_DFL (__simple_sighandler_t)(-2) + +typedef int sigset_t; + +struct sigaction +{ + __simple_sighandler_t sa_handler; + sigset_t sa_mask; + int sa_flags; +#ifdef __cplusplus + void* __sa_sigreturn = nullptr; +#else + void* __sa_sigreturn; +#endif +}; + +// Constants for sigaction's sa_flags field. +#define SA_NODEFER (1 << 0) +#define SA_RESETHAND (1 << 1) + +// Constants for the 'how' parameter in sigprocmask(). +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 1 +#define SIG_SETMASK 2 + +// The signals with explicit numbers have portable signal numbers. +enum __signals +{ + SIGHUP = 1, + SIGINT = 2, + SIGQUIT = 3, + SIGILL = 4, + SIGTRAP = 5, + SIGABRT = 6, + SIGCHLD, + SIGFPE = 8, + SIGKILL = 9, + SIGSTOP, + SIGSEGV = 11, + SIGCONT, + SIGPIPE = 13, + SIGALRM = 14, + SIGTERM = 15, + // FIXME: Add the remaining signals. + __NSIG, +}; + +#define NSIG (__NSIG - 1) + +#endif diff --git a/libc/include/bits/waitpid.h b/libc/include/bits/waitpid.h index 8b7922b4..2ed3b15e 100644 --- a/libc/include/bits/waitpid.h +++ b/libc/include/bits/waitpid.h @@ -3,6 +3,8 @@ #ifndef _BITS_WAITPID_H #define _BITS_WAITPID_H +#define _SIGBIT 0x100 + #define WNOHANG 1 #endif diff --git a/libc/include/signal.h b/libc/include/signal.h index 0b0b6bf2..814ede80 100644 --- a/libc/include/signal.h +++ b/libc/include/signal.h @@ -3,6 +3,53 @@ #ifndef _SIGNAL_H #define _SIGNAL_H +#include +#include + typedef int sig_atomic_t; +#define SIG_ERR (__simple_sighandler_t)(-3) + +#ifdef __cplusplus +extern "C" +{ +#endif + +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Wshadow" + /* Examine/change the current thread's signal disposition for a specific signal. */ + int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact); +#pragma GCC pop_options + + /* Change the current thread's signal disposition for a specific signal. */ + __simple_sighandler_t signal(int signo, __simple_sighandler_t handler); + + /* Send a signal to a specific process. */ + int kill(pid_t pid, int signo); + + /* Send a signal to the current thread. */ + int raise(int signo); + + /* Modify the current thread's signal mask. */ + int sigprocmask(int how, const sigset_t* set, sigset_t* oldset); + + /* Clear all signals from set. */ + int sigemptyset(sigset_t* set); + + /* Add all signals to set. */ + int sigfillset(sigset_t* set); + + /* Add a specific signal to set. */ + int sigaddset(sigset_t* set, int signo); + + /* Remove a specific signal from set. */ + int sigdelset(sigset_t* set, int signo); + + /* Check if a signal is in set.*/ + int sigismember(const sigset_t* set, int signo); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/libc/include/string.h b/libc/include/string.h index 3349a828..aff7a35b 100644 --- a/libc/include/string.h +++ b/libc/include/string.h @@ -62,6 +62,9 @@ extern "C" /* Return the human-readable description of an error number. */ char* strerror(int errnum); + /* Return the human-readable description of a signal number. */ + char* strsignal(int signo); + /* Return the length of the initial segment of str which consists only of bytes in accept. */ size_t strspn(const char* str, const char* accept); diff --git a/libc/include/sys/wait.h b/libc/include/sys/wait.h index 4fa9c3e5..a10d9bf1 100644 --- a/libc/include/sys/wait.h +++ b/libc/include/sys/wait.h @@ -6,8 +6,10 @@ #include #include -#define WIFEXITED(ret) ((ret) | 0) +#define WIFEXITED(ret) (((ret)&_SIGBIT) == 0) #define WEXITSTATUS(ret) ((ret)&0xff) +#define WIFSIGNALED(ret) (((ret)&_SIGBIT) == _SIGBIT) +#define WTERMSIG(ret) ((ret)&0xff) #ifdef __cplusplus extern "C" diff --git a/libc/src/arch/x86_64/sigreturn.S b/libc/src/arch/x86_64/sigreturn.S new file mode 100644 index 00000000..44aeee30 --- /dev/null +++ b/libc/src/arch/x86_64/sigreturn.S @@ -0,0 +1,5 @@ +.global sigreturn +sigreturn: + mov $47, %rax + int $66 + ud2 diff --git a/libc/src/signal.cpp b/libc/src/signal.cpp new file mode 100644 index 00000000..64849364 --- /dev/null +++ b/libc/src/signal.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include + +extern "C" void sigreturn(); + +extern "C" +{ + int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact) + { + long rc = syscall(SYS_sigaction, signo, act, oldact, sigreturn); + __errno_return(rc, int); + } + + __simple_sighandler_t signal(int signo, __simple_sighandler_t handler) + { + struct sigaction act, oldact; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + if (sigaction(signo, &act, &oldact) < 0) return SIG_ERR; + + return oldact.sa_handler; + } + + int kill(pid_t pid, int signo) + { + long rc = syscall(SYS_kill, pid, signo); + __errno_return(rc, int); + } + + int raise(int signo) + { + return kill(getpid(), signo); + } + + int sigprocmask(int how, const sigset_t* set, sigset_t* oldset) + { + long rc = syscall(SYS_sigprocmask, how, set, oldset); + __errno_return(rc, int); + } + + int sigemptyset(sigset_t* set) + { + *set = 0; + return 0; + } + + int sigfillset(sigset_t* set) + { + *set = 0xffffffff; + return 0; + } + + int sigaddset(sigset_t* set, int signo) + { + if (signo <= 0 || signo > NSIG) + { + errno = EINVAL; + return -1; + } + + *set |= (1 << (signo - 1)); + return 0; + } + + int sigdelset(sigset_t* set, int signo) + { + if (signo <= 0 || signo > NSIG) + { + errno = EINVAL; + return -1; + } + + *set &= ~(1 << (signo - 1)); + return 0; + } + + int sigismember(const sigset_t* set, int signo) + { + if (signo <= 0 || signo > NSIG) + { + errno = EINVAL; + return -1; + } + + return (*set & (1 << (signo - 1))) > 0; + } +} diff --git a/libc/src/stdlib.cpp b/libc/src/stdlib.cpp index 5a31db25..32150fc7 100644 --- a/libc/src/stdlib.cpp +++ b/libc/src/stdlib.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -99,7 +100,25 @@ extern "C" __noreturn void abort() { - syscall(SYS_exit, 255); + // First, try to unblock SIGABRT and then raise it. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGABRT); + sigprocmask(SIG_UNBLOCK, &set, nullptr); + + raise(SIGABRT); + + // Still here? The program must have catched it. Reset the disposition to default. + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGABRT, &act, nullptr); + + raise(SIGABRT); + + // There is no way we could end up here, unless there is some sort of race condition or the kernel decided to + // change the default action for SIGABRT because it's a Tuesday. __builtin_unreachable(); } @@ -172,9 +191,6 @@ extern "C" return S_ISREG(st.st_mode); } - // FIXME: During the execution of system(), SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored, in - // the process that calls system(). - pid_t child = fork(); if (child == 0) { @@ -184,9 +200,21 @@ extern "C" if (child < 0) return -1; + sigset_t set, oldset; + sigemptyset(&set); + sigaddset(&set, SIGCHLD); + sigprocmask(SIG_BLOCK, &set, &oldset); + + auto old_int = signal(SIGINT, SIG_IGN); + auto old_quit = signal(SIGQUIT, SIG_IGN); + int status; waitpid(child, &status, 0); + sigprocmask(SIG_SETMASK, &oldset, nullptr); + signal(SIGINT, old_int); + signal(SIGQUIT, old_quit); + return status; } diff --git a/libc/src/string.cpp b/libc/src/string.cpp index eabdc00a..510c465e 100644 --- a/libc/src/string.cpp +++ b/libc/src/string.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -12,4 +13,32 @@ extern "C" { return const_cast(error_string(errnum)); } + + static const char* __internal_strsignal(int signo) + { + switch (signo) + { + case SIGHUP: return "Hangup"; + case SIGINT: return "Terminal interrupt"; + case SIGQUIT: return "Terminal quit"; + case SIGILL: return "Invalid opcode"; + case SIGTRAP: return "Breakpoint trap"; + case SIGABRT: return "Aborted"; + case SIGCHLD: return "Child stopped or exited"; + case SIGFPE: return "Floating point exception"; + case SIGKILL: return "Killed"; + case SIGSTOP: return "Stopped"; + case SIGSEGV: return "Segmentation fault"; + case SIGCONT: return "Continued"; + case SIGPIPE: return "Broken pipe"; + case SIGALRM: return "Alarm signal"; + case SIGTERM: return "Terminated"; + default: return "Unknown signal"; + } + } + + char* strsignal(int signo) + { + return const_cast(__internal_strsignal(signo)); + } } diff --git a/libluna/include/luna/Syscall.h b/libluna/include/luna/Syscall.h index 5a6d6609..c89beb66 100644 --- a/libluna/include/luna/Syscall.h +++ b/libluna/include/luna/Syscall.h @@ -6,7 +6,7 @@ _e(getgid) _e(getegid) _e(setuid) _e(setgid) _e(seteuid) _e(setegid) _e(fchmodat) _e(fchownat) _e(ioctl) \ _e(fstatat) _e(chdir) _e(getcwd) _e(unlinkat) _e(uname) _e(sethostname) _e(dup2) _e(pipe) _e(mount) \ _e(umount) _e(pstat) _e(getrusage) _e(symlinkat) _e(readlinkat) _e(umask) _e(linkat) _e(faccessat) \ - _e(pivot_root) + _e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) _e(sigprocmask) enum Syscalls {