/* * Copyright (c) 2011-2016, 2021-2022 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * process.cpp * A named collection of threads. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__x86_64__) #include "x86-family/float.h" #include "x86-family/gdt.h" #endif namespace Sortix { kthread_mutex_t process_family_lock = KTHREAD_MUTEX_INITIALIZER; // The system is shutting down and creation of additional processes and threads // should be prevented. Protected by process_family_lock. static bool is_init_exiting = false; Process::Process() { program_image_path = NULL; addrspace = 0; pid = 0; nicelock = KTHREAD_MUTEX_INITIALIZER; nice = 0; idlock = KTHREAD_MUTEX_INITIALIZER; uid = euid = 0; gid = egid = 0; umask = 0022; ptrlock = KTHREAD_MUTEX_INITIALIZER; // tty set to null reference in the member constructor. // root set to null reference in the member constructor. // cwd set to null reference in the member constructor. // mtable set to null reference in the member constructor. // dtable set to null reference in the member constructor. // ptable set to null reference in the member constructor. resource_limits_lock = KTHREAD_MUTEX_INITIALIZER; for ( size_t i = 0; i < RLIMIT_NUM_DECLARED; i++ ) { resource_limits[i].rlim_cur = RLIM_INFINITY; resource_limits[i].rlim_max = RLIM_INFINITY; } signal_lock = KTHREAD_MUTEX_INITIALIZER; memset(&signal_actions, 0, sizeof(signal_actions)); for ( int i = 0; i < SIG_MAX_NUM; i++ ) { sigemptyset(&signal_actions[i].sa_mask); signal_actions[i].sa_handler = SIG_DFL; signal_actions[i].sa_cookie = NULL; signal_actions[i].sa_flags = 0; } sigemptyset(&signal_pending); sigreturn = NULL; parent = NULL; prevsibling = NULL; nextsibling = NULL; firstchild = NULL; zombiechild = NULL; group = NULL; groupprev = NULL; groupnext = NULL; groupfirst = NULL; session = NULL; sessionprev = NULL; sessionnext = NULL; sessionfirst = NULL; zombiecond = KTHREAD_COND_INITIALIZER; iszombie = false; nozombify = false; limbo = false; exit_code = -1; firstthread = NULL; threadlock = KTHREAD_MUTEX_INITIALIZER; threads_not_exiting_count = 0; threads_exiting = false; futex_lock = KTHREAD_MUTEX_INITIALIZER; futex_first_waiting = NULL; futex_last_waiting = NULL; segments = NULL; segments_used = 0; segments_length = 0; segment_write_lock = KTHREAD_MUTEX_INITIALIZER; segment_lock = KTHREAD_MUTEX_INITIALIZER; user_timers_lock = KTHREAD_MUTEX_INITIALIZER; memset(&user_timers, 0, sizeof(user_timers)); // alarm_timer initialized in member constructor. // execute_clock initialized in member constructor. // system_clock initialized in member constructor. // child_execute_clock initialized in member constructor. // child_system_clock initialized in member constructor. Time::InitializeProcessClocks(this); alarm_timer.Attach(Time::GetClock(CLOCK_MONOTONIC)); } Process::~Process() // process_family_lock taken { if ( alarm_timer.IsAttached() ) alarm_timer.Detach(); delete[] program_image_path; assert(!zombiechild); assert(!session); assert(!group); assert(!parent); assert(!sessionfirst); assert(!groupfirst); assert(!firstchild); assert(!addrspace); assert(!segments); assert(!dtable); assert(!mtable); assert(!cwd); assert(!root); assert(!threads_not_exiting_count); if ( ptable ) ptable->Free(pid); ptable.Reset(); tty.Reset(); } void Process::BootstrapTables(Ref dtable, Ref mtable) { ScopedLock lock(&ptrlock); assert(!this->dtable); assert(!this->mtable); this->dtable = dtable; this->mtable = mtable; } void Process::BootstrapDirectories(Ref root) { ScopedLock lock(&ptrlock); assert(!this->root); assert(!this->cwd); this->root = root; this->cwd = root; } void Process::OnLastThreadExit() { Process* init = Scheduler::GetInitProcess(); assert(init); // Child processes can't be reparented away if we're init. The system is // about to shut down, so broadcast SIGKILL every process and wait for every // single process to exit. The operating system is finished when init has // exited. if ( init == this ) { ScopedLock lock(&process_family_lock); // Forbid any more processes and threads from being created, so this // loop will always terminate. is_init_exiting = true; kthread_mutex_lock(&ptrlock); for ( pid_t pid = ptable->Next(0); 0 < pid; pid = ptable->Next(pid) ) { Process* process = ptable->Get(pid); if ( process->pid != 0 && process != init ) process->DeliverSignal(SIGKILL); } kthread_mutex_unlock(&ptrlock); // NotifyChildExit always signals zombiecond for init when // is_init_exiting is true. while ( firstchild ) kthread_cond_wait(&zombiecond, &process_family_lock); } } void Process::OnThreadDestruction(Thread* thread) { assert(thread->process == this); kthread_mutex_lock(&threadlock); if ( thread->prevsibling ) thread->prevsibling->nextsibling = thread->nextsibling; if ( thread->nextsibling ) thread->nextsibling->prevsibling = thread->prevsibling; if ( thread == firstthread ) firstthread = thread->nextsibling; if ( firstthread ) firstthread->prevsibling = NULL; thread->prevsibling = thread->nextsibling = NULL; bool threadsleft = firstthread; kthread_mutex_unlock(&threadlock); // We are called from the threads destructor, let it finish before we // we handle the situation by killing ourselves. if ( !threadsleft ) ScheduleDeath(); } void Process__AfterLastThreadExit(void* user) { return ((Process*) user)->AfterLastThreadExit(); } void Process::ScheduleDeath() { // All our threads must have exited at this point. assert(!firstthread); Worker::Schedule(Process__AfterLastThreadExit, this); } // Useful for killing a partially constructed process without waiting for // it to die and garbage collect its zombie. It is not safe to access this // process after this call as another thread may garbage collect it. void Process::AbortConstruction() { nozombify = true; ScheduleDeath(); } void Process::AfterLastThreadExit() { LastPrayer(); } void Process::DeleteTimers() { for ( timer_t i = 0; i < PROCESS_TIMER_NUM_MAX; i++ ) { if ( user_timers[i].timer.IsAttached() ) { user_timers[i].timer.Cancel(); user_timers[i].timer.Detach(); } } } // This function runs in a worker thread and cannot block on another worker // thread, or it may deadlock. void Process::LastPrayer() { assert(this); // This must never be called twice. assert(!iszombie); // This must be called from a thread using another address space as the // address space of this process is about to be destroyed. Thread* curthread = CurrentThread(); assert(curthread->process != this); // This can't be called if the process is still alive. assert(!firstthread); // Disarm and detach all the timers in the process. DeleteTimers(); if ( alarm_timer.IsAttached() ) { alarm_timer.Cancel(); alarm_timer.Detach(); } // We need to temporarily reload the correct addrese space of the dying // process such that we can unmap and free its memory. addr_t prevaddrspace = Memory::SwitchAddressSpace(addrspace); ResetAddressSpace(); // tty is kept alive in session leader until no longer in limbo. if ( dtable ) dtable.Reset(); if ( cwd ) cwd.Reset(); if ( root ) root.Reset(); if ( mtable ) mtable.Reset(); // Destroy the address space and safely switch to the replacement // address space before things get dangerous. Memory::DestroyAddressSpace(prevaddrspace); addrspace = 0; ScopedLock family_lock(&process_family_lock); iszombie = true; // Init is nice and will gladly raise our orphaned children and zombies. Process* init = Scheduler::GetInitProcess(); assert(init); // Child processes can't be reparented away if we're init. OnLastThreadExit // must have already killed all the child processes and prevented more from // being created. if ( init == this ) { assert(is_init_exiting); assert(!firstchild); } while ( firstchild ) { Process* process = firstchild; firstchild = process->nextsibling; process->parent = init; process->prevsibling = NULL; process->nextsibling = init->firstchild; if ( init->firstchild ) init->firstchild->prevsibling = process; init->firstchild = process; process->nozombify = true; } // Since we have no more children (they are with init now), we don't // have to worry about new zombie processes showing up, so just collect // those that are left. Then we satisfiy the invariant !zombiechild that // applies on process termination. while ( zombiechild ) { Process* zombie = zombiechild; zombiechild = zombie->nextsibling; zombie->nextsibling = NULL; if ( zombiechild ) zombiechild->prevsibling = NULL; zombie->nozombify = true; zombie->WaitedFor(); } // Remove ourself from our process group. if ( group ) group->GroupRemoveMember(this); // Remove ourself from our session. if ( session ) session->SessionRemoveMember(this); bool zombify = !nozombify; // This class instance will be destroyed by our parent process when it // has received and acknowledged our death. if ( parent ) parent->NotifyChildExit(this, zombify); // If nobody is waiting for us, then simply commit suicide. if ( !zombify ) WaitedFor(); } void Process::WaitedFor() // process_family_lock taken { parent = NULL; limbo = false; if ( groupfirst || sessionfirst ) limbo = true; if ( !limbo ) delete this; } void Process::ResetAddressSpace() { ScopedLock lock1(&segment_write_lock); ScopedLock lock2(&segment_lock); assert(Memory::GetAddressSpace() == addrspace); for ( size_t i = 0; i < segments_used; i++ ) Memory::UnmapRange(segments[i].addr, segments[i].size, PAGE_USAGE_USER_SPACE); Memory::Flush(); segments_used = segments_length = 0; free(segments); segments = NULL; } void Process::GroupRemoveMember(Process* child) // process_family_lock taken { assert(child->group == this); if ( child->groupprev ) child->groupprev->groupnext = child->groupnext; else groupfirst = child->groupnext; if ( child->groupnext ) child->groupnext->groupprev = child->groupprev; child->group = NULL; if ( IsLimboDone() ) delete this; } void Process::SessionRemoveMember(Process* child) // process_family_lock taken { assert(child->session == this); if ( child->sessionprev ) child->sessionprev->sessionnext = child->sessionnext; else sessionfirst = child->sessionnext; if ( child->sessionnext ) child->sessionnext->sessionprev = child->sessionprev; child->session = NULL; if ( !sessionfirst ) { // Remove reference to tty when session is empty. ScopedLock lock(&ptrlock); tty.Reset(); } if ( IsLimboDone() ) delete this; } bool Process::IsLimboDone() // process_family_lock taken { return limbo && !groupfirst && !sessionfirst; } // process_family_lock taken void Process::NotifyChildExit(Process* child, bool zombify) { if ( child->prevsibling ) child->prevsibling->nextsibling = child->nextsibling; if ( child->nextsibling ) child->nextsibling->prevsibling = child->prevsibling; if ( firstchild == child ) firstchild = child->nextsibling; if ( firstchild ) firstchild->prevsibling = NULL; if ( zombify ) { if ( zombiechild ) zombiechild->prevsibling = child; child->prevsibling = NULL; child->nextsibling = zombiechild; zombiechild = child; } // Notify this parent process about the child exiting if it's meant to // become a zombie process. Additionally, always notify init about children // when init is exiting, because OnLastThreadExit needs to be able to catch // every child exiting. DeliverSignal(SIGCHLD); if ( zombify || (is_init_exiting && Scheduler::GetInitProcess() == this) ) kthread_cond_broadcast(&zombiecond); } pid_t Process::Wait(pid_t thepid, int* status_ptr, int options) { // TODO: Process groups are not supported yet. if ( thepid < -1 || thepid == 0 ) return errno = ENOSYS, -1; ScopedLock lock(&process_family_lock); // A process can only wait if it has children. if ( !firstchild && !zombiechild ) return errno = ECHILD, -1; // Processes can only wait for their own children to exit. if ( 0 < thepid ) { // TODO: This is a slow but multithread safe way to verify that the // target process has the correct parent. bool found = false; for ( Process* p = firstchild; !found && p; p = p->nextsibling ) if ( p->pid == thepid && !p->nozombify ) found = true; for ( Process* p = zombiechild; !found && p; p = p->nextsibling ) if ( p->pid == thepid && !p->nozombify ) found = true; if ( !found ) return errno = ECHILD, -1; } Process* zombie = NULL; while ( !zombie ) { for ( zombie = zombiechild; zombie; zombie = zombie->nextsibling ) if ( (thepid == -1 || thepid == zombie->pid) && !zombie->nozombify ) break; if ( zombie ) break; if ( options & WNOHANG ) return 0; if ( !kthread_cond_wait_signal(&zombiecond, &process_family_lock) ) return errno = EINTR, -1; } // Remove from the list of zombies. if ( zombie->prevsibling ) zombie->prevsibling->nextsibling = zombie->nextsibling; if ( zombie->nextsibling ) zombie->nextsibling->prevsibling = zombie->prevsibling; if ( zombiechild == zombie ) zombiechild = zombie->nextsibling; if ( zombiechild ) zombiechild->prevsibling = NULL; thepid = zombie->pid; // It is safe to access these clocks directly as the child process is no // longer running at this point and the values are nicely frozen. child_execute_clock.Advance(zombie->child_execute_clock.current_time); child_system_clock.Advance(zombie->child_system_clock.current_time); int status = zombie->exit_code; if ( status < 0 ) status = WCONSTRUCT(WNATURE_SIGNALED, 128 + SIGKILL, SIGKILL); zombie->WaitedFor(); if ( status_ptr ) *status_ptr = status; return thepid; } pid_t sys_waitpid(pid_t pid, int* user_status, int options) { int status = 0; pid_t ret = CurrentProcess()->Wait(pid, &status, options); if ( 0 < ret && user_status && !CopyToUser(user_status, &status, sizeof(status)) ) return -1; return ret; } void Process::ExitThroughSignal(int signal) { ExitWithCode(WCONSTRUCT(WNATURE_SIGNALED, 128 + signal, signal)); } void Process::ExitWithCode(int requested_exit_code) { ScopedLock lock(&threadlock); if ( exit_code == -1 ) exit_code = requested_exit_code; // Broadcast SIGKILL to all our threads which will begin our long path // of process termination. We simply can't stop the threads as they may // be running in kernel mode doing dangerous stuff. This thread will be // destroyed by SIGKILL once the system call returns. for ( Thread* t = firstthread; t; t = t->nextsibling ) t->DeliverSignal(SIGKILL); } Ref Process::GetMTable() { ScopedLock lock(&ptrlock); assert(mtable); return mtable; } Ref Process::GetDTable() { ScopedLock lock(&ptrlock); assert(dtable); return dtable; } Ref Process::GetPTable() { ScopedLock lock(&ptrlock); assert(ptable); return ptable; } Ref Process::GetTTY() { ScopedLock lock(&ptrlock); return tty; } Ref Process::GetRoot() { ScopedLock lock(&ptrlock); assert(root); return root; } Ref Process::GetCWD() { ScopedLock lock(&ptrlock); assert(cwd); return cwd; } void Process::SetTTY(Ref newtty) { ScopedLock lock(&ptrlock); tty = newtty; } void Process::SetRoot(Ref newroot) { ScopedLock lock(&ptrlock); assert(newroot); root = newroot; } void Process::SetCWD(Ref newcwd) { ScopedLock lock(&ptrlock); assert(newcwd); cwd = newcwd; } Ref Process::GetDescriptor(int fd) { ScopedLock lock(&ptrlock); assert(dtable); return dtable->Get(fd); } Process* Process::Fork() { assert(CurrentProcess() == this); Process* clone = new Process; if ( !clone ) return NULL; struct segment* clone_segments = NULL; // Fork the segment list. if ( segments ) { size_t segments_size = sizeof(struct segment) * segments_used; if ( !(clone_segments = (struct segment*) malloc(segments_size)) ) { delete clone; return NULL; } memcpy(clone_segments, segments, segments_size); } // Fork address-space here and copy memory. clone->addrspace = Memory::Fork(); if ( !clone->addrspace ) { free(clone_segments); delete clone; return NULL; } // Now it's too late to clean up here, if anything goes wrong, we simply // ask the process to commit suicide before it goes live. clone->segments = clone_segments; clone->segments_used = segments_used; clone->segments_length = segments_used; kthread_mutex_lock(&process_family_lock); // Forbid the creation of new processes if init has exited. if ( is_init_exiting ) { kthread_mutex_unlock(&process_family_lock); clone->AbortConstruction(); return NULL; } // Register the new process by assigning a pid. if ( (clone->pid = (clone->ptable = ptable)->Allocate(clone)) < 0 ) { kthread_mutex_unlock(&process_family_lock); clone->AbortConstruction(); return NULL; } // Remember the relation to the child process. clone->parent = this; clone->nextsibling = firstchild; clone->prevsibling = NULL; if ( firstchild ) firstchild->prevsibling = clone; firstchild = clone; // Add the new process to the current process group. clone->group = group; clone->groupprev = NULL; if ( (clone->groupnext = group->groupfirst) ) group->groupfirst->groupprev = clone; group->groupfirst = clone; // Add the new process to the current session. clone->session = session; clone->sessionprev = NULL; if ( (clone->sessionnext = session->sessionfirst) ) session->sessionfirst->sessionprev = clone; session->sessionfirst = clone; kthread_mutex_unlock(&process_family_lock); // Initialize everything that is safe and can't fail. kthread_mutex_lock(&resource_limits_lock); for ( size_t i = 0; i < RLIMIT_NUM_DECLARED; i++ ) clone->resource_limits[i] = resource_limits[i]; kthread_mutex_unlock(&resource_limits_lock); kthread_mutex_lock(&nicelock); clone->nice = nice; kthread_mutex_unlock(&nicelock); kthread_mutex_lock(&ptrlock); clone->root = root; clone->cwd = cwd; kthread_mutex_unlock(&ptrlock); kthread_mutex_lock(&idlock); clone->uid = uid; clone->gid = gid; clone->euid = euid; clone->egid = egid; clone->umask = umask; kthread_mutex_unlock(&idlock); kthread_mutex_lock(&signal_lock); memcpy(&clone->signal_actions, &signal_actions, sizeof(signal_actions)); sigemptyset(&clone->signal_pending); clone->sigreturn = sigreturn; kthread_mutex_unlock(&signal_lock); // Initialize things that can fail and abort if needed. bool failure = false; kthread_mutex_lock(&ptrlock); if ( !(clone->dtable = dtable->Fork()) ) failure = true; //if ( !(clone->mtable = mtable->Fork()) ) // failure = true; clone->mtable = mtable; kthread_mutex_unlock(&ptrlock); if ( !(clone->program_image_path = String::Clone(program_image_path)) ) failure = true; // If the proces creation failed, ask the process to commit suicide and // not become a zombie, as we don't wait for it to exit. It will clean // up all the above resources and delete itself. if ( failure ) { clone->AbortConstruction(); return NULL; } return clone; } void Process::ResetForExecute() { DeleteTimers(); for ( int i = 0; i < SIG_MAX_NUM; i++ ) { signal_actions[i].sa_flags = 0; if ( signal_actions[i].sa_handler == SIG_DFL ) continue; if ( signal_actions[i].sa_handler == SIG_IGN ) continue; signal_actions[i].sa_handler = SIG_DFL; } sigreturn = NULL; stack_t* signal_stack = &CurrentThread()->signal_stack; memset(signal_stack, 0, sizeof(*signal_stack)); signal_stack->ss_flags = SS_DISABLE; ResetAddressSpace(); } bool Process::MapSegment(struct segment* result, void* hint, size_t size, int flags, int prot) { // process->segment_write_lock is held at this point. // process->segment_lock is held at this point. if ( !size ) size = 1; if ( !PlaceSegment(result, this, hint, size, flags) ) return false; if ( !Memory::MapMemory(this, result->addr, result->size, result->prot = prot) ) { // The caller is expected to self-destruct in this case, so the // segment just created is not removed. return false; } Memory::Flush(); return true; } int Process::Execute(const char* programname, const uint8_t* program, size_t programsize, int argc, const char* const* argv, int envc, const char* const* envp, struct thread_registers* regs) { assert(argc != INT_MAX); assert(envc != INT_MAX); assert(CurrentProcess() == this); if ( argc == 0 ) return errno = EINVAL, -1; char* programname_clone = String::Clone(programname); if ( !programname_clone ) return -1; ELF::Auxiliary aux; addr_t entry = ELF::Load(program, programsize, &aux); if ( !entry ) { delete[] programname_clone; return -1; } delete[] program_image_path; program_image_path = programname_clone; programname_clone = NULL; uintptr_t userspace_addr; size_t userspace_size; Memory::GetUserVirtualArea(&userspace_addr, &userspace_size); const size_t stack_size = 512UL * 1024UL; void* stack_hint = (void*) (userspace_addr + userspace_size - stack_size); const int stack_prot = PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE | PROT_FORK; if ( !aux.tls_mem_align ) aux.tls_mem_align = 1; if ( Page::Size() < aux.tls_mem_align ) return errno = EINVAL, -1; if ( !aux.uthread_align ) aux.uthread_align = 1; if ( Page::Size() < aux.uthread_align ) return errno = EINVAL, -1; if ( aux.uthread_size < sizeof(struct uthread) ) aux.uthread_size = sizeof(struct uthread); size_t raw_tls_size = aux.tls_mem_size; size_t raw_tls_size_aligned = -(-raw_tls_size & ~(aux.tls_mem_align-1)); if ( raw_tls_size && raw_tls_size_aligned == 0 /* overflow */ ) return errno = EINVAL, -1; int raw_tls_kprot = PROT_KWRITE | PROT_FORK; int raw_tls_prot = PROT_READ | PROT_KREAD | PROT_FORK; void* raw_tls_hint = stack_hint; size_t tls_size = raw_tls_size_aligned + aux.uthread_size; size_t tls_offset_tls = 0; size_t tls_offset_uthread = raw_tls_size_aligned; if ( aux.tls_mem_align < aux.uthread_align ) { size_t more_aligned = -(-raw_tls_size_aligned & ~(aux.uthread_align-1)); if ( raw_tls_size_aligned && more_aligned == 0 /* overflow */ ) return errno = EINVAL, -1; size_t difference = more_aligned - raw_tls_size_aligned; tls_size += difference; tls_offset_tls += difference; tls_offset_uthread += difference; } assert((tls_offset_tls & (aux.tls_mem_align-1)) == 0); assert((tls_offset_uthread & (aux.uthread_align-1)) == 0); int tls_prot = PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE | PROT_FORK; void* tls_hint = stack_hint; size_t auxcode_size = Page::Size(); int auxcode_kprot = PROT_KWRITE | PROT_FORK; int auxcode_prot = PROT_EXEC | PROT_READ | PROT_KREAD | PROT_FORK; void* auxcode_hint = stack_hint; size_t arg_size = 0; size_t argv_size = sizeof(char*) * (argc + 1); size_t envp_size = sizeof(char*) * (envc + 1); arg_size += argv_size; arg_size += envp_size; for ( int i = 0; i < argc; i++ ) arg_size += strlen(argv[i]) + 1; for ( int i = 0; i < envc; i++ ) arg_size += strlen(envp[i]) + 1; struct segment arg_segment; struct segment stack_segment; struct segment raw_tls_segment; struct segment tls_segment; struct segment auxcode_segment; kthread_mutex_lock(&segment_write_lock); kthread_mutex_lock(&segment_lock); if ( !(MapSegment(&arg_segment, stack_hint, arg_size, 0, stack_prot) && MapSegment(&stack_segment, stack_hint, stack_size, 0, stack_prot) && MapSegment(&raw_tls_segment, raw_tls_hint, raw_tls_size, 0, raw_tls_kprot) && MapSegment(&tls_segment, tls_hint, tls_size, 0, tls_prot) && MapSegment(&auxcode_segment, auxcode_hint, auxcode_size, 0, auxcode_kprot)) ) { kthread_mutex_unlock(&segment_lock); kthread_mutex_unlock(&segment_write_lock); ResetForExecute(); return errno = ENOMEM, -1; } char** target_argv = (char**) ((char*) arg_segment.addr + 0); char** target_envp = (char**) ((char*) arg_segment.addr + argv_size); char* target_strings = (char*) ((char*) arg_segment.addr + argv_size + envp_size); size_t target_strings_offset = 0; for ( int i = 0; i < argc; i++ ) { const char* arg = argv[i]; size_t arg_len = strlen(arg); char* target_arg = target_strings + target_strings_offset; strcpy(target_arg, arg); target_argv[i] = target_arg; target_strings_offset += arg_len + 1; } target_argv[argc] = (char*) NULL; for ( int i = 0; i < envc; i++ ) { const char* env = envp[i]; size_t env_len = strlen(env); char* target_env = target_strings + target_strings_offset; strcpy(target_env, env); target_envp[i] = target_env; target_strings_offset += env_len + 1; } target_envp[envc] = (char*) NULL; const uint8_t* file_raw_tls = program + aux.tls_file_offset; uint8_t* target_raw_tls = (uint8_t*) raw_tls_segment.addr; memcpy(target_raw_tls, file_raw_tls, aux.tls_file_size); memset(target_raw_tls + aux.tls_file_size, 0, aux.tls_mem_size - aux.tls_file_size); Memory::ProtectMemory(this, raw_tls_segment.addr, raw_tls_segment.size, raw_tls_prot); uint8_t* target_tls = (uint8_t*) (tls_segment.addr + tls_offset_tls); assert((((uintptr_t) target_tls) & (aux.tls_mem_align-1)) == 0); memcpy(target_tls, file_raw_tls, aux.tls_file_size); memset(target_tls + aux.tls_file_size, 0, aux.tls_mem_size - aux.tls_file_size); struct uthread* uthread = (struct uthread*) (tls_segment.addr + tls_offset_uthread); assert((((uintptr_t) uthread) & (aux.uthread_align-1)) == 0); memset(uthread, 0, sizeof(*uthread)); uthread->uthread_pointer = uthread; uthread->uthread_size = aux.uthread_size; uthread->uthread_flags = UTHREAD_FLAG_INITIAL; uthread->tls_master_mmap = (void*) raw_tls_segment.addr; uthread->tls_master_size = aux.tls_mem_size; uthread->tls_master_align = aux.tls_mem_align; uthread->tls_mmap = (void*) tls_segment.addr; uthread->tls_size = tls_size; uthread->stack_mmap = (void*) stack_segment.addr; uthread->stack_size = stack_segment.size; uthread->arg_mmap = (void*) arg_segment.addr; uthread->arg_size = arg_segment.size; memset(uthread + 1, 0, aux.uthread_size - sizeof(struct uthread)); memset(regs, 0, sizeof(*regs)); #if defined(__i386__) regs->eax = argc; regs->ebx = (size_t) target_argv; regs->edx = envc; regs->ecx = (size_t) target_envp; regs->eip = entry; regs->esp = (stack_segment.addr + stack_segment.size) & ~15UL; regs->ebp = regs->esp; regs->cs = UCS | URPL; regs->ds = UDS | URPL; regs->ss = UDS | URPL; regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; regs->signal_pending = 0; regs->gsbase = (uint32_t) uthread; regs->cr3 = addrspace; regs->kernel_stack = GDT::GetKernelStack(); memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512); #elif defined(__x86_64__) regs->rdi = argc; regs->rsi = (size_t) target_argv; regs->rdx = envc; regs->rcx = (size_t) target_envp; regs->rip = entry; regs->rsp = (stack_segment.addr + stack_segment.size) & ~15UL; regs->rbp = regs->rsp; regs->cs = UCS | URPL; regs->ds = UDS | URPL; regs->ss = UDS | URPL; regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; regs->signal_pending = 0; regs->fsbase = (uint64_t) uthread; regs->cr3 = addrspace; regs->kernel_stack = GDT::GetKernelStack(); memcpy(regs->fpuenv, Float::fpu_initialized_regs, 512); #else #warning "You need to implement initializing the first thread after execute" #endif uint8_t* auxcode = (uint8_t*) auxcode_segment.addr; #if defined(__i386__) sigreturn = (void (*)(void)) &auxcode[0]; auxcode[0] = 0xCD; /* int .... */ auxcode[1] = 0x83; /* ... $131 */ #elif defined(__x86_64__) sigreturn = (void (*)(void)) &auxcode[0]; auxcode[0] = 0xCD; /* int .... */ auxcode[1] = 0x83; /* ... $131 */ #else (void) auxcode; #warning "You need to initialize auxcode with a sigreturn routine" #endif Memory::ProtectMemory(this, auxcode_segment.addr, auxcode_segment.size, auxcode_prot); kthread_mutex_unlock(&segment_lock); kthread_mutex_unlock(&segment_write_lock); dtable->OnExecute(); return 0; } static const char* shebang_lookup_environment(const char* name, char* const* envp) { size_t equalpos = strcspn(name, "="); if ( name[equalpos] == '=' ) return NULL; size_t namelen = equalpos; for ( size_t i = 0; envp[i]; i++ ) { if ( strncmp(name, envp[i], namelen) ) continue; if ( envp[i][namelen] != '=' ) continue; return envp[i] + namelen + 1; } return NULL; } static char* shebang_tokenize(char** saved) { char* data = *saved; if ( !data ) return *saved = NULL; while ( data[0] && isspace((unsigned char) data[0]) ) data++; if ( !data[0] ) return *saved = NULL; size_t input = 0; size_t output = 0; bool singly = false; bool doubly = false; bool escaped = false; for ( ; data[input]; input++ ) { char c = data[input]; if ( !escaped && !singly && !doubly && isspace((unsigned char) c) ) break; if ( !escaped && !doubly && c == '\'' ) { singly = !singly; continue; } if ( !escaped && !singly && c == '"' ) { doubly = !doubly; continue; } if ( !singly && !escaped && c == '\\' ) { escaped = true; continue; } if ( escaped ) { switch ( c ) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'e': c = '\e'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; default: break; }; } escaped = false; data[output++] = c; } if ( data[input] ) *saved = data + input + 1; else *saved = NULL; data[output] = '\0'; return data; } static size_t shebang_count_arguments(char* line) { size_t result = 0; while ( shebang_tokenize(&line) ) result++; return result; } // NOTE: The PATH-searching logic is repeated multiple places. Until this logic // can be shared somehow, you need to keep this comment in sync as well // as the logic in these files: // * kernel/process.cpp // * libc/unistd/execvpe.c // * utils/which.c // NOTE: See comments in execvpe() for algorithmic commentary. static bool sys_execve_alloc(addralloc_t* alloc, size_t size) { if ( !AllocateKernelAddress(alloc, size) ) return false; if ( !Memory::MapRange(alloc->from, alloc->size, PROT_KREAD | PROT_KWRITE, PAGE_USAGE_EXECVE) ) return FreeKernelAddress(alloc), false; Memory::Flush(); return true; } static void sys_execve_free(addralloc_t* alloc) { Memory::UnmapRange(alloc->from, alloc->size, PAGE_USAGE_EXECVE); Memory::Flush(); FreeKernelAddress(alloc); } static int sys_execve_kernel(const char* filename, int argc, char* const* argv, int envc, char* const* envp, struct thread_registers* regs) { Process* process = CurrentProcess(); ioctx_t ctx; SetupKernelIOCtx(&ctx); Ref from = filename[0] == '/' ? process->GetRoot() : process->GetCWD(); Ref desc = from->open(&ctx, filename, O_EXEC | O_READ, 0); if ( !desc ) return -1; from.Reset(); struct stat st; if ( desc->stat(&ctx, &st) ) return -1; if ( !(st.st_mode & 0111) ) return errno = EACCES, -1; if ( st.st_size < 0 ) return errno = EINVAL, -1; if ( (uintmax_t) SIZE_MAX < (uintmax_t) st.st_size ) return errno = EFBIG, -1; size_t filesize = (size_t) st.st_size; addralloc_t buffer_alloc; if ( !sys_execve_alloc(&buffer_alloc, filesize) ) return -1; uint8_t* buffer = (uint8_t*) buffer_alloc.from; for ( size_t sofar = 0; sofar < filesize; ) { ssize_t amount = desc->read(&ctx, buffer + sofar, filesize - sofar); if ( amount < 0 ) return sys_execve_free(&buffer_alloc), -1; if ( amount == 0 ) return sys_execve_free(&buffer_alloc), errno = EEOF, -1; sofar += amount; } desc.Reset(); int result = process->Execute(filename, buffer, filesize, argc, argv, envc, envp, regs); if ( result == 0 || errno != ENOEXEC || filesize < 2 || buffer[0] != '#' || buffer[1] != '!' ) return sys_execve_free(&buffer_alloc), result; size_t line_length = 0; while ( 2 + line_length < filesize && buffer[2 + line_length] != '\n' ) line_length++; if ( line_length == filesize ) return sys_execve_free(&buffer_alloc), errno = ENOEXEC, -1; char* line = new char[line_length+1]; if ( !line ) return sys_execve_free(&buffer_alloc), -1; memcpy(line, buffer + 2, line_length); line[line_length] = '\0'; sys_execve_free(&buffer_alloc); char* line_clone = String::Clone(line); if ( !line_clone ) return delete[] line, -1; size_t argument_count = shebang_count_arguments(line_clone); delete[] line_clone; if ( !argument_count || INT_MAX < argument_count ) return delete[] line, errno = ENOEXEC, -1; int sb_argc = (int) argument_count; char** sb_argv = new char*[sb_argc]; if ( !sb_argv ) return delete[] line, -1; char* sb_saved = line; for ( int i = 0; i < sb_argc; i++ ) sb_argv[i] = shebang_tokenize(&sb_saved); if ( INT_MAX - argc <= sb_argc ) return delete[] sb_argv, delete[] line, errno = EOVERFLOW, -1; if ( !sb_argv[0] || !sb_argv[0][0] ) return delete[] sb_argv, delete[] line, errno = ENOENT, -1; int new_argc = sb_argc + argc; char** new_argv = new char*[new_argc + 1]; if ( !new_argv ) return delete[] sb_argv, delete[] line, -1; for ( int i = 0; i < sb_argc; i++ ) new_argv[i] = sb_argv[i]; new_argv[sb_argc + 0] = (char*) filename; for ( int i = 1; i < argc; i++ ) new_argv[sb_argc + i] = argv[i]; new_argv[new_argc] = (char*) NULL; result = -1; // (See the above comment block before editing this searching logic) const char* path = shebang_lookup_environment("PATH", envp); bool search_path = !strchr(sb_argv[0], '/') && path; bool any_tries = false; bool any_eacces = false; const char* new_argv0 = sb_argv[0]; while ( search_path && *path ) { size_t len = strcspn(path, ":"); if ( !len ) { path++; continue; } any_tries = true; char* dirpath = strndup(path, len); if ( !dirpath ) return -1; if ( (path += len)[0] == ':' ) path++; while ( len && dirpath[len - 1] == '/' ) dirpath[--len] = '\0'; char* fullpath; if ( asprintf(&fullpath, "%s/%s", dirpath, sb_argv[0]) < 0 ) return free(dirpath), -1; result = sys_execve_kernel(fullpath, new_argc, new_argv, envc, envp, regs); free(fullpath); free(dirpath); if ( result == 0 ) break; if ( errno == ENOENT ) continue; if ( errno == ELOOP || errno == EISDIR || errno == ENAMETOOLONG || errno == ENOTDIR ) continue; if ( errno == EACCES ) { any_eacces = true; continue; } if ( errno == EACCES ) { any_eacces = true; continue; } break; } if ( !any_tries ) result = sys_execve_kernel(new_argv0, new_argc, new_argv, envc, envp, regs); if ( result < 0 && any_eacces ) errno = EACCES; delete[] new_argv; delete[] sb_argv; delete[] line; return result; } int sys_execve(const char* user_filename, char* const* user_argv, char* const* user_envp) { char* filename; int argc; int envc; char** argv; char** envp; int result = -1; struct thread_registers regs; memset(®s, 0, sizeof(regs)); if ( !user_filename || !user_argv || !user_envp ) return errno = EFAULT, -1; if ( !(filename = GetStringFromUser(user_filename)) ) goto cleanup_done; argc = 0; while ( true ) { const char* user_arg; if ( !CopyFromUser(&user_arg, user_argv + argc, sizeof(user_arg)) ) goto cleanup_filename; if ( !user_arg ) break; if ( ++argc == INT_MAX ) { errno = E2BIG; goto cleanup_filename; } } argv = new char*[argc+1]; if ( !argv ) goto cleanup_filename; memset(argv, 0, sizeof(char*) * (argc+1)); for ( int i = 0; i < argc; i++ ) { const char* user_arg; if ( !CopyFromUser(&user_arg, user_argv + i, sizeof(user_arg)) ) goto cleanup_argv; if ( !(argv[i] = GetStringFromUser(user_arg)) ) goto cleanup_argv; } envc = 0; while ( true ) { const char* user_env; if ( !CopyFromUser(&user_env, user_envp + envc, sizeof(user_env)) ) goto cleanup_argv; if ( !user_env ) break; if ( ++envc == INT_MAX ) { errno = E2BIG; goto cleanup_argv; } } envp = new char*[envc+1]; if ( !envp ) goto cleanup_argv; memset(envp, 0, sizeof(char*) * (envc+1)); for ( int i = 0; i < envc; i++ ) { const char* user_env; if ( !CopyFromUser(&user_env, user_envp + i, sizeof(user_env)) ) goto cleanup_envp; if ( !(envp[i] = GetStringFromUser(user_envp[i])) ) goto cleanup_envp; } result = sys_execve_kernel(filename, argc, argv, envc, envp, ®s); cleanup_envp: for ( int i = 0; i < envc; i++) delete[] envp[i]; delete[] envp; cleanup_argv: for ( int i = 0; i < argc; i++) delete[] argv[i]; delete[] argv; cleanup_filename: delete[] filename; cleanup_done: if ( result == 0 ) LoadRegisters(®s); return result; } pid_t sys_tfork(int flags, struct tfork* user_regs) { struct tfork regs; if ( !CopyFromUser(®s, user_regs, sizeof(regs)) ) return -1; if ( Signal::IsPending() ) return errno = EINTR, -1; bool making_process = flags == SFFORK; bool making_thread = (flags & (SFPROC | SFPID | SFFD | SFMEM | SFCWD | SFROOT)) == SFPROC; // TODO: Properly support tfork(2). if ( !(making_thread || making_process) ) return errno = ENOSYS, -1; if ( regs.altstack.ss_flags & ~__SS_SUPPORTED_FLAGS ) return errno = EINVAL, -1; size_t stack_alignment = 16; // TODO: Is it a hack to create a new kernel stack here? Thread* curthread = CurrentThread(); size_t newkernelstacksize = curthread->kernelstacksize; uint8_t* newkernelstack = new uint8_t[newkernelstacksize + stack_alignment]; if ( !newkernelstack ) return -1; uintptr_t stack_aligned = (uintptr_t) newkernelstack; size_t stack_aligned_size = newkernelstacksize; if ( ((uintptr_t) stack_aligned) & (stack_alignment-1) ) stack_aligned = (stack_aligned + 16) & ~(stack_alignment-1); stack_aligned_size &= 0xFFFFFFF0; Process* child_process; if ( making_thread ) child_process = CurrentProcess(); else if ( !(child_process = CurrentProcess()->Fork()) ) { delete[] newkernelstack; return -1; } struct thread_registers cpuregs; memset(&cpuregs, 0, sizeof(cpuregs)); #if defined(__i386__) cpuregs.eip = regs.eip; cpuregs.esp = regs.esp; cpuregs.eax = regs.eax; cpuregs.ebx = regs.ebx; cpuregs.ecx = regs.ecx; cpuregs.edx = regs.edx; cpuregs.edi = regs.edi; cpuregs.esi = regs.esi; cpuregs.ebp = regs.ebp; cpuregs.cs = UCS | URPL; cpuregs.ds = UDS | URPL; cpuregs.ss = UDS | URPL; cpuregs.eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; cpuregs.fsbase = regs.fsbase; cpuregs.gsbase = regs.gsbase; cpuregs.cr3 = child_process->addrspace; cpuregs.kernel_stack = stack_aligned + stack_aligned_size; memcpy(&cpuregs.fpuenv, Float::fpu_initialized_regs, 512); #elif defined(__x86_64__) cpuregs.rip = regs.rip; cpuregs.rsp = regs.rsp; cpuregs.rax = regs.rax; cpuregs.rbx = regs.rbx; cpuregs.rcx = regs.rcx; cpuregs.rdx = regs.rdx; cpuregs.rdi = regs.rdi; cpuregs.rsi = regs.rsi; cpuregs.rbp = regs.rbp; cpuregs.r8 = regs.r8; cpuregs.r9 = regs.r9; cpuregs.r10 = regs.r10; cpuregs.r11 = regs.r11; cpuregs.r12 = regs.r12; cpuregs.r13 = regs.r13; cpuregs.r14 = regs.r14; cpuregs.r15 = regs.r15; cpuregs.cs = UCS | URPL; cpuregs.ds = UDS | URPL; cpuregs.ss = UDS | URPL; cpuregs.rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; cpuregs.fsbase = regs.fsbase; cpuregs.gsbase = regs.gsbase; cpuregs.cr3 = child_process->addrspace; cpuregs.kernel_stack = stack_aligned + stack_aligned_size; memcpy(&cpuregs.fpuenv, Float::fpu_initialized_regs, 512); #else #warning "You need to implement initializing the registers of the new thread" #endif // Forbid the creation of new threads if init has exited. ScopedLock process_family_lock_lock(&process_family_lock); if ( is_init_exiting ) return errno = EPERM, -1; // If the thread could not be created, make the process commit suicide // in a manner such that we don't wait for its zombie. Thread* thread = CreateKernelThread(child_process, &cpuregs, making_process ? "main" : "second"); process_family_lock_lock.Reset(); if ( !thread ) { if ( making_process ) child_process->AbortConstruction(); return -1; } thread->kernelstackpos = (addr_t) newkernelstack; thread->kernelstacksize = newkernelstacksize; thread->kernelstackmalloced = true; memcpy(&thread->signal_mask, ®s.sigmask, sizeof(sigset_t)); memcpy(&thread->signal_stack, ®s.altstack, sizeof(stack_t)); StartKernelThread(thread); return child_process->pid; } pid_t sys_getpid(void) { return CurrentProcess()->pid; } pid_t sys_getppid(void) { Process* process = CurrentProcess(); ScopedLock lock(&process_family_lock); if ( !process->parent ) return 0; return process->parent->pid; } pid_t sys_getpgid(pid_t pid) { ScopedLock lock(&process_family_lock); Process* process = !pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid); if ( !process ) return errno = ESRCH, -1; if ( !process->group ) return errno = ESRCH, -1; return process->group->pid; } pid_t sys_getsid(pid_t pid) { ScopedLock lock(&process_family_lock); Process* process = !pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid); if ( !process ) return errno = ESRCH, -1; if ( !process->session ) return errno = ESRCH, -1; return process->session->pid; } int sys_setpgid(pid_t pid, pid_t pgid) { if ( pid < 0 || pgid < 0 ) return errno = EINVAL, -1; // TODO: Either prevent changing the process group after an exec or provide // a version of this system call with a flags parameter that lets you // decide if you want this behavior. This will fix a race condition // where the shell spawns a child and both parent and child sets the // process group, but the child sets the process group and execve's // and the new program image exploits this 'bug' and also changes the // process group, and then the shell gets around to change the process // group. This probably unlikely, but correctness over all! Process* current_process = CurrentProcess(); ScopedLock lock(&process_family_lock); // Find the processes in question. Process* process = !pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid); if ( !process ) return errno = ESRCH, -1; Process* group = !pgid ? process : CurrentProcess()->GetPTable()->Get(pgid); if ( !group ) return errno = ESRCH, -1; // The process must be this one or a direct child. if ( process != current_process && process->parent != current_process ) return errno = EPERM, -1; // The process must be in this session. if ( process->session != current_process->session ) return errno = EPERM, -1; // The new group must be in the same session as the process. if ( group->session != process->session ) return errno = EPERM, -1; // The process must not be a process group leader. // TODO: Maybe POSIX actually allows this. if ( process->groupfirst ) return errno = EPERM, -1; // The process must not be a session leader. if ( process->sessionfirst ) return errno = EPERM, -1; // The group must either exist or be the process itself. if ( !group->groupfirst && group != process ) return errno = EPERM, -1; // Exit early if this is a noop. if ( process->group == group ) return 0; // Remove the process from its current process group. if ( process->group ) process->group->GroupRemoveMember(process); // Insert the process into its new process group. process->groupprev = NULL; process->groupnext = group->groupfirst; if ( group->groupfirst ) group->groupfirst->groupprev = process; group->groupfirst = process; process->group = group; return 0; } pid_t sys_setsid(void) { Process* process = CurrentProcess(); ScopedLock lock(&process_family_lock); // Test if already a process group leader. if ( process->group == process ) return errno = EPERM, -1; // Remove the process from its current process group. if ( process->group ) process->group->GroupRemoveMember(process); // Remove the process from its current session. if ( process->session ) process->session->SessionRemoveMember(process); // Insert the process into its new session. process->sessionprev = NULL; process->sessionnext = NULL; process->sessionfirst = process; process->session = process; // Insert the process into its new process group. process->groupprev = NULL; process->groupnext = NULL; process->groupfirst = process; process->group = process; return process->pid; } size_t sys_getpagesize(void) { return Page::Size(); } mode_t sys_umask(mode_t newmask) { Process* process = CurrentProcess(); ScopedLock lock(&process->idlock); mode_t oldmask = process->umask; process->umask = newmask & 0666; return oldmask; } mode_t sys_getumask(void) { Process* process = CurrentProcess(); ScopedLock lock(&process->idlock); return process->umask; } static void GetAssertInfo(struct scram_assert* info, const void* user_info_ptr) { memset(info, 0, sizeof(*info)); struct scram_assert user_info; if ( !CopyFromUser(&user_info, user_info_ptr, sizeof(user_info)) ) return; info->filename = GetStringFromUser(user_info.filename); info->line = user_info.line; info->function = GetStringFromUser(user_info.function); info->expression = GetStringFromUser(user_info.expression); } static void FreeAssertInfo(struct scram_assert* info) { delete[] (char*) info->filename; delete[] (char*) info->function; delete[] (char*) info->expression; } static void GetUndefinedBehaviorInfo(struct scram_undefined_behavior* info, const void* user_info_ptr) { memset(info, 0, sizeof(*info)); struct scram_undefined_behavior user_info; if ( !CopyFromUser(&user_info, user_info_ptr, sizeof(user_info)) ) return; info->filename = GetStringFromUser(user_info.filename); info->line = user_info.line; info->column = user_info.column; info->violation = GetStringFromUser(user_info.violation); } static void FreeUndefinedBehaviorInfo(struct scram_undefined_behavior* info) { delete[] info->filename; delete[] info->violation; } __attribute__((noreturn)) void sys_scram(int event, const void* user_info) { Process* process = CurrentProcess(); // TODO: Prohibit execve such that program_image_path is protected. process->ExitThroughSignal(SIGABRT); if ( event == SCRAM_ASSERT ) { struct scram_assert info; GetAssertInfo(&info, user_info); Log::PrintF("%s[%ji]: Assertion failure: %s:%lu: %s: %s\n", process->program_image_path, (intmax_t) process->pid, info.filename ? info.filename : "", info.line, info.function ? info.function : "", info.expression ? info.expression : ""); FreeAssertInfo(&info); } else if ( event == SCRAM_STACK_SMASH ) { Log::PrintF("%s[%ji]: Stack smashing detected\n", process->program_image_path, (intmax_t) process->pid); } else if ( event == SCRAM_UNDEFINED_BEHAVIOR ) { struct scram_undefined_behavior info; GetUndefinedBehaviorInfo(&info, user_info); Log::PrintF("%s[%ji]: Undefined behavior: %s at %s:%lu:%lu\n", process->program_image_path, (intmax_t) process->pid, info.violation ? info.violation : "", info.filename ? info.filename : "", info.line, info.column); FreeUndefinedBehaviorInfo(&info); } else { Log::PrintF("%s[%ji]: Unknown scram event %i\n", process->program_image_path, (intmax_t) process->pid, event); } // TODO: Allow debugging this event (and see signal.cpp sigreturn). kthread_exit(); } } // namespace Sortix