| // 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).data()); |
| } else { |
| issue_err = |
| Err(fxl::StringPrintf("Error launching, status = %s.", |
| debug_ipc::ZxStatusToString(status).data())); |
| } |
| } 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 |