fuchsia: Add implementation of ScopedTaskSuspend

This implementation has some limitations as documented in the header,
however, threads must be suspended in order to use the register capture
debug API so this is somewhat useful for now in the context of
generate_dump.

Also, refactor some child-object retrieval helpers used in a few places.

Bug: crashpad:196
Change-Id: I1fdae5fc3d4b43841e535724eac10c1e58af04c5
Reviewed-on: https://chromium-review.googlesource.com/1007966
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Scott Graham 2018-04-13 09:42:19 -07:00 committed by Commit Bot
parent c80bf96001
commit f5d5a41317
7 changed files with 295 additions and 115 deletions

View File

@ -20,6 +20,7 @@
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/scoped_zx_handle.h"
#include "base/logging.h"
#include "util/fuchsia/koid_utilities.h"
namespace crashpad {
@ -189,57 +190,20 @@ void ProcessReaderFuchsia::InitializeThreads() {
initialized_threads_ = true;
// Retrieve the thread koids. This is racy; better if the process is suspended
// itself, but threads could still be externally created. As there's no
// maximum, this needs to be retried in a loop until the actual threads
// retrieved is equal to the available threads.
std::vector<zx_koid_t> threads(100);
size_t actual_num_threads, available_num_threads;
for (;;) {
zx_status_t status = zx_object_get_info(process_,
ZX_INFO_PROCESS_THREADS,
&threads[0],
sizeof(threads[0]) * threads.size(),
&actual_num_threads,
&available_num_threads);
// If the buffer is too small (even zero), the result is still ZX_OK, not
// ZX_ERR_BUFFER_TOO_SMALL.
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_object_get_info ZX_INFO_PROCESS_THREADS";
break;
}
if (actual_num_threads == available_num_threads) {
threads.resize(actual_num_threads);
break;
}
// Resize to the expected number next time with a bit extra to attempt to
// handle the race between here and the next request.
threads.resize(available_num_threads + 10);
}
for (const zx_koid_t thread_koid : threads) {
zx_handle_t raw_handle;
zx_status_t status = zx_object_get_child(
process_, thread_koid, ZX_RIGHT_SAME_RIGHTS, &raw_handle);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_object_get_child";
// TODO(scottmg): Decide if it's worthwhile adding a mostly-empty Thread
// here, consisting only of the koid, but no other information. The only
// time this is expected to happen is when there's a race between getting
// the koid above, and requesting the handle here.
continue;
}
base::ScopedZxHandle thread_handle(raw_handle);
std::vector<zx_koid_t> thread_koids =
GetChildKoids(process_, ZX_INFO_PROCESS_THREADS);
std::vector<base::ScopedZxHandle> thread_handles =
GetHandlesForChildKoids(process_, thread_koids);
DCHECK_EQ(thread_koids.size(), thread_handles.size());
for (size_t i = 0; i < thread_handles.size(); ++i) {
Thread thread;
thread.id = thread_koid;
thread.id = thread_koids[i];
if (thread_handles[i].is_valid()) {
char name[ZX_MAX_NAME_LEN] = {0};
status = zx_object_get_property(
thread_handle.get(), ZX_PROP_NAME, &name, sizeof(name));
zx_status_t status = zx_object_get_property(
thread_handles[i].get(), ZX_PROP_NAME, &name, sizeof(name));
if (status != ZX_OK) {
ZX_LOG(WARNING, status) << "zx_object_get_property ZX_PROP_NAME";
} else {
@ -247,7 +211,7 @@ void ProcessReaderFuchsia::InitializeThreads() {
}
zx_info_thread_t thread_info;
status = zx_object_get_info(thread_handle.get(),
status = zx_object_get_info(thread_handles[i].get(),
ZX_INFO_THREAD,
&thread_info,
sizeof(thread_info),
@ -258,6 +222,7 @@ void ProcessReaderFuchsia::InitializeThreads() {
} else {
thread.state = thread_info.state;
}
}
threads_.push_back(thread);
}

View File

@ -49,6 +49,7 @@
#include "base/fuchsia/scoped_zx_handle.h"
#include "snapshot/fuchsia/process_snapshot_fuchsia.h"
#include "util/fuchsia/koid_utilities.h"
#include "util/fuchsia/scoped_task_suspend.h"
#elif defined(OS_LINUX) || defined(OS_ANDROID)
#include "snapshot/linux/process_snapshot_linux.h"
#endif // OS_MACOSX
@ -186,6 +187,11 @@ int GenerateDumpMain(int argc, char* argv[]) {
if (options.suspend) {
suspend.reset(new ScopedProcessSuspend(process.get()));
}
#elif defined(OS_FUCHSIA)
std::unique_ptr<ScopedTaskSuspend> suspend;
if (options.suspend) {
suspend.reset(new ScopedTaskSuspend(task.get()));
}
#endif // OS_MACOSX
#if defined(OS_MACOSX)

View File

@ -390,6 +390,8 @@ static_library("util") {
sources += [
"fuchsia/koid_utilities.cc",
"fuchsia/koid_utilities.h",
"fuchsia/scoped_task_suspend.cc",
"fuchsia/scoped_task_suspend.h",
"misc/capture_context_fuchsia.S",
"misc/paths_fuchsia.cc",
"net/http_transport_none.cc",

View File

@ -41,66 +41,22 @@ base::ScopedZxHandle GetRootJob() {
return base::ScopedZxHandle(root_job);
}
std::vector<zx_koid_t> GetChildKoids(zx_handle_t parent, uint32_t child_kind) {
constexpr size_t kNumExtraKoids = 10u;
size_t actual = 0;
size_t available = 0;
std::vector<zx_koid_t> result;
// This is inherently racy, but we retry with a bit of slop to try to get a
// complete list.
for (int pass = 0; pass < 5; pass++) {
if (actual <= available)
result.resize(available + kNumExtraKoids);
zx_status_t status = zx_object_get_info(parent,
child_kind,
result.data(),
result.size() * sizeof(zx_koid_t),
&actual,
&available);
if (actual == available) {
break;
}
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_object_get_info";
break;
}
}
result.resize(actual);
return result;
}
// type can be ZX_INFO_JOB_CHILDREN or ZX_INFO_JOB_PROCESSES.
std::vector<base::ScopedZxHandle> GetChildObjects(
const base::ScopedZxHandle& job,
zx_object_info_topic_t type) {
auto koids = GetChildKoids(job.get(), type);
std::vector<base::ScopedZxHandle> result;
result.reserve(koids.size());
for (zx_koid_t koid : koids) {
zx_handle_t handle;
if (zx_object_get_child(job.get(), koid, ZX_RIGHT_SAME_RIGHTS, &handle) ==
ZX_OK)
result.push_back(base::ScopedZxHandle(handle));
}
return result;
}
bool FindProcess(const base::ScopedZxHandle& job,
zx_koid_t koid,
base::ScopedZxHandle* out) {
for (auto& proc : GetChildObjects(job, ZX_INFO_JOB_PROCESSES)) {
for (auto& proc : GetChildHandles(job.get(), ZX_INFO_JOB_PROCESSES)) {
if (GetKoidForHandle(proc.get()) == koid) {
*out = std::move(proc);
return true;
}
}
for (const auto& child_job : GetChildObjects(job, ZX_INFO_JOB_CHILDREN)) {
// TODO(scottmg): As this is recursing down the job tree all the handles are
// kept open, so this could be very expensive in terms of number of open
// handles. This function should be replaced by a syscall in the
// not-too-distant future, so hopefully OK for now.
for (const auto& child_job :
GetChildHandles(job.get(), ZX_INFO_JOB_CHILDREN)) {
if (FindProcess(child_job, koid, out))
return true;
}
@ -110,6 +66,66 @@ bool FindProcess(const base::ScopedZxHandle& job,
} // namespace
std::vector<zx_koid_t> GetChildKoids(zx_handle_t parent,
zx_object_info_topic_t child_kind) {
size_t actual = 0;
size_t available = 0;
std::vector<zx_koid_t> result(100);
// This is inherently racy. Better if the process is suspended, but there's
// still no guarantee that a thread isn't externally created. As a result,
// must be in a retry loop.
for (;;) {
zx_status_t status = zx_object_get_info(parent,
child_kind,
result.data(),
result.size() * sizeof(zx_koid_t),
&actual,
&available);
// If the buffer is too small (even zero), the result is still ZX_OK, not
// ZX_ERR_BUFFER_TOO_SMALL.
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_object_get_info";
break;
}
if (actual == available) {
break;
}
// Resize to the expected number next time, with a bit of slop to handle the
// race between here and the next request.
result.resize(available + 10);
}
result.resize(actual);
return result;
}
std::vector<base::ScopedZxHandle> GetChildHandles(zx_handle_t parent,
zx_object_info_topic_t type) {
auto koids = GetChildKoids(parent, type);
return GetHandlesForChildKoids(parent, koids);
}
std::vector<base::ScopedZxHandle> GetHandlesForChildKoids(
zx_handle_t parent,
const std::vector<zx_koid_t>& koids) {
std::vector<base::ScopedZxHandle> result;
result.reserve(koids.size());
for (zx_koid_t koid : koids) {
zx_handle_t handle;
if (zx_object_get_child(parent, koid, ZX_RIGHT_SAME_RIGHTS, &handle) ==
ZX_OK) {
result.emplace_back(base::ScopedZxHandle(handle));
} else {
result.push_back(base::ScopedZxHandle());
}
}
return result;
}
zx_koid_t GetKoidForHandle(zx_handle_t object) {
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(

View File

@ -15,12 +15,59 @@
#ifndef CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_
#define CRASHPAD_UTIL_FUCHSIA_KOID_UTILITIES_H_
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
#include <vector>
#include "base/fuchsia/scoped_zx_handle.h"
namespace crashpad {
//! \brief Get a list of child koids for a parent handle.
//!
//! For example, the list of processes in jobs, or the list of threads in a
//! process.
//!
//! \param[in] parent The handle to the parent object.
//! \param[in] child_kind The type of children to retrieve from \a parent. Valid
//! values depend on the type of \a parent, but include
//! `ZX_INFO_JOB_CHILDREN` (child jobs of a job), `ZX_INFO_JOB_PROCESSES`
//! (child processes of a job), and `ZX_INFO_PROCESS_THREADS` (child threads
//! of a process).
//! \return A vector of the koids representing the child objects.
//!
//! \sa GetChildHandles
std::vector<zx_koid_t> GetChildKoids(zx_handle_t parent,
zx_object_info_topic_t child_kind);
//! \brief Get handles representing a list of child objects of a given parent.
//!
//! \param[in] parent The handle to the parent object.
//! \param[in] child_kind The type of children to retrieve from \a parent. Valid
//! values depend on the type of \a parent, but include
//! `ZX_INFO_JOB_CHILDREN` (child jobs of a job), `ZX_INFO_JOB_PROCESSES`
//! (child processes of a job), and `ZX_INFO_PROCESS_THREADS` (child threads
//! of a process).
//! \return The resulting list of handles corresponding to the child objects.
//!
//! \sa GetChildKoids
std::vector<base::ScopedZxHandle> GetChildHandles(
zx_handle_t parent,
zx_object_info_topic_t child_kind);
//! \brief Convert a list of koids that are all children of a particular object
//! into handles.
//!
//! \param[in] parent The parent object to which the koids belong.
//! \param[in] koids The list of koids.
//! \return The resulting list of handles corresponding to the koids. If an
//! element of \a koids is invalid or can't be retrieved, there will be a
//! corresponding `ZX_HANDLE_INVALID` entry in the return.
std::vector<base::ScopedZxHandle> GetHandlesForChildKoids(
zx_handle_t parent,
const std::vector<zx_koid_t>& koids);
//! \brief Gets a process handle given the process' koid.
//!
//! \param[in] koid The process id.

View File

@ -0,0 +1,91 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/fuchsia/scoped_task_suspend.h"
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <vector>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/scoped_zx_handle.h"
#include "base/logging.h"
#include "util/fuchsia/koid_utilities.h"
namespace crashpad {
namespace {
zx_obj_type_t GetHandleType(zx_handle_t handle) {
zx_info_handle_basic_t basic;
zx_status_t status = zx_object_get_info(
handle, ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, nullptr);
if (status != ZX_OK) {
ZX_LOG(ERROR, status) << "zx_object_get_info";
return ZX_OBJ_TYPE_NONE;
}
return basic.type;
}
bool SuspendThread(zx_handle_t thread) {
zx_status_t status = zx_task_suspend(thread);
ZX_LOG_IF(ERROR, status != ZX_OK, status) << "zx_task_suspend";
return status == ZX_OK;
}
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;
}
} // namespace
ScopedTaskSuspend::ScopedTaskSuspend(zx_handle_t task) : task_(task) {
DCHECK_NE(task_, zx_process_self());
DCHECK_NE(task_, zx_thread_self());
zx_obj_type_t type = GetHandleType(task_);
if (type == ZX_OBJ_TYPE_THREAD) {
if (!SuspendThread(task_)) {
task_ = ZX_HANDLE_INVALID;
}
} 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());
}
} else {
LOG(ERROR) << "unexpected handle type";
}
}
}
} // namespace crashpad

View File

@ -0,0 +1,53 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_
#define CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_
#include <zircon/types.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.
//!
//! 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.
//!
//! Because of these limitations, this class is limited to being a best-effort,
//! and correct suspension/resumption cannot be relied upon.
//!
//! Callers should not attempt to suspend the current task as obtained via
//! `zx_process_self()`.
class ScopedTaskSuspend {
public:
explicit ScopedTaskSuspend(zx_handle_t task);
~ScopedTaskSuspend();
private:
zx_handle_t task_; // weak
DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspend);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_FUCHSIA_SCOPED_TASK_SUSPEND_H_