blob: 624f5cf12f6f09816ee2ab1a80047051fee902f4 [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/client/target_impl.h"
#include <sstream>
#include "garnet/bin/zxdb/client/process_impl.h"
#include "garnet/bin/zxdb/client/remote_api.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/system_impl.h"
#include "garnet/bin/zxdb/client/target_observer.h"
#include "garnet/lib/debug_ipc/helper/message_loop.h"
#include "garnet/lib/debug_ipc/helper/zx_status.h"
#include "garnet/public/lib/fxl/logging.h"
#include "garnet/public/lib/fxl/strings/string_printf.h"
namespace zxdb {
TargetImpl::TargetImpl(SystemImpl* system)
: Target(system->session()),
system_(system),
symbols_(system->GetSymbols()),
impl_weak_factory_(this) {
settings_.set_fallback(&system_->settings());
}
TargetImpl::~TargetImpl() {
// If the process is still running, make sure we broadcast terminated
// notifications before deleting everything.
ImplicitlyDetach();
}
std::unique_ptr<TargetImpl> TargetImpl::Clone(SystemImpl* system) {
auto result = std::make_unique<TargetImpl>(system);
result->args_ = args_;
result->symbols_ = symbols_;
return result;
}
void TargetImpl::ProcessCreatedInJob(uint64_t koid,
const std::string& process_name) {
FXL_DCHECK(state_ == State::kNone);
FXL_DCHECK(!process_.get()); // Shouldn't have a process.
state_ = State::kRunning;
process_ = std::make_unique<ProcessImpl>(this, koid, process_name,
Process::StartType::kAttach);
system_->NotifyDidCreateProcess(process_.get());
for (auto& observer : observers())
observer.DidCreateProcess(this, process_.get(), true);
}
void TargetImpl::CreateProcessForTesting(uint64_t koid,
const std::string& process_name) {
FXL_DCHECK(state_ == State::kNone);
state_ = State::kStarting;
OnLaunchOrAttachReply(Callback(), Err(), koid, 0, process_name);
}
void TargetImpl::ImplicitlyDetach() {
if (GetProcess())
OnKillOrDetachReply(Err(), 0, [](fxl::WeakPtr<Target>, const Err&) {});
}
Target::State TargetImpl::GetState() const { return state_; }
Process* TargetImpl::GetProcess() const { return process_.get(); }
const TargetSymbols* TargetImpl::GetSymbols() const { return &symbols_; }
const std::vector<std::string>& TargetImpl::GetArgs() const { return args_; }
void TargetImpl::SetArgs(std::vector<std::string> args) {
args_ = std::move(args);
}
void TargetImpl::Launch(Callback callback) {
Err err;
if (state_ != State::kNone)
err = Err("Can't launch, program is already running or starting.");
else if (args_.empty())
err = Err("No program specified to launch.");
if (err.has_error()) {
// Avoid reentering caller to dispatch the error.
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [callback, err, weak_ptr = GetWeakPtr()]() {
callback(std::move(weak_ptr), err);
});
return;
}
state_ = State::kStarting;
debug_ipc::LaunchRequest request;
request.inferior_type = debug_ipc::InferiorType::kBinary;
request.argv = args_;
session()->remote_api()->Launch(
request, [callback, weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::LaunchReply reply) {
TargetImpl::OnLaunchOrAttachReplyThunk(weak_target, callback, err,
reply.process_koid, reply.status,
reply.process_name);
});
}
void TargetImpl::Kill(Callback callback) {
if (!process_.get()) {
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [callback, weak_ptr = GetWeakPtr()]() {
callback(std::move(weak_ptr), Err("Error detaching: No process."));
});
return;
}
debug_ipc::KillRequest request;
request.process_koid = process_->GetKoid();
session()->remote_api()->Kill(
request, [callback, weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::KillReply reply) {
if (weak_target) {
weak_target->OnKillOrDetachReply(err, reply.status,
std::move(callback));
} else {
// The reply that the process was launched came after the local
// objects were destroyed. We're still OK to dispatch either way.
callback(weak_target, err);
}
});
}
void TargetImpl::Attach(uint64_t koid, Callback callback) {
if (state_ != State::kNone) {
// Avoid reentering caller to dispatch the error.
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [callback, weak_ptr = GetWeakPtr()]() {
callback(
std::move(weak_ptr),
Err("Can't attach, program is already running or starting."));
});
return;
}
state_ = State::kAttaching;
debug_ipc::AttachRequest request;
request.koid = koid;
session()->remote_api()->Attach(
request, [koid, callback, weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::AttachReply reply) {
OnLaunchOrAttachReplyThunk(std::move(weak_target), std::move(callback),
err, koid, reply.status, reply.name);
});
}
void TargetImpl::Detach(Callback callback) {
if (!process_.get()) {
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [callback, weak_ptr = GetWeakPtr()]() {
callback(std::move(weak_ptr), Err("Error detaching: No process."));
});
return;
}
debug_ipc::DetachRequest request;
request.koid = process_->GetKoid();
session()->remote_api()->Detach(
request, [callback, weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::DetachReply reply) {
if (weak_target) {
weak_target->OnKillOrDetachReply(err, reply.status,
std::move(callback));
} else {
// The reply that the process was launched came after the local
// objects were destroyed. We're still OK to dispatch either way.
callback(weak_target, err);
}
});
}
void TargetImpl::OnProcessExiting(int return_code) {
FXL_DCHECK(state_ == State::kRunning);
state_ = State::kNone;
system_->NotifyWillDestroyProcess(process_.get());
for (auto& observer : observers()) {
observer.WillDestroyProcess(this, process_.get(),
TargetObserver::DestroyReason::kExit,
return_code);
}
process_.reset();
}
// static
void TargetImpl::OnLaunchOrAttachReplyThunk(fxl::WeakPtr<TargetImpl> target,
Callback callback, const Err& err,
uint64_t koid, uint32_t status,
const std::string& process_name) {
if (target) {
target->OnLaunchOrAttachReply(std::move(callback), err, koid, status,
process_name);
} else {
// The reply that the process was launched came after the local
// objects were destroyed.
if (err.has_error()) {
// Process not launched, forward the error.
callback(target, err);
} else {
// TODO(brettw) handle this more gracefully. Maybe kill the remote
// process?
callback(target, Err("Warning: process launch race, extra process is "
"likely running."));
}
}
}
void TargetImpl::OnLaunchOrAttachReply(Callback callback, const Err& err,
uint64_t koid, uint32_t status,
const std::string& process_name) {
FXL_DCHECK(state_ == State::kAttaching || state_ == State::kStarting);
FXL_DCHECK(!process_.get()); // Shouldn't have a process.
Err issue_err; // Error to send in callback.
if (err.has_error()) {
// Error from transport.
state_ = State::kNone;
issue_err = err;
} else if (status != 0) {
// Error from launching.
state_ = State::kNone;
if (status == debug_ipc::kZxErrIO) {
issue_err = Err("Error launching: Binary not found [%s]",
debug_ipc::ZxStatusToString(status));
} else {
issue_err =
Err(fxl::StringPrintf("Error launching, status = %s.",
debug_ipc::ZxStatusToString(status)));
}
} else {
Process::StartType start_type = state_ == State::kAttaching
? Process::StartType::kAttach
: Process::StartType::kLaunch;
state_ = State::kRunning;
process_ =
std::make_unique<ProcessImpl>(this, koid, process_name, start_type);
}
if (callback)
callback(GetWeakPtr(), issue_err);
if (state_ == State::kRunning) {
system_->NotifyDidCreateProcess(process_.get());
for (auto& observer : observers())
observer.DidCreateProcess(this, process_.get(), false);
}
}
void TargetImpl::OnKillOrDetachReply(const Err& err, uint32_t status,
Callback callback) {
FXL_DCHECK(process_.get()); // Should have a process.
Err issue_err; // Error to send in callback.
if (err.has_error()) {
// Error from transport.
state_ = State::kNone;
issue_err = err;
} else if (status != 0) {
// Error from detaching.
// TODO(davemoore): Not sure what state the target should be if we error
// upon detach.
issue_err = Err(fxl::StringPrintf("Error detaching, status = %d.", status));
} else {
// Successfully detached.
state_ = State::kNone;
system_->NotifyWillDestroyProcess(process_.get());
// Keep the process alive for the observer call, but remove it from the
// target as per the observer specification.
std::unique_ptr<ProcessImpl> doomed_process = std::move(process_);
for (auto& observer : observers()) {
observer.WillDestroyProcess(this, doomed_process.get(),
TargetObserver::DestroyReason::kDetach, 0);
}
}
callback(GetWeakPtr(), issue_err);
}
} // namespace zxdb