Use new Fuchsia kernel API to suspend threads.

We are transitioning to a token-based API and will be removing the
old one.

Changes to use a thread state wait rather than reading the registers in
a loop to determine when the thread is actually suspended.

Change-Id: I4b015bb0fc74b15177304a62be6c1d9a59b45c80
Reviewed-on: https://chromium-review.googlesource.com/1100170
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
Brett Wilson 2018-06-13 16:38:28 -07:00 committed by Commit Bot
parent 2771ebf805
commit 639cba075c
2 changed files with 33 additions and 69 deletions

View File

@ -40,80 +40,42 @@ zx_obj_type_t GetHandleType(zx_handle_t handle) {
return basic.type;
}
enum class SuspensionResult {
FailedSuspendCall,
FailedToSuspendInTimelyFashion,
Succeeded,
};
// Returns the suspend token of the suspended thread. This function attempts
// to wait a short time for the thread to actually suspend before returning
// but this is not guaranteed.
base::ScopedZxHandle SuspendThread(zx_handle_t thread) {
zx_handle_t token = ZX_HANDLE_INVALID;
zx_status_t status = zx_task_suspend_token(thread, &token);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_task_suspend";
base::ScopedZxHandle();
}
SuspensionResult SuspendThread(zx_handle_t thread) {
zx_status_t status = zx_task_suspend(thread);
ZX_LOG_IF(ERROR, status != ZX_OK, status) << "zx_task_suspend";
if (status != ZX_OK)
return SuspensionResult::FailedSuspendCall;
// zx_task_suspend() suspends the thread "sometime soon", but it's hard to
// use when it's not guaranteed to be suspended after return. Try reading the
// thread state until the registers are retrievable, which means that the
// thread is actually suspended. Don't wait forever in case the suspend
// failed for whatever reason, but try a few times.
for (int i = 0; i < 5; ++i) {
zx_thread_state_general_regs_t regs;
status = zx_thread_read_state(
thread, ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status == ZX_OK) {
return SuspensionResult::Succeeded;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
}
zx_signals_t observed = 0u;
if (zx_object_wait_one(thread, ZX_THREAD_SUSPENDED,
zx_deadline_after(ZX_MSEC(50)), &observed) != ZX_OK) {
LOG(ERROR) << "thread failed to suspend";
return SuspensionResult::FailedToSuspendInTimelyFashion;
}
bool ResumeThread(zx_handle_t thread) {
zx_status_t status = zx_task_resume(thread, 0);
ZX_LOG_IF(ERROR, status != ZX_OK, status) << "zx_task_resume";
return status == ZX_OK;
}
return base::ScopedZxHandle(token);
}
} // namespace
ScopedTaskSuspend::ScopedTaskSuspend(zx_handle_t task) : task_(task) {
DCHECK_NE(task_, zx_process_self());
DCHECK_NE(task_, zx_thread_self());
ScopedTaskSuspend::ScopedTaskSuspend(zx_handle_t task) {
DCHECK_NE(task, zx_process_self());
DCHECK_NE(task, zx_thread_self());
zx_obj_type_t type = GetHandleType(task_);
zx_obj_type_t type = GetHandleType(task);
if (type == ZX_OBJ_TYPE_THREAD) {
// Note that task_ is only marked invalid if the zx_task_suspend() call
// completely fails, otherwise the suspension might just not have taken
// effect yet, so avoid leaving it suspended forever by still resuming on
// destruction.
if (SuspendThread(task_) == SuspensionResult::FailedSuspendCall) {
task_ = ZX_HANDLE_INVALID;
}
suspend_tokens_.push_back(SuspendThread(task));
} else if (type == ZX_OBJ_TYPE_PROCESS) {
for (const auto& thread : GetChildHandles(task_, ZX_INFO_PROCESS_THREADS)) {
SuspendThread(thread.get());
}
} else {
LOG(ERROR) << "unexpected handle type";
task_ = ZX_HANDLE_INVALID;
}
}
ScopedTaskSuspend::~ScopedTaskSuspend() {
if (task_ != ZX_HANDLE_INVALID) {
zx_obj_type_t type = GetHandleType(task_);
if (type == ZX_OBJ_TYPE_THREAD) {
ResumeThread(task_);
} else if (type == ZX_OBJ_TYPE_PROCESS) {
for (const auto& thread :
GetChildHandles(task_, ZX_INFO_PROCESS_THREADS)) {
ResumeThread(thread.get());
}
for (const auto& thread : GetChildHandles(task, ZX_INFO_PROCESS_THREADS))
suspend_tokens_.push_back(SuspendThread(thread.get()));
} else {
LOG(ERROR) << "unexpected handle type";
}
}
}
ScopedTaskSuspend::~ScopedTaskSuspend() = default;
} // namespace crashpad

View File

@ -17,20 +17,21 @@
#include <zircon/types.h>
#include <vector>
#include "base/fuchsia/scoped_zx_handle.h"
#include "base/macros.h"
namespace crashpad {
//! \brief Manages the suspension of another task.
//!
//! Currently, suspends and resumes are not counted on Fuchsia, so while this
//! class attempts to manage suspension of a task, if another caller or process
//! is simultaneously suspending or resuming this task, the results may not be
//! as expected.
//! The underlying API only supports suspending threads (despite its name) not
//! entire tasks. As a result, it's possible some threads may not be correctly
//! suspended/resumed as their creation might race enumeration.
//!
//! Additionally, the underlying API only supports suspending threads (despite
//! its name) not entire tasks. As a result, it's possible some threads may not
//! be correctly suspended/resumed as their creation might race enumeration.
//! Additionally, suspending a thread is asynchronous and may take an
//! arbitrary amount of time.
//!
//! Because of these limitations, this class is limited to being a best-effort,
//! and correct suspension/resumption cannot be relied upon.
@ -43,7 +44,8 @@ class ScopedTaskSuspend {
~ScopedTaskSuspend();
private:
zx_handle_t task_; // weak
// Could be one (for a thread) or many (for every process in a thread).
std::vector<base::ScopedZxHandle> suspend_tokens_;
DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspend);
};