blob: f7564534b1aaf59ebe2e1fccc65fcf11e487b0d5 [file] [log] [blame]
// 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/thread_dispatcher.h"
#include <assert.h>
#include <inttypes.h>
#include <lib/counters.h>
#include <platform.h>
#include <string.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/syscalls/debug.h>
#include <zircon/types.h>
#include <arch/debugger.h>
#include <arch/exception.h>
#include <arch/regs.h>
#include <arch/vm.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <kernel/thread.h>
#include <object/handle.h>
#include <object/job_dispatcher.h>
#include <object/process_dispatcher.h>
#include <vm/kstack.h>
#include <vm/vm.h>
#include <vm/vm_address_region.h>
#include <vm/vm_aspace.h>
#include <vm/vm_object_paged.h>
#define LOCAL_TRACE 0
KCOUNTER(dispatcher_thread_create_count, "dispatcher.thread.create")
KCOUNTER(dispatcher_thread_destroy_count, "dispatcher.thread.destroy")
// static
zx_status_t ThreadDispatcher::Create(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags,
ktl::string_view name,
KernelHandle<ThreadDispatcher>* out_handle,
zx_rights_t* out_rights) {
// Create the user-mode thread and attach it to the process and lower level thread.
fbl::AllocChecker ac;
auto user_thread = fbl::AdoptRef(new (&ac) ThreadDispatcher(process, flags));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
// Create the lower level thread and attach it to the scheduler.
Thread* core_thread =
Thread::Create(name.data(), StartRoutine, user_thread.get(), DEFAULT_PRIORITY);
if (!core_thread) {
return ZX_ERR_NO_MEMORY;
}
// We haven't yet compeleted initialization of |user_thread|, and
// references to it haven't possibly escaped this thread. We can
// safely set |core_thread_| outside the lock.
[&user_thread, &core_thread]()
TA_NO_THREAD_SAFETY_ANALYSIS { user_thread->core_thread_ = core_thread; }();
// The syscall layer will call Initialize(), which used to be called here.
*out_rights = default_rights();
*out_handle = KernelHandle(ktl::move(user_thread));
return ZX_OK;
}
ThreadDispatcher::ThreadDispatcher(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags)
: process_(ktl::move(process)), exceptionate_(ZX_EXCEPTION_CHANNEL_TYPE_THREAD) {
LTRACE_ENTRY_OBJ;
kcounter_add(dispatcher_thread_create_count, 1);
}
ThreadDispatcher::~ThreadDispatcher() {
LTRACE_ENTRY_OBJ;
kcounter_add(dispatcher_thread_destroy_count, 1);
DEBUG_ASSERT_MSG(!HasStartedLocked() || IsDyingOrDeadLocked(),
"Thread %p killed in bad state: %s\n", this,
ThreadLifecycleToString(state_.lifecycle()));
if (state_.lifecycle() != ThreadState::Lifecycle::INITIAL) {
// We grew the pool in Initialize(), which transitioned the thread from its
// inintial state.
process_->futex_context().ShrinkFutexStatePool();
}
// If MakeRunnable hasn't been called, then our core_thread_ has never run and
// we need to be the ones to remove it.
if (!HasStartedLocked() && core_thread_ != nullptr) {
// Since the Thread is in an initialized state we can directly destruct it.
core_thread_->Forget();
core_thread_ = nullptr;
}
}
// complete initialization of the thread object outside of the constructor
zx_status_t ThreadDispatcher::Initialize() {
LTRACE_ENTRY_OBJ;
// Make sure we contribute a FutexState object to our process's futex state.
auto result = process_->futex_context().GrowFutexStatePool();
if (result != ZX_OK) {
return result;
}
Guard<Mutex> guard{get_lock()};
// Associate the proc's address space with this thread.
process_->aspace()->AttachToThread(core_thread_);
// we've entered the initialized state
SetStateLocked(ThreadState::Lifecycle::INITIALIZED);
return ZX_OK;
}
zx_status_t ThreadDispatcher::set_name(const char* name, size_t len) {
canary_.Assert();
Guard<Mutex> thread_guard{get_lock()};
if (core_thread_ == nullptr) {
return ZX_ERR_BAD_STATE;
}
core_thread_->set_name({name, len});
return ZX_OK;
}
void ThreadDispatcher::get_name(char (&out_name)[ZX_MAX_NAME_LEN]) const {
canary_.Assert();
memset(out_name, 0, ZX_MAX_NAME_LEN);
Guard<Mutex> thread_guard{get_lock()};
if (core_thread_ == nullptr) {
return;
}
strlcpy(out_name, core_thread_->name(), ZX_MAX_NAME_LEN);
}
// start a thread
zx_status_t ThreadDispatcher::Start(const EntryState& entry, bool initial_thread) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
is_initial_thread_ = initial_thread;
// add ourselves to the process, which may fail if the process is in a dead state.
// If the process is live then it will call our StartRunning routine.
return process_->AddInitializedThread(this, initial_thread, entry);
}
zx_status_t ThreadDispatcher::MakeRunnable(const EntryState& entry, bool suspended) {
if (!arch_is_valid_user_pc(entry.pc)) {
return ZX_ERR_INVALID_ARGS;
}
Guard<Mutex> guard{get_lock()};
if (state_.lifecycle() != ThreadState::Lifecycle::INITIALIZED)
return ZX_ERR_BAD_STATE;
// save the user space entry state
user_entry_ = entry;
// update our suspend count to account for our parent state
if (suspended)
suspend_count_++;
// Attach the ThreadDispatcher to the core thread. This takes out an additional
// reference on the ThreadDispatcher.
core_thread_->SetUsermodeThread(fbl::RefPtr<ThreadDispatcher>(this));
// start the thread in RUNNING state, if we're starting suspended it will transition to
// SUSPENDED when it checks thread signals before executing any user code
SetStateLocked(ThreadState::Lifecycle::RUNNING);
if (suspend_count_ == 0) {
core_thread_->Resume();
} else {
// Thread::Suspend() only fails if the underlying thread is already dead, which we should
// ignore here to match the behavior of Thread::Resume(); our Exiting() callback will run
// shortly to clean us up
core_thread_->Suspend();
}
return ZX_OK;
}
void ThreadDispatcher::Kill() {
canary_.Assert();
LTRACE_ENTRY_OBJ;
Guard<Mutex> guard{get_lock()};
switch (state_.lifecycle()) {
case ThreadState::Lifecycle::INITIAL:
case ThreadState::Lifecycle::INITIALIZED:
// thread was never started, leave in this state
break;
case ThreadState::Lifecycle::RUNNING:
case ThreadState::Lifecycle::SUSPENDED:
// deliver a kernel kill signal to the thread
core_thread_->Kill();
// enter the dying state
SetStateLocked(ThreadState::Lifecycle::DYING);
break;
case ThreadState::Lifecycle::DYING:
case ThreadState::Lifecycle::DEAD:
// already going down
break;
}
}
zx_status_t ThreadDispatcher::Suspend() {
canary_.Assert();
LTRACE_ENTRY_OBJ;
Guard<Mutex> guard{get_lock()};
LTRACEF("%p: state %s\n", this, ThreadLifecycleToString(state_.lifecycle()));
// Update |suspend_count_| in all cases so that we can always verify a sane value - it's
// possible both Suspend() and Resume() get called while the thread is DYING.
DEBUG_ASSERT(suspend_count_ >= 0);
suspend_count_++;
switch (state_.lifecycle()) {
case ThreadState::Lifecycle::INITIAL:
// Unreachable, thread leaves INITIAL state before Create() returns.
DEBUG_ASSERT(false);
__UNREACHABLE;
case ThreadState::Lifecycle::INITIALIZED:
// If the thread hasn't started yet, don't actually try to suspend it. We need to let
// Start() run first to set up userspace entry data, which will then suspend if the count
// is still >0 at that time.
return ZX_OK;
case ThreadState::Lifecycle::RUNNING:
case ThreadState::Lifecycle::SUSPENDED:
if (suspend_count_ == 1)
return core_thread_->Suspend();
return ZX_OK;
case ThreadState::Lifecycle::DYING:
case ThreadState::Lifecycle::DEAD:
return ZX_ERR_BAD_STATE;
}
DEBUG_ASSERT(false);
return ZX_ERR_BAD_STATE;
}
void ThreadDispatcher::Resume() {
canary_.Assert();
LTRACE_ENTRY_OBJ;
Guard<Mutex> guard{get_lock()};
LTRACEF("%p: state %s\n", this, ThreadLifecycleToString(state_.lifecycle()));
DEBUG_ASSERT(suspend_count_ > 0);
suspend_count_--;
switch (state_.lifecycle()) {
case ThreadState::Lifecycle::INITIAL:
// Unreachable, thread leaves INITIAL state before Create() returns.
DEBUG_ASSERT(false);
__UNREACHABLE;
case ThreadState::Lifecycle::INITIALIZED:
break;
case ThreadState::Lifecycle::RUNNING:
case ThreadState::Lifecycle::SUSPENDED:
// It's possible the thread never transitioned from RUNNING -> SUSPENDED.
if (suspend_count_ == 0)
core_thread_->Resume();
break;
case ThreadState::Lifecycle::DYING:
case ThreadState::Lifecycle::DEAD:
// If it's dying or dead then bail.
break;
}
}
bool ThreadDispatcher::IsDyingOrDead() const {
Guard<Mutex> guard{get_lock()};
return IsDyingOrDeadLocked();
}
bool ThreadDispatcher::IsDyingOrDeadLocked() const {
auto lifecycle = state_.lifecycle();
return lifecycle == ThreadState::Lifecycle::DYING || lifecycle == ThreadState::Lifecycle::DEAD;
}
bool ThreadDispatcher::HasStarted() const {
Guard<Mutex> guard{get_lock()};
return HasStartedLocked();
}
bool ThreadDispatcher::HasStartedLocked() const {
auto lifecycle = state_.lifecycle();
return lifecycle != ThreadState::Lifecycle::INITIAL &&
lifecycle != ThreadState::Lifecycle::INITIALIZED;
}
void ThreadDispatcher::ExitingCurrent() {
canary_.Assert();
LTRACE_ENTRY_OBJ;
// Set ourselves in the DYING state before calling the Debugger.
{
Guard<fbl::Mutex> guard{get_lock()};
SetStateLocked(ThreadState::Lifecycle::DYING);
}
// Notify a debugger if attached. Do this before marking the thread as
// dead: the debugger expects to see the thread in the DYING state, it may
// try to read thread registers. The debugger still has to handle the case
// where the process is also dying (and thus the thread could transition
// DYING->DEAD from underneath it), but that's life (or death :-)).
//
// Thread exit exceptions don't currently provide an iframe.
arch_exception_context_t context{};
HandleSingleShotException(process_->exceptionate(Exceptionate::Type::kDebug),
ZX_EXCP_THREAD_EXITING, context);
// Mark the thread as dead. Do this before removing the thread from the
// process because if this is the last thread then the process will be
// marked dead, and we don't want to have a state where the process is
// dead but one thread is not.
{
Guard<Mutex> guard{get_lock()};
// put ourselves into the dead state
SetStateLocked(ThreadState::Lifecycle::DEAD);
core_thread_ = nullptr;
}
// Drop our exception channel endpoint so any userspace listener
// gets the PEER_CLOSED signal.
exceptionate_.Shutdown();
// remove ourselves from our parent process's view
process_->RemoveThread(this);
LTRACE_EXIT_OBJ;
}
void ThreadDispatcher::Suspending() {
LTRACE_ENTRY_OBJ;
// Update the state before sending any notifications out. We want the
// receiver to see the new state.
{
Guard<Mutex> guard{get_lock()};
// Don't suspend if we are racing with our own death.
if (state_.lifecycle() != ThreadState::Lifecycle::DYING) {
SetStateLocked(ThreadState::Lifecycle::SUSPENDED);
}
}
LTRACE_EXIT_OBJ;
}
void ThreadDispatcher::Resuming() {
LTRACE_ENTRY_OBJ;
// Update the state before sending any notifications out. We want the
// receiver to see the new state.
{
Guard<Mutex> guard{get_lock()};
// Don't resume if we are racing with our own death.
if (state_.lifecycle() != ThreadState::Lifecycle::DYING) {
SetStateLocked(ThreadState::Lifecycle::RUNNING);
}
}
LTRACE_EXIT_OBJ;
}
// low level LK entry point for the thread
int ThreadDispatcher::StartRoutine(void* arg) {
LTRACE_ENTRY;
ThreadDispatcher* t = (ThreadDispatcher*)arg;
// IWBN to dump the values just before calling |arch_enter_uspace()|
// but at that point they're in |iframe| and may have been modified by
// the debugger user, and fetching them out of the iframe will require
// architecture-specific code. Instead just print them here. This is just
// for tracing which is generally off, and then only time the values will
// have changed is if a debugger user changes them. KISS.
LTRACEF("arch_enter_uspace SP: %#" PRIxPTR " PC: %#" PRIxPTR ", ARG1: %#" PRIxPTR
", ARG2: %#" PRIxPTR "\n",
t->user_entry_.sp, t->user_entry_.pc, t->user_entry_.arg1, t->user_entry_.arg2);
// Initialize an iframe for entry into userspace.
// We need all registers accessible from the ZX_EXCP_THREAD_STARTING
// exception handler (the debugger wants the thread to look as if the
// thread is at the first instruction). For architectural exceptions the
// general regs are left in the iframe for speed and simplicity. To keep
// things simple we use the same scheme.
iframe_t iframe{};
arch_setup_uspace_iframe(&iframe, t->user_entry_.pc, t->user_entry_.sp, t->user_entry_.arg1,
t->user_entry_.arg2);
arch_exception_context_t context{};
context.frame = &iframe;
// Notify job debugger if attached.
if (t->is_initial_thread_) {
t->process_->OnProcessStartForJobDebugger(t, &context);
}
// Notify debugger if attached.
t->HandleSingleShotException(t->process_->exceptionate(Exceptionate::Type::kDebug),
ZX_EXCP_THREAD_STARTING, context);
arch_iframe_process_pending_signals(&iframe);
// switch to user mode and start the process
arch_enter_uspace(&iframe);
__UNREACHABLE;
}
void ThreadDispatcher::SetStateLocked(ThreadState::Lifecycle lifecycle) {
canary_.Assert();
LTRACEF("thread %p: state %u (%s)\n", this, static_cast<unsigned int>(lifecycle),
ThreadLifecycleToString(lifecycle));
DEBUG_ASSERT(get_lock()->lock().IsHeld());
state_.set(lifecycle);
switch (lifecycle) {
case ThreadState::Lifecycle::RUNNING:
UpdateStateLocked(ZX_THREAD_SUSPENDED, ZX_THREAD_RUNNING);
break;
case ThreadState::Lifecycle::SUSPENDED:
UpdateStateLocked(ZX_THREAD_RUNNING, ZX_THREAD_SUSPENDED);
break;
case ThreadState::Lifecycle::DEAD:
UpdateStateLocked(ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED, ZX_THREAD_TERMINATED);
break;
default:
// Nothing to do.
// In particular, for the DYING state we don't modify the SUSPENDED
// or RUNNING signals: For observer purposes they'll only be interested
// in the transition from {SUSPENDED,RUNNING} to DEAD.
break;
}
}
bool ThreadDispatcher::InExceptionLocked() {
canary_.Assert();
LTRACE_ENTRY_OBJ;
DEBUG_ASSERT(get_lock()->lock().IsHeld());
return exception_ != nullptr;
}
bool ThreadDispatcher::SuspendedOrInExceptionLocked() {
canary_.Assert();
return state_.lifecycle() == ThreadState::Lifecycle::SUSPENDED || InExceptionLocked();
}
static zx_thread_state_t ThreadLifecycleToState(ThreadState::Lifecycle lifecycle,
ThreadDispatcher::Blocked blocked_reason) {
switch (lifecycle) {
case ThreadState::Lifecycle::INITIAL:
case ThreadState::Lifecycle::INITIALIZED:
return ZX_THREAD_STATE_NEW;
case ThreadState::Lifecycle::RUNNING:
// The thread may be "running" but be blocked in a syscall or
// exception handler.
switch (blocked_reason) {
case ThreadDispatcher::Blocked::NONE:
return ZX_THREAD_STATE_RUNNING;
case ThreadDispatcher::Blocked::EXCEPTION:
return ZX_THREAD_STATE_BLOCKED_EXCEPTION;
case ThreadDispatcher::Blocked::SLEEPING:
return ZX_THREAD_STATE_BLOCKED_SLEEPING;
case ThreadDispatcher::Blocked::FUTEX:
return ZX_THREAD_STATE_BLOCKED_FUTEX;
case ThreadDispatcher::Blocked::PORT:
return ZX_THREAD_STATE_BLOCKED_PORT;
case ThreadDispatcher::Blocked::CHANNEL:
return ZX_THREAD_STATE_BLOCKED_CHANNEL;
case ThreadDispatcher::Blocked::WAIT_ONE:
return ZX_THREAD_STATE_BLOCKED_WAIT_ONE;
case ThreadDispatcher::Blocked::WAIT_MANY:
return ZX_THREAD_STATE_BLOCKED_WAIT_MANY;
case ThreadDispatcher::Blocked::INTERRUPT:
return ZX_THREAD_STATE_BLOCKED_INTERRUPT;
case ThreadDispatcher::Blocked::PAGER:
return ZX_THREAD_STATE_BLOCKED_PAGER;
default:
DEBUG_ASSERT_MSG(false, "unexpected blocked reason: %d",
static_cast<int>(blocked_reason));
return ZX_THREAD_STATE_BLOCKED;
}
case ThreadState::Lifecycle::SUSPENDED:
return ZX_THREAD_STATE_SUSPENDED;
case ThreadState::Lifecycle::DYING:
return ZX_THREAD_STATE_DYING;
case ThreadState::Lifecycle::DEAD:
return ZX_THREAD_STATE_DEAD;
default:
DEBUG_ASSERT_MSG(false, "unexpected run state: %d", static_cast<int>(lifecycle));
return ZX_THREAD_RUNNING;
}
}
zx_status_t ThreadDispatcher::GetInfoForUserspace(zx_info_thread_t* info) {
canary_.Assert();
*info = {};
Guard<Mutex> guard{get_lock()};
info->state = ThreadLifecycleToState(state_.lifecycle(), blocked_reason_);
info->wait_exception_channel_type = exceptionate_type_;
// If we've exited then return with the lifecycle and exception type, and keep
// the cpu_affinity_mask at 0.
if (core_thread_ == nullptr) {
return ZX_OK;
}
// Get CPU affinity.
//
// We assume that we can fit the entire mask in the first word of
// cpu_affinity_mask.
static_assert(SMP_MAX_CPUS <= sizeof(info->cpu_affinity_mask.mask[0]) * 8);
info->cpu_affinity_mask.mask[0] = core_thread_->GetSoftCpuAffinity();
return ZX_OK;
}
zx_status_t ThreadDispatcher::GetStatsForUserspace(zx_info_thread_stats_t* info) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
*info = {};
Guard<Mutex> guard{get_lock()};
if (core_thread_ == nullptr) {
return ZX_ERR_BAD_STATE;
}
info->total_runtime = core_thread_->Runtime();
info->last_scheduled_cpu = core_thread_->LastCpu();
return ZX_OK;
}
zx_status_t ThreadDispatcher::GetRuntimeStats(TaskRuntimeStats* out) const {
canary_.Assert();
*out = {};
// Repeatedly try to get a consistent snapshot out of runtime stats using the generation count.
//
// We attempt to get a snapshot forever, so it is theoretically possible for us to loop forever.
// In practice, our context switching overhead is significantly higher than the runtime of this
// loop, so it is unlikely to happen.
//
// If our context switch overhead drops very significantly, we may need to revisit this
// algorithm and return an error after some number of loops.
while (true) {
uint64_t start_count;
while ((start_count = stats_generation_count_.load(ktl::memory_order_acquire)) % 2) {
// Loop until no write is happening concurrently.
}
*out = runtime_stats_.TotalRuntime();
uint64_t end_count = stats_generation_count_.load(ktl::memory_order_acquire);
if (start_count == end_count) {
return ZX_OK;
}
}
}
zx_status_t ThreadDispatcher::GetExceptionReport(zx_exception_report_t* report) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
Guard<Mutex> guard{get_lock()};
if (InExceptionLocked()) {
// We always leave exception handling before the report gets wiped
// so this must succeed.
[[maybe_unused]] bool success = exception_->FillReport(report);
DEBUG_ASSERT(success);
} else {
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
}
Exceptionate* ThreadDispatcher::exceptionate() {
canary_.Assert();
return &exceptionate_;
}
zx_status_t ThreadDispatcher::HandleException(Exceptionate* exceptionate,
fbl::RefPtr<ExceptionDispatcher> exception,
bool* sent) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
*sent = false;
// Mark as blocked before sending the exception, otherwise a handler could
// potentially query our state before we've marked ourself blocked.
ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::EXCEPTION);
{
Guard<Mutex> guard{get_lock()};
// We send the exception while locked so that if it succeeds we can
// atomically update our state.
zx_status_t status = exceptionate->SendException(exception);
if (status != ZX_OK) {
return status;
}
*sent = true;
// This state is needed by GetInfoForUserspace().
exception_ = exception;
exceptionate_type_ = exceptionate->type();
state_.set(ThreadState::Exception::UNPROCESSED);
}
LTRACEF("blocking on exception response\n");
zx_status_t status = exception->WaitForHandleClose();
LTRACEF("received exception response %d\n", status);
Guard<Mutex> guard{get_lock()};
// If both the thread was killed and the exception was resumed before we
// started waiting, the exception resume status (ZX_OK) might be returned,
// but we want thread kill to always take priority and stop exception
// handling immediately.
//
// Note that this logic will always trigger for ZX_EXCP_THREAD_EXITING
// whether the thread was killed or is exiting normally, but that's fine
// because that exception only ever goes to a single handler so we ignore
// the handler return value anyway.
if (IsDyingOrDeadLocked()) {
status = ZX_ERR_INTERNAL_INTR_KILLED;
}
exception_.reset();
state_.set(ThreadState::Exception::IDLE);
return status;
}
bool ThreadDispatcher::HandleSingleShotException(Exceptionate* exceptionate,
zx_excp_type_t exception_type,
const arch_exception_context_t& arch_context) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
// Do a quick check for valid channel first. It's still possible that the
// channel will become invalid immediately after this check, but that will
// be caught when we try to send the exception. This is just an
// optimization to avoid unnecessary setup/teardown in the common case.
if (!exceptionate->HasValidChannel()) {
return false;
}
zx_exception_report_t report = ExceptionDispatcher::BuildArchReport(exception_type, arch_context);
fbl::RefPtr<ExceptionDispatcher> exception =
ExceptionDispatcher::Create(fbl::RefPtr(this), exception_type, &report, &arch_context);
if (!exception) {
printf("KERN: failed to allocate memory for exception type %u in thread %lu.%lu\n",
exception_type, process_->get_koid(), get_koid());
return false;
}
// We're about to handle the exception (|HandleException|). Use a |ScopedThreadExceptionContext|
// to make the thread's user register state available to debuggers and exception handlers while
// the thread is "in exception".
ScopedThreadExceptionContext context(&arch_context);
bool sent = false;
HandleException(exceptionate, exception, &sent);
exception->Clear();
return sent;
}
// T is the state type to read.
// F is a function that gets state T and has signature |zx_status_t (F)(Thread*, T*)|.
template <typename T, typename F>
zx_status_t ThreadDispatcher::ReadStateGeneric(F get_state_func, user_out_ptr<void> buffer,
size_t buffer_size) {
if (buffer_size < sizeof(T)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
T state{};
{
Guard<Mutex> guard{get_lock()};
// We can't be reading regs while the thread transitions from SUSPENDED to RUNNING.
if (!SuspendedOrInExceptionLocked()) {
return ZX_ERR_BAD_STATE;
}
zx_status_t status = get_state_func(core_thread_, &state);
if (status != ZX_OK) {
return status;
}
}
// Since copy may fault, copy only after releasing the lock.
return buffer.reinterpret<T>().copy_to_user(state);
}
zx_status_t ThreadDispatcher::ReadState(zx_thread_state_topic_t state_kind,
user_out_ptr<void> buffer, size_t buffer_size) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
switch (state_kind) {
case ZX_THREAD_STATE_GENERAL_REGS:
return ReadStateGeneric<zx_thread_state_general_regs_t>(arch_get_general_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_FP_REGS:
return ReadStateGeneric<zx_thread_state_fp_regs_t>(arch_get_fp_regs, buffer, buffer_size);
case ZX_THREAD_STATE_VECTOR_REGS:
return ReadStateGeneric<zx_thread_state_vector_regs_t>(arch_get_vector_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_DEBUG_REGS:
return ReadStateGeneric<zx_thread_state_debug_regs_t>(arch_get_debug_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_SINGLE_STEP:
return ReadStateGeneric<zx_thread_state_single_step_t>(arch_get_single_step, buffer,
buffer_size);
default:
return ZX_ERR_INVALID_ARGS;
}
}
// T is the state type to write.
// F is a function that sets state T and has signature |zx_status_t (F)(Thread*, const T*)|.
template <typename T, typename F>
zx_status_t ThreadDispatcher::WriteStateGeneric(F set_state_func, user_in_ptr<const void> buffer,
size_t buffer_size) {
if (buffer_size < sizeof(T)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
T state{};
zx_status_t status = buffer.reinterpret<const T>().copy_from_user(&state);
if (status != ZX_OK) {
return status;
}
Guard<Mutex> guard{get_lock()};
// We can't be writing regs while the thread transitions from SUSPENDED to RUNNING.
if (!SuspendedOrInExceptionLocked()) {
return ZX_ERR_BAD_STATE;
}
return set_state_func(core_thread_, &state);
}
zx_status_t ThreadDispatcher::WriteState(zx_thread_state_topic_t state_kind,
user_in_ptr<const void> buffer, size_t buffer_size) {
canary_.Assert();
LTRACE_ENTRY_OBJ;
switch (state_kind) {
case ZX_THREAD_STATE_GENERAL_REGS:
return WriteStateGeneric<zx_thread_state_general_regs_t>(arch_set_general_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_FP_REGS:
return WriteStateGeneric<zx_thread_state_fp_regs_t>(arch_set_fp_regs, buffer, buffer_size);
case ZX_THREAD_STATE_VECTOR_REGS:
return WriteStateGeneric<zx_thread_state_vector_regs_t>(arch_set_vector_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_DEBUG_REGS:
return WriteStateGeneric<zx_thread_state_debug_regs_t>(arch_set_debug_regs, buffer,
buffer_size);
case ZX_THREAD_STATE_SINGLE_STEP:
return WriteStateGeneric<zx_thread_state_single_step_t>(arch_set_single_step, buffer,
buffer_size);
default:
return ZX_ERR_INVALID_ARGS;
}
}
zx_status_t ThreadDispatcher::SetPriority(int32_t priority) {
Guard<Mutex> guard{get_lock()};
if ((state_.lifecycle() == ThreadState::Lifecycle::INITIAL) ||
(state_.lifecycle() == ThreadState::Lifecycle::DYING) ||
(state_.lifecycle() == ThreadState::Lifecycle::DEAD)) {
return ZX_ERR_BAD_STATE;
}
// The priority was already validated by the Profile dispatcher.
core_thread_->SetPriority(priority);
return ZX_OK;
}
zx_status_t ThreadDispatcher::SetDeadline(const zx_sched_deadline_params_t& params) {
Guard<Mutex> guard{get_lock()};
if ((state_.lifecycle() == ThreadState::Lifecycle::INITIAL) ||
(state_.lifecycle() == ThreadState::Lifecycle::DYING) ||
(state_.lifecycle() == ThreadState::Lifecycle::DEAD)) {
return ZX_ERR_BAD_STATE;
}
// The deadline parameters are already validated by the Profile dispatcher.
core_thread_->SetDeadline(params);
return ZX_OK;
}
zx_status_t ThreadDispatcher::SetSoftAffinity(cpu_mask_t mask) {
Guard<Mutex> guard{get_lock()};
if ((state_.lifecycle() == ThreadState::Lifecycle::INITIAL) ||
(state_.lifecycle() == ThreadState::Lifecycle::DYING) ||
(state_.lifecycle() == ThreadState::Lifecycle::DEAD)) {
return ZX_ERR_BAD_STATE;
}
// The mask was already validated by the Profile dispatcher.
core_thread_->SetSoftCpuAffinity(mask);
return ZX_OK;
}
const char* ThreadLifecycleToString(ThreadState::Lifecycle lifecycle) {
switch (lifecycle) {
case ThreadState::Lifecycle::INITIAL:
return "initial";
case ThreadState::Lifecycle::INITIALIZED:
return "initialized";
case ThreadState::Lifecycle::RUNNING:
return "running";
case ThreadState::Lifecycle::SUSPENDED:
return "suspended";
case ThreadState::Lifecycle::DYING:
return "dying";
case ThreadState::Lifecycle::DEAD:
return "dead";
}
return "unknown";
}
zx_koid_t ThreadDispatcher::get_related_koid() const {
canary_.Assert();
return process_->get_koid();
}