| // 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 <lib/counters.h> | 
 | #include <lib/crypto/global_prng.h> | 
 | #include <lib/ktrace.h> | 
 | #include <string.h> | 
 | #include <trace.h> | 
 | #include <zircon/listnode.h> | 
 | #include <zircon/rights.h> | 
 |  | 
 | #include <arch/defines.h> | 
 | #include <fbl/alloc_checker.h> | 
 | #include <fbl/auto_lock.h> | 
 | #include <kernel/thread.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 <vm/vm.h> | 
 | #include <vm/vm_aspace.h> | 
 | #include <vm/vm_object.h> | 
 |  | 
 | #define LOCAL_TRACE 0 | 
 |  | 
 | namespace { | 
 |  | 
 | const uint32_t kPolicyIdToPolicyException[] = { | 
 |     ZX_EXCP_POLICY_CODE_BAD_HANDLE,             // ZX_POL_BAD_HANDLE, | 
 |     ZX_EXCP_POLICY_CODE_WRONG_OBJECT,           // ZX_POL_WRONG_OBJECT | 
 |     ZX_EXCP_POLICY_CODE_VMAR_WX,                // ZX_POL_VMAR_WX | 
 |     ZX_EXCP_POLICY_CODE_NEW_ANY,                // ZX_POL_NEW_ANY | 
 |     ZX_EXCP_POLICY_CODE_NEW_VMO,                // ZX_POL_NEW_VMO | 
 |     ZX_EXCP_POLICY_CODE_NEW_CHANNEL,            // ZX_POL_NEW_CHANNEL | 
 |     ZX_EXCP_POLICY_CODE_NEW_EVENT,              // ZX_POL_NEW_EVENT | 
 |     ZX_EXCP_POLICY_CODE_NEW_EVENTPAIR,          // ZX_POL_NEW_EVENTPAIR | 
 |     ZX_EXCP_POLICY_CODE_NEW_PORT,               // ZX_POL_NEW_PORT | 
 |     ZX_EXCP_POLICY_CODE_NEW_SOCKET,             // ZX_POL_NEW_SOCKET | 
 |     ZX_EXCP_POLICY_CODE_NEW_FIFO,               // ZX_POL_NEW_FIFO | 
 |     ZX_EXCP_POLICY_CODE_NEW_TIMER,              // ZX_POL_NEW_TIMER | 
 |     ZX_EXCP_POLICY_CODE_NEW_PROCESS,            // ZX_POL_NEW_PROCESS | 
 |     ZX_EXCP_POLICY_CODE_NEW_PROFILE,            // ZX_POL_NEW_PROFILE | 
 |     ZX_EXCP_POLICY_CODE_AMBIENT_MARK_VMO_EXEC,  // ZX_POL_AMBIENT_MARK_VMO_EXEC | 
 | }; | 
 |  | 
 | static_assert(countof(kPolicyIdToPolicyException) == ZX_POL_MAX, | 
 |               "must update mapping from policy id to synth_code generated by policy exception"); | 
 |  | 
 | }  // namespace | 
 |  | 
 | KCOUNTER(dispatcher_process_create_count, "dispatcher.process.create") | 
 | KCOUNTER(dispatcher_process_destroy_count, "dispatcher.process.destroy") | 
 |  | 
 | zx_status_t ProcessDispatcher::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) { | 
 |   fbl::AllocChecker ac; | 
 |   KernelHandle new_handle(fbl::AdoptRef(new (&ac) ProcessDispatcher(job, name, flags))); | 
 |   if (!ac.check()) | 
 |     return ZX_ERR_NO_MEMORY; | 
 |  | 
 |   zx_status_t result = new_handle.dispatcher()->Initialize(); | 
 |   if (result != ZX_OK) | 
 |     return result; | 
 |  | 
 |   // Create a dispatcher for the root VMAR. | 
 |   KernelHandle<VmAddressRegionDispatcher> new_vmar_handle; | 
 |   result = VmAddressRegionDispatcher::Create(new_handle.dispatcher()->aspace()->RootVmar(), | 
 |                                              ARCH_MMU_FLAG_PERM_USER, &new_vmar_handle, | 
 |                                              root_vmar_rights); | 
 |   if (result != ZX_OK) | 
 |     return result; | 
 |  | 
 |   // Only now that the process has been fully created and initialized can we register it with its | 
 |   // parent job. We don't want anyone to see it in a partially initalized state. | 
 |   if (!job->AddChildProcess(new_handle.dispatcher())) { | 
 |     return ZX_ERR_BAD_STATE; | 
 |   } | 
 |  | 
 |   *rights = default_rights(); | 
 |   *handle = ktl::move(new_handle); | 
 |   *root_vmar_handle = ktl::move(new_vmar_handle); | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | ProcessDispatcher::ProcessDispatcher(fbl::RefPtr<JobDispatcher> job, ktl::string_view name, | 
 |                                      uint32_t flags) | 
 |     : job_(ktl::move(job)), | 
 |       policy_(job_->GetPolicy()), | 
 |       handle_table_(this), | 
 |       exceptionate_(ZX_EXCEPTION_CHANNEL_TYPE_PROCESS), | 
 |       debug_exceptionate_(ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER), | 
 |       name_(name.data(), name.length()) { | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   kcounter_add(dispatcher_process_create_count, 1); | 
 | } | 
 |  | 
 | 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(!aspace_ || aspace_->is_destroyed()); | 
 |  | 
 |   kcounter_add(dispatcher_process_destroy_count, 1); | 
 |  | 
 |   // Remove ourselves from the parent job's raw ref to us. Note that this might | 
 |   // have been 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<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<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<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::ExitCurrent(); | 
 |  | 
 |   __UNREACHABLE; | 
 | } | 
 |  | 
 | void ProcessDispatcher::Kill(int64_t retcode) { | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   // fxbug.dev/30829: Call RemoveChildProcess outside of |get_lock()|. | 
 |   bool became_dead = false; | 
 |  | 
 |   { | 
 |     Guard<Mutex> guard{get_lock()}; | 
 |  | 
 |     // we're already dead | 
 |     if (state_ == State::DEAD) | 
 |       return; | 
 |  | 
 |     if (state_ != State::DYING) { | 
 |       DEBUG_ASSERT(retcode_ == 0); | 
 |       retcode_ = retcode; | 
 |     } | 
 |  | 
 |     // 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(); | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::Suspend() { | 
 |   canary_.Assert(); | 
 |  | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |  | 
 |   // If we're dying don't try to suspend. | 
 |   if (state_ == State::DYING || state_ == State::DEAD) | 
 |     return ZX_ERR_BAD_STATE; | 
 |  | 
 |   DEBUG_ASSERT(suspend_count_ >= 0); | 
 |   suspend_count_++; | 
 |   if (suspend_count_ == 1) { | 
 |     for (auto& thread : thread_list_) { | 
 |       // Thread suspend can only fail if the thread is already dying, which is fine here | 
 |       // since it will be removed from this process shortly, so continue to suspend whether | 
 |       // the thread suspend succeeds or fails. | 
 |       zx_status_t status = thread.Suspend(); | 
 |       DEBUG_ASSERT(status == ZX_OK || thread.IsDyingOrDead()); | 
 |     } | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | void ProcessDispatcher::Resume() { | 
 |   canary_.Assert(); | 
 |  | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |  | 
 |   // If we're in the process of dying don't try to resume, just let it continue to clean up. | 
 |   if (state_ == State::DYING || state_ == State::DEAD) | 
 |     return; | 
 |  | 
 |   DEBUG_ASSERT(suspend_count_ > 0); | 
 |   suspend_count_--; | 
 |   if (suspend_count_ == 0) { | 
 |     for (auto& thread : thread_list_) { | 
 |       thread.Resume(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void ProcessDispatcher::KillAllThreadsLocked() { | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   for (auto& thread : thread_list_) { | 
 |     LTRACEF("killing thread %p\n", &thread); | 
 |     thread.Kill(); | 
 |   } | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::AddInitializedThread(ThreadDispatcher* t, bool initial_thread, | 
 |                                                     const ThreadDispatcher::EntryState& entry) { | 
 |   LTRACE_ENTRY_OBJ; | 
 |  | 
 |   Guard<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; | 
 |   } | 
 |  | 
 |   // Now that we know our state is okay we can attempt to start the thread running. This is okay | 
 |   // since as long as the thread doesn't refuse to start running then we cannot fail from here | 
 |   // and so we will update our thread_list_ and state before we drop the lock, making this | 
 |   // whole process atomic to any observers. | 
 |   zx_status_t result = t->MakeRunnable(entry, suspend_count_ > 0); | 
 |   if (result != ZX_OK) { | 
 |     return result; | 
 |   } | 
 |  | 
 |   // 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; | 
 |  | 
 |   // fxbug.dev/30829: Call RemoveChildProcess outside of |get_lock()|. | 
 |   bool became_dead = false; | 
 |  | 
 |   { | 
 |     // we're going to check for state and possibly transition below | 
 |     Guard<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; | 
 |     } | 
 |  | 
 |     Thread::RuntimeStats child_runtime; | 
 |     if (t->GetRuntimeStats(&child_runtime) == ZX_OK) { | 
 |       aggregated_runtime_stats_.Add(child_runtime.TotalRuntime()); | 
 |     } | 
 |   } | 
 |  | 
 |   if (became_dead) | 
 |     FinishDeadTransition(); | 
 | } | 
 |  | 
 | zx_koid_t ProcessDispatcher::get_related_koid() const { return job_->get_koid(); } | 
 |  | 
 | ProcessDispatcher::State ProcessDispatcher::state() const { | 
 |   Guard<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; | 
 |  | 
 |   // It doesn't matter whether the lock is held or not while shutting down | 
 |   // the exceptionates, this is just the most convenient place to do it. | 
 |   exceptionate_.Shutdown(); | 
 |   debug_exceptionate_.Shutdown(); | 
 |  | 
 |   // clean up the handle table | 
 |   LTRACEF_LEVEL(2, "cleaning up handle table on proc %p\n", this); | 
 |   handle_table_.Clean(); | 
 |   LTRACEF_LEVEL(2, "done cleaning up handle table on proc %p\n", this); | 
 |  | 
 |   // Tear down the address space. It may not exist if Initialize() failed. | 
 |   if (aspace_) | 
 |     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. fxbug.dev/30829 | 
 |   job_->RemoveChildProcess(this); | 
 |  | 
 |   // If we are critical to a job, we need to take action. Similar to the above | 
 |   // comment, we avoid performing the actual call into the job whilst still | 
 |   // holding the lock. | 
 |   fbl::RefPtr<JobDispatcher> kill_job; | 
 |   { | 
 |     Guard<Mutex> guard{get_lock()}; | 
 |     if (critical_to_job_ != nullptr) { | 
 |       // Check if we accept any return code, or require it be non-zero. | 
 |       if (!retcode_nonzero_ || retcode_ != 0) { | 
 |         kill_job = critical_to_job_; | 
 |       } | 
 |     } | 
 |   } | 
 |   if (kill_job) { | 
 |     char proc_name[ZX_MAX_NAME_LEN]; | 
 |     char job_name[ZX_MAX_NAME_LEN]; | 
 |     get_name(proc_name); | 
 |     kill_job->get_name(job_name); | 
 |     printf("critical-process: process '%s' died, killing job '%s'\n", proc_name, job_name); | 
 |     kill_job->Kill(ZX_TASK_RETCODE_CRITICAL_PROCESS_KILL); | 
 |   } | 
 | } | 
 |  | 
 | void ProcessDispatcher::GetInfo(zx_info_process_t* info) const { | 
 |   canary_.Assert(); | 
 |  | 
 |   State state; | 
 |   // retcode_ depends on the state: make sure they're consistent. | 
 |   { | 
 |     Guard<Mutex> guard{get_lock()}; | 
 |     state = state_; | 
 |     info->return_code = retcode_; | 
 |     // TODO: Protect with rights if necessary. | 
 |     info->debugger_attached = debug_exceptionate_.HasValidChannel(); | 
 |   } | 
 |  | 
 |   switch (state) { | 
 |     case State::DEAD: | 
 |     case State::DYING: | 
 |       info->exited = true; | 
 |       __FALLTHROUGH; | 
 |     case State::RUNNING: | 
 |       info->started = true; | 
 |       break; | 
 |     case State::INITIAL: | 
 |     default: | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::GetStats(zx_info_task_stats_t* stats) const { | 
 |   DEBUG_ASSERT(stats != nullptr); | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   if (state_ == State::DEAD) { | 
 |     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::AccumulateRuntimeTo(zx_info_task_runtime_t* info) const { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   aggregated_runtime_stats_.AccumulateRuntimeTo(info); | 
 |   for (const auto& thread : thread_list_) { | 
 |     zx_status_t err = thread.AccumulateRuntimeTo(info); | 
 |     if (err != ZX_OK) { | 
 |       return err; | 
 |     } | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::GetAspaceMaps(VmAspace* current_aspace, | 
 |                                              user_out_ptr<zx_info_maps_t> maps, size_t max, | 
 |                                              size_t* actual, size_t* available) const { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   if (state_ == State::DEAD) { | 
 |     return ZX_ERR_BAD_STATE; | 
 |   } | 
 |   return GetVmAspaceMaps(current_aspace, aspace_, maps, max, actual, available); | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::GetVmos(VmAspace* current_aspace, VmoInfoWriter& vmos, size_t max, | 
 |                                        size_t* actual_out, size_t* available_out) { | 
 |   { | 
 |     Guard<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 = GetProcessVmos(this, vmos, max, &actual, &available); | 
 |   if (s != ZX_OK) { | 
 |     return s; | 
 |   } | 
 |  | 
 |   size_t actual2 = 0; | 
 |   size_t available2 = 0; | 
 |   DEBUG_ASSERT(max >= actual); | 
 |   vmos.AddOffset(actual); | 
 |   s = GetVmAspaceVmos(current_aspace, aspace_, vmos, 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) const { | 
 |   Guard<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 = ktl::move(threads); | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::SetCriticalToJob(fbl::RefPtr<JobDispatcher> critical_to_job, | 
 |                                                 bool retcode_nonzero) { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |  | 
 |   if (critical_to_job_) { | 
 |     // The process is already critical to a job. | 
 |     return ZX_ERR_ALREADY_BOUND; | 
 |   } | 
 |  | 
 |   auto job_copy = job_; | 
 |   for (auto& job = job_copy; job; job = job->parent()) { | 
 |     if (job == critical_to_job) { | 
 |       critical_to_job_ = critical_to_job; | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   if (!critical_to_job_) { | 
 |     // The provided job is not the parent of this process, or an ancestor. | 
 |     return ZX_ERR_INVALID_ARGS; | 
 |   } | 
 |  | 
 |   retcode_nonzero_ = retcode_nonzero; | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | Exceptionate* ProcessDispatcher::exceptionate(Exceptionate::Type type) { | 
 |   canary_.Assert(); | 
 |   return type == Exceptionate::Type::kDebug ? &debug_exceptionate_ : &exceptionate_; | 
 | } | 
 |  | 
 | uint32_t ProcessDispatcher::ThreadCount() const { | 
 |   canary_.Assert(); | 
 |  | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   return static_cast<uint32_t>(thread_list_.size_slow()); | 
 | } | 
 |  | 
 | size_t ProcessDispatcher::PageCount() const { | 
 |   canary_.Assert(); | 
 |  | 
 |   Guard<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::RefPtr(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<Mutex> guard{get_lock()}; | 
 |  | 
 |   auto iter = | 
 |       thread_list_.find_if([koid](const ThreadDispatcher& t) { return t.get_koid() == koid; }); | 
 |   return fbl::RefPtr(iter.CopyPointer()); | 
 | } | 
 |  | 
 | uintptr_t ProcessDispatcher::get_debug_addr() const { | 
 |   Guard<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<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; | 
 | } | 
 |  | 
 | uintptr_t ProcessDispatcher::get_dyn_break_on_load() const { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   return dyn_break_on_load_; | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::set_dyn_break_on_load(uintptr_t break_on_load) { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   dyn_break_on_load_ = break_on_load; | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t ProcessDispatcher::EnforceBasicPolicy(uint32_t condition) { | 
 |   const auto action = policy_.QueryBasicPolicy(condition); | 
 |   switch (action) { | 
 |     case ZX_POL_ACTION_ALLOW: | 
 |       // Not calling IncrementCounter here because this is the common case (fast path). | 
 |       return ZX_OK; | 
 |     case ZX_POL_ACTION_DENY: | 
 |       JobPolicy::IncrementCounter(action, condition); | 
 |       return ZX_ERR_ACCESS_DENIED; | 
 |     case ZX_POL_ACTION_ALLOW_EXCEPTION: | 
 |       Thread::Current::SignalPolicyException(kPolicyIdToPolicyException[condition], 0u); | 
 |       JobPolicy::IncrementCounter(action, condition); | 
 |       return ZX_OK; | 
 |     case ZX_POL_ACTION_DENY_EXCEPTION: | 
 |       Thread::Current::SignalPolicyException(kPolicyIdToPolicyException[condition], 0u); | 
 |       JobPolicy::IncrementCounter(action, condition); | 
 |       return ZX_ERR_ACCESS_DENIED; | 
 |     case ZX_POL_ACTION_KILL: | 
 |       Kill(ZX_TASK_RETCODE_POLICY_KILL); | 
 |       JobPolicy::IncrementCounter(action, condition); | 
 |       // Because we've killed, this return value will never make it out to usermode. However, | 
 |       // callers of this method will see and act on it. | 
 |       return ZX_ERR_ACCESS_DENIED; | 
 |   }; | 
 |   panic("unexpected policy action %u\n", action); | 
 | } | 
 |  | 
 | TimerSlack ProcessDispatcher::GetTimerSlackPolicy() const { return policy_.GetTimerSlack(); } | 
 |  | 
 | TaskRuntimeStats ProcessDispatcher::GetAggregatedRuntime() const { | 
 |   Guard<Mutex> guard{get_lock()}; | 
 |   return aggregated_runtime_stats_; | 
 | } | 
 |  | 
 | uintptr_t ProcessDispatcher::cache_vdso_code_address() { | 
 |   Guard<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"; | 
 | } | 
 |  | 
 | void ProcessDispatcher::OnProcessStartForJobDebugger(ThreadDispatcher* t, | 
 |                                                      const arch_exception_context_t* context) { | 
 |   auto job = job_; | 
 |   while (job) { | 
 |     if (t->HandleSingleShotException(job->exceptionate(Exceptionate::Type::kDebug), | 
 |                                      ZX_EXCP_PROCESS_STARTING, *context)) { | 
 |       break; | 
 |     } | 
 |  | 
 |     job = job->parent(); | 
 |   } | 
 | } |