| Index: runtime/vm/thread_interrupter_fuchsia.cc
|
| diff --git a/runtime/vm/thread_interrupter_fuchsia.cc b/runtime/vm/thread_interrupter_fuchsia.cc
|
| index 2e9b7b98642f39533c3a6080f51e33327eced470..56dbf9f0c250e20d3bd292642266b89d65b23adb 100644
|
| --- a/runtime/vm/thread_interrupter_fuchsia.cc
|
| +++ b/runtime/vm/thread_interrupter_fuchsia.cc
|
| @@ -11,6 +11,7 @@
|
| #include <magenta/status.h>
|
| #include <magenta/syscalls.h>
|
| #include <magenta/syscalls/debug.h>
|
| +#include <magenta/syscalls/object.h>
|
| #include <magenta/types.h>
|
|
|
| #include "vm/flags.h"
|
| @@ -31,113 +32,202 @@ DECLARE_FLAG(bool, trace_thread_interrupter);
|
| // When MG-430 is resolved, the code below should be rewritten to use whatever
|
| // feature is added for it.
|
|
|
| -// TODO(MG-795): The profiler is currently off by default on Fuchsia because
|
| -// suspending a thread that is in a call to pthread_cond_wait() causes
|
| -// pthread_cond_wait() to return ETIMEDOUT.
|
| +// A scope within which a target thread is suspended. When the scope is exited,
|
| +// the thread is resumed and its handle is closed.
|
| +class ThreadSuspendScope {
|
| + public:
|
| + explicit ThreadSuspendScope(mx_handle_t thread_handle)
|
| + : thread_handle_(thread_handle), suspended_(true) {
|
| + mx_status_t status = mx_task_suspend(thread_handle);
|
| + // If a thread is somewhere where suspend is impossible, mx_task_suspend()
|
| + // can return ERR_NOT_SUPPORTED.
|
| + if (status != NO_ERROR) {
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr("ThreadInterrupter: mx_task_suspend failed: %s\n",
|
| + mx_status_get_string(status));
|
| + }
|
| + suspended_ = false;
|
| + }
|
| + }
|
| +
|
| + ~ThreadSuspendScope() {
|
| + if (suspended_) {
|
| + mx_status_t status = mx_task_resume(thread_handle_, 0);
|
| + if (status != NO_ERROR) {
|
| + // If we fail to resume a thread, then it's likely the program will
|
| + // hang. Crash instead.
|
| + FATAL1("mx_task_resume failed: %s", mx_status_get_string(status));
|
| + }
|
| + }
|
| + mx_handle_close(thread_handle_);
|
| + }
|
| +
|
| + bool suspended() const { return suspended_; }
|
| +
|
| + private:
|
| + mx_handle_t thread_handle_;
|
| + bool suspended_;
|
| +
|
| + DISALLOW_ALLOCATION();
|
| + DISALLOW_COPY_AND_ASSIGN(ThreadSuspendScope);
|
| +};
|
|
|
| class ThreadInterrupterFuchsia : public AllStatic {
|
| public:
|
| +#if defined(TARGET_ARCH_X64)
|
| static bool GrabRegisters(mx_handle_t thread, InterruptedThreadState* state) {
|
| - // TODO(zra): Enable this when mx_thread_read_state() works on suspended
|
| - // threads.
|
| - while (false) {
|
| - char buf[MX_MAX_THREAD_STATE_SIZE];
|
| - uint32_t regset_size = MX_MAX_THREAD_STATE_SIZE;
|
| - mx_status_t status = mx_thread_read_state(
|
| - thread, MX_THREAD_STATE_REGSET0, &buf[0], regset_size, ®set_size);
|
| - if (status != NO_ERROR) {
|
| + mx_x86_64_general_regs_t regs;
|
| + uint32_t regset_size;
|
| + mx_status_t status = mx_thread_read_state(
|
| + thread, MX_THREAD_STATE_REGSET0, ®s, sizeof(regs), ®set_size);
|
| + if (status != NO_ERROR) {
|
| + if (FLAG_trace_thread_interrupter) {
|
| OS::PrintErr("ThreadInterrupter failed to get registers: %s\n",
|
| mx_status_get_string(status));
|
| - return false;
|
| }
|
| -#if defined(TARGET_ARCH_X64)
|
| - mx_x86_64_general_regs_t* regs =
|
| - reinterpret_cast<mx_x86_64_general_regs_t*>(&buf[0]);
|
| - state->pc = static_cast<uintptr_t>(regs->rip);
|
| - state->fp = static_cast<uintptr_t>(regs->rbp);
|
| - state->csp = static_cast<uintptr_t>(regs->rsp);
|
| - state->dsp = static_cast<uintptr_t>(regs->rsp);
|
| + return false;
|
| + }
|
| + state->pc = static_cast<uintptr_t>(regs.rip);
|
| + state->fp = static_cast<uintptr_t>(regs.rbp);
|
| + state->csp = static_cast<uintptr_t>(regs.rsp);
|
| + state->dsp = static_cast<uintptr_t>(regs.rsp);
|
| + return true;
|
| + }
|
| #elif defined(TARGET_ARCH_ARM64)
|
| - mx_arm64_general_regs_t* regs =
|
| - reinterpret_cast<mx_arm64_general_regs_t*>(&buf[0]);
|
| - state->pc = static_cast<uintptr_t>(regs->pc);
|
| - state->fp = static_cast<uintptr_t>(regs->r[FPREG]);
|
| - state->csp = static_cast<uintptr_t>(regs->sp);
|
| - state->dsp = static_cast<uintptr_t>(regs->r[SPREG]);
|
| - state->lr = static_cast<uintptr_t>(regs->lr);
|
| -#else
|
| -#error "Unsupported architecture"
|
| -#endif
|
| + static bool GrabRegisters(mx_handle_t thread, InterruptedThreadState* state) {
|
| + mx_arm64_general_regs_t regs;
|
| + uint32_t regset_size;
|
| + mx_status_t status = mx_thread_read_state(
|
| + thread, MX_THREAD_STATE_REGSET0, ®s, sizeof(regs), ®set_size);
|
| + if (status != NO_ERROR) {
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr("ThreadInterrupter failed to get registers: %s\n",
|
| + mx_status_get_string(status));
|
| + }
|
| + return false;
|
| }
|
| + state->pc = static_cast<uintptr_t>(regs.pc);
|
| + state->fp = static_cast<uintptr_t>(regs.r[FPREG]);
|
| + state->csp = static_cast<uintptr_t>(regs.sp);
|
| + state->dsp = static_cast<uintptr_t>(regs.r[SPREG]);
|
| + state->lr = static_cast<uintptr_t>(regs.lr);
|
| return true;
|
| }
|
| -
|
| +#else
|
| +#error "Unsupported architecture"
|
| +#endif
|
|
|
| static void Interrupt(OSThread* os_thread) {
|
| + ASSERT(os_thread->id() != MX_KOID_INVALID);
|
| ASSERT(!OSThread::Compare(OSThread::GetCurrentThreadId(), os_thread->id()));
|
| mx_status_t status;
|
|
|
| // Get a handle on the target thread.
|
| - mx_koid_t target_thread_koid = os_thread->id();
|
| + const mx_koid_t target_thread_koid = os_thread->id();
|
| if (FLAG_trace_thread_interrupter) {
|
| - OS::Print("ThreadInterrupter: interrupting thread with koid=%d\n",
|
| - target_thread_koid);
|
| + OS::PrintErr("ThreadInterrupter: interrupting thread with koid=%d\n",
|
| + target_thread_koid);
|
| }
|
| mx_handle_t target_thread_handle;
|
| status = mx_object_get_child(mx_process_self(), target_thread_koid,
|
| MX_RIGHT_SAME_RIGHTS, &target_thread_handle);
|
| if (status != NO_ERROR) {
|
| if (FLAG_trace_thread_interrupter) {
|
| - OS::Print("ThreadInterrupter failed to get the thread handle: %s\n",
|
| - mx_status_get_string(status));
|
| + OS::PrintErr("ThreadInterrupter: mx_object_get_child failed: %s\n",
|
| + mx_status_get_string(status));
|
| }
|
| - FATAL1("mx_object_get_child failed: %s", mx_status_get_string(status));
|
| + return;
|
| }
|
| if (target_thread_handle == MX_HANDLE_INVALID) {
|
| - FATAL("ThreadInterrupter got an invalid target thread handle!");
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr(
|
| + "ThreadInterrupter: mx_object_get_child gave an invalid "
|
| + "thread handle!");
|
| + }
|
| + return;
|
| }
|
|
|
| - // Pause the target thread.
|
| - status = mx_task_suspend(target_thread_handle);
|
| + // This scope suspends the thread. When we exit the scope, the thread is
|
| + // resumed, and the thread handle is closed.
|
| + ThreadSuspendScope tss(target_thread_handle);
|
| + if (!tss.suspended()) {
|
| + return;
|
| + }
|
| +
|
| + // Check that the thread is suspended.
|
| + status = PollThreadUntilSuspended(target_thread_handle);
|
| if (status != NO_ERROR) {
|
| - if (FLAG_trace_thread_interrupter) {
|
| - OS::Print("ThreadInterrupter failed to suspend thread %ld: %s\n",
|
| - static_cast<intptr_t>(os_thread->id()),
|
| - mx_status_get_string(status));
|
| - }
|
| - mx_handle_close(target_thread_handle);
|
| - FATAL1("mx_task_suspend failed: %s", mx_status_get_string(status));
|
| + return;
|
| }
|
|
|
| - // TODO(zra): Enable this when mx_thread_read_state() works on suspended
|
| - // threads.
|
| - while (false) {
|
| - // Grab the target thread's registers.
|
| - InterruptedThreadState its;
|
| - if (!GrabRegisters(target_thread_handle, &its)) {
|
| - // Failed to get thread registers.
|
| - status = mx_task_resume(target_thread_handle, 0);
|
| - if (status != NO_ERROR) {
|
| - FATAL1("mx_task_resume failed: %s", mx_status_get_string(status));
|
| + // Grab the target thread's registers.
|
| + InterruptedThreadState its;
|
| + if (!GrabRegisters(target_thread_handle, &its)) {
|
| + return;
|
| + }
|
| + // Currently we sample only threads that are associated
|
| + // with an isolate. It is safe to call 'os_thread->thread()'
|
| + // here as the thread which is being queried is suspended.
|
| + Thread* thread = os_thread->thread();
|
| + if (thread != NULL) {
|
| + Profiler::SampleThread(thread, its);
|
| + }
|
| + }
|
| +
|
| + private:
|
| + static const char* ThreadStateGetString(uint32_t state) {
|
| + switch (state) {
|
| + case MX_THREAD_STATE_NEW:
|
| + return "MX_THREAD_STATE_NEW";
|
| + case MX_THREAD_STATE_RUNNING:
|
| + return "MX_THREAD_STATE_RUNNING";
|
| + case MX_THREAD_STATE_SUSPENDED:
|
| + return "MX_THREAD_STATE_SUSPENDED";
|
| + case MX_THREAD_STATE_BLOCKED:
|
| + return "MX_THREAD_STATE_BLOCKED";
|
| + case MX_THREAD_STATE_DYING:
|
| + return "MX_THREAD_STATE_DYING";
|
| + case MX_THREAD_STATE_DEAD:
|
| + return "MX_THREAD_STATE_DEAD";
|
| + default:
|
| + return "<Unknown>";
|
| + }
|
| + }
|
| +
|
| + static mx_status_t PollThreadUntilSuspended(mx_handle_t thread_handle) {
|
| + const intptr_t kMaxPollAttempts = 10;
|
| + intptr_t poll_tries = 0;
|
| + while (poll_tries < kMaxPollAttempts) {
|
| + mx_info_thread_t thread_info;
|
| + mx_status_t status =
|
| + mx_object_get_info(thread_handle, MX_INFO_THREAD, &thread_info,
|
| + sizeof(thread_info), NULL, NULL);
|
| + poll_tries++;
|
| + if (status != NO_ERROR) {
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr("ThreadInterrupter: mx_object_get_info failed: %s\n",
|
| + mx_status_get_string(status));
|
| }
|
| - mx_handle_close(target_thread_handle);
|
| - return;
|
| + return status;
|
| + }
|
| + if (thread_info.state == MX_THREAD_STATE_SUSPENDED) {
|
| + // Success.
|
| + return NO_ERROR;
|
| }
|
| - // Currently we sample only threads that are associated
|
| - // with an isolate. It is safe to call 'os_thread->thread()'
|
| - // here as the thread which is being queried is suspended.
|
| - Thread* thread = os_thread->thread();
|
| - if (thread != NULL) {
|
| - Profiler::SampleThread(thread, its);
|
| + if (thread_info.state == MX_THREAD_STATE_RUNNING) {
|
| + // Poll.
|
| + continue;
|
| + }
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr("ThreadInterrupter: Thread is not suspended: %s\n",
|
| + ThreadStateGetString(thread_info.state));
|
| }
|
| + return ERR_BAD_STATE;
|
| }
|
| -
|
| - // Resume the target thread.
|
| - status = mx_task_resume(target_thread_handle, 0);
|
| - if (status != NO_ERROR) {
|
| - FATAL1("mx_task_resume failed: %s", mx_status_get_string(status));
|
| + if (FLAG_trace_thread_interrupter) {
|
| + OS::PrintErr("ThreadInterrupter: Exceeded max suspend poll tries\n");
|
| }
|
| - mx_handle_close(target_thread_handle);
|
| + return ERR_BAD_STATE;
|
| }
|
| };
|
|
|
| @@ -149,13 +239,13 @@ bool ThreadInterrupter::IsDebuggerAttached() {
|
|
|
| void ThreadInterrupter::InterruptThread(OSThread* thread) {
|
| if (FLAG_trace_thread_interrupter) {
|
| - OS::Print("ThreadInterrupter suspending %p\n",
|
| - reinterpret_cast<void*>(thread->id()));
|
| + OS::PrintErr("ThreadInterrupter suspending %p\n",
|
| + reinterpret_cast<void*>(thread->id()));
|
| }
|
| ThreadInterrupterFuchsia::Interrupt(thread);
|
| if (FLAG_trace_thread_interrupter) {
|
| - OS::Print("ThreadInterrupter resuming %p\n",
|
| - reinterpret_cast<void*>(thread->id()));
|
| + OS::PrintErr("ThreadInterrupter resuming %p\n",
|
| + reinterpret_cast<void*>(thread->id()));
|
| }
|
| }
|
|
|
|
|