| // 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/sys/cpp/termination_reason.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/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_util.h" |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" |
| #include "src/developer/debug/debug_agent/process_info.h" |
| #include "src/developer/debug/debug_agent/system_info.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/block_timer.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| #include "src/developer/debug/shared/message_loop_target.h" |
| #include "src/developer/debug/shared/stream_buffer.h" |
| #include "src/developer/debug/shared/zx_status.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/lib/fxl/strings/concatenate.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| constexpr size_t kMegabyte = 1024 * 1024; |
| |
| } // namespace |
| |
| DebugAgent::DebugAgent(std::shared_ptr<sys::ServiceDirectory> services) |
| : services_(services), weak_factory_(this) {} |
| |
| DebugAgent::~DebugAgent() = default; |
| |
| fxl::WeakPtr<DebugAgent> DebugAgent::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } |
| |
| void DebugAgent::Connect(debug_ipc::StreamBuffer* stream) { |
| FXL_DCHECK(!stream_) << "A debug agent should not be connected twice!"; |
| stream_ = stream; |
| } |
| |
| void DebugAgent::Disconnect() { |
| FXL_DCHECK(stream_); |
| stream_ = nullptr; |
| } |
| |
| debug_ipc::StreamBuffer* DebugAgent::stream() { |
| FXL_DCHECK(stream_); |
| return stream_; |
| } |
| |
| void DebugAgent::RemoveDebuggedProcess(zx_koid_t process_koid) { |
| auto found = procs_.find(process_koid); |
| if (found == procs_.end()) |
| FXL_NOTREACHED(); |
| else |
| procs_.erase(found); |
| } |
| |
| void DebugAgent::RemoveDebuggedJob(zx_koid_t job_koid) { |
| auto found = jobs_.find(job_koid); |
| if (found == jobs_.end()) |
| FXL_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::RemoveWatchpoint(uint32_t watchpoint_id) { |
| auto found = watchpoints_.find(watchpoint_id); |
| if (found != watchpoints_.end()) |
| watchpoints_.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) { |
| TIME_BLOCK(); |
| // Version and signature are default-initialized to their current values. |
| reply->arch = arch::ArchProvider::Get().GetArch(); |
| } |
| |
| void DebugAgent::OnLaunch(const debug_ipc::LaunchRequest& request, debug_ipc::LaunchReply* reply) { |
| TIME_BLOCK(); |
| 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) { |
| TIME_BLOCK(); |
| auto debug_process = GetDebuggedProcess(request.process_koid); |
| |
| if (!debug_process || !debug_process->process().is_valid()) { |
| reply->status = ZX_ERR_NOT_FOUND; |
| return; |
| } |
| debug_process->OnKill(request, reply); |
| RemoveDebuggedProcess(request.process_koid); |
| } |
| |
| 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)) { |
| FXL_LOG(WARNING) << "Got bad debugger attach request, ignoring."; |
| return; |
| } |
| |
| OnAttach(transaction_id, request); |
| } |
| |
| void DebugAgent::OnAttach(uint32_t transaction_id, const debug_ipc::AttachRequest& request) { |
| TIME_BLOCK(); |
| |
| if (request.type == debug_ipc::TaskType::kProcess) { |
| AttachToProcess(transaction_id, request.koid); |
| 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 = GetComponentJobKoid(); |
| attached_root_job_koid_ = job_koid; |
| } else if (request.type == debug_ipc::TaskType::kSystemRoot) { |
| job_koid = GetRootJobKoid(); |
| attached_root_job_koid_ = job_koid; |
| } else { |
| FXL_LOG(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 = GetJobFromKoid(job_koid); |
| if (job.is_valid()) { |
| reply.name = 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::OnDetach(const debug_ipc::DetachRequest& request, debug_ipc::DetachReply* reply) { |
| TIME_BLOCK(); |
| 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: { |
| auto debug_process = GetDebuggedProcess(request.koid); |
| if (debug_process && debug_process->process().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) { |
| TIME_BLOCK(); |
| 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) { |
| TIME_BLOCK(); |
| if (request.process_koid) { |
| // Single process. |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| if (proc) { |
| proc->OnResume(request); |
| } else { |
| FXL_LOG(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) { |
| TIME_BLOCK(); |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| if (proc) |
| proc->OnModules(reply); |
| } |
| |
| void DebugAgent::OnProcessTree(const debug_ipc::ProcessTreeRequest& request, |
| debug_ipc::ProcessTreeReply* reply) { |
| TIME_BLOCK(); |
| GetProcessTree(&reply->root); |
| } |
| |
| void DebugAgent::OnThreads(const debug_ipc::ThreadsRequest& request, |
| debug_ipc::ThreadsReply* reply) { |
| TIME_BLOCK(); |
| auto found = procs_.find(request.process_koid); |
| if (found == procs_.end()) |
| return; |
| |
| found->second->FillThreadRecords(&reply->threads); |
| } |
| |
| void DebugAgent::OnReadMemory(const debug_ipc::ReadMemoryRequest& request, |
| debug_ipc::ReadMemoryReply* reply) { |
| TIME_BLOCK(); |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| if (proc) |
| proc->OnReadMemory(request, reply); |
| } |
| |
| void DebugAgent::OnReadRegisters(const debug_ipc::ReadRegistersRequest& request, |
| debug_ipc::ReadRegistersReply* reply) { |
| TIME_BLOCK(); |
| DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid); |
| if (thread) { |
| thread->ReadRegisters(request.categories, &reply->categories); |
| } else { |
| FXL_LOG(ERROR) << "Cannot find thread with koid: " << request.thread_koid; |
| } |
| } |
| |
| void DebugAgent::OnWriteRegisters(const debug_ipc::WriteRegistersRequest& request, |
| debug_ipc::WriteRegistersReply* reply) { |
| TIME_BLOCK(); |
| DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid); |
| if (thread) { |
| reply->status = thread->WriteRegisters(request.registers); |
| } else { |
| reply->status = ZX_ERR_NOT_FOUND; |
| FXL_LOG(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: |
| return SetupBreakpoint(request, reply); |
| case debug_ipc::BreakpointType::kWatchpoint: |
| return SetupWatchpoint(request, reply); |
| case debug_ipc::BreakpointType::kLast: |
| break; |
| } |
| |
| FXL_NOTREACHED() << "Invalid Breakpoint Type."; |
| } |
| |
| void DebugAgent::OnRemoveBreakpoint(const debug_ipc::RemoveBreakpointRequest& request, |
| debug_ipc::RemoveBreakpointReply* reply) { |
| TIME_BLOCK(); |
| RemoveBreakpoint(request.breakpoint_id); |
| } |
| |
| void DebugAgent::OnSysInfo(const debug_ipc::SysInfoRequest& request, |
| debug_ipc::SysInfoReply* reply) { |
| char version[64]; |
| zx_system_get_version(version, sizeof(version)); |
| reply->version = version; |
| |
| reply->num_cpus = zx_system_get_num_cpus(); |
| reply->memory_mb = zx_system_get_physmem() / kMegabyte; |
| |
| zx_system_get_features(ZX_FEATURE_KIND_HW_BREAKPOINT_COUNT, &reply->hw_breakpoint_count); |
| |
| zx_system_get_features(ZX_FEATURE_KIND_HW_WATCHPOINT_COUNT, &reply->hw_watchpoint_count); |
| } |
| |
| void DebugAgent::OnThreadStatus(const debug_ipc::ThreadStatusRequest& request, |
| debug_ipc::ThreadStatusReply* reply) { |
| TIME_BLOCK(); |
| DebuggedThread* thread = GetDebuggedThread(request.process_koid, request.thread_koid); |
| if (thread) { |
| thread->FillThreadRecord(debug_ipc::ThreadRecord::StackAmount::kFull, nullptr, &reply->record); |
| } 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()) { |
| found = breakpoints_ |
| .emplace(std::piecewise_construct, std::forward_as_tuple(id), |
| std::forward_as_tuple(this)) |
| .first; |
| } |
| reply->status = found->second.SetSettings(request.breakpoint_type, request.breakpoint); |
| } |
| |
| zx_status_t DebugAgent::RegisterWatchpoint(Watchpoint* wp, zx_koid_t process_koid, |
| const debug_ipc::AddressRange& range) { |
| DebuggedProcess* process = GetDebuggedProcess(process_koid); |
| if (!process) { |
| // The process might legitimately be not found if there was a race between |
| // the process terminating and a watchpoint add/change. |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| return process->RegisterWatchpoint(wp, range); |
| } |
| |
| void DebugAgent::UnregisterWatchpoint(Watchpoint* wp, zx_koid_t process_koid, |
| const debug_ipc::AddressRange& range) { |
| // The process might legitimately be not found if there was a race between |
| // the process terminating and a watchpoint add/change. |
| DebuggedProcess* process = GetDebuggedProcess(process_koid); |
| if (!process) |
| return; |
| |
| process->UnregisterWatchpoint(wp, range); |
| } |
| |
| void DebugAgent::SetupWatchpoint(const debug_ipc::AddOrChangeBreakpointRequest& request, |
| debug_ipc::AddOrChangeBreakpointReply* reply) { |
| auto id = request.breakpoint.id; |
| |
| auto wp_it = watchpoints_.find(id); |
| if (wp_it == watchpoints_.end()) { |
| wp_it = watchpoints_ |
| .emplace(std::piecewise_construct, std::forward_as_tuple(id), |
| std::forward_as_tuple(this)) |
| .first; |
| } |
| reply->status = wp_it->second.SetSettings(request.breakpoint); |
| } |
| |
| void DebugAgent::OnAddressSpace(const debug_ipc::AddressSpaceRequest& request, |
| debug_ipc::AddressSpaceReply* reply) { |
| TIME_BLOCK(); |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| if (proc) |
| proc->OnAddressSpace(request, reply); |
| } |
| |
| void DebugAgent::OnJobFilter(const debug_ipc::JobFilterRequest& request, |
| debug_ipc::JobFilterReply* reply) { |
| TIME_BLOCK(); |
| DebuggedJob* job = GetDebuggedJob(request.job_koid); |
| if (!job) { |
| reply->status = ZX_ERR_INVALID_ARGS; |
| return; |
| } |
| job->SetFilters(std::move(request.filters)); |
| reply->status = ZX_OK; |
| } |
| |
| void DebugAgent::OnWriteMemory(const debug_ipc::WriteMemoryRequest& request, |
| debug_ipc::WriteMemoryReply* reply) { |
| TIME_BLOCK(); |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| if (proc) |
| proc->OnWriteMemory(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>(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) { |
| zx_koid_t process_koid = create_info.koid; |
| auto proc = std::make_unique<DebuggedProcess>(this, std::move(create_info)); |
| zx_status_t status = proc->Init(); |
| if (status != ZX_OK) |
| return status; |
| |
| procs_[process_koid] = std::move(proc); |
| return ZX_OK; |
| } |
| |
| void DebugAgent::AttachToProcess(uint32_t transaction_id, zx_koid_t process_koid) { |
| // Don't return early without sending this reply, even in the failure case. |
| debug_ipc::AttachReply reply; |
| reply.status = ZX_ERR_NOT_FOUND; |
| |
| zx::process process = GetProcessFromKoid(process_koid); |
| if (process.is_valid()) { |
| reply.name = NameForObject(process); |
| reply.koid = process_koid; |
| |
| // TODO(donosoc): change resume thread setting once we have global |
| // settings. |
| DebuggedProcessCreateInfo create_info; |
| create_info.name = reply.name; |
| create_info.koid = process_koid; |
| create_info.handle = std::move(process); |
| reply.status = AddDebuggedProcess(std::move(create_info)); |
| } |
| |
| // Send the reply. |
| debug_ipc::MessageWriter writer; |
| debug_ipc::WriteReply(reply, transaction_id, &writer); |
| stream()->Write(writer.MessageComplete()); |
| |
| // For valid attaches, follow up with the current module and thread lists. |
| DebuggedProcess* new_process = GetDebuggedProcess(process_koid); |
| if (new_process) { |
| new_process->PopulateCurrentThreads(); |
| |
| if (new_process->RegisterDebugState()) { |
| // Suspend all threads while the module list is being sent. The client |
| // will resume the threads once it's loaded symbols and processed |
| // breakpoints (this may take a while and we'd like to get any |
| // breakpoints as early as possible). |
| std::vector<uint64_t> paused_thread_koids; |
| new_process->SuspendAll(false, &paused_thread_koids); |
| new_process->SendModuleNotification(std::move(paused_thread_koids)); |
| } |
| } |
| } |
| |
| void DebugAgent::LaunchProcess(const debug_ipc::LaunchRequest& request, |
| debug_ipc::LaunchReply* reply) { |
| FXL_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 = KoidForObject(process); |
| |
| DebuggedProcessCreateInfo create_info; |
| create_info.koid = process_koid; |
| create_info.handle = std::move(process); |
| create_info.out = launcher.ReleaseStdout(); |
| create_info.err = launcher.ReleaseStderr(); |
| zx_status_t status = AddDebuggedProcess(std::move(create_info)); |
| 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 = 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; |
| } |
| FXL_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) { |
| FXL_LOG(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) { |
| FXL_LOG(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) { |
| TIME_BLOCK(); |
| 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 = NameForObject(process_handle); |
| } |
| |
| auto process_koid = 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::move(process_handle); |
| create_info.name = description.process_name; |
| create_info.out = std::move(handles.out); |
| create_info.err = std::move(handles.err); |
| AddDebuggedProcess(std::move(create_info)); |
| } |
| |
| 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) { |
| FXL_LOG(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(); |
| } |
| } |
| |
| } // namespace debug_agent |