blob: cc1e069782419069e97482b11501c1e476ed83c7 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "exception_port.h"
#include <cinttypes>
#include <string>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fit/function.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include "lib/fsl/handles/object_info.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "garnet/lib/debugger_utils/util.h"
#include "thread.h"
using std::lock_guard;
using std::mutex;
namespace inferior_control {
namespace {
std::string IOPortPacketTypeToString(const zx_port_packet_t& pkt) {
if (ZX_PKT_IS_EXCEPTION(pkt.type)) {
return "ZX_PKT_TYPE_EXCEPTION";
}
#define CASE_TO_STR(x) \
case x: \
return #x
switch (pkt.type) {
CASE_TO_STR(ZX_PKT_TYPE_USER);
CASE_TO_STR(ZX_PKT_TYPE_SIGNAL_ONE);
CASE_TO_STR(ZX_PKT_TYPE_SIGNAL_REP);
default:
break;
}
#undef CASE_TO_STR
return "(unknown)";
}
} // namespace
// static
ExceptionPort::Key ExceptionPort::g_key_counter = 0;
ExceptionPort::ExceptionPort(async_dispatcher_t* dispatcher)
: keep_running_(false), origin_dispatcher_(dispatcher) {
FXL_DCHECK(origin_dispatcher_);
}
ExceptionPort::~ExceptionPort() {
if (eport_)
Quit();
}
bool ExceptionPort::Run() {
FXL_DCHECK(!eport_);
FXL_DCHECK(!keep_running_);
// Create an I/O port.
zx_status_t status = zx::port::create(0, &eport_);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to create the exception port: "
<< debugger_utils::ZxErrorString(status);
return false;
}
FXL_DCHECK(eport_);
keep_running_ = true;
io_thread_ = std::thread(fit::bind_member(this, &ExceptionPort::Worker));
return true;
}
void ExceptionPort::Quit() {
FXL_DCHECK(eport_);
FXL_DCHECK(keep_running_);
FXL_LOG(INFO) << "Quitting exception port I/O loop";
// Close the I/O port. This should cause zx_port_wait to return if one is
// pending.
keep_running_ = false;
{
lock_guard<mutex> lock(eport_mutex_);
// The only way it seems possible to make the I/O thread return from
// zx_port_wait is to queue a dummy packet on the port.
zx_port_packet_t packet;
memset(&packet, 0, sizeof(packet));
packet.type = ZX_PKT_TYPE_USER;
eport_.queue(&packet);
}
io_thread_.join();
FXL_LOG(INFO) << "Exception port I/O loop exited";
}
ExceptionPort::Key ExceptionPort::Bind(zx_handle_t process_handle,
Callback callback) {
FXL_DCHECK(process_handle != ZX_HANDLE_INVALID);
FXL_DCHECK(callback);
FXL_DCHECK(eport_);
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(process_handle, ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "zx_object_get_info_failed: "
<< debugger_utils::ZxErrorString(status);
return 0;
}
FXL_DCHECK(info.type == ZX_OBJ_TYPE_PROCESS);
zx_koid_t process_koid = info.koid;
Key next_key = g_key_counter + 1;
// Check for overflows. We don't keep track of which keys are ready to use and
// which aren't. A 64-bit range is pretty big, so if we run out, we run out.
if (!next_key) {
FXL_LOG(ERROR) << "Ran out of keys!";
return 0;
}
status = zx_task_bind_exception_port(process_handle, eport_.get(),
next_key, ZX_EXCEPTION_PORT_DEBUGGER);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to bind exception port: "
<< debugger_utils::ZxErrorString(status);
return 0;
}
// Also watch for process terminated signals.
status = zx_object_wait_async(process_handle, eport_.get(), next_key,
ZX_TASK_TERMINATED, ZX_WAIT_ASYNC_ONCE);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to async wait for process: "
<< debugger_utils::ZxErrorString(status);
return 0;
}
// |next_key| should not have been used before.
FXL_DCHECK(callbacks_.find(next_key) == callbacks_.end());
callbacks_[next_key] =
BindData(process_handle, process_koid, std::move(callback));
++g_key_counter;
FXL_VLOG(1) << "Exception port bound to process handle " << process_handle
<< " with key " << next_key;
return next_key;
}
bool ExceptionPort::Unbind(const Key key) {
const auto& iter = callbacks_.find(key);
if (iter == callbacks_.end()) {
FXL_VLOG(1) << "|key| not bound; Cannot unbind exception port";
return false;
}
// Unbind the exception port. This is a best effort operation so if it fails,
// there isn't really anything we can do to recover.
zx_task_bind_exception_port(iter->second.process_handle, ZX_HANDLE_INVALID,
key, ZX_EXCEPTION_PORT_DEBUGGER);
callbacks_.erase(iter);
return true;
}
zx::unowned_port ExceptionPort::GetUnownedExceptionPort() {
lock_guard<mutex> lock(eport_mutex_);
return zx::unowned_port(eport_.get());
}
void ExceptionPort::Worker() {
FXL_DCHECK(eport_);
// Give this thread an identifiable name for debugging purposes.
fsl::SetCurrentThreadName("exception port reader");
FXL_VLOG(1) << "ExceptionPort I/O thread started";
zx_handle_t eport;
{
lock_guard<mutex> lock(eport_mutex_);
eport = eport_.get();
}
while (keep_running_) {
zx_port_packet_t packet;
zx_status_t status = zx_port_wait(eport, ZX_TIME_INFINITE, &packet);
if (status < 0) {
FXL_LOG(ERROR) << "zx_port_wait returned error: "
<< debugger_utils::ZxErrorString(status);
}
FXL_VLOG(2) << "IO port packet received - key: " << packet.key
<< " type: " << IOPortPacketTypeToString(packet);
if (ZX_PKT_IS_EXCEPTION(packet.type)) {
FXL_VLOG(1) << "Exception received: "
<< debugger_utils::ExceptionName(
static_cast<const zx_excp_type_t>(packet.type))
<< " (" << packet.type << "), pid: " << packet.exception.pid
<< ", tid: " << packet.exception.tid;
} else if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE) {
FXL_VLOG(1) << "Signal received:"
<< " trigger=0x" << std::hex << packet.signal.trigger
<< " observed=0x" << std::hex << packet.signal.observed;
} else if (packet.type == ZX_PKT_TYPE_USER) {
// Sent when exiting loop, just ignore.
continue;
} else {
FXL_LOG(WARNING) << "Unexpected packet type: " << packet.type;
continue;
}
// Handle the exception/signal on the main thread.
async::PostTask(origin_dispatcher_, [packet, this] {
const auto& iter = callbacks_.find(packet.key);
if (iter == callbacks_.end()) {
FXL_VLOG(1) << "No handler registered for exception";
return;
}
zx_exception_report_t report;
if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE) {
// Process terminated.
memset(&report, 0, sizeof(report));
} else if (ZX_EXCP_IS_ARCH(packet.type)) {
// TODO(dje): We already maintain a table of threads plus their
// handles. Rewrite this to work with that table. Now would be a fine
// time to notice new threads, but for existing threads there's no
// point in doing a lookup to get a new handle.
zx_handle_t thread;
zx_status_t status = zx_object_get_child(iter->second.process_handle,
packet.exception.tid,
ZX_RIGHT_SAME_RIGHTS, &thread);
if (status < 0) {
FXL_VLOG(1) << "Failed to get a handle to [" << packet.exception.pid
<< "." << packet.exception.tid << "]";
return;
}
status = zx_object_get_info(thread, ZX_INFO_THREAD_EXCEPTION_REPORT,
&report, sizeof(report), NULL, NULL);
zx_handle_close(thread);
if (status < 0) {
FXL_VLOG(1) << "Failed to get exception report for ["
<< packet.exception.pid << "." << packet.exception.tid
<< "]";
return;
}
} else {
// Fill in |report| for a synthetic exception.
memset(&report, 0, sizeof(report));
report.header.size = sizeof(report);
report.header.type = packet.type;
}
iter->second.callback(packet, report.context);
});
}
// Close the I/O port.
{
lock_guard<mutex> lock(eport_mutex_);
eport_.reset();
}
}
void PrintException(FILE* out, const Thread* thread, zx_excp_type_t type,
const zx_exception_context_t& context) {
if (ZX_EXCP_IS_ARCH(type)) {
fprintf(out, "Thread %s: received exception %s\n",
thread->GetDebugName().c_str(),
debugger_utils::ExceptionToString(type, context).c_str());
zx_vaddr_t pc = thread->registers()->GetPC();
fprintf(out, "PC 0x%" PRIxPTR "\n", pc);
} else {
// Remember that we can't do this:
// const char* thread_name = thread->GetDebugName().c_str();
// The lifetime of the c++ string object ends at the ;.
std::string thread_name = thread->GetDebugName();
switch (type) {
case ZX_EXCP_THREAD_STARTING:
fprintf(out, "Thread %s: starting\n", thread_name.c_str());
break;
case ZX_EXCP_THREAD_EXITING:
fprintf(out, "Thread %s: exiting\n", thread_name.c_str());
break;
case ZX_EXCP_POLICY_ERROR:
fprintf(out, "Thread %s: policy error\n", thread_name.c_str());
break;
default:
fprintf(out, "Thread %s: unknown exception %u\n",
thread_name.c_str(), type);
break;
}
}
}
void PrintSignals(FILE* out, const Thread* thread, zx_signals_t signals) {
std::string description;
if (signals & ZX_THREAD_RUNNING)
description += ", running";
if (signals & ZX_THREAD_SUSPENDED)
description += ", suspended";
if (signals & ZX_THREAD_TERMINATED)
description += ", terminated";
zx_signals_t mask =
(ZX_THREAD_RUNNING | ZX_THREAD_SUSPENDED | ZX_THREAD_TERMINATED);
if (signals & ~mask)
description += fxl::StringPrintf(", unknown (0x%x)", signals & ~mask);
if (description.length() == 0)
description = ", none";
fprintf(out, "Thread %s got signals: %s\n", thread->GetDebugName().c_str(),
description.c_str() + 2);
}
} // namespace inferior_control