| // 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 |
| |
| #ifndef ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PROCESS_DISPATCHER_H_ |
| #define ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PROCESS_DISPATCHER_H_ |
| |
| #include <zircon/syscalls/object.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/array.h> |
| #include <fbl/canary.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <fbl/name.h> |
| #include <fbl/ref_counted.h> |
| #include <fbl/ref_ptr.h> |
| #include <kernel/brwlock.h> |
| #include <kernel/event.h> |
| #include <kernel/mutex.h> |
| #include <kernel/task_runtime_stats.h> |
| #include <kernel/thread.h> |
| #include <ktl/array.h> |
| #include <ktl/forward.h> |
| #include <ktl/span.h> |
| #include <object/dispatcher.h> |
| #include <object/exceptionate.h> |
| #include <object/futex_context.h> |
| #include <object/handle.h> |
| #include <object/handle_table.h> |
| #include <object/job_policy.h> |
| #include <object/shareable_process_state.h> |
| #include <object/thread_dispatcher.h> |
| #include <vm/vm_aspace.h> |
| |
| class JobDispatcher; |
| class ProcessMapsInfoWriter; |
| class VmoInfoWriter; |
| |
| // To allow this function to be friended by ProcessDispatcher. |
| template <typename T> |
| [[noreturn]] extern void RestrictedLeave(const T* restricted_state_source, |
| zx_restricted_reason_t reason); |
| |
| namespace internal { |
| // Tag for a ProcessDispatcher's parent JobDispatcher's raw job list. |
| struct ProcessDispatcherRawJobListTag {}; |
| // Tag for a ProcessDispatcher's parent JobDispatcher's job list. |
| struct ProcessDispatcherJobListTag {}; |
| } // namespace internal |
| |
| class ProcessDispatcher final |
| : public SoloDispatcher<ProcessDispatcher, ZX_DEFAULT_PROCESS_RIGHTS>, |
| public fbl::ContainableBaseClasses< |
| fbl::TaggedDoublyLinkedListable<ProcessDispatcher*, |
| internal::ProcessDispatcherRawJobListTag>, |
| fbl::TaggedSinglyLinkedListable<fbl::RefPtr<ProcessDispatcher>, |
| internal::ProcessDispatcherJobListTag>> { |
| public: |
| using RawJobListTag = internal::ProcessDispatcherRawJobListTag; |
| using JobListTag = internal::ProcessDispatcherJobListTag; |
| |
| static zx_status_t Create(fbl::RefPtr<JobDispatcher> job, ktl::string_view name, uint32_t flags, |
| KernelHandle<ProcessDispatcher>* handle, zx_rights_t* rights, |
| KernelHandle<VmAddressRegionDispatcher>* root_vmar_handle, |
| zx_rights_t* root_vmar_rights); |
| |
| // Creates a new process dispatcher for a process that will share its `shareable_state_` with |
| // other processes. |
| // |
| // The shared state will be instantiated from `shared_proc`. |
| // |
| // `restricted_vmar_handle` is the VMAR for the restricted aspace. |
| static zx_status_t CreateShared(fbl::RefPtr<ProcessDispatcher> shared_proc, ktl::string_view name, |
| uint32_t flags, KernelHandle<ProcessDispatcher>* handle, |
| zx_rights_t* rights, |
| KernelHandle<VmAddressRegionDispatcher>* restricted_vmar_handle, |
| zx_rights_t* restricted_vmar_rights); |
| |
| static ProcessDispatcher* GetCurrent() { |
| ThreadDispatcher* current = ThreadDispatcher::GetCurrent(); |
| DEBUG_ASSERT(current); |
| return current->process(); |
| } |
| |
| static void ExitCurrent(int64_t retcode) __NO_RETURN { |
| ThreadDispatcher* current = ThreadDispatcher::GetCurrent(); |
| DEBUG_ASSERT(current); |
| current->process()->Exit(retcode); |
| } |
| |
| // Dispatcher implementation |
| zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_PROCESS; } |
| void on_zero_handles() final; |
| zx_koid_t get_related_koid() const final; |
| |
| ~ProcessDispatcher() final; |
| |
| // state of the process |
| enum class State { |
| INITIAL, // initial state, no thread present in process |
| RUNNING, // first thread has started and is running |
| DYING, // process has delivered kill signal to all threads |
| DEAD, // all threads have entered DEAD state and potentially dropped refs on process |
| }; |
| |
| // The type of address space used to initialize a ProcessDispatcher for a shared process. |
| enum class SharedAspaceType { |
| // Top half: a new shareable address space |
| // Bottom half: nothing |
| New, |
| // Top half: shared address space from another process |
| // Bottom half: a new restricted address space |
| Shared |
| }; |
| |
| // Performs initialization on a newly constructed ProcessDispatcher |
| // |
| // This should be used to initialize ProcessDispatchers without a restricted aspace. |
| // |
| // If this fails, then the object is invalid and should be deleted |
| zx_status_t Initialize(); |
| |
| // Performs initialization on a newly constructed ProcessDispatcher |
| // If this fails, then the object is invalid and should be deleted |
| // |
| // This should be used to initialize ProcessDispatchers with a restricted aspace. |
| // |
| // |type| is used to determine how to initialize the restricted and normal aspaces. |
| zx_status_t Initialize(SharedAspaceType type); |
| |
| // Accessors. |
| HandleTable& handle_table() { return shareable_state_->handle_table(); } |
| const HandleTable& handle_table() const { return shareable_state_->handle_table(); } |
| |
| FutexContext& futex_context() { return shareable_state_->futex_context(); } |
| |
| // Returns a pointer to the process's VmAspace containing |va| if such an aspace exists, otherwise |
| // it returns the normal aspace of the process. |
| VmAspace* aspace_at(vaddr_t va); |
| |
| #if ARCH_X86 |
| // Returns an identifier that can be used to associate hardware trace |
| // data with this process. |
| uintptr_t hw_trace_context_id() const { |
| // TODO(https://fxbug.dev/42055932): Figure out how to make HW tracing work in restricted mode. |
| return shareable_state_->aspace()->arch_aspace().pt_phys(); |
| } |
| #endif |
| |
| uintptr_t arch_table_phys() const { |
| // TODO(https://fxbug.dev/42055932): Figure out how to make tracing works in restricted mode. |
| return shareable_state_->aspace()->arch_aspace().arch_table_phys(); |
| } |
| |
| uintptr_t vdso_base_address() { return shareable_state_->aspace()->vdso_base_address(); } |
| |
| void EnumerateAspaceChildren(VmEnumerator* ve) { |
| shareable_state_->aspace()->EnumerateChildren(ve); |
| if (restricted_aspace_) { |
| restricted_aspace_->EnumerateChildren(ve); |
| } |
| } |
| |
| void DumpAspace(bool verbose) { |
| shareable_state_->aspace()->Dump(true); |
| if (restricted_aspace_) { |
| restricted_aspace_->Dump(true); |
| } |
| } |
| |
| State state() const; |
| |
| fbl::RefPtr<JobDispatcher> job(); |
| |
| [[nodiscard]] zx_status_t get_name(char (&out_name)[ZX_MAX_NAME_LEN]) const final; |
| [[nodiscard]] zx_status_t set_name(const char* name, size_t len) final; |
| |
| void Kill(int64_t retcode); |
| |
| // Suspends the process. |
| // |
| // Suspending a process causes all child threads to suspend as well as any new children |
| // that are added until the process is resumed. Suspend() is cumulative, so the process |
| // will only resume once Resume() has been called an equal number of times. |
| // |
| // Returns ZX_OK on success, or ZX_ERR_BAD_STATE iff the process is dying or dead. |
| zx_status_t Suspend(); |
| void Resume(); |
| |
| // Syscall helpers |
| void GetInfo(zx_info_process_t* info) const; |
| zx_status_t GetStats(zx_info_task_stats_t* stats) const; |
| |
| // Get the runtime of all threads that previously ran or are currently running under this process. |
| TaskRuntimeStats GetTaskRuntimeStats() const TA_EXCL(get_lock()); |
| |
| zx_status_t GetAspaceMaps(ProcessMapsInfoWriter& maps, size_t max, size_t* actual, |
| size_t* available) const; |
| zx_status_t GetVmos(VmoInfoWriter& vmos, size_t max, size_t* actual, size_t* available); |
| zx_status_t GetThreads(fbl::Array<zx_koid_t>* threads) const; |
| zx_status_t SetCriticalToJob(fbl::RefPtr<JobDispatcher> critical_to_job, bool retcode_nonzero); |
| |
| bool CriticalToRootJob() const; |
| |
| Exceptionate* exceptionate(); |
| Exceptionate* debug_exceptionate(); |
| |
| // The following two methods can be slow and inaccurate and should only be |
| // called from diagnostics code. |
| uint32_t ThreadCount() const; |
| VmObject::AttributionCounts PageCount() const; |
| |
| // Look up a process given its koid. |
| // Returns nullptr if not found. |
| static fbl::RefPtr<ProcessDispatcher> LookupProcessById(zx_koid_t koid); |
| |
| // Look up a thread in this process given its koid. |
| // Returns nullptr if not found. |
| fbl::RefPtr<ThreadDispatcher> LookupThreadById(zx_koid_t koid); |
| |
| uintptr_t get_debug_addr() const; |
| zx_status_t set_debug_addr(uintptr_t addr); |
| |
| uintptr_t get_dyn_break_on_load() const; |
| zx_status_t set_dyn_break_on_load(uintptr_t break_on_load); |
| |
| // Checks |condition| and enforces the parent job's policy. |
| // |
| // Depending on the parent job's policy, this method may signal an exception |
| // on the calling thread or signal that the current process should be |
| // killed. |
| // |
| // Must be called by syscalls before performing an action represented by an |
| // ZX_POL_xxxxx condition. If the return value is ZX_OK the action can |
| // proceed; otherwise, the process is not allowed to perform the action, |
| // and the status value should be returned to the usermode caller. |
| // |
| // E.g., in sys_channel_create: |
| // |
| // auto up = ProcessDispatcher::GetCurrent(); |
| // zx_status_t res = up->EnforceBasicPolicy(ZX_POL_NEW_CHANNEL); |
| // if (res != ZX_OK) { |
| // // Channel creation denied by the calling process's |
| // // parent job's policy. |
| // return res; |
| // } |
| // // Ok to create a channel. |
| __WARN_UNUSED_RESULT |
| zx_status_t EnforceBasicPolicy(uint32_t condition); |
| |
| // Returns this job's timer slack policy. |
| TimerSlack GetTimerSlackPolicy() const; |
| |
| // return a cached copy of the vdso code address or compute a new one |
| uintptr_t vdso_code_address() { |
| if (unlikely(vdso_code_address_ == 0)) { |
| return cache_vdso_code_address(); |
| } |
| return vdso_code_address_; |
| } |
| |
| // Allocates a handle with the given rights to the given dispatcher. The handle is added to the |
| // calling process' handle table, and its value is returned in out. |
| zx_status_t MakeAndAddHandle(fbl::RefPtr<Dispatcher> dispatcher, zx_rights_t rights, |
| zx_handle_t* out); |
| // Allocates a handle with the given rights to the dispatcher enclosed in the given kernel handle. |
| // The handle is added to the calling process' handle table, and its value is returned in out. |
| zx_status_t MakeAndAddHandle(KernelHandle<Dispatcher> kernel_handle, zx_rights_t rights, |
| zx_handle_t* out); |
| |
| // Returns the "restricted" address space for a process, or nullptr if it does not have a |
| // restricted address space. |
| // |
| // The restricted address space spans the bottom half of the process' total address space, and is |
| // private to the process. Threads executing in restricted mode are restricted to this address |
| // space. |
| VmAspace* restricted_aspace() { return restricted_aspace_.get(); } |
| |
| // Dispatch a user exception to job debugger exception channels. |
| void OnUserExceptionForJobDebugger(ThreadDispatcher* t, const arch_exception_context_t* context); |
| |
| private: |
| // Returns the normal address space for this process. |
| // |
| // All processes have a normal address space. The normal aspace is the |
| // address space that's active when a thread is in normal mode. |
| // |
| // For "shared processes", on architectures that support unified aspaces, the normal aspace |
| // is a unified aspace. A unified aspace is an aspace that spans both the shared and restricted |
| // aspace, and is used by threads in normal mode to avoid having to switch between the shared and |
| // restricted aspaces. |
| // |
| // On architectures that don't yet support unified aspaces, the normal |
| // aspace is a shared aspace (`ShareableProcessState::aspace()`). |
| // |
| // For non-shared processes (regular ones), the normal aspace is the one and only aspace |
| // belonging to the process (`ShareableProcessState::aspace()`). |
| // |
| // TODO(https://fxbug.dev/42083004): Update this comment once all architectures support unified |
| // aspaces. |
| VmAspace* normal_aspace() { |
| if (unified_aspace_) { |
| return unified_aspace_.get(); |
| } |
| return shareable_state_->aspace(); |
| } |
| |
| // Restricted mode is allowed to know about the internals of the aspaces. |
| friend zx_status_t RestrictedEnter(uint32_t options, uintptr_t vector_table_ptr, |
| uintptr_t context); |
| friend void RedirectRestrictedExceptionToNormalMode(RestrictedState* rs); |
| template <typename T> |
| friend void RestrictedLeave(const T* restricted_state_source, zx_restricted_reason_t reason); |
| |
| // Exit the current Process. It is an error to call this on anything other than the current |
| // process. Please use ExitCurrent() instead of calling this directly. |
| void Exit(int64_t retcode) __NO_RETURN; |
| |
| // compute the vdso code address and store in vdso_code_address_ |
| uintptr_t cache_vdso_code_address(); |
| |
| // The diagnostic code is allow to know about the internals of this code. |
| friend void DumpProcessList(); |
| friend void KillProcess(zx_koid_t id); |
| friend void DumpProcessMemoryUsage(const char* prefix, size_t min_pages); |
| |
| ProcessDispatcher(fbl::RefPtr<ShareableProcessState> shareable_state, |
| fbl::RefPtr<JobDispatcher> job, ktl::string_view name, uint32_t flags); |
| |
| ProcessDispatcher(const ProcessDispatcher&) = delete; |
| ProcessDispatcher& operator=(const ProcessDispatcher&) = delete; |
| |
| void OnProcessStartForJobDebugger(ThreadDispatcher* t, const arch_exception_context_t* context); |
| |
| // Thread lifecycle support. |
| friend class ThreadDispatcher; |
| // Takes the given ThreadDispatcher and transitions it from the INITIALIZED state to a runnable |
| // state (RUNNING or SUSPENDED depending on whether this process is suspended) by calling |
| // ThreadDispatcher::MakeRunnable. The thread is then added to the thread_list_ for this process |
| // and we transition to running if this is the initial_thread. |
| // |
| // If `ensure_initial_thread` is true, adding the thread will fail if is not the initial thread in |
| // the process. |
| zx_status_t AddInitializedThread(ThreadDispatcher* t, bool ensure_initial_thread, |
| const ThreadDispatcher::EntryState& entry); |
| void RemoveThread(ThreadDispatcher* t); |
| |
| void SetStateLocked(State) TA_REQ(get_lock()); |
| void FinishDeadTransition(); |
| |
| // Kill all threads |
| void KillAllThreadsLocked() TA_REQ(get_lock()); |
| |
| const fbl::RefPtr<ShareableProcessState> shareable_state_; |
| |
| // the enclosing job |
| const fbl::RefPtr<JobDispatcher> job_; |
| |
| // Job that this process is critical to. |
| // |
| // We require that the job is the parent of this process, or an ancestor. |
| fbl::RefPtr<JobDispatcher> critical_to_job_ TA_GUARDED(get_lock()); |
| bool retcode_nonzero_ TA_GUARDED(get_lock()) = false; |
| |
| // Policy set by the Job during Create(). |
| // |
| // It is critical that this field is immutable as it will be accessed without synchronization. |
| const JobPolicy policy_; |
| |
| // list of threads in this process |
| fbl::DoublyLinkedList<ThreadDispatcher*> thread_list_ TA_GUARDED(get_lock()); |
| |
| // The address space used when a thread of this process is executing in restricted mode. This |
| // field is only non-null if this process is a "shared process". |
| // |
| // This field is logically const and may not be changed after initialization. Resetting or |
| // assigning to this field post-initialization is a programming error. |
| fbl::RefPtr<VmAspace> restricted_aspace_; |
| |
| // The address space used when a thread is in normal mode but has a restricted address space. |
| // This can be null if the process was not initialized with a restricted aspace, or the |
| // architecture does not support unified aspaces. |
| fbl::RefPtr<VmAspace> unified_aspace_; |
| |
| // our state |
| State state_ TA_GUARDED(get_lock()) = State::INITIAL; |
| |
| // Suspend count; incremented on Suspend(), decremented on Resume(). |
| int suspend_count_ TA_GUARDED(get_lock()) = 0; |
| |
| // True if FinishDeadTransition has been called. |
| // This is used as a sanity check only. |
| bool completely_dead_ = false; |
| |
| // process return code |
| int64_t retcode_ = 0; |
| |
| Exceptionate exceptionate_; |
| Exceptionate debug_exceptionate_; |
| |
| // This is the value of _dl_debug_addr from ld.so. |
| // See third_party/ulib/musl/ldso/dynlink.c. |
| uintptr_t debug_addr_ TA_GUARDED(get_lock()) = 0; |
| |
| // Whether the dynamic loader should issue a debug trap when loading a shared library, |
| // either initially or when running (e.g. dlopen). |
| // |
| // See docs/reference/syscalls/object_get_property.md |
| // See third_party/ulib/musl/ldso/dynlink.c. |
| uintptr_t dyn_break_on_load_ TA_GUARDED(get_lock()) = 0; |
| |
| // This is a cache of aspace()->vdso_code_address(). |
| uintptr_t vdso_code_address_ = 0; |
| |
| // The time at which the process was started. |
| zx_time_t start_time_ = 0; |
| |
| // Hold accumulated stats for threads who have exited. |
| TaskRuntimeStats accumulated_stats_ TA_GUARDED(get_lock()); |
| |
| // The user-friendly process name. For debug purposes only. That |
| // is, there is no mechanism to mint a handle to a process via this name. |
| fbl::Name<ZX_MAX_NAME_LEN> name_; |
| }; |
| |
| const char* StateToString(ProcessDispatcher::State state); |
| |
| #endif // ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PROCESS_DISPATCHER_H_ |