| // Copyright 2016 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <object/thread_dispatcher.h> |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <inttypes.h> |
| #include <platform.h> |
| #include <string.h> |
| #include <trace.h> |
| |
| #include <arch/debugger.h> |
| #include <arch/exception.h> |
| |
| #include <kernel/thread.h> |
| #include <vm/kstack.h> |
| #include <vm/vm.h> |
| #include <vm/vm_address_region.h> |
| #include <vm/vm_aspace.h> |
| #include <vm/vm_object_paged.h> |
| |
| #include <zircon/rights.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/types.h> |
| |
| #include <object/excp_port.h> |
| #include <object/handle.h> |
| #include <object/job_dispatcher.h> |
| #include <object/process_dispatcher.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| |
| #include <lib/counters.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| KCOUNTER(dispatcher_thread_create_count, "dispatcher.thread.create") |
| KCOUNTER(dispatcher_thread_destroy_count, "dispatcher.thread.destroy") |
| |
| // static |
| zx_status_t ThreadDispatcher::Create(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags, |
| fbl::StringPiece name, |
| KernelHandle<ThreadDispatcher>* out_handle, |
| zx_rights_t* out_rights) { |
| // Make sure we contribute a FutexState object to our process's futex state |
| // pool before allocating the thread. If we cannot grow the pool, then we |
| // cannot make a thread. |
| zx_status_t result = process->futex_context().GrowFutexStatePool(); |
| if (result != ZX_OK) |
| return result; |
| |
| fbl::AllocChecker ac; |
| KernelHandle new_handle(fbl::AdoptRef(new (&ac) ThreadDispatcher(process, flags))); |
| if (!ac.check()) { |
| // We grew the state pool, but failed to allocate the thread. Go ahead |
| // and shrink the pool back down to size. Otherwise, the thread class |
| // we created will shrink the pool when the time comes |
| process->futex_context().ShrinkFutexStatePool(); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| result = new_handle.dispatcher()->Initialize(name.data(), name.length()); |
| if (result != ZX_OK) |
| return result; |
| |
| *out_rights = default_rights(); |
| *out_handle = ktl::move(new_handle); |
| return ZX_OK; |
| } |
| |
| ThreadDispatcher::ThreadDispatcher(fbl::RefPtr<ProcessDispatcher> process, |
| uint32_t flags) |
| : process_(ktl::move(process)), exceptionate_(ExceptionPort::Type::THREAD) { |
| LTRACE_ENTRY_OBJ; |
| |
| kcounter_add(dispatcher_thread_create_count, 1); |
| } |
| |
| ThreadDispatcher::~ThreadDispatcher() { |
| LTRACE_ENTRY_OBJ; |
| |
| kcounter_add(dispatcher_thread_destroy_count, 1); |
| |
| DEBUG_ASSERT(&thread_ != get_current_thread()); |
| |
| switch (state_.lifecycle()) { |
| case ThreadState::Lifecycle::DEAD: { |
| // join the LK thread before doing anything else to clean up LK state and ensure |
| // the thread we're destroying has stopped. |
| LTRACEF("joining LK thread to clean up state\n"); |
| __UNUSED auto ret = thread_join(&thread_, nullptr, ZX_TIME_INFINITE); |
| LTRACEF("done joining LK thread\n"); |
| DEBUG_ASSERT_MSG(ret == ZX_OK, "thread_join returned something other than ZX_OK\n"); |
| break; |
| } |
| case ThreadState::Lifecycle::INITIAL: |
| // this gets a pass, we can destruct a partially constructed thread |
| break; |
| case ThreadState::Lifecycle::INITIALIZED: |
| // as we've been initialized previously, forget the LK thread. |
| // note that thread_forget is not called for self since the thread is not running |
| thread_forget(&thread_); |
| break; |
| default: |
| DEBUG_ASSERT_MSG(false, "bad state %s, this %p\n", |
| ThreadLifecycleToString(state_.lifecycle()), this); |
| } |
| |
| event_destroy(&exception_event_); |
| |
| process_->futex_context().ShrinkFutexStatePool(); |
| } |
| |
| // complete initialization of the thread object outside of the constructor |
| zx_status_t ThreadDispatcher::Initialize(const char* name, size_t len) { |
| LTRACE_ENTRY_OBJ; |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // Make sure LK's max name length agrees with ours. |
| static_assert(THREAD_NAME_LENGTH == ZX_MAX_NAME_LEN, "name length issue"); |
| if (len >= ZX_MAX_NAME_LEN) |
| len = ZX_MAX_NAME_LEN - 1; |
| |
| char thread_name[THREAD_NAME_LENGTH]; |
| memcpy(thread_name, name, len); |
| memset(thread_name + len, 0, ZX_MAX_NAME_LEN - len); |
| |
| // create an underlying LK thread |
| thread_t* lkthread = thread_create_etc( |
| &thread_, thread_name, StartRoutine, this, DEFAULT_PRIORITY, nullptr); |
| |
| if (!lkthread) { |
| TRACEF("error creating thread\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| DEBUG_ASSERT(lkthread == &thread_); |
| |
| // register an event handler with the LK kernel |
| thread_set_user_callback(&thread_, &ThreadUserCallback); |
| |
| // set the per-thread pointer |
| lkthread->user_thread = this; |
| |
| // associate the proc's address space with this thread |
| process_->aspace()->AttachToThread(lkthread); |
| |
| // we've entered the initialized state |
| SetStateLocked(ThreadState::Lifecycle::INITIALIZED); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t ThreadDispatcher::set_name(const char* name, size_t len) { |
| canary_.Assert(); |
| |
| // ignore characters after the first NUL |
| len = strnlen(name, len); |
| |
| if (len >= ZX_MAX_NAME_LEN) |
| len = ZX_MAX_NAME_LEN - 1; |
| |
| Guard<SpinLock, IrqSave> guard{&name_lock_}; |
| memcpy(thread_.name, name, len); |
| memset(thread_.name + len, 0, ZX_MAX_NAME_LEN - len); |
| return ZX_OK; |
| } |
| |
| void ThreadDispatcher::get_name(char out_name[ZX_MAX_NAME_LEN]) const { |
| canary_.Assert(); |
| |
| Guard<SpinLock, IrqSave> guard{&name_lock_}; |
| memset(out_name, 0, ZX_MAX_NAME_LEN); |
| strlcpy(out_name, thread_.name, ZX_MAX_NAME_LEN); |
| } |
| |
| // start a thread |
| zx_status_t ThreadDispatcher::Start(const EntryState& entry, bool initial_thread) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| is_initial_thread_ = initial_thread; |
| |
| // add ourselves to the process, which may fail if the process is in a dead state. |
| // If the process is live then it will call our StartRunning routine. |
| return process_->AddInitializedThread(this, initial_thread, entry); |
| } |
| |
| zx_status_t ThreadDispatcher::MakeRunnable(const EntryState& entry, bool suspended) { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| if (state_.lifecycle() != ThreadState::Lifecycle::INITIALIZED) |
| return ZX_ERR_BAD_STATE; |
| |
| // save the user space entry state |
| user_entry_ = entry; |
| |
| // update our suspend count to account for our parent state |
| if (suspended) |
| suspend_count_++; |
| |
| // bump the ref on this object that the LK thread state will now own until the lk thread has exited |
| AddRef(); |
| thread_.user_tid = get_koid(); |
| thread_.user_pid = process_->get_koid(); |
| |
| // start the thread in RUNNING state, if we're starting suspended it will transition to |
| // SUSPENDED when it checks thread signals before executing any user code |
| SetStateLocked(ThreadState::Lifecycle::RUNNING); |
| |
| if (suspend_count_ == 0) { |
| thread_resume(&thread_); |
| } else { |
| // thread_suspend() only fails if the underlying thread is already dead, which we should |
| // ignore here to match the behavior of thread_resume(); our Exiting() callback will run |
| // shortly to clean us up |
| thread_suspend(&thread_); |
| } |
| |
| return ZX_OK; |
| } |
| |
| // called in the context of our thread |
| void ThreadDispatcher::Exit() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // only valid to call this on the current thread |
| DEBUG_ASSERT(get_current_thread() == &thread_); |
| |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| SetStateLocked(ThreadState::Lifecycle::DYING); |
| } |
| |
| // exit here |
| // this will recurse back to us in ::Exiting() |
| thread_exit(0); |
| |
| __UNREACHABLE; |
| } |
| |
| void ThreadDispatcher::Kill() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| switch (state_.lifecycle()) { |
| case ThreadState::Lifecycle::INITIAL: |
| case ThreadState::Lifecycle::INITIALIZED: |
| // thread was never started, leave in this state |
| break; |
| case ThreadState::Lifecycle::RUNNING: |
| case ThreadState::Lifecycle::SUSPENDED: |
| // deliver a kernel kill signal to the thread |
| thread_kill(&thread_); |
| |
| // enter the dying state |
| SetStateLocked(ThreadState::Lifecycle::DYING); |
| break; |
| case ThreadState::Lifecycle::DYING: |
| case ThreadState::Lifecycle::DEAD: |
| // already going down |
| break; |
| } |
| } |
| |
| zx_status_t ThreadDispatcher::Suspend() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| LTRACEF("%p: state %s\n", this, ThreadLifecycleToString(state_.lifecycle())); |
| |
| // Update |suspend_count_| in all cases so that we can always verify a sane value - it's |
| // possible both Suspend() and Resume() get called while the thread is DYING. |
| DEBUG_ASSERT(suspend_count_ >= 0); |
| suspend_count_++; |
| |
| switch (state_.lifecycle()) { |
| case ThreadState::Lifecycle::INITIAL: |
| // Unreachable, thread leaves INITIAL state before Create() returns. |
| DEBUG_ASSERT(false); |
| __UNREACHABLE; |
| case ThreadState::Lifecycle::INITIALIZED: |
| // If the thread hasn't started yet, don't actually try to suspend it. We need to let |
| // Start() run first to set up userspace entry data, which will then suspend if the count |
| // is still >0 at that time. |
| return ZX_OK; |
| case ThreadState::Lifecycle::RUNNING: |
| case ThreadState::Lifecycle::SUSPENDED: |
| if (suspend_count_ == 1) |
| return thread_suspend(&thread_); |
| return ZX_OK; |
| case ThreadState::Lifecycle::DYING: |
| case ThreadState::Lifecycle::DEAD: |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| DEBUG_ASSERT(false); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| void ThreadDispatcher::Resume() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| LTRACEF("%p: state %s\n", this, ThreadLifecycleToString(state_.lifecycle())); |
| |
| DEBUG_ASSERT(suspend_count_ > 0); |
| suspend_count_--; |
| |
| switch (state_.lifecycle()) { |
| case ThreadState::Lifecycle::INITIAL: |
| // Unreachable, thread leaves INITIAL state before Create() returns. |
| DEBUG_ASSERT(false); |
| __UNREACHABLE; |
| case ThreadState::Lifecycle::INITIALIZED: |
| break; |
| case ThreadState::Lifecycle::RUNNING: |
| case ThreadState::Lifecycle::SUSPENDED: |
| // It's possible the thread never transitioned from RUNNING -> SUSPENDED. |
| if (suspend_count_ == 0) |
| thread_resume(&thread_); |
| break; |
| case ThreadState::Lifecycle::DYING: |
| case ThreadState::Lifecycle::DEAD: |
| // If it's dying or dead then bail. |
| break; |
| } |
| } |
| |
| bool ThreadDispatcher::IsDyingOrDead() const { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| return IsDyingOrDeadLocked(); |
| } |
| |
| bool ThreadDispatcher::IsDyingOrDeadLocked() const { |
| return state_.lifecycle() == ThreadState::Lifecycle::DYING || |
| state_.lifecycle() == ThreadState::Lifecycle::DEAD; |
| } |
| |
| static void ThreadCleanupDpc(dpc_t* d) { |
| LTRACEF("dpc %p\n", d); |
| |
| ThreadDispatcher* t = reinterpret_cast<ThreadDispatcher*>(d->arg); |
| DEBUG_ASSERT(t); |
| |
| delete t; |
| } |
| |
| void ThreadDispatcher::Exiting() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // Notify a debugger if attached. Do this before marking the thread as |
| // dead: the debugger expects to see the thread in the DYING state, it may |
| // try to read thread registers. The debugger still has to handle the case |
| // where the process is also dying (and thus the thread could transition |
| // DYING->DEAD from underneath it), but that's life (or death :-)). |
| // N.B. OnThreadExitForDebugger will block in ExceptionHandlerExchange, so |
| // don't hold the process's |state_lock_| across the call. |
| { |
| fbl::RefPtr<ExceptionPort> eport(process_->debugger_exception_port()); |
| if (eport) { |
| eport->OnThreadExitForDebugger(this); |
| } |
| } |
| // Thread exit exceptions don't currently provide an iframe. |
| arch_exception_context_t context{}; |
| HandleSingleShotException(process_->exceptionate(Exceptionate::Type::kDebug), |
| ZX_EXCP_THREAD_EXITING, context); |
| |
| // Mark the thread as dead. Do this before removing the thread from the |
| // process because if this is the last thread then the process will be |
| // marked dead, and we don't want to have a state where the process is |
| // dead but one thread is not. |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // put ourselves into the dead state |
| SetStateLocked(ThreadState::Lifecycle::DEAD); |
| } |
| |
| // Drop our exception channel endpoint so any userspace listener |
| // gets the PEER_CLOSED signal. |
| exceptionate_.Shutdown(); |
| |
| // remove ourselves from our parent process's view |
| process_->RemoveThread(this); |
| |
| // drop LK's reference |
| if (Release()) { |
| // We're the last reference, so will need to destruct ourself while running, which is not possible |
| // Use a dpc to pull this off |
| cleanup_dpc_.func = ThreadCleanupDpc; |
| cleanup_dpc_.arg = this; |
| |
| // disable interrupts before queuing the dpc to prevent starving the DPC thread if it starts running |
| // before we're completed. |
| // disabling interrupts effectively raises us to maximum priority on this cpu. |
| // note this is only safe because we're about to exit the thread permanently so the context |
| // switch will effectively reenable interrupts in the new thread. |
| arch_disable_ints(); |
| |
| // queue without reschedule since us exiting is a reschedule event already |
| dpc_queue(&cleanup_dpc_, false); |
| } |
| |
| // after this point the thread will stop permanently |
| LTRACE_EXIT_OBJ; |
| } |
| |
| void ThreadDispatcher::Suspending() { |
| LTRACE_ENTRY_OBJ; |
| |
| // Update the state before sending any notifications out. We want the |
| // receiver to see the new state. |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // Don't suspend if we are racing with our own death. |
| if (state_.lifecycle() != ThreadState::Lifecycle::DYING) { |
| SetStateLocked(ThreadState::Lifecycle::SUSPENDED); |
| } |
| } |
| |
| LTRACE_EXIT_OBJ; |
| } |
| |
| void ThreadDispatcher::Resuming() { |
| LTRACE_ENTRY_OBJ; |
| |
| // Update the state before sending any notifications out. We want the |
| // receiver to see the new state. |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // Don't resume if we are racing with our own death. |
| if (state_.lifecycle() != ThreadState::Lifecycle::DYING) { |
| SetStateLocked(ThreadState::Lifecycle::RUNNING); |
| } |
| } |
| |
| LTRACE_EXIT_OBJ; |
| } |
| |
| // low level LK callback in thread's context just before exiting |
| void ThreadDispatcher::ThreadUserCallback(enum thread_user_state_change new_state, thread_t* arg) { |
| ThreadDispatcher* t = arg->user_thread; |
| DEBUG_ASSERT(t != nullptr); |
| |
| switch (new_state) { |
| case THREAD_USER_STATE_EXIT: |
| t->Exiting(); |
| return; |
| case THREAD_USER_STATE_SUSPEND: |
| t->Suspending(); |
| return; |
| case THREAD_USER_STATE_RESUME: |
| t->Resuming(); |
| return; |
| } |
| } |
| |
| // low level LK entry point for the thread |
| int ThreadDispatcher::StartRoutine(void* arg) { |
| LTRACE_ENTRY; |
| |
| ThreadDispatcher* t = (ThreadDispatcher*)arg; |
| |
| // IWBN to dump the values just before calling |arch_enter_uspace()| |
| // but at that point they're in |iframe| and may have been modified by |
| // the debugger user, and fetching them out of the iframe will require |
| // architecture-specific code. Instead just print them here. This is just |
| // for tracing which is generally off, and then only time the values will |
| // have changed is if a debugger user changes them. KISS. |
| LTRACEF("arch_enter_uspace SP: %#" PRIxPTR " PC: %#" PRIxPTR |
| ", ARG1: %#" PRIxPTR ", ARG2: %#" PRIxPTR "\n", |
| t->user_entry_.sp, t->user_entry_.pc, t->user_entry_.arg1, t->user_entry_.arg2); |
| |
| // Initialize an iframe for entry into userspace. |
| // We need all registers accessible from the ZX_EXCP_THREAD_STARTING |
| // exception handler (the debugger wants the thread to look as if the |
| // thread is at the first instruction). For architectural exceptions the |
| // general regs are left in the iframe for speed and simplicity. To keep |
| // things simple we use the same scheme. |
| iframe_t iframe{}; |
| arch_setup_uspace_iframe(&iframe, t->user_entry_.pc, t->user_entry_.sp, |
| t->user_entry_.arg1, t->user_entry_.arg2); |
| |
| arch_exception_context_t context{}; |
| context.frame = &iframe; |
| |
| // Notify job debugger if attached. |
| if (t->is_initial_thread_) { |
| t->process_->OnProcessStartForJobDebugger(t, &context); |
| } |
| |
| // Notify debugger if attached. |
| // This is done by first obtaining our own reference to the port so the |
| // test can be done safely. Note that this function doesn't return so we |
| // need the reference to go out of scope before then. |
| { |
| fbl::RefPtr<ExceptionPort> debugger_port(t->process_->debugger_exception_port()); |
| if (debugger_port) { |
| debugger_port->OnThreadStartForDebugger(t, &context); |
| } |
| } |
| t->HandleSingleShotException(t->process_->exceptionate(Exceptionate::Type::kDebug), |
| ZX_EXCP_THREAD_STARTING, context); |
| |
| thread_process_pending_signals(); |
| |
| // switch to user mode and start the process |
| arch_enter_uspace(&iframe); |
| __UNREACHABLE; |
| } |
| |
| void ThreadDispatcher::SetStateLocked(ThreadState::Lifecycle lifecycle) { |
| canary_.Assert(); |
| |
| LTRACEF("thread %p: state %u (%s)\n", this, static_cast<unsigned int>(lifecycle), |
| ThreadLifecycleToString(lifecycle)); |
| |
| DEBUG_ASSERT(get_lock()->lock().IsHeld()); |
| |
| state_.set(lifecycle); |
| |
| switch (lifecycle) { |
| case ThreadState::Lifecycle::RUNNING: |
| UpdateStateLocked(ZX_THREAD_SUSPENDED, ZX_THREAD_RUNNING); |
| break; |
| case ThreadState::Lifecycle::SUSPENDED: |
| UpdateStateLocked(ZX_THREAD_RUNNING, ZX_THREAD_SUSPENDED); |
| break; |
| case ThreadState::Lifecycle::DEAD: |
| UpdateStateLocked(ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED, ZX_THREAD_TERMINATED); |
| break; |
| default: |
| // Nothing to do. |
| // In particular, for the DYING state we don't modify the SUSPENDED |
| // or RUNNING signals: For observer purposes they'll only be interested |
| // in the transition from {SUSPENDED,RUNNING} to DEAD. |
| break; |
| } |
| } |
| |
| zx_status_t ThreadDispatcher::SetExceptionPort(fbl::RefPtr<ExceptionPort> eport) { |
| canary_.Assert(); |
| |
| DEBUG_ASSERT(eport->type() == ExceptionPort::Type::THREAD); |
| |
| // Lock |state_lock_| to ensure the thread doesn't transition to dead |
| // while we're setting the exception handler. |
| Guard<fbl::Mutex> guard{get_lock()}; |
| if (state_.lifecycle() == ThreadState::Lifecycle::DEAD) |
| return ZX_ERR_NOT_FOUND; |
| if (exception_port_) |
| return ZX_ERR_ALREADY_BOUND; |
| exception_port_ = eport; |
| |
| return ZX_OK; |
| } |
| |
| bool ThreadDispatcher::ResetExceptionPort() { |
| canary_.Assert(); |
| |
| fbl::RefPtr<ExceptionPort> eport; |
| |
| // Remove the exception handler first. If the thread resumes execution |
| // we don't want it to hit another exception and get back into |
| // ExceptionHandlerExchange. |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| exception_port_.swap(eport); |
| if (eport == nullptr) { |
| // Attempted to unbind when no exception port is bound. |
| return false; |
| } |
| // This method must guarantee that no caller will return until |
| // OnTargetUnbind has been called on the port-to-unbind. |
| // This becomes important when a manual unbind races with a |
| // PortDispatcher::on_zero_handles auto-unbind. |
| // |
| // If OnTargetUnbind were called outside of the lock, it would lead to |
| // a race (for threads A and B): |
| // |
| // A: Calls ResetExceptionPort; acquires the lock |
| // A: Sees a non-null exception_port_, swaps it into the eport local. |
| // exception_port_ is now null. |
| // A: Releases the lock |
| // |
| // B: Calls ResetExceptionPort; acquires the lock |
| // B: Sees a null exception_port_ and returns. But OnTargetUnbind() |
| // hasn't yet been called for the port. |
| // |
| // So, call it before releasing the lock |
| eport->OnTargetUnbind(); |
| } |
| |
| OnExceptionPortRemoval(eport); |
| return true; |
| } |
| |
| fbl::RefPtr<ExceptionPort> ThreadDispatcher::exception_port() { |
| canary_.Assert(); |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| return exception_port_; |
| } |
| |
| zx_status_t ThreadDispatcher::ExceptionHandlerExchange( |
| fbl::RefPtr<ExceptionPort> eport, |
| const zx_exception_report_t* report, |
| const arch_exception_context_t* arch_context, |
| ThreadState::Exception* out_estatus) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // Note: As far as userspace is concerned there is no state change that we would notify state |
| // tracker observers of, currently. |
| // |
| // Send message, wait for reply. Note that there is a "race" that we need handle: We need to |
| // send the exception report before going to sleep, but what if the receiver of the report gets |
| // it and processes it before we are asleep? This is handled by locking state_lock_ in places |
| // where the handler can see/modify thread state. |
| |
| EnterException(eport, report, arch_context); |
| |
| zx_status_t status; |
| |
| { |
| // The caller may have already done this, but do it again for the |
| // one-off callers like the debugger synthetic exceptions. |
| AutoBlocked by(Blocked::EXCEPTION); |
| |
| // There's no need to send the message under the lock, but we do need to make sure our |
| // exception state and blocked state are up to date before sending the message. Otherwise, a |
| // debugger could get the packet and observe them before we've updated them. Thus, send the |
| // packet after updating both exception state and blocked state. |
| status = eport->SendPacket(this, report->header.type); |
| if (status != ZX_OK) { |
| // Can't send the request to the exception handler. Report the error, which will |
| // probably kill the process. |
| LTRACEF("SendPacket returned %d\n", status); |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // The report didn't go out, but we're in an exception here so a |
| // buggy handler could have tried to respond. Clear any event out. |
| event_unsignal(&exception_event_); |
| |
| ExitExceptionLocked(); |
| return status; |
| } |
| |
| // Continue to wait for the exception response if we get suspended. |
| // If it is suspended, the suspension will be processed after the |
| // exception response is received (requiring a second resume). |
| // Exceptions and suspensions are essentially treated orthogonally. |
| |
| do { |
| status = event_wait_with_mask(&exception_event_, THREAD_SIGNAL_SUSPEND); |
| } while (status == ZX_ERR_INTERNAL_INTR_RETRY); |
| } |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // Note: If |status| != ZX_OK, then |state_| is still |
| // ThreadState::Exception::UNPROCESSED. |
| switch (status) { |
| case ZX_OK: |
| // Fetch TRY_NEXT/RESUME status. |
| *out_estatus = state_.exception(); |
| DEBUG_ASSERT(*out_estatus == ThreadState::Exception::TRY_NEXT || |
| *out_estatus == ThreadState::Exception::RESUME); |
| break; |
| case ZX_ERR_INTERNAL_INTR_KILLED: |
| // Thread was killed. |
| // Ensure |exception_event_| is no longer signaled. |
| event_unsignal(&exception_event_); |
| break; |
| default: |
| ASSERT_MSG(false, "unexpected exception result: %d\n", status); |
| __UNREACHABLE; |
| } |
| |
| ExitExceptionLocked(); |
| |
| LTRACEF("returning status %d, estatus %d\n", |
| status, static_cast<int>(*out_estatus)); |
| return status; |
| } |
| |
| void ThreadDispatcher::EnterException(fbl::RefPtr<ExceptionPort> eport, |
| const zx_exception_report_t* report, |
| const arch_exception_context_t* arch_context) { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // |exception_event_| can't be signaled yet, we're not in an exception yet. |
| DEBUG_ASSERT(!event_signaled(&exception_event_)); |
| |
| // Mark that we're in an exception. |
| thread_.exception_context = arch_context; |
| |
| // For GetExceptionReport. |
| exception_report_ = report; |
| |
| // For OnExceptionPortRemoval in case the port is unbound. |
| DEBUG_ASSERT(exception_wait_port_ == nullptr); |
| exception_wait_port_ = eport; |
| |
| state_.set(ThreadState::Exception::UNPROCESSED); |
| } |
| |
| void ThreadDispatcher::ExitExceptionLocked() { |
| // It's critical that at this point the event no longer be armed. |
| // Otherwise the next time we get an exception we'll fall right through |
| // without waiting for an exception response. |
| // Note: The event could be signaled after event_wait_with_mask returns |
| // if the thread was killed while the event was signaled. |
| DEBUG_ASSERT(!event_signaled(&exception_event_)); |
| |
| exception_wait_port_.reset(); |
| exception_report_ = nullptr; |
| thread_.exception_context = nullptr; |
| state_.set(ThreadState::Exception::IDLE); |
| } |
| |
| zx_status_t ThreadDispatcher::MarkExceptionHandledWorker(PortDispatcher* eport, |
| ThreadState::Exception handled_state) { |
| canary_.Assert(); |
| |
| LTRACEF("obj %p, handled_state %d\n", this, static_cast<int>(handled_state)); |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| if (!InPortExceptionLocked()) |
| return ZX_ERR_BAD_STATE; |
| |
| // The exception port isn't used directly but is instead proof that the caller has |
| // permission to resume from the exception. So validate that it corresponds to the |
| // task being resumed. |
| if (!exception_wait_port_->PortMatches(eport, /* allow_null */ false)) |
| return ZX_ERR_ACCESS_DENIED; |
| |
| // The thread can be in several states at this point. Alas this is a bit complicated because |
| // there is a window in the middle of ExceptionHandlerExchange between the thread going to sleep |
| // and after the thread waking up where we can obtain the lock. Things are further complicated |
| // by the fact that OnExceptionPortRemoval could get there first, or we might get called a |
| // second time for the same exception. It's critical that we don't re-arm the event after the |
| // thread wakes up. To keep things simple we take a first-one-wins approach. |
| if (state_.exception() != ThreadState::Exception::UNPROCESSED) |
| return ZX_ERR_BAD_STATE; |
| |
| state_.set(handled_state); |
| event_signal(&exception_event_, true); |
| return ZX_OK; |
| } |
| |
| zx_status_t ThreadDispatcher::MarkExceptionHandled(PortDispatcher* eport) { |
| return MarkExceptionHandledWorker(eport, ThreadState::Exception::RESUME); |
| } |
| |
| zx_status_t ThreadDispatcher::MarkExceptionNotHandled(PortDispatcher* eport) { |
| return MarkExceptionHandledWorker(eport, ThreadState::Exception::TRY_NEXT); |
| } |
| |
| void ThreadDispatcher::OnExceptionPortRemoval(const fbl::RefPtr<ExceptionPort>& eport) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| Guard<fbl::Mutex> guard{get_lock()}; |
| if (!InPortExceptionLocked()) |
| return; |
| if (exception_wait_port_ == eport) { |
| // Leave things alone if already processed. See MarkExceptionHandled. |
| if (state_.exception() == ThreadState::Exception::UNPROCESSED) { |
| state_.set(ThreadState::Exception::TRY_NEXT); |
| event_signal(&exception_event_, true); |
| } |
| } |
| } |
| |
| bool ThreadDispatcher::InPortExceptionLocked() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| DEBUG_ASSERT(get_lock()->lock().IsHeld()); |
| return thread_stopped_in_exception(&thread_); |
| } |
| |
| bool ThreadDispatcher::InChannelExceptionLocked() { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| DEBUG_ASSERT(get_lock()->lock().IsHeld()); |
| return exception_ != nullptr; |
| } |
| |
| bool ThreadDispatcher::SuspendedOrInExceptionLocked() { |
| canary_.Assert(); |
| return state_.lifecycle() == ThreadState::Lifecycle::SUSPENDED || |
| InPortExceptionLocked() || InChannelExceptionLocked(); |
| } |
| |
| zx_status_t ThreadDispatcher::GetInfoForUserspace(zx_info_thread_t* info) { |
| canary_.Assert(); |
| |
| ThreadState state; |
| Blocked blocked_reason; |
| ExceptionPort::Type excp_port_type; |
| // We need to fetch all these values under lock, but once we have them |
| // we no longer need the lock. |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| state = state_; |
| blocked_reason = blocked_reason_; |
| if (InChannelExceptionLocked()) { |
| excp_port_type = channel_exception_wait_type_; |
| } else { |
| if (InPortExceptionLocked() && |
| // A port type of !NONE here indicates to the caller that the |
| // thread is waiting for an exception response. So don't return |
| // !NONE if the thread just woke up but hasn't reacquired |
| // |state_lock_|. |
| state_.exception() == ThreadState::Exception::UNPROCESSED) { |
| DEBUG_ASSERT(exception_wait_port_ != nullptr); |
| excp_port_type = exception_wait_port_->type(); |
| } else { |
| // Either we're not in an exception, or we're in the window where |
| // event_wait_deadline has woken up but |state_lock_| has |
| // not been reacquired. |
| DEBUG_ASSERT(exception_wait_port_ == nullptr || |
| state_.exception() != ThreadState::Exception::UNPROCESSED); |
| excp_port_type = ExceptionPort::Type::NONE; |
| } |
| } |
| } |
| |
| switch (state.lifecycle()) { |
| case ThreadState::Lifecycle::INITIAL: |
| case ThreadState::Lifecycle::INITIALIZED: |
| info->state = ZX_THREAD_STATE_NEW; |
| break; |
| case ThreadState::Lifecycle::RUNNING: |
| // The thread may be "running" but be blocked in a syscall or |
| // exception handler. |
| switch (blocked_reason) { |
| case Blocked::NONE: |
| info->state = ZX_THREAD_STATE_RUNNING; |
| break; |
| case Blocked::EXCEPTION: |
| info->state = ZX_THREAD_STATE_BLOCKED_EXCEPTION; |
| break; |
| case Blocked::SLEEPING: |
| info->state = ZX_THREAD_STATE_BLOCKED_SLEEPING; |
| break; |
| case Blocked::FUTEX: |
| info->state = ZX_THREAD_STATE_BLOCKED_FUTEX; |
| break; |
| case Blocked::PORT: |
| info->state = ZX_THREAD_STATE_BLOCKED_PORT; |
| break; |
| case Blocked::CHANNEL: |
| info->state = ZX_THREAD_STATE_BLOCKED_CHANNEL; |
| break; |
| case Blocked::WAIT_ONE: |
| info->state = ZX_THREAD_STATE_BLOCKED_WAIT_ONE; |
| break; |
| case Blocked::WAIT_MANY: |
| info->state = ZX_THREAD_STATE_BLOCKED_WAIT_MANY; |
| break; |
| case Blocked::INTERRUPT: |
| info->state = ZX_THREAD_STATE_BLOCKED_INTERRUPT; |
| break; |
| case Blocked::PAGER: |
| info->state = ZX_THREAD_STATE_BLOCKED_PAGER; |
| break; |
| default: |
| DEBUG_ASSERT_MSG(false, "unexpected blocked reason: %d", |
| static_cast<int>(blocked_reason)); |
| break; |
| } |
| break; |
| case ThreadState::Lifecycle::SUSPENDED: |
| info->state = ZX_THREAD_STATE_SUSPENDED; |
| break; |
| case ThreadState::Lifecycle::DYING: |
| info->state = ZX_THREAD_STATE_DYING; |
| break; |
| case ThreadState::Lifecycle::DEAD: |
| info->state = ZX_THREAD_STATE_DEAD; |
| break; |
| default: |
| DEBUG_ASSERT_MSG(false, "unexpected run state: %d", |
| static_cast<int>(state.lifecycle())); |
| break; |
| } |
| |
| switch (excp_port_type) { |
| case ExceptionPort::Type::NONE: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_NONE; |
| break; |
| case ExceptionPort::Type::DEBUGGER: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_DEBUGGER; |
| break; |
| case ExceptionPort::Type::JOB_DEBUGGER: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_JOB_DEBUGGER; |
| break; |
| case ExceptionPort::Type::THREAD: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_THREAD; |
| break; |
| case ExceptionPort::Type::PROCESS: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_PROCESS; |
| break; |
| case ExceptionPort::Type::JOB: |
| info->wait_exception_port_type = ZX_EXCEPTION_PORT_TYPE_JOB; |
| break; |
| default: |
| DEBUG_ASSERT_MSG(false, "unexpected exception port type: %d", |
| static_cast<int>(excp_port_type)); |
| break; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t ThreadDispatcher::GetStatsForUserspace(zx_info_thread_stats_t* info) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| *info = {}; |
| |
| info->total_runtime = runtime_ns(); |
| return ZX_OK; |
| } |
| |
| zx_status_t ThreadDispatcher::GetExceptionReport(zx_exception_report_t* report) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| if (InPortExceptionLocked()) { |
| DEBUG_ASSERT(exception_report_ != nullptr); |
| *report = *exception_report_; |
| } else if (InChannelExceptionLocked()) { |
| // We always leave exception handling before the report gets wiped |
| // so this must succeed. |
| [[maybe_unused]] bool success = exception_->FillReport(report); |
| DEBUG_ASSERT(success); |
| } else { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| Exceptionate* ThreadDispatcher::exceptionate() { |
| canary_.Assert(); |
| return &exceptionate_; |
| } |
| |
| zx_status_t ThreadDispatcher::HandleException(Exceptionate* exceptionate, |
| fbl::RefPtr<ExceptionDispatcher> exception, |
| bool* sent) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| *sent = false; |
| |
| // Mark as blocked before sending the exception, otherwise a handler could |
| // potentially query our state before we've marked ourself blocked. |
| ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::EXCEPTION); |
| |
| { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| // We send the exception while locked so that if it succeeds we can |
| // atomically update our state. |
| zx_status_t status = exceptionate->SendException(exception); |
| if (status != ZX_OK) { |
| return status; |
| } |
| *sent = true; |
| |
| // This state is needed by GetInfoForUserspace(). |
| exception_ = exception; |
| channel_exception_wait_type_ = exceptionate->port_type(); |
| state_.set(ThreadState::Exception::UNPROCESSED); |
| } |
| |
| LTRACEF("blocking on exception response\n"); |
| |
| zx_status_t status = exception->WaitForResponse(); |
| |
| LTRACEF("received exception response %d\n", status); |
| |
| Guard<fbl::Mutex> guard{get_lock()}; |
| exception_.reset(); |
| channel_exception_wait_type_ = ExceptionPort::Type::NONE; |
| state_.set(ThreadState::Exception::IDLE); |
| |
| return status; |
| } |
| |
| bool ThreadDispatcher::HandleSingleShotException(Exceptionate* exceptionate, |
| zx_excp_type_t exception_type, |
| const arch_exception_context_t& context) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // Do a quick check for valid channel first. It's still possible that the |
| // channel will become invalid immediately after this check, but that will |
| // be caught when we try to send the exception. This is just an |
| // optimization to avoid unnecessary setup/teardown in the common case. |
| if (!exceptionate->HasValidChannel()) { |
| return false; |
| } |
| |
| arch_install_context_regs(&thread_, &context); |
| auto auto_call = fbl::MakeAutoCall([this]() { arch_remove_context_regs(&thread_); }); |
| |
| zx_exception_report_t report; |
| ExceptionPort::BuildArchReport(&report, exception_type, &context); |
| |
| fbl::RefPtr<ExceptionDispatcher> exception = ExceptionDispatcher::Create( |
| fbl::WrapRefPtr(this), exception_type, &report, &context); |
| if (!exception) { |
| printf("KERN: failed to allocate memory for exception type %u in thread %lu.%lu\n", |
| exception_type, process_->get_koid(), get_koid()); |
| return false; |
| } |
| |
| bool sent = false; |
| HandleException(exceptionate, exception, &sent); |
| |
| exception->Clear(); |
| |
| return sent; |
| } |
| |
| // Note: buffer must be sufficiently aligned |
| |
| zx_status_t ThreadDispatcher::ReadState(zx_thread_state_topic_t state_kind, |
| void* buffer, size_t buffer_len) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // We can't be reading regs while the thread transitions from |
| // SUSPENDED to RUNNING. |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| if (!SuspendedOrInExceptionLocked()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| switch (state_kind) { |
| case ZX_THREAD_STATE_GENERAL_REGS: { |
| // We are temporarily supporting programs that pass in the length of |
| // the old struct. |
| // TODO(ZX-3883): remove after old struct users have been migrated |
| if (buffer_len != sizeof(zx_thread_state_general_regs_t) && |
| buffer_len != sizeof(__old_zx_thread_state_general_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_get_general_regs( |
| &thread_, static_cast<zx_thread_state_general_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_FP_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_fp_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_get_fp_regs( |
| &thread_, static_cast<zx_thread_state_fp_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_VECTOR_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_vector_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_get_vector_regs( |
| &thread_, static_cast<zx_thread_state_vector_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_DEBUG_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_debug_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_get_debug_regs( |
| &thread_, static_cast<zx_thread_state_debug_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_SINGLE_STEP: { |
| if (buffer_len != sizeof(zx_thread_state_single_step_t)) |
| return ZX_ERR_INVALID_ARGS; |
| bool single_step; |
| zx_status_t status = arch_get_single_step(&thread_, &single_step); |
| if (status != ZX_OK) |
| return status; |
| *static_cast<zx_thread_state_single_step_t*>(buffer) = |
| static_cast<zx_thread_state_single_step_t>(single_step); |
| return ZX_OK; |
| } |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| // Note: buffer must be sufficiently aligned |
| |
| zx_status_t ThreadDispatcher::WriteState(zx_thread_state_topic_t state_kind, |
| const void* buffer, size_t buffer_len) { |
| canary_.Assert(); |
| |
| LTRACE_ENTRY_OBJ; |
| |
| // We can't be reading regs while the thread transitions from |
| // SUSPENDED to RUNNING. |
| Guard<fbl::Mutex> guard{get_lock()}; |
| |
| if (!SuspendedOrInExceptionLocked()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| switch (state_kind) { |
| case ZX_THREAD_STATE_GENERAL_REGS: { |
| // We are temporarily supporting programs that pass in the length of |
| // the old struct. |
| // TODO(ZX-3883): remove after old struct users have been migrated |
| if (buffer_len != sizeof(zx_thread_state_general_regs_t) && |
| buffer_len != sizeof(__old_zx_thread_state_general_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_set_general_regs( |
| &thread_, static_cast<const zx_thread_state_general_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_FP_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_fp_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_set_fp_regs( |
| &thread_, static_cast<const zx_thread_state_fp_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_VECTOR_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_vector_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_set_vector_regs( |
| &thread_, static_cast<const zx_thread_state_vector_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_DEBUG_REGS: { |
| if (buffer_len != sizeof(zx_thread_state_debug_regs_t)) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_set_debug_regs( |
| &thread_, static_cast<const zx_thread_state_debug_regs_t*>(buffer)); |
| } |
| case ZX_THREAD_STATE_SINGLE_STEP: { |
| if (buffer_len != sizeof(zx_thread_state_single_step_t)) |
| return ZX_ERR_INVALID_ARGS; |
| const zx_thread_state_single_step_t* single_step = |
| static_cast<const zx_thread_state_single_step_t*>(buffer); |
| if (*single_step != 0 && *single_step != 1) |
| return ZX_ERR_INVALID_ARGS; |
| return arch_set_single_step(&thread_, !!*single_step); |
| } |
| default: |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| zx_status_t ThreadDispatcher::SetPriority(int32_t priority) { |
| Guard<fbl::Mutex> guard{get_lock()}; |
| if ((state_.lifecycle() == ThreadState::Lifecycle::INITIAL) || |
| (state_.lifecycle() == ThreadState::Lifecycle::DYING) || |
| (state_.lifecycle() == ThreadState::Lifecycle::DEAD)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| // The priority was already validated by the Profile dispatcher. |
| thread_set_priority(&thread_, priority); |
| return ZX_OK; |
| } |
| |
| const char* ThreadLifecycleToString(ThreadState::Lifecycle lifecycle) { |
| switch (lifecycle) { |
| case ThreadState::Lifecycle::INITIAL: |
| return "initial"; |
| case ThreadState::Lifecycle::INITIALIZED: |
| return "initialized"; |
| case ThreadState::Lifecycle::RUNNING: |
| return "running"; |
| case ThreadState::Lifecycle::SUSPENDED: |
| return "suspended"; |
| case ThreadState::Lifecycle::DYING: |
| return "dying"; |
| case ThreadState::Lifecycle::DEAD: |
| return "dead"; |
| } |
| return "unknown"; |
| } |
| |
| zx_koid_t ThreadDispatcher::get_related_koid() const { |
| canary_.Assert(); |
| |
| return process_->get_koid(); |
| } |