| // 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 <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 <zircon/types.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #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_process.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/protocol.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| |
| #if defined(__linux__) |
| #include <unistd.h> |
| #endif |
| |
| 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) |
| : adapter_(std::make_unique<RemoteAPIAdapter>(this, nullptr)), |
| system_interface_(std::move(system_interface)), |
| weak_factory_(this) { |
| // Register ourselves to receive component events and limbo events. |
| // |
| // It's safe to pass |this| here because |this| owns |system_interface|, which owns |
| // |ComponentManager| and |LimboProvider|. |
| system_interface_->GetComponentManager().SetDebugAgent(this); |
| system_interface_->GetLimboProvider().set_on_enter_limbo( |
| [this](const LimboProvider::Record& record) { OnProcessEnteredLimbo(record); }); |
| } |
| |
| fxl::WeakPtr<DebugAgent> DebugAgent::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } |
| |
| void DebugAgent::TakeAndConnectRemoteAPIStream(std::unique_ptr<debug::BufferedStream> stream) { |
| // Now we can create a BufferedZxSocket to pass to the RemoteAPIAdapter. The data path is: |
| // |
| // BufferedZxSocket -> RemoteAPIAdapter -> DebugAgent |
| // |
| // DebugAgent owns both the BufferedZxSocket and RemoteAPIAdapter, since the DebugAgent can be |
| // started without a socket connection to a host tool, so it is safe to capture |this|. When the |
| // socket is closed, we exit. |
| adapter_->set_stream(&stream->stream()); |
| stream->set_data_available_callback([this]() { adapter_->OnStreamReadable(); }); |
| stream->set_error_callback([this]() { |
| // Unconditionally quit when the debug_ipc socket is closed. |
| Disconnect(); |
| ClearState(); |
| debug::MessageLoop::Current()->QuitNow(); |
| }); |
| |
| // Start listening. |
| Connect(std::move(stream)); |
| } |
| |
| void DebugAgent::Connect(std::unique_ptr<debug::BufferedStream> stream) { |
| FX_DCHECK(stream) << "Cannot connect to an invalid stream!"; |
| |
| buffered_stream_ = std::move(stream); |
| |
| FX_CHECK(buffered_stream_->Start()) << "Failed to connect to the FIDL socket"; |
| |
| #ifdef __Fuchsia__ |
| // Watch the root job. |
| root_job_ = system_interface_->GetRootJob(); |
| auto status = root_job_->WatchJobExceptions(this); |
| if (status.has_error()) { |
| LOGS(Error) << "Failed to watch the root job: " << status.message(); |
| } |
| #endif // __Fuchsia__ |
| } |
| |
| void DebugAgent::Disconnect() { |
| // Can only disconnect from a connected state. |
| FX_DCHECK(buffered_stream_); |
| |
| // Release all resources associated with the previous connection. |
| buffered_stream_->Reset(); |
| } |
| |
| void DebugAgent::ClearState() { |
| // Reset debugging State |
| debug::LogBackend::Unset(); |
| |
| // Stop watching for process starting. |
| root_job_.reset(); |
| // Removes breakpoints before we detach from the processes, although it should also be safe |
| // to reverse the order. |
| breakpoints_.clear(); |
| // Detach us from the processes. |
| procs_.clear(); |
| } |
| |
| void DebugAgent::RemoveDebuggedProcess(zx_koid_t process_koid) { |
| auto found = procs_.find(process_koid); |
| if (found == procs_.end()) |
| FX_NOTREACHED(); |
| else |
| procs_.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::OnHello(const debug_ipc::HelloRequest& request, debug_ipc::HelloReply* reply) { |
| if (request.version >= debug_ipc::kMinimumProtocolVersion && |
| request.version <= debug_ipc::kCurrentProtocolVersion) { |
| // Downgrade only when the requested version is supported by us. |
| ipc_version_ = request.version; |
| } else { |
| LOGS(Error) << "Unsupported IPC version: " << request.version << ", supported range is " |
| << debug_ipc::kMinimumProtocolVersion << "-" << debug_ipc::kCurrentProtocolVersion; |
| ipc_version_ = debug_ipc::kCurrentProtocolVersion; |
| } |
| |
| // Signature is default-initialized. |
| reply->version = ipc_version_; |
| reply->arch = arch::GetCurrentArch(); |
| reply->platform = debug::CurrentSystemPlatform(); |
| |
| #if defined(__Fuchsia__) |
| reply->page_size = zx_system_get_page_size(); |
| #elif defined(__linux__) |
| reply->page_size = getpagesize(); |
| #else |
| #error Need platform page size. |
| #endif |
| |
| // Only enable log backend after the handshake is finished. |
| debug::LogBackend::Set(this, true); |
| } |
| |
| 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(); |
| |
| process_record.components = |
| system_interface_->GetComponentManager().FindComponentInfo(proc->process_handle()); |
| |
| 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::kMinimal)); |
| } |
| |
| reply->processes.emplace_back(std::move(process_record)); |
| } |
| |
| reply->breakpoints.reserve(breakpoints_.size()); |
| for (auto& [_, bp] : breakpoints_) { |
| reply->breakpoints.push_back(bp.settings()); |
| } |
| |
| reply->filters.reserve(filters_.size()); |
| for (auto& filter : filters_) { |
| reply->filters.push_back(filter.filter()); |
| } |
| |
| // 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(); |
| |
| process_record.components = |
| system_interface_->GetComponentManager().FindComponentInfo(*record.process); |
| |
| // 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::OnRunBinary(const debug_ipc::RunBinaryRequest& request, |
| debug_ipc::RunBinaryReply* reply) { |
| reply->timestamp = GetNowTimestamp(); |
| if (request.argv.empty()) { |
| reply->status = debug::Status("No launch arguments provided"); |
| return; |
| } |
| |
| LaunchProcess(request, reply); |
| } |
| |
| void DebugAgent::OnRunComponent(const debug_ipc::RunComponentRequest& request, |
| debug_ipc::RunComponentReply* reply) { |
| reply->status = system_interface_->GetComponentManager().LaunchComponent(request.url); |
| } |
| |
| void DebugAgent::OnRunTest(const debug_ipc::RunTestRequest& request, |
| debug_ipc::RunTestReply* reply) { |
| reply->status = system_interface_->GetComponentManager().LaunchTest(request.url, request.realm, |
| request.case_filters); |
| } |
| |
| 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(); |
| |
| // 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) { |
| 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."); |
| } |
| } |
| |
| void DebugAgent::OnPause(const debug_ipc::PauseRequest& request, debug_ipc::PauseReply* reply) { |
| std::vector<debug_ipc::ProcessThreadId> paused; |
| DEBUG_LOG(Agent) << "Got Pause request"; |
| |
| 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 { |
| LOGS(Warn) << "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::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 { |
| LOGS(Warn) << "Could not find thread by koid: " << id.thread; |
| } |
| } else { |
| // All threads in the process. |
| proc->OnResume(request); |
| } |
| } else { |
| LOGS(Warn) << "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 { |
| 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."); |
| 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::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::OnUpdateFilter(const debug_ipc::UpdateFilterRequest& request, |
| debug_ipc::UpdateFilterReply* reply) { |
| DEBUG_LOG(Agent) << "Received UpdateFilter request size=" << request.filters.size(); |
| filters_.clear(); |
| filters_.reserve(request.filters.size()); |
| for (const auto& filter : request.filters) { |
| filters_.emplace_back(filter); |
| auto matched_processes = filters_.back().ApplyToJob(*root_job_, *system_interface_); |
| if (!matched_processes.empty()) { |
| reply->matched_processes_for_filter.emplace_back(filter.id, std::move(matched_processes)); |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| void DebugAgent::OnSaveMinidump(const debug_ipc::SaveMinidumpRequest& request, |
| debug_ipc::SaveMinidumpReply* reply) { |
| reply->status = debug::Status(); |
| |
| DebuggedProcess* proc = GetDebuggedProcess(request.process_koid); |
| |
| if (!proc) { |
| reply->status = |
| debug::Status("No process found to save core from. Is there an attached process?"); |
| return; |
| } |
| |
| proc->OnSaveMinidump(request, reply); |
| } |
| |
| DebuggedProcess* DebugAgent::GetDebuggedProcess(zx_koid_t koid) { |
| auto found = procs_.find(koid); |
| if (found == procs_.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::AddDebuggedProcess(DebuggedProcessCreateInfo&& create_info, |
| DebuggedProcess** new_process) { |
| *new_process = nullptr; |
| |
| auto proc = std::make_unique<DebuggedProcess>(this); |
| |
| // Need to register the process before calling DebuggedProcess::Init() because Init() can |
| // do things like make breakpoints that call back into this class. |
| auto process_id = create_info.handle->GetKoid(); |
| *new_process = proc.get(); |
| procs_[process_id] = std::move(proc); |
| |
| if (auto status = (*new_process)->Init(std::move(create_info)); status.has_error()) { |
| // Undo registration. |
| procs_.erase(process_id); |
| *new_process = nullptr; |
| return status; |
| } |
| 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 --------------------------------------------------------------------------------------- |
| |
| void DebugAgent::OnAttach(const debug_ipc::AttachRequest& request, debug_ipc::AttachReply* reply) { |
| DEBUG_LOG(Agent) << "Attemping to attach to process " << request.koid; |
| reply->timestamp = GetNowTimestamp(); |
| |
| // See if we're already attached to this process. |
| for (auto& [koid, proc] : procs_) { |
| if (koid == request.koid) { |
| reply->status = debug::Status(debug::Status::kAlreadyExists, |
| "Already attached to process " + std::to_string(proc->koid())); |
| DEBUG_LOG(Agent) << reply->status.message(); |
| return; |
| } |
| } |
| |
| // First check if the process is in limbo. Sends the appropiate replies/notifications. |
| if (system_interface_->GetLimboProvider().Valid()) { |
| reply->status = AttachToLimboProcess(request.koid, reply); |
| if (reply->status.ok()) |
| return; |
| |
| DEBUG_LOG(Agent) << "Could not attach to process in limbo: " << reply->status.message(); |
| } |
| |
| // Attempt to attach to an existing process. Sends the appropriate replies/notifications. |
| reply->status = AttachToExistingProcess(request.koid, request.weak, reply); |
| if (reply->status.ok()) |
| return; |
| |
| // We didn't find a process. |
| DEBUG_LOG(Agent) << "Could not attach to existing process: " << reply->status.message(); |
| } |
| |
| debug::Status DebugAgent::AttachToLimboProcess(zx_koid_t process_koid, |
| debug_ipc::AttachReply* reply) { |
| 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* process = nullptr; |
| debug::Status status = AddDebuggedProcess(std::move(create_info), &process); |
| if (status.has_error()) |
| return status; |
| |
| reply->koid = process->koid(); |
| reply->name = process->process_handle().GetName(); |
| reply->components = |
| system_interface_->GetComponentManager().FindComponentInfo(process->process_handle()); |
| |
| // Send the reply first, then the notifications about the process and threads. |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, [weak_this = GetWeakPtr(), koid = reply->koid, |
| exception = std::move(exception)]() mutable { |
| if (!weak_this) |
| return; |
| if (DebuggedProcess* process = weak_this->GetDebuggedProcess(koid)) { |
| process->PopulateCurrentThreads(); |
| process->SuspendAndSendModules(); |
| |
| zx_koid_t thread_koid = exception.thread->GetKoid(); |
| |
| // Pass in the exception handle to the corresponding thread. |
| DebuggedThread* exception_thread = nullptr; |
| for (DebuggedThread* thread : 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, bool weak, |
| debug_ipc::AttachReply* reply) { |
| std::unique_ptr<ProcessHandle> process_handle = system_interface_->GetProcess(process_koid); |
| if (!process_handle) |
| return debug::Status("Can't find process " + std::to_string(process_koid) + " to attach to."); |
| |
| DebuggedProcess* process = nullptr; |
| DebuggedProcessCreateInfo create_info(std::move(process_handle)); |
| create_info.weak = weak; |
| if (auto status = AddDebuggedProcess(std::move(create_info), &process); status.has_error()) |
| return status; |
| |
| reply->koid = process->koid(); |
| reply->name = process->process_handle().GetName(); |
| reply->components = |
| system_interface_->GetComponentManager().FindComponentInfo(process->process_handle()); |
| |
| // Send the reply first, then the notifications about the process and threads. |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [weak_this = GetWeakPtr(), koid = reply->koid, weak]() mutable { |
| if (!weak_this) |
| return; |
| if (DebuggedProcess* process = weak_this->GetDebuggedProcess(koid)) { |
| process->PopulateCurrentThreads(); |
| if (!weak) |
| process->SuspendAndSendModules(); |
| } |
| }); |
| |
| return debug::Status(); |
| } |
| |
| void DebugAgent::LaunchProcess(const debug_ipc::RunBinaryRequest& request, |
| debug_ipc::RunBinaryReply* reply) { |
| FX_DCHECK(!request.argv.empty()); |
| 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::OnProcessStarting(std::unique_ptr<ProcessHandle> process_handle) { |
| OnProcessChanged(true, std::move(process_handle)); |
| } |
| |
| void DebugAgent::OnProcessNameChanged(std::unique_ptr<ProcessHandle> process_handle) { |
| OnProcessChanged(false, std::move(process_handle)); |
| } |
| |
| void DebugAgent::OnProcessChanged(bool starting, std::unique_ptr<ProcessHandle> process_handle) { |
| if (procs_.find(process_handle->GetKoid()) != procs_.end()) { |
| return; // The process might have been attached in |LaunchProcess|. |
| } |
| |
| debug_ipc::NotifyProcessStarting::Type type = debug_ipc::NotifyProcessStarting::Type::kLast; |
| StdioHandles stdio; // Will be filled in only for components. |
| std::string process_name_override; |
| const debug_ipc::Filter* matched_filter = nullptr; |
| |
| if (starting && system_interface_->GetComponentManager().OnProcessStart(*process_handle, &stdio, |
| &process_name_override)) { |
| type = debug_ipc::NotifyProcessStarting::Type::kLaunch; |
| } else if (std::any_of(filters_.begin(), filters_.end(), [&](const Filter& filter) { |
| if (filter.MatchesProcess(*process_handle, *system_interface_)) { |
| matched_filter = &filter.filter(); |
| return true; |
| } |
| return false; |
| })) { |
| type = debug_ipc::NotifyProcessStarting::Type::kAttach; |
| } else { |
| #ifdef __linux__ |
| // For now, unconditionally attach to all forked processes on Linux. |
| // TODO(brettw) This should be revisited when we get better frontend UI for dealing with forks. |
| type = debug_ipc::NotifyProcessStarting::Type::kAttach; |
| #else |
| return; |
| #endif |
| } |
| |
| DEBUG_LOG(Process) << "Process starting, koid: " << process_handle->GetKoid(); |
| |
| // Prepare the notification but don't send yet because |process_handle| will be moved and |
| // |AddDebuggedProcess| may fail. |
| debug_ipc::NotifyProcessStarting notify; |
| notify.type = type; |
| notify.koid = process_handle->GetKoid(); |
| notify.name = process_name_override.empty() ? process_handle->GetName() : process_name_override; |
| notify.timestamp = GetNowTimestamp(); |
| notify.components = system_interface_->GetComponentManager().FindComponentInfo(*process_handle); |
| notify.filter_id = matched_filter ? matched_filter->id : debug_ipc::kInvalidFilterId; |
| |
| bool weak = matched_filter ? matched_filter->weak : false; |
| |
| DebuggedProcessCreateInfo create_info(std::move(process_handle)); |
| create_info.stdio = std::move(stdio); |
| create_info.weak = weak; |
| |
| DebuggedProcess* new_process = nullptr; |
| debug::Status status = AddDebuggedProcess(std::move(create_info), &new_process); |
| |
| if (status.has_error()) { |
| LOGS(Warn) << "Failed to attach to process " << notify.koid << ": " << status.message(); |
| return; |
| } |
| |
| SendNotification(notify); |
| |
| new_process->PopulateCurrentThreads(); |
| |
| // If this wasn't a weak attach, we need to send modules here. We cannot wait for the client to |
| // request all the modules later because we won't be able to load symbols early enough to set |
| // breakpoints on things like _dl_start, which will resolve from the first modules being sent |
| // now. The rest of the modules will be sent later on when the client requests them or we hit the |
| // loader breakpoint. |
| if (!weak) { |
| new_process->SuspendAndSendModules(); |
| } |
| } |
| |
| void DebugAgent::OnComponentDiscovered(const std::string& moniker, const std::string& url) { |
| auto matched_filters = GetMatchingFiltersForComponentInfo(moniker, url); |
| |
| for (auto filter : matched_filters) { |
| if (filter != nullptr && filter->filter().recursive) { |
| // When any recursive filter matches here, we install a component moniker prefix filter so |
| // that any sub-components created as children of this one are attached implicitly. Only one |
| // filter match needs to be recursive for us to install the prefix filter for |moniker|, and |
| // we only need to install one new filter per invocation of this function. The client is |
| // notified of this filter so that it is not removed on subsequent UpdateFilter requests, |
| // which the client will do shortly after receiving this notification. The new version of |
| // this filter will include a filter id and with all of the settings given here. Notably, we |
| // do not enable the recursive flag on this filter, which would be redundant with the parent |
| // filter. |
| debug_ipc::Filter realm_filter; |
| realm_filter.type = debug_ipc::Filter::Type::kComponentMonikerPrefix; |
| realm_filter.pattern = moniker; |
| realm_filter.weak = filter->filter().weak; |
| |
| filters_.emplace_back(realm_filter); |
| |
| debug_ipc::NotifyComponentDiscovered notify; |
| notify.filter = realm_filter; |
| |
| SendNotification(notify); |
| return; |
| } |
| } |
| } |
| |
| void DebugAgent::OnComponentStarted(const std::string& moniker, const std::string& url) { |
| if (!GetMatchingFiltersForComponentInfo(moniker, url).empty()) { |
| debug_ipc::NotifyComponentStarting notify; |
| notify.component.moniker = moniker; |
| notify.component.url = url; |
| notify.timestamp = GetNowTimestamp(); |
| |
| SendNotification(notify); |
| } |
| } |
| |
| void DebugAgent::OnComponentExited(const std::string& moniker, const std::string& url) { |
| if (!GetMatchingFiltersForComponentInfo(moniker, url).empty()) { |
| debug_ipc::NotifyComponentExiting notify; |
| notify.component.moniker = moniker; |
| notify.component.url = url; |
| notify.timestamp = GetNowTimestamp(); |
| |
| SendNotification(notify); |
| } |
| } |
| |
| void DebugAgent::OnTestComponentExited(const std::string& url) { |
| debug_ipc::NotifyTestExited notify; |
| notify.url = url; |
| notify.timestamp = GetNowTimestamp(); |
| |
| SendNotification(notify); |
| } |
| |
| 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(); |
| |
| SendNotification(process_starting); |
| } |
| |
| std::vector<const Filter*> DebugAgent::GetMatchingFiltersForComponentInfo( |
| const std::string& moniker, const std::string& url) const { |
| std::vector<const Filter*> matches; |
| for (const auto& filter : filters_) { |
| if (filter.MatchesComponent(moniker, url)) { |
| matches.push_back(&filter); |
| } |
| } |
| |
| return matches; |
| } |
| |
| void DebugAgent::WriteLog(debug::LogSeverity severity, const debug::FileLineFunction& location, |
| std::string log) { |
| debug_ipc::NotifyLog notify; |
| switch (severity) { |
| case debug::LogSeverity::kInfo: |
| return; // Only forward warnings and errors for now. |
| case debug::LogSeverity::kWarn: |
| notify.severity = debug_ipc::NotifyLog::Severity::kWarn; |
| break; |
| case debug::LogSeverity::kError: |
| notify.severity = debug_ipc::NotifyLog::Severity::kError; |
| break; |
| } |
| notify.location.file = location.file(); |
| notify.location.function = location.function(); |
| notify.location.line = location.line(); |
| notify.log = log; |
| |
| SendNotification(notify); |
| } |
| |
| } // namespace debug_agent |