blob: 74df413360be39db6354619291e892e522470041 [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 "src/developer/debug/zxdb/client/target_impl.h"
#include <lib/syslog/cpp/macros.h>
#include <algorithm>
#include <optional>
#include <sstream>
#include <utility>
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/status.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/process_impl.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/system.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
TargetImpl::TargetImpl(System* 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(System* system) {
auto result = std::make_unique<TargetImpl>(system);
result->args_ = args_;
result->symbols_ = symbols_;
return result;
}
void TargetImpl::CreateProcess(Process::StartType start_type, uint64_t koid,
const std::string& process_name, uint64_t timestamp,
const std::vector<debug_ipc::ComponentInfo>& component_info,
std::optional<debug_ipc::AddressRegion> shared_aspace) {
FX_DCHECK(!process_.get()); // Shouldn't have a process.
state_ = State::kRunning;
process_ = std::make_unique<ProcessImpl>(this, koid, process_name, start_type,
std::move(component_info), std::move(shared_aspace));
for (auto& observer : session()->process_observers())
observer.DidCreateProcess(process_.get(), timestamp);
}
void TargetImpl::CreateProcessForTesting(uint64_t koid, const std::string& process_name) {
FX_DCHECK(state_ == State::kNone);
state_ = State::kStarting;
uint64_t cur_mock_timestamp = mock_timestamp_;
mock_timestamp_ += 1000;
OnLaunchOrAttachReply(CallbackWithTimestamp(), Err(), koid, process_name, cur_mock_timestamp, {},
std::nullopt);
}
void TargetImpl::ImplicitlyDetach() {
if (GetProcess()) {
OnKillOrDetachReply(
ProcessObserver::DestroyReason::kDetach, Err(), debug::Status(),
[](fxl::WeakPtr<Target>, const Err&) {}, 0);
}
}
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(CallbackWithTimestamp 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::MessageLoop::Current()->PostTask(
FROM_HERE, [callback = std::move(callback), err, weak_ptr = GetWeakPtr()]() mutable {
callback(std::move(weak_ptr), err, 0);
});
return;
}
state_ = State::kStarting;
debug_ipc::RunBinaryRequest request;
request.argv = args_;
session()->remote_api()->RunBinary(
request, [callback = std::move(callback), weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::RunBinaryReply reply) mutable {
TargetImpl::OnLaunchOrAttachReplyThunk(weak_target, std::move(callback), err,
reply.process_id, reply.status, reply.process_name,
reply.timestamp, {}, std::nullopt);
});
}
void TargetImpl::Kill(Callback callback) {
if (!process_.get()) {
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [callback = std::move(callback), weak_ptr = GetWeakPtr()]() mutable {
callback(std::move(weak_ptr), Err("Error killing: No process."));
});
return;
}
debug_ipc::KillRequest request;
request.process_koid = process_->GetKoid();
session()->remote_api()->Kill(
request, [callback = std::move(callback), weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::KillReply reply) mutable {
if (weak_target) {
weak_target->OnKillOrDetachReply(ProcessObserver::DestroyReason::kKill, err, reply.status,
std::move(callback), reply.timestamp);
} 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, debug_ipc::AttachConfig config,
CallbackWithTimestamp callback) {
if (state_ != State::kNone) {
// Avoid reentering caller to dispatch the error.
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [callback = std::move(callback), weak_ptr = GetWeakPtr()]() mutable {
callback(std::move(weak_ptr),
Err("Can't attach, program is already running or starting."), 0);
});
return;
}
state_ = State::kAttaching;
debug_ipc::AttachRequest request;
request.koid = koid;
request.config = config;
session()->remote_api()->Attach(
request,
[koid, callback = std::move(callback), weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::AttachReply reply) mutable {
OnLaunchOrAttachReplyThunk(std::move(weak_target), std::move(callback), err, koid,
reply.status, reply.name, reply.timestamp,
std::move(reply.components), reply.shared_address_space);
});
}
void TargetImpl::Detach(Callback callback) {
if (!process_.get()) {
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [callback = std::move(callback), weak_ptr = GetWeakPtr()]() mutable {
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 = std::move(callback), weak_target = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::DetachReply reply) mutable {
if (weak_target) {
weak_target->OnKillOrDetachReply(ProcessObserver::DestroyReason::kDetach, err,
reply.status, std::move(callback), reply.timestamp);
} 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, uint64_t timestamp) {
FX_DCHECK(state_ == State::kRunning);
state_ = State::kNone;
for (auto& observer : session()->process_observers())
observer.WillDestroyProcess(process_.get(), ProcessObserver::DestroyReason::kExit, return_code,
timestamp);
symbols_.WillDestroyProcess();
process_.reset();
}
// static
void TargetImpl::OnLaunchOrAttachReplyThunk(
fxl::WeakPtr<TargetImpl> target, CallbackWithTimestamp callback, const Err& input_err,
uint64_t koid, const debug::Status& status, const std::string& process_name, uint64_t timestamp,
const std::vector<debug_ipc::ComponentInfo>& component_info,
std::optional<debug_ipc::AddressRegion> shared_aspace) {
// The input Err indicates a transport error while the debug::Status indicates the remote error,
// map them to a single value.
Err err = input_err;
if (err.ok())
err = Err(status);
if (target) {
target->OnLaunchOrAttachReply(std::move(callback), err, koid, process_name, timestamp,
std::move(component_info), std::move(shared_aspace));
} 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, timestamp);
} else {
// TODO(brettw) handle this more gracefully. Maybe kill the remote process?
callback(target, Err("Warning: process launch race, extra process is likely running."),
timestamp);
}
}
}
void TargetImpl::OnLaunchOrAttachReply(CallbackWithTimestamp callback, const Err& err,
uint64_t koid, const std::string& process_name,
uint64_t timestamp,
const std::vector<debug_ipc::ComponentInfo>& component_info,
std::optional<debug_ipc::AddressRegion> shared_aspace) {
FX_DCHECK(state_ == State::kAttaching || state_ == State::kStarting);
FX_DCHECK(!process_.get()); // Shouldn't have a process.
if (err.has_error()) {
if (err.type() == ErrType::kAlreadyExists) {
FX_DCHECK(system_->ProcessFromKoid(koid));
callback(GetWeakPtr(),
Err(err.type(), "Process " + std::to_string(koid) + " is already being debugged."),
timestamp);
return;
}
state_ = State::kNone;
} else {
Process::StartType start_type =
state_ == State::kAttaching ? Process::StartType::kAttach : Process::StartType::kLaunch;
CreateProcess(start_type, koid, process_name, timestamp, std::move(component_info),
std::move(shared_aspace));
}
if (callback)
callback(GetWeakPtr(), err, timestamp);
}
void TargetImpl::OnKillOrDetachReply(ProcessObserver::DestroyReason reason, const Err& err,
const debug::Status& status, Callback callback,
uint64_t timestamp) {
FX_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.has_error()) {
// Error from detaching.
// TODO(davemoore): Not sure what state the target should be if we error upon detach.
issue_err =
Err(fxl::StringPrintf("Error %sing: %s", ProcessObserver::DestroyReasonToString(reason),
status.message().c_str()));
} else {
// Successfully detached.
state_ = State::kNone;
// Keep the process alive for the observer call, but remove it from the target as per the
// observer specification.
for (auto& observer : session()->process_observers())
observer.WillDestroyProcess(process_.get(), reason, 0, timestamp);
symbols_.WillDestroyProcess();
process_.reset();
}
callback(GetWeakPtr(), issue_err);
}
void TargetImpl::AssignPreviousConnectedProcess(const debug_ipc::ProcessRecord& record) {
process_ = ProcessImpl::FromPreviousProcess(this, record);
state_ = State::kRunning;
for (auto& observer : session()->process_observers()) {
observer.DidCreateProcess(process_.get(), 0);
}
// We won't get a modules notification, since DebugAgent is already attached to this process, so
// we must request them explicitly. We don't need to do anything in the callback, failure modes
// will be output separately (like failed downloads) and observer notifications will be sent from
// the symbol handlers.
process_->GetModules(false, [](const Err& err, std::vector<debug_ipc::Module> modules) {});
}
} // namespace zxdb