blob: 270aef509363ba00a36f4a4d432c976e0b525b11 [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 <string>
#include <src/lib/fxl/logging.h>
#include <src/lib/fxl/strings/string_printf.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include "garnet/lib/debugger_utils/breakpoints.h"
#include "garnet/lib/debugger_utils/threads.h"
#include "garnet/lib/debugger_utils/util.h"
#include "arch.h"
#include "process.h"
#include "server.h"
#include "thread.h"
namespace inferior_control {
// static
const char* Thread::StateName(Thread::State state) {
#define CASE_TO_STR(x) \
case Thread::State::x: \
return #x
switch (state) {
CASE_TO_STR(kNew);
CASE_TO_STR(kInException);
CASE_TO_STR(kSuspended);
CASE_TO_STR(kRunning);
CASE_TO_STR(kStepping);
CASE_TO_STR(kExiting);
CASE_TO_STR(kGone);
default:
break;
}
#undef CASE_TO_STR
return "(unknown)";
}
Thread::Thread(Process* process, zx_handle_t handle, zx_koid_t id)
: process_(process),
handle_(handle),
id_(id),
state_(State::kNew),
breakpoints_(this),
weak_ptr_factory_(this) {
FXL_DCHECK(process_);
FXL_DCHECK(handle_ != ZX_HANDLE_INVALID);
FXL_DCHECK(id_ != ZX_KOID_INVALID);
name_ = debugger_utils::GetObjectName(handle_);
registers_ = Registers::Create(this);
FXL_DCHECK(registers_.get());
}
Thread::~Thread() {
FXL_VLOG(4) << "Destructing thread " << GetDebugName();
Clear();
}
std::string Thread::GetName() const {
if (name_.empty()) {
return fxl::StringPrintf("%lu.%lu", process_->id(), id_);
}
return fxl::StringPrintf("%lu.%lu(%s)",
process_->id(), id_, name_.c_str());
}
std::string Thread::GetDebugName() const {
if (name_.empty()) {
return fxl::StringPrintf("%lu.%lu(%lx.%lx)",
process_->id(), id_, process_->id(), id_);
}
return fxl::StringPrintf("%lu.%lu(%lx.%lx)(%s)",
process_->id(), id_, process_->id(), id_,
name_.c_str());
}
void Thread::set_state(State state) {
FXL_DCHECK(state != State::kNew);
state_ = state;
}
bool Thread::IsLive() const {
switch (state_) {
case State::kNew:
case State::kInException:
case State::kSuspended:
case State::kRunning:
case State::kStepping:
return true;
default:
return false;
}
}
void Thread::Clear() {
// We close the handle here so the o/s will release the thread.
if (handle_ != ZX_HANDLE_INVALID)
zx_handle_close(handle_);
handle_ = ZX_HANDLE_INVALID;
}
zx_handle_t Thread::GetExceptionPortHandle() {
return process_->server()->exception_port_handle();
}
fxl::WeakPtr<Thread> Thread::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
GdbSignal Thread::GetGdbSignal() const {
if (!exception_context_) {
// TODO(dje): kNone may be a better value to return here.
return GdbSignal::kUnsupported;
}
return ComputeGdbSignal(*exception_context_);
}
void Thread::OnException(const zx_excp_type_t type,
const zx_exception_context_t& context) {
// TODO(dje): While having a pointer allows for a simple "do we have a
// context" check, it might be simpler to just store the struct in the class.
exception_context_.reset(new zx_exception_context_t);
*exception_context_ = context;
State prev_state = state_;
set_state(State::kInException);
// If we were singlestepping turn it off.
// If the user wants to try the singlestep again it must be re-requested.
// If the thread has exited we may not be able to, and there's no point
// anyway.
if (prev_state == State::kStepping && type != ZX_EXCP_THREAD_EXITING) {
FXL_DCHECK(breakpoints_.SingleStepBreakpointInserted());
if (!breakpoints_.RemoveSingleStepBreakpoint()) {
FXL_LOG(ERROR) << "Unable to clear single-step bkpt for thread "
<< GetName();
} else {
FXL_VLOG(4) << "Single-step bkpt cleared for thread "
<< GetDebugName();
}
}
FXL_VLOG(2) << ExceptionToString(type, context);
}
void Thread::OnTermination() {
set_state(State::kGone);
process_->delegate()->OnThreadTermination(this);
FXL_VLOG(2) << SignalsToString(ZX_THREAD_TERMINATED);
}
void Thread::OnSuspension() {
set_state(State::kSuspended);
process_->delegate()->OnThreadSuspension(this);
FXL_VLOG(2) << SignalsToString(ZX_THREAD_SUSPENDED);
}
void Thread::OnResumption() {
set_state(State::kRunning);
process_->delegate()->OnThreadResumption(this);
FXL_VLOG(2) << SignalsToString(ZX_THREAD_RUNNING);
}
void Thread::OnSignal(zx_signals_t signals) {
if (signals & ZX_THREAD_TERMINATED) {
OnTermination();
return;
}
switch (signals & (ZX_THREAD_SUSPENDED | ZX_THREAD_RUNNING)) {
case 0:
break;
case ZX_THREAD_SUSPENDED:
OnSuspension();
break;
case ZX_THREAD_RUNNING:
OnResumption();
break;
case ZX_THREAD_SUSPENDED | ZX_THREAD_RUNNING: {
// These signals can get folded together.
uint32_t state = debugger_utils::GetThreadOsState(handle_);
switch (ZX_THREAD_STATE_BASIC(state)) {
case ZX_THREAD_STATE_RUNNING:
OnResumption();
break;
case ZX_THREAD_STATE_SUSPENDED:
OnSuspension();
break;
case ZX_THREAD_STATE_BLOCKED:
// If we're blocked in a syscall or some such we're still running
// as far as we're concerned.
OnResumption();
break;
case ZX_THREAD_STATE_DYING:
case ZX_THREAD_STATE_DEAD:
// These are handled elsewhere, e.g., on receipt of ZX_THREAD_TERMINATED.
// But if we were suspended we no longer are.
if (state_ == State::kSuspended) {
// The transition to kExiting or kGone is handled elsewhere.
// Here we just process the fact that we got SUSPENDED|RUNNING signals.
OnResumption();
}
break;
default:
FXL_NOTREACHED();
break;
}
break;
}
}
}
bool Thread::TryNext(zx_handle_t eport) {
if (state() != State::kInException && state() != State::kNew) {
FXL_LOG(ERROR) << "Cannot try-next thread " << GetName()
<< " while in state " << StateName(state());
return false;
}
FXL_VLOG(4) << "Thread " << GetDebugName()
<< ": trying next exception handler";
zx_status_t status =
zx_task_resume_from_exception(handle_, eport, ZX_RESUME_TRY_NEXT);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to try-next thread "
<< GetName() << ": "
<< debugger_utils::ZxErrorString(status);
return false;
}
return true;
}
bool Thread::ResumeFromException(zx_handle_t eport) {
if (state() != State::kInException && state() != State::kNew) {
FXL_LOG(ERROR) << "Cannot resume thread " << GetName()
<< " while in state: " << StateName(state());
return false;
}
// This is printed here before resuming the task so that this is always
// printed before any subsequent exception report (which is read by another
// thread).
FXL_VLOG(4) << "Resuming thread " << GetDebugName() << " after an exception";
zx_status_t status =
zx_task_resume_from_exception(handle_, eport, 0);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to resume thread " << GetName() << ": "
<< debugger_utils::ZxErrorString(status);
return false;
}
state_ = State::kRunning;
return true;
}
bool Thread::ResumeAfterSoftwareBreakpointInstruction(zx_handle_t eport) {
FXL_DCHECK(state() == State::kInException);
if (!registers_->RefreshGeneralRegisters()) {
return false;
}
zx_vaddr_t pc = registers_->GetPC();
zx_vaddr_t new_pc = debugger_utils::IncrementPcAfterBreak(pc);
FXL_VLOG(4) << "Changing pc 0x" << std::hex << pc << " -> 0x" << new_pc;
registers_->SetPC(new_pc);
if (!registers_->WriteGeneralRegisters()) {
return false;
}
if (!ResumeFromException(eport)) {
return false;
}
return true;
}
void Thread::ResumeForExit(zx_handle_t eport) {
switch (state()) {
case State::kNew:
case State::kInException:
case State::kExiting:
break;
default:
FXL_DCHECK(false) << "unexpected state " << StateName(state());
break;
}
FXL_VLOG(4) << "Thread " << GetDebugName() << " is exiting";
auto status =
zx_task_resume_from_exception(handle_, eport, 0);
if (status < 0) {
// This might fail if the process has been killed in the interim.
// It shouldn't otherwise fail. Just log the failure, nothing else
// we can do.
zx_info_process_t info;
auto info_status =
zx_object_get_info(process()->process().get(), ZX_INFO_PROCESS, &info,
sizeof(info), nullptr, nullptr);
if (info_status != ZX_OK) {
FXL_LOG(ERROR) << "Error getting process info for thread "
<< GetName() << ": "
<< debugger_utils::ZxErrorString(info_status);
}
if (info_status == ZX_OK && info.exited) {
FXL_VLOG(4) << "Process " << process()->GetName() << " exited too";
} else {
FXL_LOG(ERROR) << "Failed to resume thread " << GetName()
<< " for exit: "
<< debugger_utils::ZxErrorString(status);
}
}
set_state(State::kGone);
Clear();
}
bool Thread::RequestSuspend() {
FXL_DCHECK(!suspend_token_);
switch (state_) {
case State::kGone:
FXL_VLOG(2) << "Thread " << GetDebugName() << " is not live";
return false;
default:
break;
}
FXL_LOG(INFO) << "Suspending thread " << id_;
FXL_DCHECK(handle_ != ZX_HANDLE_INVALID);
auto status = zx_task_suspend(handle_, suspend_token_.reset_and_get_address());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to suspend thread " << GetName() << ": "
<< debugger_utils::ZxErrorString(status);
return false;
}
return true;
}
void Thread::ResumeFromSuspension() {
FXL_DCHECK(suspend_token_);
suspend_token_.reset();
}
bool Thread::Step() {
if (state() != State::kInException) {
FXL_LOG(ERROR) << "Cannot step thread " << GetName() << " while in state "
<< StateName(state());
return false;
}
if (!registers_->RefreshGeneralRegisters()) {
FXL_LOG(ERROR) << "Failed refreshing gregs for thread " << GetName();
return false;
}
zx_vaddr_t pc = registers_->GetPC();
if (!breakpoints_.InsertSingleStepBreakpoint(pc))
return false;
// This is printed here before resuming the task so that this is always
// printed before any subsequent exception report (which is read by another
// thread).
FXL_LOG(INFO) << "Thread " << GetName() << " is now stepping";
// TODO(dje): Handle stopped by suspension.
zx_status_t status =
zx_task_resume_from_exception(handle_, GetExceptionPortHandle(), 0);
if (status < 0) {
breakpoints_.RemoveSingleStepBreakpoint();
FXL_LOG(ERROR) << "Failed to resume thread " << GetName() << " for step: "
<< debugger_utils::ZxErrorString(status);
return false;
}
state_ = State::kStepping;
return true;
}
zx_status_t Thread::GetExceptionReport(zx_exception_report_t* report) const {
zx_status_t status =
zx_object_get_info(handle_, ZX_INFO_THREAD_EXCEPTION_REPORT,
report, sizeof(*report), nullptr, nullptr);
// This could fail if the process terminates before we get a chance to
// look at it.
if (status == ZX_ERR_BAD_STATE) {
// The signal notifying us the thread/process death may not have been
// processed yet, so get the thread's state directly.
zx_thread_state_t state = debugger_utils::GetThreadOsState(handle_);
if (state != ZX_THREAD_STATE_DEAD) {
FXL_LOG(WARNING) << "No exception report for thread " << id_;
}
}
return status;
}
void Thread::Dump() {
if (state_ == State::kInException ||
state_ == State::kSuspended) {
FXL_LOG(INFO) << "Thread " << GetDebugName() << " dump";
debugger_utils::DumpThread(process()->process().get(), handle(),
state_ == State::kInException);
} else {
FXL_LOG(INFO) << "Thread " << id_ << " not stopped, skipping dump";
}
}
std::string Thread::ExceptionToString(
zx_excp_type_t type, const zx_exception_context_t& context) const {
std::string description = fxl::StringPrintf(
"Thread %s: received exception %s",
GetDebugName().c_str(),
debugger_utils::ExceptionNameAsString(type).c_str());
if (ZX_EXCP_IS_ARCH(type)) {
Registers* regs = registers();
if (regs->RefreshGeneralRegisters()) {
zx_vaddr_t pc = regs->GetPC();
description += fxl::StringPrintf(", @PC 0x%lx", pc);
}
}
return description;
}
std::string Thread::SignalsToString(zx_signals_t signals) const {
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";
return fxl::StringPrintf("Thread %s got signals: %s",
GetDebugName().c_str(), description.c_str() + 2);
}
} // namespace inferior_control