blob: e39c97996e2dc9ddf05d0d76c02dec35d4bcce84 [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 "thread.h"
#include <cinttypes>
#include <string>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "garnet/lib/debugger_utils/util.h"
#include "arch.h"
#include "process.h"
#include "server.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(kStopped);
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);
registers_ = Registers::Create(this);
FXL_DCHECK(registers_.get());
}
Thread::~Thread() {
FXL_VLOG(2) << "Destructing thread " << GetDebugName();
Clear();
}
std::string Thread::GetName() const {
return fxl::StringPrintf("%" PRId64 ".%" PRId64, process_->id(), id());
}
std::string Thread::GetDebugName() const {
return fxl::StringPrintf("%" PRId64 ".%" PRId64 "(%" PRIx64 ".%" PRIx64 ")",
process_->id(), id(), process_->id(), id());
}
void Thread::set_state(State state) {
FXL_DCHECK(state != State::kNew);
state_ = state;
}
bool Thread::IsLive() const {
switch (state_) {
case State::kNew:
case State::kStopped:
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().GetUnownedExceptionPort()->get();
}
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::kStopped);
// 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";
} else {
FXL_VLOG(2) << "Single-step bkpt cleared";
}
}
}
bool Thread::Resume() {
if (state() != State::kStopped && state() != State::kNew) {
FXL_LOG(ERROR) << "Cannot resume a thread 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(2) << "Thread " << GetName() << " is now running";
zx_status_t status =
zx_task_resume_from_exception(handle_, GetExceptionPortHandle(), 0);
if (status < 0) {
FXL_LOG(ERROR) << "Failed to resume thread: "
<< debugger_utils::ZxErrorString(status);
return false;
}
state_ = State::kRunning;
return true;
}
void Thread::ResumeForExit() {
switch (state()) {
case State::kNew:
case State::kStopped:
case State::kExiting:
break;
default:
FXL_DCHECK(false) << "unexpected state " << StateName(state());
break;
}
FXL_VLOG(2) << "Thread " << GetName() << " is exiting";
auto status =
zx_task_resume_from_exception(handle_, GetExceptionPortHandle(), 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()->handle(), ZX_INFO_PROCESS, &info,
sizeof(info), nullptr, nullptr);
if (info_status != ZX_OK) {
FXL_LOG(ERROR) << "error getting process info: "
<< debugger_utils::ZxErrorString(info_status);
}
if (info_status == ZX_OK && info.exited) {
FXL_VLOG(2) << "Process " << process()->GetName() << " exited too";
} else {
FXL_LOG(ERROR) << "Failed to resume thread for exit: "
<< debugger_utils::ZxErrorString(status);
}
}
set_state(State::kGone);
Clear();
}
bool Thread::Step() {
if (state() != State::kStopped) {
FXL_LOG(ERROR) << "Cannot resume a thread while in state: "
<< StateName(state());
return false;
}
if (!registers_->RefreshGeneralRegisters()) {
FXL_LOG(ERROR) << "Failed refreshing gregs";
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";
zx_status_t status =
zx_task_resume_from_exception(handle_, GetExceptionPortHandle(), 0);
if (status < 0) {
breakpoints_.RemoveSingleStepBreakpoint();
FXL_LOG(ERROR) << "Failed to resume thread for step: "
<< debugger_utils::ZxErrorString(status);
return false;
}
state_ = State::kStepping;
return true;
}
} // namespace inferior_control