blob: d491dc308226689eaf4a7c864be288b7cdd042e6 [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/debug_agent/debug_agent.h"
#include <inttypes.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fit/defer.h>
#include <lib/sys/cpp/termination_reason.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/features.h>
#include <zircon/status.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/arch_provider_impl.h"
#include "src/developer/debug/debug_agent/binary_launcher.h"
#include "src/developer/debug/debug_agent/component_launcher.h"
#include "src/developer/debug/debug_agent/debugged_thread.h"
#include "src/developer/debug/debug_agent/object_provider.h"
#include "src/developer/debug/debug_agent/process_breakpoint.h"
#include "src/developer/debug/debug_agent/system_info.h"
#include "src/developer/debug/debug_agent/thread_exception.h"
#include "src/developer/debug/debug_agent/zircon_process_handle.h"
#include "src/developer/debug/ipc/agent_protocol.h"
#include "src/developer/debug/ipc/message_reader.h"
#include "src/developer/debug/ipc/message_writer.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/developer/debug/shared/stream_buffer.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/lib/fxl/strings/concatenate.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_agent {
// SystemProviders ---------------------------------------------------------------------------------
SystemProviders SystemProviders::CreateDefaults(std::shared_ptr<sys::ServiceDirectory> services) {
SystemProviders system_providers;
system_providers.arch_provider = std::make_shared<ArchProviderImpl>();
system_providers.limbo_provider = std::make_shared<LimboProvider>(std::move(services));
system_providers.object_provider = std::make_shared<ObjectProvider>();
zx_status_t status = system_providers.limbo_provider->Init();
if (status != ZX_OK)
FX_LOGS(WARNING) << "Could not initialize limbo provider: " << zx_status_get_string(status);
return system_providers;
}
// DebugAgent --------------------------------------------------------------------------------------
namespace {
constexpr size_t kMegabyte = 1024 * 1024;
std::string LogResumeRequest(const debug_ipc::ResumeRequest& request) {
std::stringstream ss;
ss << "Got resume request for process " << request.process_koid;
// Print thread koids.
if (!request.thread_koids.empty()) {
ss << ", Threads: (";
for (size_t i = 0; i < request.thread_koids.size(); i++) {
if (i > 0)
ss << ", ";
ss << request.thread_koids[i];
}
ss << ")";
}
// Print step range.
if (request.range_begin != request.range_end)
ss << ", Range: [" << std::hex << request.range_begin << ", " << request.range_end << "]";
return ss.str();
}
} // namespace
DebugAgent::DebugAgent(std::shared_ptr<sys::ServiceDirectory> services, SystemProviders providers)
: services_(services),
arch_provider_(std::move(providers.arch_provider)),
limbo_provider_(std::move(providers.limbo_provider)),
object_provider_(std::move(providers.object_provider)),
weak_factory_(this) {
FX_DCHECK(arch_provider_);
FX_DCHECK(limbo_provider_);
FX_DCHECK(object_provider_);
// Get some resources.
uint32_t val = 0;
if (zx_status_t status = zx_system_get_features(ZX_FEATURE_KIND_HW_BREAKPOINT_COUNT, &val);
status == ZX_OK) {
DEBUG_LOG(Agent) << "Got HW breakpoint count: " << val;
arch_provider_->set_hw_breakpoint_count(val);
} else {
FX_LOGS(WARNING) << "Could not get HW breakpoint count: " << zx_status_get_string(status);
}
if (zx_status_t status = zx_system_get_features(ZX_FEATURE_KIND_HW_WATCHPOINT_COUNT, &val);
status == ZX_OK) {
DEBUG_LOG(Agent) << "Got watchpoint count: " << val;
arch_provider_->set_watchpoint_count(val);
} else {
FX_LOGS(WARNING) << "Could not get Watchpoint count: " << zx_status_get_string(status);
}
// Set a callback to the limbo_provider to let us know when new processes enter the limbo.
limbo_provider_->set_on_enter_limbo(
[agent = GetWeakPtr()](std::vector<fuchsia::exception::ProcessExceptionMetadata> processes) {
if (!agent)
return;
agent->OnProcessesEnteredLimbo(std::move(processes));
});
}
DebugAgent::~DebugAgent() = default;
fxl::WeakPtr<DebugAgent> DebugAgent::GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
void DebugAgent::Connect(debug_ipc::StreamBuffer* stream) {
FX_DCHECK(!stream_) << "A debug agent should not be connected twice!";
stream_ = stream;
}
void DebugAgent::Disconnect() {
FX_DCHECK(stream_);
stream_ = nullptr;
}
debug_ipc::StreamBuffer* DebugAgent::stream() {
FX_DCHECK(stream_);
return stream_;
}
void DebugAgent::RemoveDebuggedProcess(zx_koid_t process_koid) {
auto found = procs_.find(process_koid);
if (found == procs_.end())
FX_NOTREACHED();
else
procs_.erase(found);
}
void DebugAgent::RemoveDebuggedJob(zx_koid_t job_koid) {
auto found = jobs_.find(job_koid);
if (found == jobs_.end())
FX_NOTREACHED();
else
jobs_.erase(found);
}
void DebugAgent::RemoveBreakpoint(uint32_t breakpoint_id) {
auto found = breakpoints_.find(breakpoint_id);
if (found != breakpoints_.end())
breakpoints_.erase(found);
}
void DebugAgent::OnConfigAgent(const debug_ipc::ConfigAgentRequest& request,
debug_ipc::ConfigAgentReply* reply) {
reply->results = HandleActions(request.actions, &configuration_);
}
void DebugAgent::OnHello(const debug_ipc::HelloRequest& request, debug_ipc::HelloReply* reply) {
// Version and signature are default-initialized to their current values.
reply->arch = arch_provider_->GetArch();
}
void DebugAgent::OnStatus(const debug_ipc::StatusRequest& request, debug_ipc::StatusReply* reply) {
// Get the attached processes.
reply->processes.reserve(procs_.size());
for (auto& [process_koid, proc] : procs_) {
debug_ipc::ProcessRecord process_record = {};
process_record.process_koid = process_koid;
process_record.process_name = proc->name();
auto threads = proc->GetThreads();
process_record.threads.reserve(threads.size());
for (auto* thread : threads) {
process_record.threads.emplace_back(
thread->GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kNone));
}
reply->processes.emplace_back(std::move(process_record));
}
// Get the limbo processes.
if (limbo_provider_->Valid()) {
std::vector<fuchsia::exception::ProcessExceptionMetadata> limbo_processes;
for (auto& [process_koid, process_metadata] : limbo_provider_->Limbo()) {
debug_ipc::ProcessRecord process_record = {};
process_record.process_koid = process_metadata.info().process_koid;
process_record.process_name = object_provider_->NameForObject(process_metadata.process());
// For now, only fill the thread on exception.
debug_ipc::ThreadRecord thread_record = {};
thread_record.process_koid = process_record.process_koid;
thread_record.thread_koid = process_metadata.info().thread_koid;
thread_record.name = object_provider_->NameForObject(process_metadata.thread());
thread_record.state = debug_ipc::ThreadRecord::State::kBlocked;
thread_record.blocked_reason = debug_ipc::ThreadRecord::BlockedReason::kException;
process_record.threads.push_back(std::move(thread_record));
reply->limbo.push_back(std::move(process_record));
}
}
}
void DebugAgent::OnLaunch(const debug_ipc::LaunchRequest& request, debug_ipc::LaunchReply* reply) {
switch (request.inferior_type) {
case debug_ipc::InferiorType::kBinary:
LaunchProcess(request, reply);
return;
case debug_ipc::InferiorType::kComponent:
LaunchComponent(request, reply);
return;
case debug_ipc::InferiorType::kLast:
break;
}
reply->status = ZX_ERR_INVALID_ARGS;
}
void DebugAgent::OnKill(const debug_ipc::KillRequest& request, debug_ipc::KillReply* reply) {
// See first if the process is on limbo.
auto limbo_it = limbo_provider_->Limbo().find(request.process_koid);
if (limbo_it != limbo_provider_->Limbo().end()) {
// Release if from limbo, which will effectivelly kill it.
reply->status = limbo_provider_->ReleaseProcess(request.process_koid);
return;
}
// Otherwise we search locally.
auto debug_process = GetDebuggedProcess(request.process_koid);
if (!debug_process || !debug_process->handle().is_valid()) {
reply->status = ZX_ERR_NOT_FOUND;
return;
}
debug_process->OnKill(request, reply);
// We check if this was a limbo "kill". If so, we mark this process to be removed from limbo when
// it re-enters it and tell the client that we successfully killed it.
if (reply->status == ZX_ERR_ACCESS_DENIED && debug_process->from_limbo()) {
killed_limbo_procs_.insert(debug_process->koid());
reply->status = ZX_OK;
}
RemoveDebuggedProcess(request.process_koid);
}
void DebugAgent::OnDetach(const debug_ipc::DetachRequest& request, debug_ipc::DetachReply* reply) {
switch (request.type) {
case debug_ipc::TaskType::kJob: {
auto debug_job = GetDebuggedJob(request.koid);
if (debug_job && debug_job->job().is_valid()) {
RemoveDebuggedJob(request.koid);
reply->status = ZX_OK;
} else {
reply->status = ZX_ERR_NOT_FOUND;
}
break;
}
case debug_ipc::TaskType::kProcess: {
// First check if the process is waiting in limbo. If so, we release it.
auto limbo_it = limbo_provider_->Limbo().find(request.koid);
if (limbo_it != limbo_provider_->Limbo().end()) {
reply->status = limbo_provider_->ReleaseProcess(request.koid);
return;
}
auto debug_process = GetDebuggedProcess(request.koid);
if (debug_process && debug_process->handle().is_valid()) {
RemoveDebuggedProcess(request.koid);
reply->status = ZX_OK;
} else {
reply->status = ZX_ERR_NOT_FOUND;
}
break;
}
default:
reply->status = ZX_ERR_INVALID_ARGS;
}
}
void DebugAgent::OnPause(const debug_ipc::PauseRequest& request, debug_ipc::PauseReply* reply) {
if (request.process_koid) {
// Single process.
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnPause(request, reply);
} else {
// All debugged processes.
for (const auto& pair : procs_)
pair.second->OnPause(request, reply);
}
}
void DebugAgent::OnQuitAgent(const debug_ipc::QuitAgentRequest& request,
debug_ipc::QuitAgentReply* reply) {
debug_ipc::MessageLoop::Current()->QuitNow();
};
void DebugAgent::OnResume(const debug_ipc::ResumeRequest& request, debug_ipc::ResumeReply* reply) {
DEBUG_LOG(Agent) << LogResumeRequest(request);
if (request.process_koid) {
// Single process.
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc) {
proc->OnResume(request);
} else {
FX_LOGS(WARNING) << "Could not find process by koid: " << request.process_koid;
}
} else {
// All debugged processes.
for (const auto& pair : procs_)
pair.second->OnResume(request);
}
}
void DebugAgent::OnModules(const debug_ipc::ModulesRequest& request,
debug_ipc::ModulesReply* reply) {
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnModules(reply);
}
void DebugAgent::OnProcessTree(const debug_ipc::ProcessTreeRequest& request,
debug_ipc::ProcessTreeReply* reply) {
GetProcessTree(&reply->root, *object_provider_);
}
void DebugAgent::OnThreads(const debug_ipc::ThreadsRequest& request,
debug_ipc::ThreadsReply* reply) {
auto found = procs_.find(request.process_koid);
if (found == procs_.end())
return;
reply->threads = found->second->GetThreadRecords();
}
void DebugAgent::OnReadMemory(const debug_ipc::ReadMemoryRequest& request,
debug_ipc::ReadMemoryReply* reply) {
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnReadMemory(request, reply);
}
void DebugAgent::OnReadRegisters(const debug_ipc::ReadRegistersRequest& request,
debug_ipc::ReadRegistersReply* reply) {
DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid);
if (thread) {
reply->registers = thread->ReadRegisters(request.categories);
} else {
FX_LOGS(ERROR) << "Cannot find thread with koid: " << request.thread_koid;
}
}
void DebugAgent::OnWriteRegisters(const debug_ipc::WriteRegistersRequest& request,
debug_ipc::WriteRegistersReply* reply) {
DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid);
if (thread) {
reply->status = ZX_OK;
reply->registers = thread->WriteRegisters(request.registers);
} else {
reply->status = ZX_ERR_NOT_FOUND;
FX_LOGS(ERROR) << "Cannot find thread with koid: " << request.thread_koid;
}
}
void DebugAgent::OnAddOrChangeBreakpoint(const debug_ipc::AddOrChangeBreakpointRequest& request,
debug_ipc::AddOrChangeBreakpointReply* reply) {
switch (request.breakpoint.type) {
case debug_ipc::BreakpointType::kSoftware:
case debug_ipc::BreakpointType::kHardware:
case debug_ipc::BreakpointType::kReadWrite:
case debug_ipc::BreakpointType::kWrite:
return SetupBreakpoint(request, reply);
case debug_ipc::BreakpointType::kLast:
break;
}
FX_NOTREACHED() << "Invalid Breakpoint Type: " << static_cast<int>(request.breakpoint.type);
}
void DebugAgent::OnRemoveBreakpoint(const debug_ipc::RemoveBreakpointRequest& request,
debug_ipc::RemoveBreakpointReply* reply) {
RemoveBreakpoint(request.breakpoint_id);
}
void DebugAgent::OnSysInfo(const debug_ipc::SysInfoRequest& request,
debug_ipc::SysInfoReply* reply) {
reply->version = std::string(zx_system_get_version_string());
reply->num_cpus = zx_system_get_num_cpus();
reply->memory_mb = zx_system_get_physmem() / kMegabyte;
reply->hw_watchpoint_count = arch_provider_->hw_breakpoint_count();
reply->hw_watchpoint_count = arch_provider_->watchpoint_count();
}
void DebugAgent::OnProcessStatus(const debug_ipc::ProcessStatusRequest& request,
debug_ipc::ProcessStatusReply* reply) {
auto it = procs_.find(request.process_koid);
if (it == procs_.end()) {
reply->status = ZX_ERR_NOT_FOUND;
return;
}
DebuggedProcess* process = it->second.get();
debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, [this, process]() mutable {
debug_ipc::NotifyProcessStarting notify = {};
notify.koid = process->koid();
notify.name = process->name();
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyProcessStarting(notify, &writer);
stream()->Write(writer.MessageComplete());
// Send the modules notification.
process->SuspendAndSendModulesIfKnown();
});
reply->status = ZX_OK;
}
void DebugAgent::OnThreadStatus(const debug_ipc::ThreadStatusRequest& request,
debug_ipc::ThreadStatusReply* reply) {
DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid);
if (thread) {
reply->record = thread->GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kFull);
} else {
// When the thread is not found the thread record is set to "dead".
reply->record.process_koid = request.process_koid;
reply->record.thread_koid = request.thread_koid;
reply->record.state = debug_ipc::ThreadRecord::State::kDead;
}
}
zx_status_t DebugAgent::RegisterBreakpoint(Breakpoint* bp, zx_koid_t process_koid,
uint64_t address) {
DebuggedProcess* proc = GetDebuggedProcess(process_koid);
if (proc)
return proc->RegisterBreakpoint(bp, address);
// The process might legitimately be not found if there was a race between
// the process terminating and a breakpoint add/change.
return ZX_ERR_NOT_FOUND;
}
void DebugAgent::UnregisterBreakpoint(Breakpoint* bp, zx_koid_t process_koid, uint64_t address) {
// The process might legitimately be not found if it was terminated.
DebuggedProcess* proc = GetDebuggedProcess(process_koid);
if (proc)
proc->UnregisterBreakpoint(bp, address);
}
void DebugAgent::SetupBreakpoint(const debug_ipc::AddOrChangeBreakpointRequest& request,
debug_ipc::AddOrChangeBreakpointReply* reply) {
uint32_t id = request.breakpoint.id;
auto found = breakpoints_.find(id);
if (found == breakpoints_.end()) {
DEBUG_LOG(Agent) << "Creating new breakpoint " << request.breakpoint.id << " ("
<< request.breakpoint.name << ").";
found = breakpoints_
.emplace(std::piecewise_construct, std::forward_as_tuple(id),
std::forward_as_tuple(this))
.first;
}
reply->status = found->second.SetSettings(request.breakpoint);
}
zx_status_t DebugAgent::RegisterWatchpoint(Breakpoint* bp, zx_koid_t process_koid,
const debug_ipc::AddressRange& range) {
DebuggedProcess* proc = GetDebuggedProcess(process_koid);
if (proc)
return proc->RegisterWatchpoint(bp, range);
// The process might legitimately be not found if there was a race between
// the process terminating and a breakpoint add/change.
return ZX_ERR_NOT_FOUND;
}
void DebugAgent::UnregisterWatchpoint(Breakpoint* bp, zx_koid_t process_koid,
const debug_ipc::AddressRange& range) {
// The process might legitimately be not found if it was terminated.
DebuggedProcess* proc = GetDebuggedProcess(process_koid);
if (proc)
proc->UnregisterWatchpoint(bp, range);
}
void DebugAgent::OnAddressSpace(const debug_ipc::AddressSpaceRequest& request,
debug_ipc::AddressSpaceReply* reply) {
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnAddressSpace(request, reply);
}
void DebugAgent::OnJobFilter(const debug_ipc::JobFilterRequest& request,
debug_ipc::JobFilterReply* reply) {
DebuggedJob* job = GetDebuggedJob(request.job_koid);
if (!job) {
reply->status = ZX_ERR_INVALID_ARGS;
return;
}
auto matches = job->SetFilters(std::move(request.filters));
for (zx_koid_t process_koid : matches) {
reply->matched_processes.push_back(process_koid);
}
reply->status = ZX_OK;
}
void DebugAgent::OnWriteMemory(const debug_ipc::WriteMemoryRequest& request,
debug_ipc::WriteMemoryReply* reply) {
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnWriteMemory(request, reply);
else
reply->status = ZX_ERR_NOT_FOUND;
}
void DebugAgent::OnLoadInfoHandleTable(const debug_ipc::LoadInfoHandleTableRequest& request,
debug_ipc::LoadInfoHandleTableReply* reply) {
DebuggedProcess* proc = GetDebuggedProcess(request.process_koid);
if (proc)
proc->OnLoadInfoHandleTable(request, reply);
else
reply->status = ZX_ERR_NOT_FOUND;
}
DebuggedProcess* DebugAgent::GetDebuggedProcess(zx_koid_t koid) {
auto found = procs_.find(koid);
if (found == procs_.end())
return nullptr;
return found->second.get();
}
DebuggedJob* DebugAgent::GetDebuggedJob(zx_koid_t koid) {
auto found = jobs_.find(koid);
if (found == jobs_.end())
return nullptr;
return found->second.get();
}
DebuggedThread* DebugAgent::GetDebuggedThread(zx_koid_t process_koid, zx_koid_t thread_koid) {
DebuggedProcess* process = GetDebuggedProcess(process_koid);
if (!process)
return nullptr;
return process->GetThread(thread_koid);
}
zx_status_t DebugAgent::AddDebuggedJob(zx_koid_t job_koid, zx::job zx_job) {
auto job = std::make_unique<DebuggedJob>(object_provider_, this, job_koid, std::move(zx_job));
zx_status_t status = job->Init();
if (status != ZX_OK)
return status;
jobs_[job_koid] = std::move(job);
return ZX_OK;
}
zx_status_t DebugAgent::AddDebuggedProcess(DebuggedProcessCreateInfo&& create_info,
DebuggedProcess** new_process) {
*new_process = nullptr;
zx_koid_t process_koid = create_info.koid;
auto proc = std::make_unique<DebuggedProcess>(this, std::move(create_info));
if (zx_status_t status = proc->Init(); status != ZX_OK)
return status;
*new_process = proc.get();
procs_[process_koid] = std::move(proc);
return ZX_OK;
}
// Attaching ---------------------------------------------------------------------------------------
namespace {
void SendAttachReply(DebugAgent* debug_agent, uint32_t transaction_id, zx_status_t status,
zx_koid_t process_koid = ZX_HANDLE_INVALID,
const std::string& process_name = "") {
debug_ipc::AttachReply reply = {};
reply.status = status;
reply.koid = process_koid;
reply.name = process_name;
debug_ipc::MessageWriter writer;
debug_ipc::WriteReply(reply, transaction_id, &writer);
debug_agent->stream()->Write(writer.MessageComplete());
}
} // namespace
void DebugAgent::OnAttach(std::vector<char> serialized) {
debug_ipc::MessageReader reader(std::move(serialized));
debug_ipc::AttachRequest request;
uint32_t transaction_id = 0;
if (!debug_ipc::ReadRequest(&reader, &request, &transaction_id)) {
FX_LOGS(WARNING) << "Got bad debugger attach request, ignoring.";
return;
}
OnAttach(transaction_id, request);
}
void DebugAgent::OnAttach(uint32_t transaction_id, const debug_ipc::AttachRequest& request) {
if (request.type == debug_ipc::TaskType::kProcess) {
AttachToProcess(request.koid, transaction_id);
return;
}
// All other attach types are variants of job attaches, find the KOID.
zx_koid_t job_koid = 0;
if (request.type == debug_ipc::TaskType::kJob) {
job_koid = request.koid;
} else if (request.type == debug_ipc::TaskType::kComponentRoot) {
job_koid = object_provider_->GetComponentJobKoid();
attached_root_job_koid_ = job_koid;
} else if (request.type == debug_ipc::TaskType::kSystemRoot) {
job_koid = object_provider_->GetRootJobKoid();
attached_root_job_koid_ = job_koid;
} else {
FX_LOGS(WARNING) << "Got bad debugger attach request type, ignoring.";
return;
}
debug_ipc::AttachReply reply;
reply.status = ZX_ERR_NOT_FOUND;
// Don't return early since we always need to send the reply, even on fail.
zx::job job = object_provider_->GetJobFromKoid(job_koid);
if (job.is_valid()) {
reply.name = object_provider_->NameForObject(job);
reply.koid = job_koid;
reply.status = AddDebuggedJob(job_koid, std::move(job));
}
DEBUG_LOG(Agent) << "Attaching to job " << job_koid << ": " << zx_status_get_string(reply.status);
// Send the reply.
debug_ipc::MessageWriter writer;
debug_ipc::WriteReply(reply, transaction_id, &writer);
stream()->Write(writer.MessageComplete());
}
void DebugAgent::AttachToProcess(zx_koid_t process_koid, uint32_t transaction_id) {
DEBUG_LOG(Agent) << "Attemping to attach to process " << process_koid;
// We see if we're already attached to this process.
for (auto& [koid, proc] : procs_) {
if (koid == process_koid) {
DEBUG_LOG(Agent) << "Already attached to " << proc->name() << "(" << proc->koid() << ").";
SendAttachReply(this, transaction_id, ZX_ERR_ALREADY_BOUND);
return;
}
}
// First check if the process is in limbo. Sends the appropiate replies/notifications.
if (limbo_provider_->Valid()) {
zx_status_t status = AttachToLimboProcess(process_koid, transaction_id);
if (status == ZX_OK)
return;
DEBUG_LOG(Agent) << "Could not attach to process in limbo: " << zx_status_get_string(status);
}
// Attempt to attach to an existing process. Sends the appropiate replies/notifications.
zx_status_t status = AttachToExistingProcess(process_koid, transaction_id);
if (status == ZX_OK)
return;
DEBUG_LOG(Agent) << "Could not attach to existing process: " << zx_status_get_string(status);
// We didn't find a process.
SendAttachReply(this, transaction_id, status);
}
zx_status_t DebugAgent::AttachToLimboProcess(zx_koid_t process_koid, uint32_t transaction_id) {
FX_DCHECK(limbo_provider_->Valid());
// Go over processes.
const fuchsia::exception::ProcessExceptionMetadata* process_metadata = nullptr;
for (const auto& [process_koid, exception] : limbo_provider_->Limbo()) {
FX_DCHECK(exception.has_info());
DEBUG_LOG(Agent) << "Found process in limbo: " << exception.info().process_koid;
if (exception.info().process_koid == process_koid) {
process_metadata = &exception;
break;
}
}
if (!process_metadata)
return ZX_ERR_NOT_FOUND;
// Obtain the process and exception from limbo.
auto retrieved = limbo_provider_->RetrieveException(process_koid);
if (retrieved.is_error()) {
zx_status_t status = retrieved.error_value();
DEBUG_LOG(Agent) << "Could not retrieve exception from limbo: " << zx_status_get_string(status);
return status;
}
auto [exception, process] = std::move(retrieved.value());
DebuggedProcessCreateInfo create_info;
create_info.koid = process_koid;
create_info.arch_provider = arch_provider_;
create_info.object_provider = object_provider_;
create_info.from_limbo = true;
create_info.name = object_provider_->NameForObject(process.get());
create_info.handle = std::make_unique<ZirconProcessHandle>(process_koid, std::move(process));
DebuggedProcess* debugged_process = nullptr;
zx_status_t status = AddDebuggedProcess(std::move(create_info), &debugged_process);
if (status != ZX_OK)
return status;
// Send the response, then the notifications about the process and threads.
SendAttachReply(this, transaction_id, ZX_OK, process_koid, debugged_process->name());
debugged_process->PopulateCurrentThreads();
debugged_process->SuspendAndSendModulesIfKnown();
// Pass in the exception handle to the corresponding thread.
DebuggedThread* exception_thread = nullptr;
for (DebuggedThread* thread : debugged_process->GetThreads()) {
auto koid = exception->GetThreadKoid();
if (koid.is_error()) {
DEBUG_LOG(Agent) << "failed to get koid of exception's thread";
return koid.error_value();
}
if (thread->koid() == koid.value()) {
exception_thread = thread;
break;
}
}
FX_DCHECK(exception_thread);
exception_thread->set_exception_handle(std::move(exception));
return ZX_OK;
}
zx_status_t DebugAgent::AttachToExistingProcess(zx_koid_t process_koid, uint32_t transaction_id) {
zx::process process_handle = object_provider_->GetProcessFromKoid(process_koid);
if (!process_handle.is_valid())
return ZX_ERR_NOT_FOUND;
// We attach to the process.
DebuggedProcessCreateInfo create_info = {};
create_info.koid = process_koid;
create_info.name = object_provider_->NameForObject(process_handle);
create_info.handle =
std::make_unique<ZirconProcessHandle>(process_koid, std::move(process_handle));
create_info.arch_provider = arch_provider_;
create_info.object_provider = object_provider_;
DebuggedProcess* process = nullptr;
zx_status_t status = AddDebuggedProcess(std::move(create_info), &process);
if (status != ZX_OK)
return status;
// Send the response, then the notifications about the process and threads.
SendAttachReply(this, transaction_id, ZX_OK, process_koid, process->name());
process->PopulateCurrentThreads();
process->SuspendAndSendModulesIfKnown();
return ZX_OK;
}
void DebugAgent::LaunchProcess(const debug_ipc::LaunchRequest& request,
debug_ipc::LaunchReply* reply) {
FX_DCHECK(!request.argv.empty());
reply->inferior_type = debug_ipc::InferiorType::kBinary;
DEBUG_LOG(Process) << "Launching binary " << request.argv.front();
BinaryLauncher launcher(services_);
reply->status = launcher.Setup(request.argv);
if (reply->status != ZX_OK)
return;
zx::process process = launcher.GetProcess();
zx_koid_t process_koid = object_provider_->KoidForObject(process);
DebuggedProcessCreateInfo create_info;
create_info.koid = process_koid;
create_info.handle = std::make_unique<ZirconProcessHandle>(process_koid, std::move(process));
create_info.out = launcher.ReleaseStdout();
create_info.err = launcher.ReleaseStderr();
create_info.arch_provider = arch_provider_;
create_info.object_provider = object_provider_;
DebuggedProcess* new_process = nullptr;
zx_status_t status = AddDebuggedProcess(std::move(create_info), &new_process);
if (status != ZX_OK) {
reply->status = status;
return;
}
reply->status = launcher.Start();
if (reply->status != ZX_OK) {
RemoveDebuggedProcess(process_koid);
return;
}
// Success, fill out the reply.
reply->process_id = process_koid;
reply->process_name = object_provider_->NameForObject(process);
reply->status = ZX_OK;
}
void DebugAgent::LaunchComponent(const debug_ipc::LaunchRequest& request,
debug_ipc::LaunchReply* reply) {
*reply = {};
reply->inferior_type = debug_ipc::InferiorType::kComponent;
ComponentLauncher component_launcher(services_);
ComponentDescription description;
ComponentHandles handles;
zx_status_t status = component_launcher.Prepare(request.argv, &description, &handles);
if (status != ZX_OK) {
reply->status = status;
return;
}
FX_DCHECK(expected_components_.count(description.filter) == 0);
// Create the filter.
//
// This is a hack. It will fail if the debugger isn't already attached to
// either the system or component root jobs. Ideally we would get the exact
// parent job for the component being launched and not depend on what the
// client may have already attached to.
DebuggedJob* job = GetDebuggedJob(attached_root_job_koid_);
if (!job) {
FX_LOGS(WARNING) << "Could not obtain component root job. Are you running "
"attached to another debugger?";
reply->status = ZX_ERR_BAD_STATE;
return;
}
job->AppendFilter(description.filter);
if (debug_ipc::IsDebugModeActive()) {
std::stringstream ss;
ss << "Launching component. " << std::endl
<< "Url: " << description.url << std::endl
<< ", name: " << description.process_name << std::endl
<< ", filter: " << description.filter << std::endl
<< ", component_id: " << description.component_id << std::endl;
auto& filters = job->filters();
ss << "Current component filters: " << filters.size();
for (auto& filter : filters) {
ss << std::endl << "* " << filter.filter;
}
DEBUG_LOG(Process) << ss.str();
}
reply->component_id = description.component_id;
// Launch the component.
auto controller = component_launcher.Launch();
if (!controller) {
FX_LOGS(WARNING) << "Could not launch component " << description.url;
reply->status = ZX_ERR_BAD_STATE;
return;
}
// TODO(donosoc): This should hook into the debug agent so it can correctly
// shutdown the state associated with waiting for this
// component.
controller.events().OnTerminated = [agent = GetWeakPtr(), description](
int64_t return_code,
fuchsia::sys::TerminationReason reason) {
// If the agent is gone, there isn't anything more to do.
if (!agent)
return;
agent->OnComponentTerminated(return_code, description, reason);
};
ExpectedComponent expected_component;
expected_component.description = description;
expected_component.handles = std::move(handles);
expected_component.controller = std::move(controller);
expected_components_[description.filter] = std::move(expected_component);
reply->status = ZX_OK;
}
void DebugAgent::OnProcessStart(const std::string& filter, zx::process process_handle) {
ComponentDescription description;
ComponentHandles handles;
auto it = expected_components_.find(filter);
if (it != expected_components_.end()) {
description = std::move(it->second.description);
handles = std::move(it->second.handles);
// Add to the list of running components.
running_components_[description.component_id] = std::move(it->second.controller);
expected_components_.erase(it);
} else {
description.process_name = object_provider_->NameForObject(process_handle);
}
auto process_koid = object_provider_->KoidForObject(process_handle);
DEBUG_LOG(Process) << "Process starting. Name: " << description.process_name
<< ", koid: " << process_koid << ", filter: " << filter
<< ", component id: " << description.component_id;
// Send notification, then create debug process so that thread notification is sent after this.
debug_ipc::NotifyProcessStarting notify;
notify.koid = process_koid;
notify.name = description.process_name;
notify.component_id = description.component_id;
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyProcessStarting(notify, &writer);
stream()->Write(writer.MessageComplete());
DebuggedProcessCreateInfo create_info;
create_info.koid = process_koid;
create_info.handle =
std::make_unique<ZirconProcessHandle>(process_koid, std::move(process_handle));
create_info.name = description.process_name;
create_info.out = std::move(handles.out);
create_info.err = std::move(handles.err);
create_info.arch_provider = arch_provider_;
create_info.object_provider = object_provider_;
DebuggedProcess* new_process = nullptr;
AddDebuggedProcess(std::move(create_info), &new_process);
if (new_process) {
// In some edge-cases (see DebuggedProcess::RegisterDebugState() for more) the loader state is
// known at startup. Send it if so.
new_process->SuspendAndSendModulesIfKnown();
}
}
void DebugAgent::InjectProcessForTest(std::unique_ptr<DebuggedProcess> process) {
procs_[process->koid()] = std::move(process);
}
void DebugAgent::OnComponentTerminated(int64_t return_code, const ComponentDescription& description,
fuchsia::sys::TerminationReason reason) {
DEBUG_LOG(Process) << "Component " << description.url << " exited with "
<< sys::HumanReadableTerminationReason(reason);
// TODO(donosoc): This need to be communicated over to the client.
if (reason != fuchsia::sys::TerminationReason::EXITED) {
FX_LOGS(WARNING) << "Component " << description.url << " exited with "
<< sys::HumanReadableTerminationReason(reason);
}
// We look for the filter and remove it.
// If we couldn't find it, the component was already caught and cleaned.
expected_components_.erase(description.filter);
if (debug_ipc::IsDebugModeActive()) {
std::stringstream ss;
ss << "Still expecting the following components: " << expected_components_.size();
for (auto& expected : expected_components_) {
ss << std::endl << "* " << expected.first;
}
DEBUG_LOG(Process) << ss.str();
}
}
void DebugAgent::OnProcessesEnteredLimbo(
std::vector<fuchsia::exception::ProcessExceptionMetadata> processes) {
for (auto& process : processes) {
// We first check if we were to "kill" this process.
auto it = killed_limbo_procs_.find(process.info().process_koid);
if (it != killed_limbo_procs_.end()) {
limbo_provider_->ReleaseProcess(process.info().process_koid);
killed_limbo_procs_.erase(it);
continue;
}
std::string process_name = object_provider_->NameForObject(process.process());
DEBUG_LOG(Agent) << "Process " << process_name << " (" << process.info().process_koid
<< ") entered limbo.";
debug_ipc::NotifyProcessStarting process_starting = {};
process_starting.type = debug_ipc::NotifyProcessStarting::Type::kLimbo;
process_starting.koid = process.info().process_koid;
process_starting.name = std::move(process_name);
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyProcessStarting(std::move(process_starting), &writer);
stream()->Write(writer.MessageComplete());
}
}
} // namespace debug_agent