|  | // 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/process_dispatcher.h> | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <inttypes.h> | 
|  | #include <list.h> | 
|  | #include <rand.h> | 
|  | #include <string.h> | 
|  | #include <trace.h> | 
|  |  | 
|  | #include <arch/defines.h> | 
|  |  | 
|  | #include <kernel/thread.h> | 
|  | #include <vm/vm.h> | 
|  | #include <vm/vm_aspace.h> | 
|  | #include <vm/vm_object.h> | 
|  |  | 
|  | #include <lib/crypto/global_prng.h> | 
|  | #include <lib/ktrace.h> | 
|  |  | 
|  | #include <zircon/rights.h> | 
|  |  | 
|  | #include <object/diagnostics.h> | 
|  | #include <object/futex_context.h> | 
|  | #include <object/handle.h> | 
|  | #include <object/job_dispatcher.h> | 
|  | #include <object/thread_dispatcher.h> | 
|  | #include <object/vm_address_region_dispatcher.h> | 
|  | #include <object/vm_object_dispatcher.h> | 
|  |  | 
|  | #include <fbl/alloc_checker.h> | 
|  | #include <fbl/auto_lock.h> | 
|  |  | 
|  | #define LOCAL_TRACE 0 | 
|  |  | 
|  | static zx_handle_t map_handle_to_value(const Handle* handle, uint32_t mixer) { | 
|  | // Ensure that the last bit of the result is not zero, and make sure | 
|  | // we don't lose any base_value bits or make the result negative | 
|  | // when shifting. | 
|  | DEBUG_ASSERT((mixer & ((1<<31) | 0x1)) == 0); | 
|  | DEBUG_ASSERT((handle->base_value() & 0xc0000000) == 0); | 
|  |  | 
|  | auto handle_id = (handle->base_value() << 1) | 0x1; | 
|  | return static_cast<zx_handle_t>(mixer ^ handle_id); | 
|  | } | 
|  |  | 
|  | static Handle* map_value_to_handle(zx_handle_t value, uint32_t mixer) { | 
|  | auto handle_id = (static_cast<uint32_t>(value) ^ mixer) >> 1; | 
|  | return Handle::FromU32(handle_id); | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::Create( | 
|  | fbl::RefPtr<JobDispatcher> job, fbl::StringPiece name, uint32_t flags, | 
|  | fbl::RefPtr<Dispatcher>* dispatcher, zx_rights_t* rights, | 
|  | fbl::RefPtr<VmAddressRegionDispatcher>* root_vmar_disp, | 
|  | zx_rights_t* root_vmar_rights) { | 
|  | fbl::AllocChecker ac; | 
|  | fbl::RefPtr<ProcessDispatcher> process = | 
|  | fbl::AdoptRef(new (&ac) ProcessDispatcher(job, name, flags)); | 
|  | if (!ac.check()) | 
|  | return ZX_ERR_NO_MEMORY; | 
|  |  | 
|  | if (!job->AddChildProcess(process)) | 
|  | return ZX_ERR_BAD_STATE; | 
|  |  | 
|  | zx_status_t result = process->Initialize(); | 
|  | if (result != ZX_OK) | 
|  | return result; | 
|  |  | 
|  | fbl::RefPtr<VmAddressRegion> vmar(process->aspace()->RootVmar()); | 
|  |  | 
|  | // Create a dispatcher for the root VMAR. | 
|  | fbl::RefPtr<Dispatcher> new_vmar_dispatcher; | 
|  | result = VmAddressRegionDispatcher::Create(vmar, ARCH_MMU_FLAG_PERM_USER, | 
|  | &new_vmar_dispatcher, | 
|  | root_vmar_rights); | 
|  | if (result != ZX_OK) { | 
|  | process->aspace_->Destroy(); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | *rights = ZX_DEFAULT_PROCESS_RIGHTS; | 
|  | *dispatcher = fbl::move(process); | 
|  | *root_vmar_disp = DownCastDispatcher<VmAddressRegionDispatcher>( | 
|  | &new_vmar_dispatcher); | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | ProcessDispatcher::ProcessDispatcher(fbl::RefPtr<JobDispatcher> job, | 
|  | fbl::StringPiece name, | 
|  | uint32_t flags) | 
|  | : job_(fbl::move(job)), policy_(job_->GetPolicy()), | 
|  | name_(name.data(), name.length()) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | // Generate handle XOR mask with top bit and bottom two bits cleared | 
|  | uint32_t secret; | 
|  | auto prng = crypto::GlobalPRNG::GetInstance(); | 
|  | prng->Draw(&secret, sizeof(secret)); | 
|  |  | 
|  | // Handle values cannot be negative values, so we mask the high bit. | 
|  | handle_rand_ = (secret << 2) & INT_MAX; | 
|  | } | 
|  |  | 
|  | ProcessDispatcher::~ProcessDispatcher() { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | DEBUG_ASSERT(state_ == State::INITIAL || state_ == State::DEAD); | 
|  |  | 
|  | // Assert that the -> DEAD transition cleaned up what it should have. | 
|  | DEBUG_ASSERT(handles_.is_empty()); | 
|  | DEBUG_ASSERT(exception_port_ == nullptr); | 
|  | DEBUG_ASSERT(debugger_exception_port_ == nullptr); | 
|  |  | 
|  | // Remove ourselves from the parent job's raw ref to us. Note that this might | 
|  | // have beeen called when transitioning State::DEAD. The Job can handle double calls. | 
|  | job_->RemoveChildProcess(this); | 
|  |  | 
|  | LTRACE_EXIT_OBJ; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::on_zero_handles() { | 
|  | // If the process is in the initial state and the last handle is closed | 
|  | // we never detach from the parent job, so run the shutdown sequence for | 
|  | // that case. | 
|  | { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ != State::INITIAL) { | 
|  | // Use the normal cleanup path instead. | 
|  | return; | 
|  | } | 
|  | SetStateLocked(State::DEAD); | 
|  | } | 
|  |  | 
|  | FinishDeadTransition(); | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::get_name(char out_name[ZX_MAX_NAME_LEN]) const { | 
|  | name_.get(ZX_MAX_NAME_LEN, out_name); | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::set_name(const char* name, size_t len) { | 
|  | return name_.set(name, len); | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::Initialize() { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | DEBUG_ASSERT(state_ == State::INITIAL); | 
|  |  | 
|  | // create an address space for this process, named after the process's koid. | 
|  | char aspace_name[ZX_MAX_NAME_LEN]; | 
|  | snprintf(aspace_name, sizeof(aspace_name), "proc:%" PRIu64, get_koid()); | 
|  | aspace_ = VmAspace::Create(VmAspace::TYPE_USER, aspace_name); | 
|  | if (!aspace_) { | 
|  | TRACEF("error creating address space\n"); | 
|  | return ZX_ERR_NO_MEMORY; | 
|  | } | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::Exit(int64_t retcode) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | DEBUG_ASSERT(ProcessDispatcher::GetCurrent() == this); | 
|  |  | 
|  | { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | // check that we're in the RUNNING state or we're racing with something | 
|  | // else that has already pushed us until the DYING state | 
|  | DEBUG_ASSERT_MSG(state_ == State::RUNNING || state_ == State::DYING, | 
|  | "state is %s", StateToString(state_)); | 
|  |  | 
|  | // Set the exit status if there isn't already an exit in progress. | 
|  | if (state_ != State::DYING) { | 
|  | DEBUG_ASSERT(retcode_ == 0); | 
|  | retcode_ = retcode; | 
|  | } | 
|  |  | 
|  | // enter the dying state, which should kill all threads | 
|  | SetStateLocked(State::DYING); | 
|  | } | 
|  |  | 
|  | ThreadDispatcher::GetCurrent()->Exit(); | 
|  |  | 
|  | __UNREACHABLE; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::Kill() { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | // ZX-880: Call RemoveChildProcess outside of |get_lock()|. | 
|  | bool became_dead = false; | 
|  |  | 
|  | { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | // we're already dead | 
|  | if (state_ == State::DEAD) | 
|  | return; | 
|  |  | 
|  | if (state_ != State::DYING) { | 
|  | // If there isn't an Exit already in progress, set a nonzero exit | 
|  | // status so e.g. crashing tests don't appear to have succeeded. | 
|  | DEBUG_ASSERT(retcode_ == 0); | 
|  | retcode_ = -1; | 
|  | } | 
|  |  | 
|  | // if we have no threads, enter the dead state directly | 
|  | if (thread_list_.is_empty()) { | 
|  | SetStateLocked(State::DEAD); | 
|  | became_dead = true; | 
|  | } else { | 
|  | // enter the dying state, which should trigger a thread kill. | 
|  | // the last thread exiting will transition us to DEAD | 
|  | SetStateLocked(State::DYING); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (became_dead) | 
|  | FinishDeadTransition(); | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::KillAllThreadsLocked() { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | for (auto& thread : thread_list_) { | 
|  | LTRACEF("killing thread %p\n", &thread); | 
|  | thread.Kill(); | 
|  | } | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::AddThread(ThreadDispatcher* t, bool initial_thread) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | if (initial_thread) { | 
|  | if (state_ != State::INITIAL) | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } else { | 
|  | // We must not add a thread when in the DYING or DEAD states. | 
|  | // Also, we want to ensure that this is not the first thread. | 
|  | if (state_ != State::RUNNING) | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  |  | 
|  | // add the thread to our list | 
|  | DEBUG_ASSERT(thread_list_.is_empty() == initial_thread); | 
|  | thread_list_.push_back(t); | 
|  |  | 
|  | DEBUG_ASSERT(t->process() == this); | 
|  |  | 
|  | if (initial_thread) | 
|  | SetStateLocked(State::RUNNING); | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | // This is called within thread T's context when it is exiting. | 
|  |  | 
|  | void ProcessDispatcher::RemoveThread(ThreadDispatcher* t) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  |  | 
|  | // ZX-880: Call RemoveChildProcess outside of |get_lock()|. | 
|  | bool became_dead = false; | 
|  |  | 
|  | { | 
|  | // we're going to check for state and possibly transition below | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | // remove the thread from our list | 
|  | DEBUG_ASSERT(t != nullptr); | 
|  | thread_list_.erase(*t); | 
|  |  | 
|  | // if this was the last thread, transition directly to DEAD state | 
|  | if (thread_list_.is_empty()) { | 
|  | LTRACEF("last thread left the process %p, entering DEAD state\n", this); | 
|  | SetStateLocked(State::DEAD); | 
|  | became_dead = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (became_dead) | 
|  | FinishDeadTransition(); | 
|  | } | 
|  |  | 
|  | zx_koid_t ProcessDispatcher::get_related_koid() const { | 
|  | return job_->get_koid(); | 
|  | } | 
|  |  | 
|  | ProcessDispatcher::State ProcessDispatcher::state() const { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | return state_; | 
|  | } | 
|  |  | 
|  | fbl::RefPtr<JobDispatcher> ProcessDispatcher::job() { | 
|  | return job_; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::SetStateLocked(State s) { | 
|  | LTRACEF("process %p: state %u (%s)\n", this, static_cast<unsigned int>(s), StateToString(s)); | 
|  |  | 
|  | DEBUG_ASSERT(get_lock()->lock().IsHeld()); | 
|  |  | 
|  | // look for some invalid state transitions | 
|  | if (state_ == State::DEAD && s != State::DEAD) { | 
|  | panic("ProcessDispatcher::SetStateLocked invalid state transition from DEAD to !DEAD\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // transitions to your own state are okay | 
|  | if (s == state_) | 
|  | return; | 
|  |  | 
|  | state_ = s; | 
|  |  | 
|  | if (s == State::DYING) { | 
|  | // send kill to all of our threads | 
|  | KillAllThreadsLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Finish processing of the transition to State::DEAD. Some things need to be done | 
|  | // outside of holding |get_lock()|. Beware this is called from several places | 
|  | // including on_zero_handles(). | 
|  | void ProcessDispatcher::FinishDeadTransition() { | 
|  | DEBUG_ASSERT(!completely_dead_); | 
|  | completely_dead_ = true; | 
|  |  | 
|  | // clean up the handle table | 
|  | LTRACEF_LEVEL(2, "cleaning up handle table on proc %p\n", this); | 
|  |  | 
|  | fbl::DoublyLinkedList<Handle*> to_clean; | 
|  | { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | for (auto& handle : handles_) { | 
|  | handle.set_process_id(0u); | 
|  | } | 
|  | to_clean.swap(handles_); | 
|  | } | 
|  |  | 
|  | // zx-1544: Here is where if we're the last holder of a handle of one of | 
|  | // our exception ports then ResetExceptionPort will get called (by | 
|  | // ExceptionPort::OnPortZeroHandles) and will need to grab |get_lock()|. | 
|  | // This needs to be done outside of |get_lock()|. | 
|  | while (!to_clean.is_empty()) { | 
|  | // Delete handle via HandleOwner dtor. | 
|  | HandleOwner ho(to_clean.pop_front()); | 
|  | } | 
|  |  | 
|  | LTRACEF_LEVEL(2, "done cleaning up handle table on proc %p\n", this); | 
|  |  | 
|  | // tear down the address space | 
|  | aspace_->Destroy(); | 
|  |  | 
|  | // signal waiter | 
|  | LTRACEF_LEVEL(2, "signaling waiters\n"); | 
|  | UpdateState(0u, ZX_TASK_TERMINATED); | 
|  |  | 
|  | // The PROC_CREATE record currently emits a uint32_t koid. | 
|  | uint32_t koid = static_cast<uint32_t>(get_koid()); | 
|  | ktrace(TAG_PROC_EXIT, koid, 0, 0, 0); | 
|  |  | 
|  | // Call job_->RemoveChildProcess(this) outside of |get_lock()|. Otherwise | 
|  | // we risk a deadlock as we have |get_lock()| and RemoveChildProcess grabs | 
|  | // the job's |lock_|, whereas JobDispatcher::EnumerateChildren obtains the | 
|  | // locks in the opposite order. We want to keep lock acquisition order | 
|  | // consistent, and JobDispatcher::EnumerateChildren's order makes | 
|  | // sense. We don't need |get_lock()| when calling RemoveChildProcess | 
|  | // here. ZX-880 | 
|  | // RemoveChildProcess is called soon after releasing |get_lock()| so that | 
|  | // the semantics of signaling ZX_JOB_NO_PROCESSES match that of | 
|  | // ZX_TASK_TERMINATED. | 
|  | job_->RemoveChildProcess(this); | 
|  | } | 
|  |  | 
|  | // process handle manipulation routines | 
|  | zx_handle_t ProcessDispatcher::MapHandleToValue(const Handle* handle) const { | 
|  | return map_handle_to_value(handle, handle_rand_); | 
|  | } | 
|  |  | 
|  | zx_handle_t ProcessDispatcher::MapHandleToValue(const HandleOwner& handle) const { | 
|  | return map_handle_to_value(handle.get(), handle_rand_); | 
|  | } | 
|  |  | 
|  | Handle* ProcessDispatcher::GetHandleLocked(zx_handle_t handle_value, | 
|  | bool skip_policy) { | 
|  | auto handle = map_value_to_handle(handle_value, handle_rand_); | 
|  | if (handle && handle->process_id() == get_koid()) | 
|  | return handle; | 
|  |  | 
|  | // Handle lookup failed.  We potentially generate an exception, | 
|  | // depending on the job policy.  Note that we don't use the return | 
|  | // value from QueryPolicy() here: ZX_POL_ACTION_ALLOW and | 
|  | // ZX_POL_ACTION_DENY are equivalent for ZX_POL_BAD_HANDLE. | 
|  | if (likely(!skip_policy)) | 
|  | QueryPolicy(ZX_POL_BAD_HANDLE); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::AddHandle(HandleOwner handle) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | AddHandleLocked(fbl::move(handle)); | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::AddHandleLocked(HandleOwner handle) { | 
|  | handle->set_process_id(get_koid()); | 
|  | handles_.push_front(handle.release()); | 
|  | } | 
|  |  | 
|  | HandleOwner ProcessDispatcher::RemoveHandle(zx_handle_t handle_value) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | return RemoveHandleLocked(handle_value); | 
|  | } | 
|  |  | 
|  | HandleOwner ProcessDispatcher::RemoveHandleLocked(zx_handle_t handle_value) { | 
|  | auto handle = GetHandleLocked(handle_value); | 
|  | if (!handle) | 
|  | return nullptr; | 
|  |  | 
|  | handle->set_process_id(0u); | 
|  | handles_.erase(*handle); | 
|  |  | 
|  | return HandleOwner(handle); | 
|  | } | 
|  |  | 
|  |  | 
|  | zx_status_t ProcessDispatcher::RemoveHandles(user_in_ptr<const zx_handle_t> user_handles, | 
|  | size_t num_handles) { | 
|  | zx_status_t status = ZX_OK; | 
|  | size_t offset = 0; | 
|  | while (offset < num_handles) { | 
|  | // We process |num_handles| in chunks of |kMaxMessageHandles| | 
|  | // because we don't have a limit on how large |num_handles| | 
|  | // can be. | 
|  | auto chunk_size = fbl::min<size_t>(num_handles - offset, kMaxMessageHandles); | 
|  |  | 
|  | zx_handle_t handles[kMaxMessageHandles]; | 
|  |  | 
|  | // If we fail |copy_array_from_user|, then we might discard some, but | 
|  | // not all, of the handles |user_handles| specified. | 
|  | if (user_handles.copy_array_from_user(handles, chunk_size, offset) != ZX_OK) | 
|  | return status; | 
|  |  | 
|  | { | 
|  | Guard<fbl::Mutex> guard{handle_table_lock()}; | 
|  | for (size_t ix = 0; ix != chunk_size; ++ix) { | 
|  | if (handles[ix] == ZX_HANDLE_INVALID) | 
|  | continue; | 
|  | auto handle = RemoveHandleLocked(handles[ix]); | 
|  | if (!handle) | 
|  | status = ZX_ERR_BAD_HANDLE; | 
|  | } | 
|  | } | 
|  |  | 
|  | offset += chunk_size; | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | zx_koid_t ProcessDispatcher::GetKoidForHandle(zx_handle_t handle_value) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | Handle* handle = GetHandleLocked(handle_value); | 
|  | if (!handle) | 
|  | return ZX_KOID_INVALID; | 
|  | return handle->dispatcher()->get_koid(); | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetDispatcherInternal(zx_handle_t handle_value, | 
|  | fbl::RefPtr<Dispatcher>* dispatcher, | 
|  | zx_rights_t* rights) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | Handle* handle = GetHandleLocked(handle_value); | 
|  | if (!handle) | 
|  | return ZX_ERR_BAD_HANDLE; | 
|  |  | 
|  | *dispatcher = handle->dispatcher(); | 
|  | if (rights) | 
|  | *rights = handle->rights(); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetDispatcherWithRightsInternal(zx_handle_t handle_value, | 
|  | zx_rights_t desired_rights, | 
|  | fbl::RefPtr<Dispatcher>* dispatcher_out, | 
|  | zx_rights_t* out_rights) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | Handle* handle = GetHandleLocked(handle_value); | 
|  | if (!handle) | 
|  | return ZX_ERR_BAD_HANDLE; | 
|  |  | 
|  | if (!handle->HasRights(desired_rights)) | 
|  | return ZX_ERR_ACCESS_DENIED; | 
|  |  | 
|  | *dispatcher_out = handle->dispatcher(); | 
|  | if (out_rights) | 
|  | *out_rights = handle->rights(); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetInfo(zx_info_process_t* info) { | 
|  | memset(info, 0, sizeof(*info)); | 
|  |  | 
|  | State state; | 
|  | // retcode_ depends on the state: make sure they're consistent. | 
|  | { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | state = state_; | 
|  | info->return_code = retcode_; | 
|  | // TODO: Protect with rights if necessary. | 
|  | info->debugger_attached = debugger_exception_port_ != nullptr; | 
|  | } | 
|  |  | 
|  | switch (state) { | 
|  | case State::DEAD: | 
|  | case State::DYING: | 
|  | info->exited = true; | 
|  | __FALLTHROUGH; | 
|  | case State::RUNNING: | 
|  | info->started = true; | 
|  | break; | 
|  | case State::INITIAL: | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetStats(zx_info_task_stats_t* stats) { | 
|  | DEBUG_ASSERT(stats != nullptr); | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ != State::RUNNING) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  | VmAspace::vm_usage_t usage; | 
|  | zx_status_t s = aspace_->GetMemoryUsage(&usage); | 
|  | if (s != ZX_OK) { | 
|  | return s; | 
|  | } | 
|  | stats->mem_mapped_bytes = usage.mapped_pages * PAGE_SIZE; | 
|  | stats->mem_private_bytes = usage.private_pages * PAGE_SIZE; | 
|  | stats->mem_shared_bytes = usage.shared_pages * PAGE_SIZE; | 
|  | stats->mem_scaled_shared_bytes = usage.scaled_shared_bytes; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetAspaceMaps( | 
|  | user_out_ptr<zx_info_maps_t> maps, size_t max, | 
|  | size_t* actual, size_t* available) { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ != State::RUNNING) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  | return GetVmAspaceMaps(aspace_, maps, max, actual, available); | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetVmos( | 
|  | user_out_ptr<zx_info_vmo_t> vmos, size_t max, | 
|  | size_t* actual_out, size_t* available_out) { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ != State::RUNNING) { | 
|  | return ZX_ERR_BAD_STATE; | 
|  | } | 
|  | size_t actual = 0; | 
|  | size_t available = 0; | 
|  | zx_status_t s = GetProcessVmosViaHandles(this, vmos, max, &actual, &available); | 
|  | if (s != ZX_OK) { | 
|  | return s; | 
|  | } | 
|  | size_t actual2 = 0; | 
|  | size_t available2 = 0; | 
|  | DEBUG_ASSERT(max >= actual); | 
|  | s = GetVmAspaceVmos(aspace_, vmos.element_offset(actual), max - actual, | 
|  | &actual2, &available2); | 
|  | if (s != ZX_OK) { | 
|  | return s; | 
|  | } | 
|  | *actual_out = actual + actual2; | 
|  | *available_out = available + available2; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::GetThreads(fbl::Array<zx_koid_t>* out_threads) { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | size_t n = thread_list_.size_slow(); | 
|  | fbl::Array<zx_koid_t> threads; | 
|  | fbl::AllocChecker ac; | 
|  | threads.reset(new (&ac) zx_koid_t[n], n); | 
|  | if (!ac.check()) | 
|  | return ZX_ERR_NO_MEMORY; | 
|  | size_t i = 0; | 
|  | for (auto& thread : thread_list_) { | 
|  | threads[i] = thread.get_koid(); | 
|  | ++i; | 
|  | } | 
|  | DEBUG_ASSERT(i == n); | 
|  | *out_threads = fbl::move(threads); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::SetExceptionPort(fbl::RefPtr<ExceptionPort> eport) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  | bool debugger = false; | 
|  | switch (eport->type()) { | 
|  | case ExceptionPort::Type::DEBUGGER: | 
|  | debugger = true; | 
|  | break; | 
|  | case ExceptionPort::Type::PROCESS: | 
|  | break; | 
|  | default: | 
|  | DEBUG_ASSERT_MSG(false, "unexpected port type: %d", | 
|  | static_cast<int>(eport->type())); | 
|  | break; | 
|  | } | 
|  |  | 
|  | // Lock |get_lock()| to ensure the process doesn't transition to dead | 
|  | // while we're setting the exception handler. | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ == State::DEAD) | 
|  | return ZX_ERR_NOT_FOUND; | 
|  | if (debugger) { | 
|  | if (debugger_exception_port_) | 
|  | return ZX_ERR_ALREADY_BOUND; | 
|  | debugger_exception_port_ = eport; | 
|  | } else { | 
|  | if (exception_port_) | 
|  | return ZX_ERR_ALREADY_BOUND; | 
|  | exception_port_ = eport; | 
|  | } | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | bool ProcessDispatcher::ResetExceptionPort(bool debugger, bool quietly) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  | fbl::RefPtr<ExceptionPort> eport; | 
|  |  | 
|  | // Remove the exception handler first. As we resume threads we don't | 
|  | // want them to hit another exception and get back into | 
|  | // ExceptionHandlerExchange. | 
|  | { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (debugger) { | 
|  | debugger_exception_port_.swap(eport); | 
|  | } else { | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | if (!quietly) { | 
|  | OnExceptionPortRemoval(eport); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | fbl::RefPtr<ExceptionPort> ProcessDispatcher::exception_port() { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | return exception_port_; | 
|  | } | 
|  |  | 
|  | fbl::RefPtr<ExceptionPort> ProcessDispatcher::debugger_exception_port() { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | return debugger_exception_port_; | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::OnExceptionPortRemoval( | 
|  | const fbl::RefPtr<ExceptionPort>& eport) { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | for (auto& thread : thread_list_) { | 
|  | thread.OnExceptionPortRemoval(eport); | 
|  | } | 
|  | } | 
|  |  | 
|  | uint32_t ProcessDispatcher::ThreadCount() const { | 
|  | canary_.Assert(); | 
|  |  | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | return static_cast<uint32_t>(thread_list_.size_slow()); | 
|  | } | 
|  |  | 
|  | size_t ProcessDispatcher::PageCount() const { | 
|  | canary_.Assert(); | 
|  |  | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | if (state_ != State::RUNNING) { | 
|  | return 0; | 
|  | } | 
|  | return aspace_->AllocatedPages(); | 
|  | } | 
|  |  | 
|  | class FindProcessByKoid final : public JobEnumerator { | 
|  | public: | 
|  | FindProcessByKoid(zx_koid_t koid) : koid_(koid) {} | 
|  | FindProcessByKoid(const FindProcessByKoid&) = delete; | 
|  |  | 
|  | // To be called after enumeration. | 
|  | fbl::RefPtr<ProcessDispatcher> get_pd() { return pd_; } | 
|  |  | 
|  | private: | 
|  | bool OnProcess(ProcessDispatcher* process) final { | 
|  | if (process->get_koid() == koid_) { | 
|  | pd_ = fbl::WrapRefPtr(process); | 
|  | // Stop the enumeration. | 
|  | return false; | 
|  | } | 
|  | // Keep looking. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const zx_koid_t koid_; | 
|  | fbl::RefPtr<ProcessDispatcher> pd_ = nullptr; | 
|  | }; | 
|  |  | 
|  | // static | 
|  | fbl::RefPtr<ProcessDispatcher> ProcessDispatcher::LookupProcessById(zx_koid_t koid) { | 
|  | FindProcessByKoid finder(koid); | 
|  | GetRootJobDispatcher()->EnumerateChildren(&finder, /* recurse */ true); | 
|  | return finder.get_pd(); | 
|  | } | 
|  |  | 
|  | fbl::RefPtr<ThreadDispatcher> ProcessDispatcher::LookupThreadById(zx_koid_t koid) { | 
|  | LTRACE_ENTRY_OBJ; | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  |  | 
|  | auto iter = thread_list_.find_if([koid](const ThreadDispatcher& t) { return t.get_koid() == koid; }); | 
|  | return fbl::WrapRefPtr(iter.CopyPointer()); | 
|  | } | 
|  |  | 
|  | uintptr_t ProcessDispatcher::get_debug_addr() const { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | return debug_addr_; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::set_debug_addr(uintptr_t addr) { | 
|  | if (addr == 0u) | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | // Only allow the value to be set to a nonzero or magic debug break once: | 
|  | // Once ld.so has set it that's it. | 
|  | if (!(debug_addr_ == 0u || debug_addr_ == ZX_PROCESS_DEBUG_ADDR_BREAK_ON_SET)) | 
|  | return ZX_ERR_ACCESS_DENIED; | 
|  | debug_addr_ = addr; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t ProcessDispatcher::QueryPolicy(uint32_t condition) const { | 
|  | auto action = GetSystemPolicyManager()->QueryBasicPolicy(policy_, condition); | 
|  | if (action & ZX_POL_ACTION_EXCEPTION) { | 
|  | thread_signal_policy_exception(); | 
|  | } | 
|  | // TODO(cpu): check for the ZX_POL_KILL bit and return an error code | 
|  | // that abigen understands as termination. | 
|  | return (action & ZX_POL_ACTION_DENY) ? ZX_ERR_ACCESS_DENIED : ZX_OK; | 
|  | } | 
|  |  | 
|  | uintptr_t ProcessDispatcher::cache_vdso_code_address() { | 
|  | Guard<fbl::Mutex> guard{get_lock()}; | 
|  | vdso_code_address_ = aspace_->vdso_code_address(); | 
|  | return vdso_code_address_; | 
|  | } | 
|  |  | 
|  | const char* StateToString(ProcessDispatcher::State state) { | 
|  | switch (state) { | 
|  | case ProcessDispatcher::State::INITIAL: | 
|  | return "initial"; | 
|  | case ProcessDispatcher::State::RUNNING: | 
|  | return "running"; | 
|  | case ProcessDispatcher::State::DYING: | 
|  | return "dying"; | 
|  | case ProcessDispatcher::State::DEAD: | 
|  | return "dead"; | 
|  | } | 
|  | return "unknown"; | 
|  | } | 
|  |  | 
|  | bool ProcessDispatcher::IsHandleValid(zx_handle_t handle_value) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | return (GetHandleLocked(handle_value) != nullptr); | 
|  | } | 
|  |  | 
|  | bool ProcessDispatcher::IsHandleValidNoPolicyCheck(zx_handle_t handle_value) { | 
|  | Guard<fbl::Mutex> guard{&handle_table_lock_}; | 
|  | return (GetHandleLocked(handle_value, true) != nullptr); | 
|  | } | 
|  |  | 
|  | void ProcessDispatcher::OnProcessStartForJobDebugger(ThreadDispatcher *t) { | 
|  | auto job = job_; | 
|  | while (job) { | 
|  | auto port = job->debugger_exception_port(); | 
|  | if (port) { | 
|  | port->OnProcessStartForDebugger(t); | 
|  | break; | 
|  | } else { | 
|  | job = job->parent(); | 
|  | } | 
|  | } | 
|  | } |