|  | // 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/dpc.h> | 
|  | #include <kernel/event.h> | 
|  | #include <kernel/thread.h> | 
|  | #include <vm/vm_address_region.h> | 
|  | #include <object/channel_dispatcher.h> | 
|  | #include <object/dispatcher.h> | 
|  | #include <object/excp_port.h> | 
|  | #include <object/futex_node.h> | 
|  |  | 
|  | #include <zircon/compiler.h> | 
|  | #include <zircon/syscalls/debug.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 SoloDispatcher<ThreadDispatcher> { | 
|  | 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, | 
|  | }; | 
|  |  | 
|  | // When in a blocking syscall, or blocked in an exception, the blocking reason. | 
|  | // There is one of these for each syscall marked "blocking". | 
|  | // See syscalls.abigen. | 
|  | enum class Blocked { | 
|  | // Not blocked. | 
|  | NONE, | 
|  | // The thread is blocked in an exception. | 
|  | EXCEPTION, | 
|  | // The thread is sleeping (zx_nanosleep). | 
|  | SLEEPING, | 
|  | // zx_futex_wait | 
|  | FUTEX, | 
|  | // zx_port_wait | 
|  | PORT, | 
|  | // zx_channel_call | 
|  | CHANNEL, | 
|  | // zx_object_wait_one | 
|  | WAIT_ONE, | 
|  | // zx_object_wait_many | 
|  | WAIT_MANY, | 
|  | // zx_interrupt_wait | 
|  | INTERRUPT, | 
|  | }; | 
|  |  | 
|  | 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; } | 
|  | bool has_state_tracker() const final { return true; } | 
|  | 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(); } | 
|  |  | 
|  | zx_status_t set_name(const char* name, size_t len) final __NONNULL((2)); | 
|  | void get_name(char out_name[ZX_MAX_NAME_LEN]) const final __NONNULL((2)); | 
|  | 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. | 
|  | // TODO(brettw) ZX-1072 Remove this when all callers are updated to use | 
|  | // the exception port variant below. | 
|  | zx_status_t MarkExceptionHandled(ExceptionStatus estatus); | 
|  |  | 
|  | // Called when an exception handler is finished processing the exception. | 
|  | // The exception is only continued if the eport corresponds to the current | 
|  | // exception port. | 
|  | zx_status_t MarkExceptionHandled(PortDispatcher* eport, ExceptionStatus status); | 
|  |  | 
|  | // 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. | 
|  | // |get_lock()| must be held. | 
|  | bool InExceptionLocked() TA_REQ(get_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. | 
|  | zx_status_t ReadState(zx_thread_state_topic_t state_kind, void* buffer, size_t buffer_len); | 
|  | zx_status_t WriteState(zx_thread_state_topic_t state_kind, const void* buffer, | 
|  | size_t buffer_len); | 
|  | // Profile support | 
|  | zx_status_t SetPriority(int32_t priority); | 
|  |  | 
|  | // For ChannelDispatcher use. | 
|  | ChannelDispatcher::MessageWaiter* GetMessageWaiter() { return &channel_waiter_; } | 
|  |  | 
|  | // Blocking syscalls, once they commit to a path that will likely block the | 
|  | // thread, use this helper class to properly set/restore |blocked_reason_|. | 
|  | class AutoBlocked final { | 
|  | public: | 
|  | explicit AutoBlocked(Blocked reason) | 
|  | : thread_(ThreadDispatcher::GetCurrent()), | 
|  | prev_reason(thread_->blocked_reason_) { | 
|  | DEBUG_ASSERT(reason != Blocked::NONE); | 
|  | thread_->blocked_reason_ = reason; | 
|  | } | 
|  | ~AutoBlocked() { | 
|  | thread_->blocked_reason_ = prev_reason; | 
|  | } | 
|  | private: | 
|  | ThreadDispatcher* const thread_; | 
|  | const Blocked prev_reason; | 
|  | }; | 
|  |  | 
|  | 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(get_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; | 
|  |  | 
|  | State state_ TA_GUARDED(get_lock()) = State::INITIAL; | 
|  |  | 
|  | // This is only valid while |state_ == State::RUNNING|. | 
|  | // This is just a volatile, and not something like an atomic, because | 
|  | // the only writer is the thread itself, and readers can just pick up | 
|  | // whatever value is currently here. This value is written when the thread | 
|  | // is likely to be put on a wait queue, and the following context switch | 
|  | // will force this value's visibility to other cpus. If the thread doesn't | 
|  | // get put on a wait queue, the thread was never really blocked. | 
|  | volatile Blocked blocked_reason_ = Blocked::NONE; | 
|  |  | 
|  | // A thread-level exception port for this thread. | 
|  | fbl::RefPtr<ExceptionPort> exception_port_ TA_GUARDED(get_lock()); | 
|  |  | 
|  | // Support for sending an exception to an exception handler and then waiting for a response. | 
|  | ExceptionStatus exception_status_ TA_GUARDED(get_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(get_lock()); | 
|  | const zx_exception_report_t* exception_report_ TA_GUARDED(get_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}; | 
|  |  | 
|  | // Tracks the number of times Suspend() has been called. Resume() will resume this thread | 
|  | // only when this reference count reaches 0. | 
|  | int suspend_count_ = 0; | 
|  |  | 
|  | // Used to protect thread name read/writes | 
|  | mutable DECLARE_SPINLOCK(ThreadDispatcher) 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); |