| // 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 |
| |
| #pragma once |
| |
| #include <sys/types.h> |
| |
| #include <arch/exception.h> |
| #include <kernel/event.h> |
| #include <kernel/thread.h> |
| #include <vm/vm_address_region.h> |
| #include <lib/dpc.h> |
| #include <object/channel_dispatcher.h> |
| #include <object/dispatcher.h> |
| #include <object/excp_port.h> |
| #include <object/futex_node.h> |
| #include <object/state_tracker.h> |
| |
| #include <zircon/syscalls/exception.h> |
| #include <zircon/types.h> |
| #include <fbl/canary.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <fbl/mutex.h> |
| #include <fbl/ref_counted.h> |
| #include <fbl/ref_ptr.h> |
| #include <fbl/string_piece.h> |
| |
| class ProcessDispatcher; |
| |
| class ThreadDispatcher final : public Dispatcher { |
| public: |
| // Traits to belong in the parent process's list. |
| struct ThreadListTraits { |
| static fbl::DoublyLinkedListNodeState<ThreadDispatcher*>& node_state( |
| ThreadDispatcher& obj) { |
| return obj.dll_thread_; |
| } |
| }; |
| |
| // state of the thread |
| enum class State { |
| INITIAL, // newly created thread |
| INITIALIZED, // LK thread state is initialized |
| RUNNING, // thread is running |
| SUSPENDED, // thread is suspended |
| DYING, // thread has been signaled for kill, but has not exited yet |
| DEAD, // thread has exited and is not running |
| }; |
| |
| // the exception status (disposition?) of the thread |
| enum class ExceptionStatus { |
| // The thread is not in an exception |
| IDLE, |
| |
| // The thread is blocked in an exception, waiting for a response |
| UNPROCESSED, |
| |
| // The exception is unhandled, try the next handler. |
| // If this is the last handler then the process is killed. |
| // As an analogy, this would be like typing "c" in gdb after a |
| // segfault. In linux the signal would be delivered to the thread, |
| // which would either terminate the process or run a signal handler if |
| // defined. In zircon this gives the next signal handler in the list |
| // a crack at the exception. |
| TRY_NEXT, |
| |
| // The exception has been handled, resume the thread. |
| // As an analogy, this would be like typing "sig 0" in gdb after a |
| // segfault. The faulting instruction will be retried. If, for example, |
| // it segfaults again then the user is back in the debugger again, |
| // which is working as intended. |
| // Note: We don't, currently at least, support delivering a different |
| // exception (signal in linux parlance) to the thread. As an analogy, |
| // this would be like typing "sig 8" in gdb after getting a segfault |
| // (which is signal 11). |
| RESUME, |
| }; |
| |
| static zx_status_t Create(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags, |
| fbl::StringPiece name, |
| fbl::RefPtr<Dispatcher>* out_dispatcher, |
| zx_rights_t* out_rights); |
| ~ThreadDispatcher(); |
| |
| static ThreadDispatcher* GetCurrent() { |
| return reinterpret_cast<ThreadDispatcher*>(get_current_thread()->user_thread); |
| } |
| |
| // Dispatcher implementation. |
| zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_THREAD; } |
| StateTracker* get_state_tracker() final { return &state_tracker_; } |
| void on_zero_handles() final; |
| zx_koid_t get_related_koid() const final; |
| |
| // Performs initialization on a newly constructed ThreadDispatcher |
| // If this fails, then the object is invalid and should be deleted |
| zx_status_t Initialize(const char* name, size_t len); |
| zx_status_t Start(uintptr_t pc, uintptr_t sp, uintptr_t arg1, uintptr_t arg2, |
| bool initial_thread); |
| void Exit() __NO_RETURN; |
| void Kill(); |
| |
| zx_status_t Suspend(); |
| zx_status_t Resume(); |
| |
| // accessors |
| ProcessDispatcher* process() const { return process_.get(); } |
| |
| FutexNode* futex_node() { return &futex_node_; } |
| zx_status_t set_name(const char* name, size_t len) final; |
| void get_name(char out_name[ZX_MAX_NAME_LEN]) const final; |
| uint64_t runtime_ns() const { return thread_runtime(&thread_); } |
| |
| zx_status_t SetExceptionPort(fbl::RefPtr<ExceptionPort> eport); |
| // Returns true if a port had been set. |
| bool ResetExceptionPort(bool quietly); |
| fbl::RefPtr<ExceptionPort> exception_port(); |
| |
| // Send a report to the associated exception handler of |eport| and wait |
| // for a response. |
| // Note this takes a specific exception port as an argument because there are several: |
| // debugger, thread, process, and system. The kind of the exception port is |
| // specified by |eport->type()|. |
| // Returns: |
| // ZX_OK: the exception was handled in some way, and |*out_estatus| |
| // specifies how. |
| // ZX_ERR_INTERNAL_INTR_KILLED: the thread was killed (probably via zx_task_kill) |
| zx_status_t ExceptionHandlerExchange(fbl::RefPtr<ExceptionPort> eport, |
| const zx_exception_report_t* report, |
| const arch_exception_context_t* arch_context, |
| ExceptionStatus* out_estatus); |
| // Called when an exception handler is finished processing the exception. |
| zx_status_t MarkExceptionHandled(ExceptionStatus estatus); |
| // Called when exception port |eport| is removed. |
| // If the thread is waiting for the associated exception handler, continue |
| // exception processing as if the exception port had not been installed. |
| void OnExceptionPortRemoval(const fbl::RefPtr<ExceptionPort>& eport); |
| // Return true if waiting for an exception response. |
| // |state_lock_| must be held. |
| bool InExceptionLocked() TA_REQ(state_lock_); |
| // Assuming the thread is stopped waiting for an exception response, |
| // fill in |*report| with the exception report. |
| // Returns ZX_ERR_BAD_STATE if not in an exception. |
| zx_status_t GetExceptionReport(zx_exception_report_t* report); |
| |
| // Fetch the state of the thread for userspace tools. |
| zx_status_t GetInfoForUserspace(zx_info_thread_t* info); |
| |
| // Fetch per thread stats for userspace. |
| zx_status_t GetStatsForUserspace(zx_info_thread_stats_t* info); |
| |
| // For debugger usage. |
| // TODO(dje): The term "state" here conflicts with "state tracker". |
| uint32_t get_num_state_kinds() const; |
| // TODO(dje): Consider passing an Array<uint8_t> here and in WriteState. |
| zx_status_t ReadState(uint32_t state_kind, void* buffer, uint32_t* buffer_len); |
| zx_status_t WriteState(uint32_t state_kind, const void* buffer, uint32_t buffer_len); |
| |
| // For ChannelDispatcher use. |
| ChannelDispatcher::MessageWaiter* GetMessageWaiter() { return &channel_waiter_; } |
| |
| private: |
| ThreadDispatcher(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags); |
| ThreadDispatcher(const ThreadDispatcher&) = delete; |
| ThreadDispatcher& operator=(const ThreadDispatcher&) = delete; |
| |
| // kernel level entry point |
| static int StartRoutine(void* arg); |
| |
| // callback from kernel when thread is exiting, just before it stops for good. |
| void Exiting(); |
| |
| // callback from kernel when thread is suspending |
| void Suspending(); |
| // callback from kernel when thread is resuming |
| void Resuming(); |
| |
| // Dispatch routine for state changes that LK tells us about |
| static void ThreadUserCallback(enum thread_user_state_change new_state, void* arg); |
| |
| // change states of the object, do what is appropriate for the state transition |
| void SetStateLocked(State) TA_REQ(state_lock_); |
| |
| fbl::Canary<fbl::magic("THRD")> canary_; |
| |
| // The containing process holds a list of all its threads. |
| fbl::DoublyLinkedListNodeState<ThreadDispatcher*> dll_thread_; |
| |
| // a ref pointer back to the parent process |
| fbl::RefPtr<ProcessDispatcher> process_; |
| |
| // User thread starting register values. |
| uintptr_t user_entry_ = 0; |
| uintptr_t user_sp_ = 0; |
| uintptr_t user_arg1_ = 0; |
| uintptr_t user_arg2_ = 0; |
| |
| // our State |
| State state_ TA_GUARDED(state_lock_) = State::INITIAL; |
| fbl::Mutex state_lock_; |
| |
| // Node for linked list of threads blocked on a futex |
| FutexNode futex_node_; |
| |
| StateTracker state_tracker_; |
| |
| // A thread-level exception port for this thread. |
| fbl::RefPtr<ExceptionPort> exception_port_ TA_GUARDED(exception_lock_); |
| fbl::Mutex exception_lock_; |
| |
| // Support for sending an exception to an exception handler and then waiting for a response. |
| ExceptionStatus exception_status_ TA_GUARDED(state_lock_) |
| = ExceptionStatus::IDLE; |
| // The exception port of the handler the thread is waiting for a response from. |
| fbl::RefPtr<ExceptionPort> exception_wait_port_ TA_GUARDED(state_lock_); |
| const zx_exception_report_t* exception_report_ TA_GUARDED(state_lock_); |
| event_t exception_event_ = |
| EVENT_INITIAL_VALUE(exception_event_, false, EVENT_FLAG_AUTOUNSIGNAL); |
| |
| // cleanup dpc structure |
| dpc_t cleanup_dpc_ = {LIST_INITIAL_CLEARED_VALUE, nullptr, nullptr}; |
| |
| // Used to protect thread name read/writes |
| mutable SpinLock name_lock_; |
| |
| // hold a reference to the mapping and vmar used to wrap the mapping of this |
| // thread's kernel stack |
| fbl::RefPtr<VmMapping> kstack_mapping_; |
| fbl::RefPtr<VmAddressRegion> kstack_vmar_; |
| #if __has_feature(safe_stack) |
| fbl::RefPtr<VmMapping> unsafe_kstack_mapping_; |
| fbl::RefPtr<VmAddressRegion> unsafe_kstack_vmar_; |
| #endif |
| |
| // Per-thread structure used while waiting in a ChannelDispatcher::Call. |
| // Needed to support the requirements of being able to interrupt a Call |
| // in order to suspend a thread. |
| ChannelDispatcher::MessageWaiter channel_waiter_; |
| |
| // LK thread structure |
| // put last to ease debugging since this is a pretty large structure |
| // (~1.5K on x86_64). |
| // Also, a simple experiment to move this to the first member (after the |
| // canary) resulted in a 1K increase in text size (x86_64). |
| thread_t thread_ = {}; |
| }; |
| |
| const char* StateToString(ThreadDispatcher::State state); |