| // 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/fit/defer.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/binary_launcher.h" | |
| #include "src/developer/debug/debug_agent/component_manager.h" | |
| #include "src/developer/debug/debug_agent/debugged_thread.h" | |
| #include "src/developer/debug/debug_agent/exception_handle.h" | |
| #include "src/developer/debug/debug_agent/process_breakpoint.h" | |
| #include "src/developer/debug/debug_agent/system_interface.h" | |
| #include "src/developer/debug/debug_agent/time.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 { | |
| namespace { | |
| constexpr size_t kMegabyte = 1024 * 1024; | |
| std::string LogResumeRequest(const debug_ipc::ResumeRequest& request) { | |
| std::stringstream ss; | |
| ss << "Got resume request for "; | |
| // Print thread koids. | |
| if (request.ids.empty()) { | |
| ss << "all processes."; | |
| } else { | |
| for (size_t i = 0; i < request.ids.size(); i++) { | |
| if (i > 0) | |
| ss << ", "; | |
| ss << "(" << request.ids[i].process << ", " << request.ids[i].thread << ")"; | |
| } | |
| } | |
| // 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::unique_ptr<SystemInterface> system_interface) | |
| : system_interface_(std::move(system_interface)), weak_factory_(this) { | |
| // Set a callback to the LimboProvider to let us know when new processes enter the limbo. | |
| system_interface_->GetLimboProvider().set_on_enter_limbo( | |
| [agent = GetWeakPtr()](const LimboProvider::Record& record) { | |
| if (!agent) | |
| return; | |
| agent->OnProcessEnteredLimbo(record); | |
| }); | |
| } | |
| DebugAgent::~DebugAgent() { | |
| // Clear the callback to prevent dangling pointers. | |
| system_interface_->GetLimboProvider().set_on_enter_limbo(LimboProvider::OnEnterLimboCallback()); | |
| } | |
| fxl::WeakPtr<DebugAgent> DebugAgent::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } | |
| void DebugAgent::Connect(debug::StreamBuffer* stream) { | |
| FX_DCHECK(!stream_) << "A debug agent should not be connected twice!"; | |
| stream_ = stream; | |
| } | |
| void DebugAgent::Disconnect() { | |
| FX_DCHECK(stream_); | |
| stream_ = nullptr; | |
| // Remove all DebuggedProcess and DebuggedJob on disconnect. The logic here should be consistent | |
| // with zxdb::System::DidDisconnect() so that further reconnecting won't create any surprise. | |
| jobs_.clear(); | |
| // Removes breakpoints before we detach from the processes, although it should also be safe to | |
| // reverse the order. | |
| breakpoints_.clear(); | |
| // The destructor of the DebuggedProcess will detach us from the process. | |
| procs_.clear(); | |
| } | |
| debug::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); | |
| } | |
| Breakpoint* DebugAgent::GetBreakpoint(uint32_t breakpoint_id) { | |
| if (auto found = breakpoints_.find(breakpoint_id); found != breakpoints_.end()) | |
| return &found->second; | |
| return nullptr; | |
| } | |
| void DebugAgent::RemoveBreakpoint(uint32_t breakpoint_id) { | |
| if (auto found = breakpoints_.find(breakpoint_id); 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::GetCurrentArch(); | |
| } | |
| 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->process_handle().GetName(); | |
| 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 (system_interface_->GetLimboProvider().Valid()) { | |
| for (const auto& [process_koid, record] : | |
| system_interface_->GetLimboProvider().GetLimboRecords()) { | |
| debug_ipc::ProcessRecord process_record = {}; | |
| process_record.process_koid = process_koid; | |
| process_record.process_name = record.process->GetName(); | |
| // For now, only fill the thread blocked on exception. | |
| process_record.threads.push_back(record.thread->GetThreadRecord(process_koid)); | |
| 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->timestamp = GetNowTimestamp(); | |
| reply->status = debug::Status("Invalid inferior type to launch."); | |
| } | |
| void DebugAgent::OnKill(const debug_ipc::KillRequest& request, debug_ipc::KillReply* reply) { | |
| reply->timestamp = GetNowTimestamp(); | |
| // See first if the process is in limbo. | |
| LimboProvider& limbo = system_interface_->GetLimboProvider(); | |
| if (limbo.Valid() && limbo.IsProcessInLimbo(request.process_koid)) { | |
| // Release if from limbo, which will effectivelly kill it. | |
| reply->status = limbo.ReleaseProcess(request.process_koid); | |
| return; | |
| } | |
| // Otherwise search locally. | |
| auto debug_process = GetDebuggedProcess(request.process_koid); | |
| if (!debug_process) { | |
| reply->status = debug::Status("Process is not currently being debugged."); | |
| return; | |
| } | |
| debug_process->OnKill(request, reply); | |
| // Check if this was a limbo "kill". If so, 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.has_error() && debug_process->from_limbo()) { | |
| killed_limbo_procs_.insert(debug_process->koid()); | |
| reply->status = debug::Status(); | |
| } | |
| RemoveDebuggedProcess(request.process_koid); | |
| } | |
| void DebugAgent::OnDetach(const debug_ipc::DetachRequest& request, debug_ipc::DetachReply* reply) { | |
| reply->timestamp = GetNowTimestamp(); | |
| switch (request.type) { | |
| case debug_ipc::TaskType::kJob: { | |
| if (auto debug_job = GetDebuggedJob(request.koid)) { | |
| RemoveDebuggedJob(request.koid); | |
| reply->status = debug::Status(); | |
| } else { | |
| reply->status = debug::Status("Not currently attached to job " + | |
| std::to_string(request.koid) + " to detach from."); | |
| } | |
| break; | |
| } | |
| case debug_ipc::TaskType::kProcess: { | |
| // First check if the process is waiting in limbo. If so, release it. | |
| LimboProvider& limbo = system_interface_->GetLimboProvider(); | |
| if (limbo.Valid() && limbo.IsProcessInLimbo(request.koid)) { | |
| reply->status = limbo.ReleaseProcess(request.koid); | |
| return; | |
| } | |
| auto debug_process = GetDebuggedProcess(request.koid); | |
| if (debug_process && debug_process->handle().is_valid()) { | |
| RemoveDebuggedProcess(request.koid); | |
| reply->status = debug::Status(); | |
| } else { | |
| reply->status = debug::Status("Not currently attached to process " + | |
| std::to_string(request.koid) + " to detach from."); | |
| } | |
| break; | |
| } | |
| default: | |
| reply->status = debug::Status("Bad task type to detach from."); | |
| } | |
| } | |
| void DebugAgent::OnPause(const debug_ipc::PauseRequest& request, debug_ipc::PauseReply* reply) { | |
| std::vector<debug_ipc::ProcessThreadId> paused; | |
| if (request.ids.empty()) { | |
| // Pause everything. | |
| paused = ClientSuspendAll(); | |
| } else { | |
| // Pause specific threads. | |
| for (const debug_ipc::ProcessThreadId& id : request.ids) { | |
| if (DebuggedProcess* proc = GetDebuggedProcess(id.process)) { | |
| if (id.thread) { | |
| // Single thread in that process. | |
| if (DebuggedThread* thread = proc->GetThread(id.thread)) { | |
| thread->ClientSuspend(true); | |
| paused.push_back(id); | |
| } else { | |
| FX_LOGS(WARNING) << "Could not find thread by koid: " << id.thread; | |
| } | |
| } else { | |
| // All threads in the process. | |
| std::vector<debug_ipc::ProcessThreadId> proc_threads = proc->ClientSuspendAllThreads(); | |
| paused.insert(paused.end(), proc_threads.begin(), proc_threads.end()); | |
| } | |
| } | |
| } | |
| } | |
| // Save the affected thread info. | |
| for (const debug_ipc::ProcessThreadId& id : paused) { | |
| if (DebuggedThread* thread = GetDebuggedThread(id)) { | |
| reply->threads.push_back( | |
| thread->GetThreadRecord(debug_ipc::ThreadRecord::StackAmount::kMinimal)); | |
| } | |
| } | |
| } | |
| void DebugAgent::OnQuitAgent(const debug_ipc::QuitAgentRequest& request, | |
| debug_ipc::QuitAgentReply* reply) { | |
| debug::MessageLoop::Current()->QuitNow(); | |
| } | |
| void DebugAgent::OnResume(const debug_ipc::ResumeRequest& request, debug_ipc::ResumeReply* reply) { | |
| DEBUG_LOG(Agent) << LogResumeRequest(request); | |
| if (request.ids.empty()) { | |
| // All debugged processes. | |
| for (const auto& pair : procs_) | |
| pair.second->OnResume(request); | |
| } else { | |
| // Explicit list. | |
| for (const auto& id : request.ids) { | |
| if (DebuggedProcess* proc = GetDebuggedProcess(id.process)) { | |
| if (id.thread) { | |
| // Single thread in that process. | |
| if (DebuggedThread* thread = proc->GetThread(id.thread)) { | |
| thread->ClientResume(request); | |
| } else { | |
| FX_LOGS(WARNING) << "Could not find thread by koid: " << id.thread; | |
| } | |
| } else { | |
| // All threads in the process. | |
| proc->OnResume(request); | |
| } | |
| } else { | |
| FX_LOGS(WARNING) << "Could not find process by koid: " << id.process; | |
| } | |
| } | |
| } | |
| } | |
| 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) { | |
| reply->root = system_interface_->GetProcessTree(); | |
| } | |
| 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.id); | |
| if (thread) { | |
| reply->registers = thread->ReadRegisters(request.categories); | |
| } else { | |
| FX_LOGS(ERROR) << "Cannot find thread with koid: " << request.id.thread; | |
| } | |
| } | |
| void DebugAgent::OnWriteRegisters(const debug_ipc::WriteRegistersRequest& request, | |
| debug_ipc::WriteRegistersReply* reply) { | |
| DebuggedThread* thread = GetDebuggedThread(request.id); | |
| if (thread) { | |
| reply->status = debug::Status(); | |
| reply->registers = thread->WriteRegisters(request.registers); | |
| } else { | |
| reply->status = debug::Status("Can not find thread " + std::to_string(request.id.thread) + | |
| " to write registers."); | |
| FX_LOGS(ERROR) << "Cannot find thread with koid: " << request.id.thread; | |
| } | |
| } | |
| 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 = system_interface_->GetSystemVersion(); | |
| reply->num_cpus = system_interface_->GetNumCpus(); | |
| reply->memory_mb = system_interface_->GetPhysicalMemory() / kMegabyte; | |
| reply->hw_breakpoint_count = arch::GetHardwareBreakpointCount(); | |
| reply->hw_watchpoint_count = arch::GetHardwareWatchpointCount(); | |
| } | |
| 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 = | |
| debug::Status("Not currently attached to process " + std::to_string(request.process_koid)); | |
| return; | |
| } | |
| DebuggedProcess* process = it->second.get(); | |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, [this, process]() mutable { | |
| debug_ipc::NotifyProcessStarting notify = {}; | |
| notify.koid = process->koid(); | |
| notify.name = process->process_handle().GetName(); | |
| notify.timestamp = GetNowTimestamp(); | |
| debug_ipc::MessageWriter writer; | |
| debug_ipc::WriteNotifyProcessStarting(notify, &writer); | |
| stream()->Write(writer.MessageComplete()); | |
| // Send the modules notification. | |
| process->SuspendAndSendModulesIfKnown(); | |
| }); | |
| reply->status = debug::Status(); | |
| } | |
| void DebugAgent::OnThreadStatus(const debug_ipc::ThreadStatusRequest& request, | |
| debug_ipc::ThreadStatusReply* reply) { | |
| if (DebuggedThread* thread = GetDebuggedThread(request.id)) { | |
| 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.id = request.id; | |
| reply->record.state = debug_ipc::ThreadRecord::State::kDead; | |
| } | |
| } | |
| debug::Status 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 debug::Status("Process not found when adding breakpoint"); | |
| } | |
| 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); | |
| } | |
| debug::Status DebugAgent::RegisterWatchpoint(Breakpoint* bp, zx_koid_t process_koid, | |
| const debug::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 debug::Status("Process not found when adding watchpoint"); | |
| } | |
| void DebugAgent::UnregisterWatchpoint(Breakpoint* bp, zx_koid_t process_koid, | |
| const debug::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 = debug::Status("Not attached to job " + std::to_string(request.job_koid) + | |
| " while adding a filter."); | |
| return; | |
| } | |
| for (const auto& match : job->SetFilters(std::move(request.filters))) | |
| reply->matched_processes.push_back(match->GetKoid()); | |
| reply->status = debug::Status(); | |
| } | |
| 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 = debug::Status("Not attached to process " + | |
| std::to_string(request.process_koid) + " while writing memory."); | |
| } | |
| } | |
| 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 = | |
| debug::Status("Not attached to process " + std::to_string(request.process_koid) + | |
| " while getting the handle table."); | |
| } | |
| void DebugAgent::OnUpdateGlobalSettings(const debug_ipc::UpdateGlobalSettingsRequest& request, | |
| debug_ipc::UpdateGlobalSettingsReply* reply) { | |
| for (const auto& update : request.exception_strategies) { | |
| exception_strategies_[update.type] = update.value; | |
| } | |
| } | |
| 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(const debug_ipc::ProcessThreadId& id) { | |
| DebuggedProcess* process = GetDebuggedProcess(id.process); | |
| if (!process) | |
| return nullptr; | |
| return process->GetThread(id.thread); | |
| } | |
| std::vector<debug_ipc::ProcessThreadId> DebugAgent::ClientSuspendAll(zx_koid_t except_process, | |
| zx_koid_t except_thread) { | |
| // Neither or both koids must be supplied. | |
| FX_DCHECK((except_process == ZX_KOID_INVALID && except_thread == ZX_KOID_INVALID) || | |
| (except_process != ZX_KOID_INVALID && except_thread != ZX_KOID_INVALID)); | |
| std::vector<debug_ipc::ProcessThreadId> affected; | |
| for (const auto& [process_koid, process] : procs_) { | |
| std::vector<debug_ipc::ProcessThreadId> proc_threads; | |
| if (process_koid == except_process) { | |
| proc_threads = process->ClientSuspendAllThreads(except_thread); | |
| } else { | |
| proc_threads = process->ClientSuspendAllThreads(); | |
| } | |
| affected.insert(affected.end(), proc_threads.begin(), proc_threads.end()); | |
| } | |
| return affected; | |
| } | |
| debug::Status DebugAgent::AddDebuggedJob(std::unique_ptr<JobHandle> job_handle) { | |
| zx_koid_t koid = job_handle->GetKoid(); | |
| auto job = std::make_unique<DebuggedJob>(this, std::move(job_handle)); | |
| if (auto status = job->Init(); status.has_error()) | |
| return status; | |
| jobs_[koid] = std::move(job); | |
| return debug::Status(); | |
| } | |
| debug::Status DebugAgent::AddDebuggedProcess(DebuggedProcessCreateInfo&& create_info, | |
| DebuggedProcess** new_process) { | |
| *new_process = nullptr; | |
| auto proc = std::make_unique<DebuggedProcess>(this, std::move(create_info)); | |
| if (auto status = proc->Init(); status.has_error()) | |
| return status; | |
| auto process_id = proc->koid(); | |
| *new_process = proc.get(); | |
| procs_[process_id] = std::move(proc); | |
| return debug::Status(); | |
| } | |
| debug_ipc::ExceptionStrategy DebugAgent::GetExceptionStrategy(debug_ipc::ExceptionType type) { | |
| auto strategy = exception_strategies_.find(type); | |
| if (strategy == exception_strategies_.end()) { | |
| return debug_ipc::ExceptionStrategy::kFirstChance; | |
| } | |
| return strategy->second; | |
| } | |
| // Attaching --------------------------------------------------------------------------------------- | |
| namespace { | |
| void SendAttachReply(DebugAgent* debug_agent, uint32_t transaction_id, const debug::Status& 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; | |
| reply.timestamp = GetNowTimestamp(); | |
| 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. | |
| std::unique_ptr<JobHandle> job; | |
| if (request.type == debug_ipc::TaskType::kJob) { | |
| job = system_interface_->GetJob(request.koid); | |
| } else if (request.type == debug_ipc::TaskType::kComponentRoot) { | |
| job = system_interface_->GetComponentRootJob(); | |
| if (job) | |
| attached_root_job_koid_ = job->GetKoid(); | |
| } else if (request.type == debug_ipc::TaskType::kSystemRoot) { | |
| job = system_interface_->GetRootJob(); | |
| if (job) | |
| attached_root_job_koid_ = job->GetKoid(); | |
| } else { | |
| FX_LOGS(WARNING) << "Got bad debugger attach request type, ignoring."; | |
| return; | |
| } | |
| debug_ipc::AttachReply reply; | |
| // Don't return early since we always need to send the reply, even on fail. | |
| if (job) { | |
| DEBUG_LOG(Agent) << "Attaching to job " << job->GetKoid(); | |
| reply.name = job->GetName(); | |
| reply.koid = job->GetKoid(); | |
| reply.status = AddDebuggedJob(std::move(job)); | |
| reply.timestamp = GetNowTimestamp(); | |
| } else { | |
| DEBUG_LOG(Agent) << "Failed to attach to job."; | |
| reply.status = debug::Status("Could not attach to job."); | |
| } | |
| // 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; | |
| // See if we're already attached to this process. | |
| for (auto& [koid, proc] : procs_) { | |
| if (koid == process_koid) { | |
| debug::Status error(debug::Status::kAlreadyExists, | |
| "Already attached to process " + std::to_string(proc->koid())); | |
| DEBUG_LOG(Agent) << error.message(); | |
| SendAttachReply(this, transaction_id, error); | |
| return; | |
| } | |
| } | |
| // First check if the process is in limbo. Sends the appropiate replies/notifications. | |
| if (system_interface_->GetLimboProvider().Valid()) { | |
| auto status = AttachToLimboProcess(process_koid, transaction_id); | |
| if (status.ok()) | |
| return; | |
| DEBUG_LOG(Agent) << "Could not attach to process in limbo: " << status.message(); | |
| } | |
| // Attempt to attach to an existing process. Sends the appropriate replies/notifications. | |
| auto status = AttachToExistingProcess(process_koid, transaction_id); | |
| if (status.ok()) | |
| return; | |
| DEBUG_LOG(Agent) << "Could not attach to existing process: " << status.message(); | |
| // We didn't find a process. | |
| SendAttachReply(this, transaction_id, status); | |
| } | |
| debug::Status DebugAgent::AttachToLimboProcess(zx_koid_t process_koid, uint32_t transaction_id) { | |
| LimboProvider& limbo = system_interface_->GetLimboProvider(); | |
| FX_DCHECK(limbo.Valid()); | |
| // Obtain the process and exception from limbo. | |
| auto retrieved = limbo.RetrieveException(process_koid); | |
| if (retrieved.is_error()) { | |
| debug::Status status = retrieved.error_value(); | |
| DEBUG_LOG(Agent) << "Could not retrieve exception from limbo: " << status.message(); | |
| return status; | |
| } | |
| LimboProvider::RetrievedException& exception = retrieved.value(); | |
| DebuggedProcessCreateInfo create_info(std::move(exception.process)); | |
| create_info.from_limbo = true; | |
| DebuggedProcess* debugged_process = nullptr; | |
| debug::Status status = AddDebuggedProcess(std::move(create_info), &debugged_process); | |
| if (status.has_error()) | |
| return status; | |
| // Send the response, then the notifications about the process and threads. | |
| // | |
| // DO NOT RETURN FAILURE AFTER THIS POINT or the attach reply will be duplicated (the caller will | |
| // fall back on non-limbo processes if this function fails and will send another reply). | |
| SendAttachReply(this, transaction_id, debug::Status(), process_koid, | |
| debugged_process->process_handle().GetName()); | |
| debugged_process->PopulateCurrentThreads(); | |
| debugged_process->SuspendAndSendModulesIfKnown(); | |
| zx_koid_t thread_koid = exception.thread->GetKoid(); | |
| // Pass in the exception handle to the corresponding thread. | |
| DebuggedThread* exception_thread = nullptr; | |
| for (DebuggedThread* thread : debugged_process->GetThreads()) { | |
| if (thread->koid() == thread_koid) { | |
| exception_thread = thread; | |
| break; | |
| } | |
| } | |
| if (exception_thread) | |
| exception_thread->set_exception_handle(std::move(exception.exception)); | |
| return debug::Status(); | |
| } | |
| debug::Status DebugAgent::AttachToExistingProcess(zx_koid_t process_koid, uint32_t transaction_id) { | |
| std::unique_ptr<ProcessHandle> process_handle = system_interface_->GetProcess(process_koid); | |
| if (!process_handle) | |
| return debug::Status("Can not fild process " + std::to_string(process_koid) + " to attach to."); | |
| DebuggedProcess* process = nullptr; | |
| if (auto status = | |
| AddDebuggedProcess(DebuggedProcessCreateInfo(std::move(process_handle)), &process); | |
| status.has_error()) | |
| return status; | |
| // Send the response, then the notifications about the process and threads. | |
| SendAttachReply(this, transaction_id, debug::Status(), process_koid, | |
| process->process_handle().GetName()); | |
| process->PopulateCurrentThreads(); | |
| process->SuspendAndSendModulesIfKnown(); | |
| return debug::Status(); | |
| } | |
| 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(); | |
| std::unique_ptr<BinaryLauncher> launcher = system_interface_->GetLauncher(); | |
| reply->status = launcher->Setup(request.argv); | |
| if (reply->status.has_error()) | |
| return; | |
| DebuggedProcessCreateInfo create_info(launcher->GetProcess()); | |
| create_info.stdio = launcher->ReleaseStdioHandles(); | |
| // The DebuggedProcess must be attached to the new process' exception port before actually | |
| // Starting the process to avoid racing with the program initialization. | |
| DebuggedProcess* new_process = nullptr; | |
| reply->status = AddDebuggedProcess(std::move(create_info), &new_process); | |
| if (reply->status.has_error()) | |
| return; | |
| reply->status = launcher->Start(); | |
| if (reply->status.has_error()) { | |
| RemoveDebuggedProcess(new_process->koid()); | |
| return; | |
| } | |
| // Success, fill out the reply. | |
| reply->process_id = new_process->koid(); | |
| reply->process_name = new_process->process_handle().GetName(); | |
| } | |
| void DebugAgent::LaunchComponent(const debug_ipc::LaunchRequest& request, | |
| debug_ipc::LaunchReply* reply) { | |
| *reply = {}; | |
| // 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) { | |
| reply->status = debug::Status( | |
| "Could not obtain component root job. It could be another debugger is " | |
| "already running or this is a user OS build."); | |
| FX_LOGS(WARNING) << reply->status.message(); | |
| return; | |
| } | |
| reply->inferior_type = debug_ipc::InferiorType::kComponent; | |
| reply->status = system_interface_->GetComponentManager().LaunchComponent(job, request.argv, | |
| &reply->component_id); | |
| } | |
| void DebugAgent::OnProcessStart(const std::string& filter, | |
| std::unique_ptr<ProcessHandle> process_handle) { | |
| DEBUG_LOG(Process) << "Process starting, koid: " << process_handle->GetKoid(); | |
| auto process_koid = process_handle->GetKoid(); | |
| StdioHandles stdio; // Will be filled in only for components. | |
| uint64_t component_id = system_interface_->GetComponentManager().OnProcessStart(filter, stdio); | |
| // Send notification, then create debug process so that thread notification is sent after this. | |
| debug_ipc::NotifyProcessStarting notify; | |
| notify.koid = process_koid; | |
| notify.name = process_handle->GetName(); | |
| notify.component_id = component_id; | |
| notify.timestamp = GetNowTimestamp(); | |
| debug_ipc::MessageWriter writer; | |
| debug_ipc::WriteNotifyProcessStarting(notify, &writer); | |
| stream()->Write(writer.MessageComplete()); | |
| DebuggedProcessCreateInfo create_info(std::move(process_handle)); | |
| create_info.stdio = std::move(stdio); | |
| 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::OnProcessEnteredLimbo(const LimboProvider::Record& record) { | |
| zx_koid_t process_koid = record.process->GetKoid(); | |
| // First check if we were to "kill" this process. | |
| if (auto it = killed_limbo_procs_.find(process_koid); it != killed_limbo_procs_.end()) { | |
| system_interface_->GetLimboProvider().ReleaseProcess(process_koid); | |
| killed_limbo_procs_.erase(it); | |
| return; | |
| } | |
| std::string process_name = record.process->GetName(); | |
| DEBUG_LOG(Agent) << "Process " << process_name << " (" << process_koid << ") entered limbo."; | |
| debug_ipc::NotifyProcessStarting process_starting = {}; | |
| process_starting.type = debug_ipc::NotifyProcessStarting::Type::kLimbo; | |
| process_starting.koid = process_koid; | |
| process_starting.name = std::move(process_name); | |
| process_starting.timestamp = GetNowTimestamp(); | |
| debug_ipc::MessageWriter writer; | |
| debug_ipc::WriteNotifyProcessStarting(std::move(process_starting), &writer); | |
| stream()->Write(writer.MessageComplete()); | |
| } | |
| } // namespace debug_agent |