| // 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/job.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <sstream> | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/developer/debug/shared/message_loop.h" | |
| #include "src/developer/debug/shared/zx_status.h" | |
| #include "src/developer/debug/zxdb/client/filter.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 { | |
| Job::Job(Session* session, bool is_implicit_root) | |
| : ClientObject(session), is_implicit_root_(is_implicit_root), weak_factory_(this) { | |
| session->AddFilterObserver(this); | |
| RefreshFilters(); | |
| } | |
| Job::~Job() { | |
| // If the job is still running, make sure we broadcast terminated notifications before deleting | |
| // everything. | |
| ImplicitlyDetach(); | |
| session()->RemoveFilterObserver(this); | |
| } | |
| fxl::WeakPtr<Job> Job::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } | |
| void Job::ImplicitlyDetach() { | |
| if (state_ == State::kAttached) | |
| OnDetachReply(Err(), 0, [](fxl::WeakPtr<Job>, const Err&) {}); | |
| } | |
| // static | |
| void Job::OnAttachReplyThunk(fxl::WeakPtr<Job> job, Callback callback, const Err& err, | |
| uint64_t koid, uint32_t status, const std::string& job_name) { | |
| if (job) { | |
| job->OnAttachReply(std::move(callback), err, koid, status, job_name); | |
| if (!job->filters_.empty()) { | |
| job->SendAndUpdateFilters(job->filters_, true); | |
| } | |
| } else { | |
| // The reply that the job was launched came after the local objects were destroyed. | |
| if (err.has_error()) { | |
| // Process not launched, forward the error. | |
| callback(job, err); | |
| } else { | |
| callback(job, Err("Warning: job attach race, extra job is likely attached.")); | |
| } | |
| } | |
| } | |
| void Job::OnAttachReply(Callback callback, const Err& err, uint64_t koid, uint32_t status, | |
| const std::string& job_name) { | |
| FX_DCHECK(state_ == State::kAttaching); | |
| 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; | |
| issue_err = Err(fxl::StringPrintf("Error attaching, status = %d.", status)); | |
| } else { | |
| state_ = State::kAttached; | |
| koid_ = koid; | |
| name_ = job_name; | |
| } | |
| callback(GetWeakPtr(), issue_err); | |
| } | |
| void Job::AttachInternal(debug_ipc::TaskType type, uint64_t koid, Callback callback) { | |
| if (state_ != State::kNone) { | |
| // Avoid reentering caller to dispatch the error. | |
| debug_ipc::MessageLoop::Current()->PostTask( | |
| FROM_HERE, [callback = std::move(callback), weak_ptr = GetWeakPtr()]() mutable { | |
| callback(std::move(weak_ptr), Err("Can't attach, job is already running or starting.")); | |
| }); | |
| return; | |
| } | |
| state_ = State::kAttaching; | |
| debug_ipc::AttachRequest request; | |
| request.koid = koid; | |
| request.type = type; | |
| session()->remote_api()->Attach( | |
| request, [callback = std::move(callback), weak_job = weak_factory_.GetWeakPtr()]( | |
| const Err& err, debug_ipc::AttachReply reply) mutable { | |
| OnAttachReplyThunk(std::move(weak_job), std::move(callback), err, reply.koid, reply.status, | |
| reply.name); | |
| }); | |
| } | |
| void Job::Attach(uint64_t koid, Callback callback) { | |
| AttachInternal(debug_ipc::TaskType::kJob, koid, std::move(callback)); | |
| } | |
| void Job::AttachToSystemRoot(Callback callback) { | |
| AttachInternal(debug_ipc::TaskType::kSystemRoot, 0, std::move(callback)); | |
| } | |
| void Job::AttachToComponentRoot(Callback callback) { | |
| AttachInternal(debug_ipc::TaskType::kComponentRoot, 0, std::move(callback)); | |
| } | |
| void Job::AttachForTesting(uint64_t koid, const std::string& name) { | |
| state_ = State::kAttached; | |
| koid_ = koid; | |
| name_ = name; | |
| } | |
| void Job::Detach(Callback callback) { | |
| if (state_ != State::kAttached) { | |
| debug_ipc::MessageLoop::Current()->PostTask( | |
| FROM_HERE, [callback = std::move(callback), weak_ptr = GetWeakPtr()]() mutable { | |
| callback(std::move(weak_ptr), Err("Error detaching: No job.")); | |
| }); | |
| return; | |
| } | |
| // This job could have been the one automatically created. If the user explicitly detaches it, the | |
| // user is taking control over what job it's attached to so we don't want to track it implicitly | |
| // any more. | |
| is_implicit_root_ = false; | |
| debug_ipc::DetachRequest request; | |
| request.koid = koid_; | |
| request.type = debug_ipc::TaskType::kJob; | |
| session()->remote_api()->Detach( | |
| request, [callback = std::move(callback), weak_job = weak_factory_.GetWeakPtr()]( | |
| const Err& err, debug_ipc::DetachReply reply) mutable { | |
| if (weak_job) { | |
| weak_job->OnDetachReply(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_job, err); | |
| } | |
| }); | |
| } | |
| void Job::SendAndUpdateFilters(std::vector<std::string> filters) { | |
| SendAndUpdateFilters(filters, last_filter_set_failed_); | |
| } | |
| void Job::SendAndUpdateFilters(std::vector<std::string> filters, bool force_send) { | |
| last_filter_set_failed_ = false; | |
| if (state_ != State::kAttached) { | |
| filters_ = std::move(filters); | |
| return; | |
| } | |
| DEBUG_LOG(Job) << "Updating filters for job " << name_; | |
| if (!force_send && filters_ == filters) { | |
| return; | |
| } | |
| debug_ipc::JobFilterRequest request; | |
| request.job_koid = koid_; | |
| request.filters = filters; | |
| session()->remote_api()->JobFilter(request, [filters, weak_job = weak_factory_.GetWeakPtr()]( | |
| const Err& err, debug_ipc::JobFilterReply reply) { | |
| if (reply.status != 0) { | |
| FX_LOGS(ERROR) << "Error adding filter: " << debug_ipc::ZxStatusToString(reply.status); | |
| // Agent failed, mark that we had trouble setting filters and return. | |
| if (weak_job) | |
| weak_job->last_filter_set_failed_ = true; | |
| return; | |
| } | |
| if (weak_job) | |
| weak_job->filters_ = std::move(filters); | |
| // Let the observers know that we had some matches. | |
| if (!reply.matched_processes.empty()) { | |
| for (FilterObserver& filter_observer : weak_job->session()->filter_observers()) { | |
| filter_observer.OnFilterMatches(weak_job.get(), reply.matched_processes); | |
| } | |
| } | |
| }); | |
| } | |
| void Job::OnDetachReply(const Err& err, uint32_t status, Callback callback) { | |
| FX_DCHECK(state_ == State::kAttached); // Should have a job. | |
| 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(donosoc): Print error using ZxStatusToString | |
| issue_err = Err(fxl::StringPrintf("Error detaching, status = %d.", status)); | |
| } else { | |
| // Successfully detached. | |
| state_ = State::kNone; | |
| } | |
| if (state_ == State::kNone) { | |
| koid_ = 0; | |
| name_.clear(); | |
| } | |
| callback(GetWeakPtr(), issue_err); | |
| } | |
| void Job::DidCreateFilter(Filter* filter) { | |
| if (!filter->is_valid()) { | |
| return; | |
| } | |
| if (filter->job() && filter->job() != this) { | |
| return; | |
| } | |
| RefreshFilters(); | |
| } | |
| void Job::DidChangeFilter(Filter* filter, std::optional<Job*> previous_job) { | |
| if (!filter->is_valid()) { | |
| // The filter only becomes invalid if the job it applies to dies. We're not dead, so this filter | |
| // never applied to us. | |
| return; | |
| } | |
| if ((previous_job && (*previous_job == this || !*previous_job)) || filter->job() == this || | |
| !filter->job()) { | |
| RefreshFilters(); | |
| } | |
| } | |
| void Job::WillDestroyFilter(Filter* filter) { | |
| // Same process. | |
| DidCreateFilter(filter); | |
| } | |
| void Job::RefreshFilters() { | |
| std::vector<std::string> items; | |
| for (const auto& filter : session()->system().GetFilters()) { | |
| if (!filter->is_valid()) { | |
| continue; | |
| } | |
| if (filter->job() == this || !filter->job()) { | |
| // The IPC protocol uses the empty string to mean "all processes". | |
| if (filter->pattern() == Filter::kAllProcessesPattern) | |
| items.emplace_back(); | |
| else | |
| items.push_back(filter->pattern()); | |
| } | |
| } | |
| SendAndUpdateFilters(items); | |
| } | |
| } // namespace zxdb |