blob: 948de606c4b2bf98d3d3451e619390477ee6b6cc [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/job_context_impl.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/job_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_impl.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
JobContextImpl::JobContextImpl(SystemImpl* system, bool is_implicit_root)
: JobContext(system->session()),
system_(system),
is_implicit_root_(is_implicit_root),
impl_weak_factory_(this) {
session()->AddFilterObserver(this);
RefreshFilters();
}
JobContextImpl::~JobContextImpl() {
// If the job is still running, make sure we broadcast terminated notifications before deleting
// everything.
ImplicitlyDetach();
session()->RemoveFilterObserver(this);
}
void JobContextImpl::ImplicitlyDetach() {
if (GetJob())
OnDetachReply(Err(), 0, [](fxl::WeakPtr<JobContext>, const Err&) {});
}
JobContext::State JobContextImpl::GetState() const { return state_; }
Job* JobContextImpl::GetJob() const { return job_.get(); }
// static
void JobContextImpl::OnAttachReplyThunk(fxl::WeakPtr<JobContextImpl> job_context, Callback callback,
const Err& err, uint64_t koid, uint32_t status,
const std::string& job_name) {
if (job_context) {
job_context->OnAttachReply(std::move(callback), err, koid, status, job_name);
if (!job_context->filters_.empty()) {
job_context->SendAndUpdateFilters(job_context->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_context, err);
} else {
callback(job_context, Err("Warning: job attach race, extra job is "
"likely attached."));
}
}
}
void JobContextImpl::OnAttachReply(Callback callback, const Err& err, uint64_t koid,
uint32_t status, const std::string& job_name) {
FXL_DCHECK(state_ == State::kAttaching);
FXL_DCHECK(!job_.get()); // Shouldn't 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 launching.
state_ = State::kNone;
issue_err = Err(fxl::StringPrintf("Error attaching, status = %d.", status));
} else {
state_ = State::kAttached;
job_ = std::make_unique<JobImpl>(this, koid, job_name);
}
callback(GetWeakPtr(), issue_err);
}
void JobContextImpl::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_context = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::AttachReply reply) mutable {
OnAttachReplyThunk(std::move(weak_job_context), std::move(callback), err, reply.koid,
reply.status, reply.name);
});
}
void JobContextImpl::Attach(uint64_t koid, Callback callback) {
AttachInternal(debug_ipc::TaskType::kJob, koid, std::move(callback));
}
void JobContextImpl::AttachToSystemRoot(Callback callback) {
AttachInternal(debug_ipc::TaskType::kSystemRoot, 0, std::move(callback));
}
void JobContextImpl::AttachToComponentRoot(Callback callback) {
AttachInternal(debug_ipc::TaskType::kComponentRoot, 0, std::move(callback));
}
void JobContextImpl::AddJobImplForTesting(uint64_t koid, const std::string& name) {
job_ = std::make_unique<JobImpl>(this, koid, name);
}
void JobContextImpl::Detach(Callback callback) {
if (!job_.get()) {
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 = job_->GetKoid();
request.type = debug_ipc::TaskType::kJob;
session()->remote_api()->Detach(
request, [callback = std::move(callback), weak_job_context = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::DetachReply reply) mutable {
if (weak_job_context) {
weak_job_context->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_context, err);
}
});
}
void JobContextImpl::SendAndUpdateFilters(std::vector<std::string> filters) {
SendAndUpdateFilters(filters, last_filter_set_failed_);
}
void JobContextImpl::SendAndUpdateFilters(std::vector<std::string> filters, bool force_send) {
last_filter_set_failed_ = false;
if (!job_.get()) {
filters_ = std::move(filters);
return;
}
DEBUG_LOG(Job) << "Updating filters for job " << job_->GetName();
if (!force_send && filters_ == filters) {
return;
}
debug_ipc::JobFilterRequest request;
request.job_koid = job_->GetKoid();
request.filters = filters;
session()->remote_api()->JobFilter(
request, [filters, weak_job_context = impl_weak_factory_.GetWeakPtr()](
const Err& err, debug_ipc::JobFilterReply reply) {
if (reply.status != 0) {
FXL_LOG(ERROR) << "Error adding filter: " << debug_ipc::ZxStatusToString(reply.status);
// Agent failed, mark that we had trouble setting filters and return.
if (weak_job_context)
weak_job_context->last_filter_set_failed_ = true;
return;
}
if (weak_job_context)
weak_job_context->filters_ = std::move(filters);
// Let the observers know that we had some matches.
if (!reply.matched_processes.empty()) {
for (FilterObserver& filter_observer : weak_job_context->session()->filter_observers()) {
filter_observer.OnFilterMatches(weak_job_context.get(), reply.matched_processes);
}
}
});
}
void JobContextImpl::OnDetachReply(const Err& err, uint32_t status, Callback callback) {
FXL_DCHECK(job_.get()); // 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;
job_.reset();
}
callback(GetWeakPtr(), issue_err);
}
void JobContextImpl::DidCreateFilter(Filter* filter) {
if (!filter->is_valid()) {
return;
}
if (filter->job() && filter->job() != this) {
return;
}
RefreshFilters();
}
void JobContextImpl::DidChangeFilter(Filter* filter, std::optional<JobContext*> 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 JobContextImpl::WillDestroyFilter(Filter* filter) {
// Same process.
DidCreateFilter(filter);
}
void JobContextImpl::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